blob: f33d95dbf13c23612064b23cedea7ab1581efe3b [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <getopt.h>
#include <fstream>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include "arc/codec-test/common.h"
#include "arc/codec-test/mediacodec_decoder.h"
#include "arc/codec-test/video_frame.h"
namespace android {
// Environment to store test video data for all test cases.
class ArcVideoDecoderTestEnvironment;
namespace {
ArcVideoDecoderTestEnvironment* g_env;
} // namespace
class ArcVideoDecoderTestEnvironment : public testing::Environment {
public:
explicit ArcVideoDecoderTestEnvironment(const std::string& data,
const std::string& output_frames_path)
: test_video_data_(data), output_frames_path_(output_frames_path) {}
void SetUp() override { ParseTestVideoData(); }
// The syntax of test video data is:
// "input_file_path:width:height:num_frames:num_fragments:min_fps_render:
// min_fps_no_render:video_codec_profile[:output_file_path]"
// - |input_file_path| is compressed video stream in H264 Annex B (NAL) format
// (H264) or IVF (VP8/9).
// - |width| and |height| are visible frame size in pixels.
// - |num_frames| is the number of picture frames for the input stream.
// - |num_fragments| is the number of AU (H264) or frame (VP8/9) in the input
// stream. (Unused. Test will automatically parse the number.)
// - |min_fps_render| and |min_fps_no_render| are minimum frames/second speeds
// expected to be achieved with and without rendering respective.
// (The former is unused because no rendering case here.)
// (The latter is Optional.)
// - |video_codec_profile| is the VideoCodecProfile set during Initialization.
void ParseTestVideoData() {
std::vector<std::string> fields = SplitString(test_video_data_, ':');
ASSERT_EQ(fields.size(), 8U)
<< "The number of fields of test_video_data is not 8: "
<< test_video_data_;
input_file_path_ = fields[0];
int width = std::stoi(fields[1]);
int height = std::stoi(fields[2]);
visible_size_ = Size(width, height);
ASSERT_FALSE(visible_size_.IsEmpty());
num_frames_ = std::stoi(fields[3]);
ASSERT_GT(num_frames_, 0);
// Unused fields[4] --> num_fragments
// Unused fields[5] --> min_fps_render
if (!fields[6].empty()) {
min_fps_no_render_ = std::stoi(fields[6]);
}
video_codec_profile_ = static_cast<VideoCodecProfile>(std::stoi(fields[7]));
ASSERT_NE(VideoCodecProfileToType(video_codec_profile_),
VideoCodecType::UNKNOWN);
}
// Get the corresponding frame-wise golden MD5 file path.
std::string GoldenMD5FilePath() const {
return input_file_path_ + ".frames.md5";
}
std::string output_frames_path() const { return output_frames_path_; }
std::string input_file_path() const { return input_file_path_; }
Size visible_size() const { return visible_size_; }
int num_frames() const { return num_frames_; }
int min_fps_no_render() const { return min_fps_no_render_; }
VideoCodecProfile video_codec_profile() const { return video_codec_profile_; }
protected:
std::string test_video_data_;
std::string output_frames_path_;
std::string input_file_path_;
Size visible_size_;
int num_frames_ = 0;
int min_fps_no_render_ = 0;
VideoCodecProfile video_codec_profile_;
};
// The struct to record output formats.
struct OutputFormat {
Size coded_size;
Size visible_size;
int32_t color_format = 0;
};
// The helper class to validate video frame by MD5 and ouput to I420 raw stream
// if needed.
class VideoFrameValidator {
public:
VideoFrameValidator() = default;
~VideoFrameValidator() { output_file_.close(); }
// Set |md5_golden_path| as the path of golden frame-wise MD5 file. Return
// false if the file is failed to read.
bool SetGoldenMD5File(const std::string& md5_golden_path) {
golden_md5_file_ =
std::unique_ptr<InputFileASCII>(new InputFileASCII(md5_golden_path));
return golden_md5_file_->IsValid();
}
// Set |output_frames_path| as the path for output raw I420 stream. Return
// false if the file is failed to open.
bool SetOutputFile(const std::string& output_frames_path) {
if (output_frames_path.empty())
return false;
output_file_.open(output_frames_path, std::ofstream::binary);
if (!output_file_.is_open()) {
printf("[ERR] Failed to open file: %s\n", output_frames_path.c_str());
return false;
}
printf("[LOG] Decode output to file: %s\n", output_frames_path.c_str());
write_to_file_ = true;
return true;
}
// Callback function of output buffer ready to validate frame data by
// VideoFrameValidator, write into file if needed.
void VerifyMD5(const uint8_t* data, size_t buffer_size, int output_index) {
std::string golden;
ASSERT_TRUE(golden_md5_file_ && golden_md5_file_->IsValid());
ASSERT_TRUE(golden_md5_file_->ReadLine(&golden))
<< "Failed to read golden MD5 at frame#" << output_index;
std::unique_ptr<VideoFrame> video_frame = VideoFrame::Create(
data, buffer_size, output_format_.coded_size,
output_format_.visible_size, output_format_.color_format);
ASSERT_TRUE(video_frame)
<< "Failed to create video frame on VerifyMD5 at frame#"
<< output_index;
ASSERT_TRUE(video_frame->VerifyMD5(golden))
<< "MD5 mismatched at frame#" << output_index;
// Update color_format.
output_format_.color_format = video_frame->color_format();
}
// Callback function of output buffer ready to validate frame data by
// VideoFrameValidator, write into file if needed.
void OutputToFile(const uint8_t* data, size_t buffer_size, int output_index) {
if (!write_to_file_)
return;
std::unique_ptr<VideoFrame> video_frame = VideoFrame::Create(
data, buffer_size, output_format_.coded_size,
output_format_.visible_size, output_format_.color_format);
ASSERT_TRUE(video_frame)
<< "Failed to create video frame on OutputToFile at frame#"
<< output_index;
if (!video_frame->WriteFrame(&output_file_)) {
printf("[ERR] Failed to write output buffer into file.\n");
// Stop writing frames to file once it is failed.
write_to_file_ = false;
}
}
// Callback function of output format changed to update output format.
void UpdateOutputFormat(const Size& coded_size,
const Size& visible_size,
int32_t color_format) {
output_format_.coded_size = coded_size;
output_format_.visible_size = visible_size;
output_format_.color_format = color_format;
}
private:
// The wrapper of input MD5 golden file.
std::unique_ptr<InputFileASCII> golden_md5_file_;
// The output file to write the decoded raw video.
std::ofstream output_file_;
// Only output video frame to file if True.
bool write_to_file_ = false;
// This records output format, color_format might be revised in flexible
// format case.
OutputFormat output_format_;
};
class ArcVideoDecoderE2ETest : public testing::Test {
public:
// Callback function of output buffer ready to count frame.
void CountFrame(const uint8_t* /* data */,
size_t /* buffer_size */,
int /* output_index */) {
decoded_frames_++;
}
// Callback function of output format changed to verify output format.
void VerifyOutputFormat(const Size& coded_size,
const Size& visible_size,
int32_t color_format) {
ASSERT_FALSE(coded_size.IsEmpty());
ASSERT_FALSE(visible_size.IsEmpty());
ASSERT_LE(visible_size.width, coded_size.width);
ASSERT_LE(visible_size.height, coded_size.height);
printf(
"[LOG] Got format changed { coded_size: %dx%d, visible_size: %dx%d, "
"color_format: 0x%x\n",
coded_size.width, coded_size.height, visible_size.width,
visible_size.height, color_format);
output_format_.coded_size = coded_size;
output_format_.visible_size = visible_size;
output_format_.color_format = color_format;
}
protected:
void SetUp() override {
decoder_ = MediaCodecDecoder::Create(g_env->input_file_path(),
g_env->video_codec_profile(),
g_env->visible_size());
ASSERT_TRUE(decoder_);
decoder_->Rewind();
ASSERT_TRUE(decoder_->Configure());
ASSERT_TRUE(decoder_->Start());
decoder_->AddOutputBufferReadyCb(std::bind(
&ArcVideoDecoderE2ETest::CountFrame, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3));
decoder_->AddOutputFormatChangedCb(std::bind(
&ArcVideoDecoderE2ETest::VerifyOutputFormat, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
}
void TearDown() override {
EXPECT_TRUE(decoder_->Stop());
EXPECT_EQ(g_env->visible_size().width, output_format_.visible_size.width);
EXPECT_EQ(g_env->visible_size().height, output_format_.visible_size.height);
EXPECT_EQ(g_env->num_frames(), decoded_frames_);
decoder_.reset();
}
// The wrapper of the mediacodec decoder.
std::unique_ptr<MediaCodecDecoder> decoder_;
// The counter of obtained decoded output frames.
int decoded_frames_ = 0;
// This records formats from output format change.
OutputFormat output_format_;
};
TEST_F(ArcVideoDecoderE2ETest, TestSimpleDecode) {
VideoFrameValidator video_frame_validator;
ASSERT_TRUE(
video_frame_validator.SetGoldenMD5File(g_env->GoldenMD5FilePath()))
<< "Failed to open MD5 file: " << g_env->GoldenMD5FilePath();
decoder_->AddOutputBufferReadyCb(std::bind(
&VideoFrameValidator::VerifyMD5, &video_frame_validator,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
if (video_frame_validator.SetOutputFile(g_env->output_frames_path())) {
decoder_->AddOutputBufferReadyCb(std::bind(
&VideoFrameValidator::OutputToFile, &video_frame_validator,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
}
decoder_->AddOutputFormatChangedCb(std::bind(
&VideoFrameValidator::UpdateOutputFormat, &video_frame_validator,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
EXPECT_TRUE(decoder_->Decode());
}
TEST_F(ArcVideoDecoderE2ETest, TestFPS) {
FPSCalculator fps_calculator;
auto callback = [&fps_calculator](const uint8_t* /* data */,
size_t /* buffer_size */,
int /* output_index */) {
ASSERT_TRUE(fps_calculator.RecordFrameTimeDiff());
};
decoder_->AddOutputBufferReadyCb(callback);
EXPECT_TRUE(decoder_->Decode());
double fps = fps_calculator.CalculateFPS();
printf("[LOG] Measured decoder FPS: %.4f\n", fps);
EXPECT_GE(fps, static_cast<double>(g_env->min_fps_no_render()));
}
} // namespace android
bool GetOption(int argc,
char** argv,
std::string* test_video_data,
std::string* output_frames_path) {
const char* const optstring = "t:o:";
static const struct option opts[] = {
{"test_video_data", required_argument, nullptr, 't'},
{"output_frames_path", required_argument, nullptr, 'o'},
{nullptr, 0, nullptr, 0},
};
int opt;
while ((opt = getopt_long(argc, argv, optstring, opts, nullptr)) != -1) {
switch (opt) {
case 't':
*test_video_data = optarg;
break;
case 'o':
*output_frames_path = optarg;
break;
default:
printf("[WARN] Unknown option: getopt_long() returned code 0x%x.\n",
opt);
break;
}
}
if (test_video_data->empty()) {
printf("[ERR] Please assign test video data by --test_video_data\n");
return false;
}
return true;
}
int main(int argc, char** argv) {
std::string test_video_data;
std::string output_frames_path;
if (!GetOption(argc, argv, &test_video_data, &output_frames_path))
return EXIT_FAILURE;
android::g_env = reinterpret_cast<android::ArcVideoDecoderTestEnvironment*>(
testing::AddGlobalTestEnvironment(
new android::ArcVideoDecoderTestEnvironment(test_video_data,
output_frames_path)));
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}