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