1 // Copyright 2019 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // #define LOG_NDEBUG 0
6 #define LOG_TAG "Decoder_E2E"
7
8 #include <getopt.h>
9
10 #include <fstream>
11 #include <functional>
12 #include <memory>
13 #include <string>
14 #include <vector>
15
16 #include <gtest/gtest.h>
17 #include <log/log.h>
18
19 #include "common.h"
20 #include "e2e_test_jni.h"
21 #include "mediacodec_decoder.h"
22 #include "video_frame.h"
23
24 namespace android {
25
26 // Environment to store test video data for all test cases.
27 class C2VideoDecoderTestEnvironment;
28
29 namespace {
30 C2VideoDecoderTestEnvironment* g_env;
31
32 } // namespace
33
34 class C2VideoDecoderTestEnvironment : public testing::Environment {
35 public:
C2VideoDecoderTestEnvironment(bool loop,bool use_sw_decoder,bool use_fake_renderer,const std::string & data,const std::string & output_frames_path,ANativeWindow * surface,ConfigureCallback * cb)36 C2VideoDecoderTestEnvironment(bool loop, bool use_sw_decoder, bool use_fake_renderer,
37 const std::string& data, const std::string& output_frames_path,
38 ANativeWindow* surface, ConfigureCallback* cb)
39 : loop_(loop),
40 use_sw_decoder_(use_sw_decoder),
41 use_fake_renderer_(use_fake_renderer),
42 test_video_data_(data),
43 output_frames_path_(output_frames_path),
44 surface_(surface),
45 configure_cb_(cb) {}
46
SetUp()47 void SetUp() override { ParseTestVideoData(); }
48
49 // The syntax of test video data is:
50 // "input_file_path:width:height:num_frames:num_fragments:min_fps_render:
51 // min_fps_no_render:video_codec_profile[:output_file_path]"
52 // - |input_file_path| is compressed video stream in H264 Annex B (NAL) format
53 // (H264) or IVF (VP8/9).
54 // - |width| and |height| are visible frame size in pixels.
55 // - |num_frames| is the number of picture frames for the input stream.
56 // - |num_fragments| is the number of AU (H264) or frame (VP8/9) in the input
57 // stream. (Unused. Test will automatically parse the number.)
58 // - |min_fps_render| and |min_fps_no_render| are minimum frames/second speeds
59 // expected to be achieved with and without rendering respective.
60 // (The former is unused because no rendering case here.)
61 // (The latter is Optional.)
62 // - |video_codec_profile| is the VideoCodecProfile set during Initialization.
63 // - |frame_rate| the expected framerate of the video.
ParseTestVideoData()64 void ParseTestVideoData() {
65 std::vector<std::string> fields = SplitString(test_video_data_, ':');
66 ASSERT_EQ(fields.size(), 9U)
67 << "The number of fields of test_video_data is not 9: " << test_video_data_;
68
69 input_file_path_ = fields[0];
70 int width = std::stoi(fields[1]);
71 int height = std::stoi(fields[2]);
72 visible_size_ = Size(width, height);
73 ASSERT_FALSE(visible_size_.IsEmpty());
74
75 configure_cb_->OnSizeChanged(width, height);
76
77 num_frames_ = std::stoi(fields[3]);
78 ASSERT_GT(num_frames_, 0);
79
80 // Unused fields[4] --> num_fragments
81 // Unused fields[5] --> min_fps_render
82
83 if (!fields[6].empty()) {
84 min_fps_no_render_ = std::stoi(fields[6]);
85 }
86
87 video_codec_profile_ = static_cast<VideoCodecProfile>(std::stoi(fields[7]));
88 ASSERT_NE(VideoCodecProfileToType(video_codec_profile_), VideoCodecType::UNKNOWN);
89
90 frame_rate_ = std::stoi(fields[8]);
91 }
92
93 // Get the corresponding frame-wise golden MD5 file path.
GoldenMD5FilePath() const94 std::string GoldenMD5FilePath() const { return input_file_path_ + ".frames.md5"; }
95
output_frames_path() const96 std::string output_frames_path() const { return output_frames_path_; }
97
input_file_path() const98 std::string input_file_path() const { return input_file_path_; }
visible_size() const99 Size visible_size() const { return visible_size_; }
num_frames() const100 int num_frames() const { return num_frames_; }
min_fps_no_render() const101 int min_fps_no_render() const { return min_fps_no_render_; }
video_codec_profile() const102 VideoCodecProfile video_codec_profile() const { return video_codec_profile_; }
frame_rate() const103 int frame_rate() const { return frame_rate_; }
configure_cb() const104 ConfigureCallback* configure_cb() const { return configure_cb_; }
loop() const105 bool loop() const { return loop_; }
use_sw_decoder() const106 bool use_sw_decoder() const { return use_sw_decoder_; }
use_fake_renderer() const107 bool use_fake_renderer() const { return use_fake_renderer_; }
108
surface() const109 ANativeWindow* surface() const { return surface_; }
110
111 protected:
112 bool loop_;
113 bool use_sw_decoder_;
114 bool use_fake_renderer_;
115 std::string test_video_data_;
116 std::string output_frames_path_;
117
118 std::string input_file_path_;
119 Size visible_size_;
120 int num_frames_ = 0;
121 int min_fps_no_render_ = 0;
122 VideoCodecProfile video_codec_profile_;
123 int frame_rate_ = 0;
124
125 ANativeWindow* surface_;
126 ConfigureCallback* configure_cb_;
127 };
128
129 // The struct to record output formats.
130 struct OutputFormat {
131 Size coded_size;
132 Size visible_size;
133 int32_t color_format = 0;
134 };
135
136 // The helper class to validate video frame by MD5 and output to I420 raw stream
137 // if needed.
138 class VideoFrameValidator {
139 public:
140 VideoFrameValidator() = default;
~VideoFrameValidator()141 ~VideoFrameValidator() { output_file_.close(); }
142
143 // Set |md5_golden_path| as the path of golden frame-wise MD5 file. Return
144 // false if the file is failed to read.
SetGoldenMD5File(const std::string & md5_golden_path)145 bool SetGoldenMD5File(const std::string& md5_golden_path) {
146 golden_md5_file_ = std::unique_ptr<InputFileASCII>(new InputFileASCII(md5_golden_path));
147 return golden_md5_file_->IsValid();
148 }
149
150 // Set |output_frames_path| as the path for output raw I420 stream. Return
151 // false if the file is failed to open.
SetOutputFile(const std::string & output_frames_path)152 bool SetOutputFile(const std::string& output_frames_path) {
153 if (output_frames_path.empty()) return false;
154
155 output_file_.open(output_frames_path, std::ofstream::binary);
156 if (!output_file_.is_open()) {
157 printf("[ERR] Failed to open file: %s\n", output_frames_path.c_str());
158 return false;
159 }
160 printf("[LOG] Decode output to file: %s\n", output_frames_path.c_str());
161 write_to_file_ = true;
162 return true;
163 }
164
165 // Callback function of output buffer ready to validate frame data by
166 // VideoFrameValidator, write into file if needed.
VerifyMD5(const uint8_t * data,size_t buffer_size,int output_index)167 void VerifyMD5(const uint8_t* data, size_t buffer_size, int output_index) {
168 ASSERT_TRUE(data != nullptr);
169
170 std::string golden;
171 ASSERT_TRUE(golden_md5_file_ && golden_md5_file_->IsValid());
172 ASSERT_TRUE(golden_md5_file_->ReadLine(&golden))
173 << "Failed to read golden MD5 at frame#" << output_index;
174
175 std::unique_ptr<VideoFrame> video_frame =
176 VideoFrame::Create(data, buffer_size, output_format_.coded_size,
177 output_format_.visible_size, output_format_.color_format);
178 ASSERT_TRUE(video_frame) << "Failed to create video frame on VerifyMD5 at frame#"
179 << output_index;
180
181 ASSERT_TRUE(video_frame->VerifyMD5(golden)) << "MD5 mismatched at frame#" << output_index;
182
183 // Update color_format.
184 output_format_.color_format = video_frame->color_format();
185 }
186
187 // Callback function of output buffer ready to validate frame data by
188 // VideoFrameValidator, write into file if needed.
OutputToFile(const uint8_t * data,size_t buffer_size,int output_index)189 void OutputToFile(const uint8_t* data, size_t buffer_size, int output_index) {
190 if (!write_to_file_) return;
191
192 ASSERT_TRUE(data != nullptr);
193
194 std::unique_ptr<VideoFrame> video_frame =
195 VideoFrame::Create(data, buffer_size, output_format_.coded_size,
196 output_format_.visible_size, output_format_.color_format);
197 ASSERT_TRUE(video_frame) << "Failed to create video frame on OutputToFile at frame#"
198 << output_index;
199 if (!video_frame->WriteFrame(&output_file_)) {
200 printf("[ERR] Failed to write output buffer into file.\n");
201 // Stop writing frames to file once it is failed.
202 write_to_file_ = false;
203 }
204 }
205
206 // Callback function of output format changed to update output format.
UpdateOutputFormat(const Size & coded_size,const Size & visible_size,int32_t color_format)207 void UpdateOutputFormat(const Size& coded_size, const Size& visible_size,
208 int32_t color_format) {
209 output_format_.coded_size = coded_size;
210 output_format_.visible_size = visible_size;
211 output_format_.color_format = color_format;
212 }
213
214 private:
215 // The wrapper of input MD5 golden file.
216 std::unique_ptr<InputFileASCII> golden_md5_file_;
217 // The output file to write the decoded raw video.
218 std::ofstream output_file_;
219
220 // Only output video frame to file if True.
221 bool write_to_file_ = false;
222 // This records output format, color_format might be revised in flexible
223 // format case.
224 OutputFormat output_format_;
225 };
226
227 class C2VideoDecoderE2ETest : public testing::Test {
228 public:
229 // Callback function of output buffer ready to count frame.
CountFrame(const uint8_t *,size_t,int)230 void CountFrame(const uint8_t* /* data */, size_t /* buffer_size */, int /* output_index */) {
231 decoded_frames_++;
232 }
233
234 // Callback function of output format changed to verify output format.
VerifyOutputFormat(const Size & coded_size,const Size & visible_size,int32_t color_format)235 void VerifyOutputFormat(const Size& coded_size, const Size& visible_size,
236 int32_t color_format) {
237 ASSERT_FALSE(coded_size.IsEmpty());
238 ASSERT_FALSE(visible_size.IsEmpty());
239 ASSERT_LE(visible_size.width, coded_size.width);
240 ASSERT_LE(visible_size.height, coded_size.height);
241 printf("[LOG] Got format changed { coded_size: %dx%d, visible_size: %dx%d, "
242 "color_format: 0x%x\n",
243 coded_size.width, coded_size.height, visible_size.width, visible_size.height,
244 color_format);
245 output_format_.coded_size = coded_size;
246 output_format_.visible_size = visible_size;
247 output_format_.color_format = color_format;
248 }
249
250 virtual bool useSurface() = 0;
251 virtual bool renderOnRelease() = 0;
252
253 protected:
SetUp()254 void SetUp() override {
255 ANativeWindow* surface = useSurface() ? g_env->surface() : nullptr;
256 decoder_ = MediaCodecDecoder::Create(g_env->input_file_path(), g_env->video_codec_profile(),
257 g_env->use_sw_decoder(), g_env->visible_size(),
258 g_env->frame_rate(), surface, renderOnRelease(),
259 g_env->loop(), g_env->use_fake_renderer());
260
261 ASSERT_TRUE(decoder_);
262 g_env->configure_cb()->OnCodecReady(decoder_.get());
263
264 decoder_->Rewind();
265
266 ASSERT_TRUE(decoder_->Configure());
267 ASSERT_TRUE(decoder_->Start());
268
269 decoder_->AddOutputBufferReadyCb(std::bind(&C2VideoDecoderE2ETest::CountFrame, this,
270 std::placeholders::_1, std::placeholders::_2,
271 std::placeholders::_3));
272 decoder_->AddOutputFormatChangedCb(std::bind(&C2VideoDecoderE2ETest::VerifyOutputFormat,
273 this, std::placeholders::_1,
274 std::placeholders::_2, std::placeholders::_3));
275 }
276
TearDown()277 void TearDown() override {
278 if (!decoder_) {
279 return;
280 }
281 EXPECT_TRUE(decoder_->Stop());
282
283 EXPECT_EQ(g_env->visible_size().width, output_format_.visible_size.width);
284 EXPECT_EQ(g_env->visible_size().height, output_format_.visible_size.height);
285
286 if (g_env->loop()) {
287 EXPECT_EQ(decoded_frames_ % g_env->num_frames(), 0);
288 } else {
289 EXPECT_EQ(g_env->num_frames(), decoded_frames_);
290 }
291
292 g_env->configure_cb()->OnCodecReady(nullptr);
293 decoder_.reset();
294 }
295
296 void TestFPSBody();
297
298 // The wrapper of the mediacodec decoder.
299 std::unique_ptr<MediaCodecDecoder> decoder_;
300
301 // The counter of obtained decoded output frames.
302 int decoded_frames_ = 0;
303 // This records formats from output format change.
304 OutputFormat output_format_;
305 };
306
307 class C2VideoDecoderSurfaceE2ETest : public C2VideoDecoderE2ETest {
308 private:
useSurface()309 bool useSurface() override { return true; }
renderOnRelease()310 bool renderOnRelease() override { return true; }
311 };
312
313 class C2VideoDecoderSurfaceNoRenderE2ETest : public C2VideoDecoderE2ETest {
314 private:
useSurface()315 bool useSurface() override { return true; }
renderOnRelease()316 bool renderOnRelease() override { return false; }
317 };
318
319 class C2VideoDecoderByteBufferE2ETest : public C2VideoDecoderE2ETest {
320 private:
useSurface()321 bool useSurface() override { return false; }
renderOnRelease()322 bool renderOnRelease() override { return false; }
323 };
324
TEST_F(C2VideoDecoderByteBufferE2ETest,TestSimpleDecode)325 TEST_F(C2VideoDecoderByteBufferE2ETest, TestSimpleDecode) {
326 VideoFrameValidator video_frame_validator;
327
328 ASSERT_TRUE(video_frame_validator.SetGoldenMD5File(g_env->GoldenMD5FilePath()))
329 << "Failed to open MD5 file: " << g_env->GoldenMD5FilePath();
330
331 decoder_->AddOutputBufferReadyCb(std::bind(&VideoFrameValidator::VerifyMD5,
332 &video_frame_validator, std::placeholders::_1,
333 std::placeholders::_2, std::placeholders::_3));
334
335 if (video_frame_validator.SetOutputFile(g_env->output_frames_path())) {
336 decoder_->AddOutputBufferReadyCb(std::bind(&VideoFrameValidator::OutputToFile,
337 &video_frame_validator, std::placeholders::_1,
338 std::placeholders::_2, std::placeholders::_3));
339 }
340
341 decoder_->AddOutputFormatChangedCb(std::bind(&VideoFrameValidator::UpdateOutputFormat,
342 &video_frame_validator, std::placeholders::_1,
343 std::placeholders::_2, std::placeholders::_3));
344
345 EXPECT_TRUE(decoder_->Decode());
346 }
347
TestFPSBody()348 void C2VideoDecoderE2ETest::TestFPSBody() {
349 FPSCalculator fps_calculator;
350 auto callback = [&fps_calculator](const uint8_t* /* data */, size_t /* buffer_size */,
351 int /* output_index */) {
352 ASSERT_TRUE(fps_calculator.RecordFrameTimeDiff());
353 };
354
355 decoder_->AddOutputBufferReadyCb(callback);
356
357 EXPECT_TRUE(decoder_->Decode());
358
359 double fps = fps_calculator.CalculateFPS();
360 printf("[LOG] Measured decoder FPS: %.4f\n", fps);
361 EXPECT_GE(fps, static_cast<double>(g_env->min_fps_no_render()));
362 printf("[LOG] Dropped frames rate: %lf\n", decoder_->dropped_frame_rate());
363 }
364
TEST_F(C2VideoDecoderSurfaceE2ETest,TestFPS)365 TEST_F(C2VideoDecoderSurfaceE2ETest, TestFPS) {
366 TestFPSBody();
367 }
368
TEST_F(C2VideoDecoderSurfaceNoRenderE2ETest,TestFPS)369 TEST_F(C2VideoDecoderSurfaceNoRenderE2ETest, TestFPS) {
370 TestFPSBody();
371 }
372
373 } // namespace android
374
GetOption(int argc,char ** argv,std::string * test_video_data,std::string * output_frames_path,bool * loop,bool * use_sw_decoder,bool * use_fake_renderer)375 bool GetOption(int argc, char** argv, std::string* test_video_data, std::string* output_frames_path,
376 bool* loop, bool* use_sw_decoder, bool* use_fake_renderer) {
377 const char* const optstring = "t:o:";
378 static const struct option opts[] = {
379 {"test_video_data", required_argument, nullptr, 't'},
380 {"output_frames_path", required_argument, nullptr, 'o'},
381 {"loop", no_argument, nullptr, 'l'},
382 {"use_sw_decoder", no_argument, nullptr, 's'},
383 {"fake_renderer", no_argument, nullptr, 'f'},
384 {nullptr, 0, nullptr, 0},
385 };
386
387 int opt;
388 optind = 1;
389 while ((opt = getopt_long(argc, argv, optstring, opts, nullptr)) != -1) {
390 switch (opt) {
391 case 't':
392 *test_video_data = optarg;
393 break;
394 case 'o':
395 *output_frames_path = optarg;
396 break;
397 case 'l':
398 *loop = true;
399 break;
400 case 's':
401 *use_sw_decoder = true;
402 break;
403 case 'f':
404 *use_fake_renderer = true;
405 break;
406 default:
407 printf("[WARN] Unknown option: getopt_long() returned code 0x%x.\n", opt);
408 break;
409 }
410 }
411
412 if (test_video_data->empty()) {
413 printf("[ERR] Please assign test video data by --test_video_data\n");
414 return false;
415 }
416 return true;
417 }
418
RunDecoderTests(char ** test_args,int test_args_count,ANativeWindow * surface,android::ConfigureCallback * cb)419 int RunDecoderTests(char** test_args, int test_args_count, ANativeWindow* surface,
420 android::ConfigureCallback* cb) {
421 std::string test_video_data;
422 std::string output_frames_path;
423 bool loop = false;
424 bool use_sw_decoder = false;
425 bool use_fake_renderer = false;
426 if (!GetOption(test_args_count, test_args, &test_video_data, &output_frames_path, &loop,
427 &use_sw_decoder, &use_fake_renderer)) {
428 ALOGE("GetOption failed");
429 return EXIT_FAILURE;
430 }
431
432 if (android::g_env == nullptr) {
433 android::g_env = reinterpret_cast<android::C2VideoDecoderTestEnvironment*>(
434 testing::AddGlobalTestEnvironment(new android::C2VideoDecoderTestEnvironment(
435 loop, use_sw_decoder, use_fake_renderer, test_video_data,
436 output_frames_path, surface, cb)));
437 } else {
438 ALOGE("Trying to reuse test process");
439 return EXIT_FAILURE;
440 }
441 testing::InitGoogleTest(&test_args_count, test_args);
442
443 return RUN_ALL_TESTS();
444 }
445