1 // Copyright 2020 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 #include <getopt.h>
6 #include <inttypes.h>
7 
8 #include <algorithm>
9 #include <cmath>
10 #include <fstream>
11 #include <functional>
12 #include <map>
13 #include <memory>
14 #include <string>
15 #include <vector>
16 
17 #include <gtest/gtest.h>
18 #include <log/log.h>
19 
20 #include "common.h"
21 #include "e2e_test_jni.h"
22 #include "mediacodec_encoder.h"
23 
24 namespace android {
25 
26 // Environment to store test stream data for all test cases.
27 class C2VideoEncoderTestEnvironment;
28 
29 namespace {
30 // Default initial bitrate.
31 const unsigned int kDefaultBitrate = 2000000;
32 // Default ratio of requested_subsequent_bitrate_ to initial_bitrate
33 // (see test parameters below) if one is not provided.
34 const double kDefaultSubsequentBitrateRatio = 2.0;
35 // Default initial framerate.
36 const unsigned int kDefaultFramerate = 30;
37 // Default ratio of requested_subsequent_framerate_ to initial_framerate
38 // (see test parameters below) if one is not provided.
39 const double kDefaultSubsequentFramerateRatio = 0.1;
40 // Tolerance factor for how encoded bitrate can differ from requested bitrate.
41 const double kBitrateTolerance = 0.1;
42 // The minimum number of encoded frames. If the frame number of the input stream
43 // is less than this value, then we circularly encode the input stream.
44 constexpr size_t kMinNumEncodedFrames = 300;
45 // The percentiles to measure for encode latency.
46 constexpr int kLoggedLatencyPercentiles[] = {50, 75, 95};
47 
48 C2VideoEncoderTestEnvironment* g_env;
49 }  // namespace
50 
51 // Store the arguments passed from command line.
52 struct CmdlineArgs {
53     std::string test_stream_data;
54     bool run_at_fps = false;
55     size_t num_encoded_frames = 0;
56     bool use_sw_encoder = false;
57 };
58 
59 class C2VideoEncoderTestEnvironment : public testing::Environment {
60 public:
C2VideoEncoderTestEnvironment(const CmdlineArgs & args,ConfigureCallback * cb)61     explicit C2VideoEncoderTestEnvironment(const CmdlineArgs& args, ConfigureCallback* cb)
62           : args_(args), configure_cb_(cb) {}
63 
SetUp()64     void SetUp() override { ParseTestStreamData(); }
65 
66     // The syntax of test stream is:
67     // "input_file_path:width:height:profile:output_file_path:requested_bitrate
68     //  :requested_framerate:requestedSubsequentBitrate
69     //  :requestedSubsequentFramerate:pixelFormat"
70     // - |input_file_path| is YUV raw stream. Its format must be |pixelFormat|
71     //   (see http://www.fourcc.org/yuv.php#IYUV).
72     // - |width| and |height| are in pixels.
73     // - |profile| to encode into (values of VideoCodecProfile).
74     // - |output_file_path| filename to save the encoded stream to (optional).
75     //   The format for H264 is Annex-B byte stream.
76     // - |requested_bitrate| requested bitrate in bits per second.
77     //   Bitrate is only forced for tests that test bitrate.
78     // - |requested_framerate| requested initial framerate.
79     // - |requestedSubsequentBitrate| bitrate to switch to in the middle of the
80     //   stream. NOTE: This value is not supported yet.
81     // - |requestedSubsequentFramerate| framerate to switch to in the middle of
82     //   the stream. NOTE: This value is not supported yet.
83     // - |pixelFormat| is the VideoPixelFormat of |input_file_path|.
84     //   NOTE: Only PIXEL_FORMAT_I420 is supported. Now we just ignore this value.
ParseTestStreamData()85     void ParseTestStreamData() {
86         std::vector<std::string> fields = SplitString(args_.test_stream_data, ':');
87         ALOG_ASSERT(fields.size() >= 3U, "The fields of test_stream_data is not enough: %s",
88                     args_.test_stream_data.c_str());
89         ALOG_ASSERT(fields.size() <= 10U, "The fields of test_stream_data is too much: %s",
90                     args_.test_stream_data.c_str());
91 
92         input_file_path_ = fields[0];
93         int width = std::stoi(fields[1]);
94         int height = std::stoi(fields[2]);
95         visible_size_ = Size(width, height);
96         ASSERT_FALSE(visible_size_.IsEmpty());
97 
98         if (fields.size() >= 4 && !fields[3].empty()) {
99             int profile = stoi(fields[3]);
100             switch (profile) {
101             case VideoCodecProfile::H264PROFILE_MAIN:
102                 codec_ = VideoCodecType::H264;
103                 break;
104             case VideoCodecProfile::VP8PROFILE_ANY:
105                 codec_ = VideoCodecType::VP8;
106                 break;
107             case VideoCodecProfile::VP9PROFILE_PROFILE0:
108                 codec_ = VideoCodecType::VP9;
109                 break;
110             default:
111                 printf("[WARN] Only H264PROFILE_MAIN, VP8PROFILE_ANY and VP9PROFILE_PROFILE0 are"
112                        "supported.\n");
113                 codec_ = VideoCodecType::H264;
114             }
115         }
116 
117         if (fields.size() >= 5 && !fields[4].empty()) {
118             output_file_path_ = fields[4];
119         }
120 
121         if (fields.size() >= 6 && !fields[5].empty()) {
122             requested_bitrate_ = stoi(fields[5]);
123             ASSERT_GT(requested_bitrate_, 0);
124         } else {
125             requested_bitrate_ = kDefaultBitrate;
126         }
127 
128         if (fields.size() >= 7 && !fields[6].empty()) {
129             requested_framerate_ = std::stoi(fields[6]);
130             ASSERT_GT(requested_framerate_, 0);
131         } else {
132             requested_framerate_ = kDefaultFramerate;
133         }
134 
135         if (fields.size() >= 8 && !fields[7].empty()) {
136             requested_subsequent_bitrate_ = std::stoi(fields[7]);
137             ASSERT_GT(requested_subsequent_bitrate_, 0);
138         } else {
139             requested_subsequent_bitrate_ = requested_bitrate_ * kDefaultSubsequentBitrateRatio;
140         }
141 
142         if (fields.size() >= 9 && !fields[8].empty()) {
143             requested_subsequent_framerate_ = std::stoi(fields[8]);
144             ASSERT_GT(requested_subsequent_framerate_, 0);
145         } else {
146             requested_subsequent_framerate_ =
147                     requested_framerate_ * kDefaultSubsequentFramerateRatio;
148         }
149 
150         if (fields.size() >= 10 && !fields[9].empty()) {
151             int format = std::stoi(fields[9]);
152             if (format != 1 /* PIXEL_FORMAT_I420 */) printf("[WARN] Only I420 is suppported.\n");
153         }
154     }
155 
visible_size() const156     Size visible_size() const { return visible_size_; }
codec() const157     VideoCodecType codec() const { return codec_; }
input_file_path() const158     std::string input_file_path() const { return input_file_path_; }
output_file_path() const159     std::string output_file_path() const { return output_file_path_; }
requested_bitrate() const160     int requested_bitrate() const { return requested_bitrate_; }
requested_framerate() const161     int requested_framerate() const { return requested_framerate_; }
requested_subsequent_bitrate() const162     int requested_subsequent_bitrate() const { return requested_subsequent_bitrate_; }
requested_subsequent_framerate() const163     int requested_subsequent_framerate() const { return requested_subsequent_framerate_; }
164 
run_at_fps() const165     bool run_at_fps() const { return args_.run_at_fps; }
num_encoded_frames() const166     size_t num_encoded_frames() const { return args_.num_encoded_frames; }
use_sw_encoder() const167     bool use_sw_encoder() const { return args_.use_sw_encoder; }
168 
configure_cb() const169     ConfigureCallback* configure_cb() const { return configure_cb_; }
170 
171 private:
172     const CmdlineArgs args_;
173     ConfigureCallback* configure_cb_;
174 
175     Size visible_size_;
176     VideoCodecType codec_;
177     std::string input_file_path_;
178     std::string output_file_path_;
179 
180     int requested_bitrate_;
181     int requested_framerate_;
182     int requested_subsequent_bitrate_;
183     int requested_subsequent_framerate_;
184 };
185 
186 class C2VideoEncoderE2ETest : public testing::Test {
187 public:
188     // Callback functions of getting output buffers from encoder.
WriteOutputBufferToFile(VideoCodecType type,const uint8_t * data,const AMediaCodecBufferInfo & info)189     void WriteOutputBufferToFile(VideoCodecType type, const uint8_t* data,
190                                  const AMediaCodecBufferInfo& info) {
191         if (output_file_.IsOpen() && !output_file_.WriteFrame(info.size, data)) {
192             printf("[ERR] Failed to write encoded buffer into file.\n");
193         }
194     }
195 
AccumulateOutputBufferSize(const uint8_t *,const AMediaCodecBufferInfo & info)196     void AccumulateOutputBufferSize(const uint8_t* /* data */, const AMediaCodecBufferInfo& info) {
197         total_output_buffer_size_ += info.size;
198     }
199 
200 protected:
SetUp()201     void SetUp() override {
202         encoder_ = MediaCodecEncoder::Create(g_env->input_file_path(), g_env->codec(),
203                                              g_env->visible_size(), g_env->use_sw_encoder());
204         ASSERT_TRUE(encoder_);
205         g_env->configure_cb()->OnCodecReady(encoder_.get());
206 
207         encoder_->Rewind();
208 
209         ASSERT_TRUE(encoder_->Configure(static_cast<int32_t>(g_env->requested_bitrate()),
210                                         static_cast<int32_t>(g_env->requested_framerate())));
211         ASSERT_TRUE(encoder_->Start());
212     }
213 
TearDown()214     void TearDown() override {
215         EXPECT_TRUE(encoder_->Stop());
216 
217         output_file_.Close();
218         encoder_.reset();
219     }
220 
CreateOutputFile()221     bool CreateOutputFile() {
222         if (g_env->output_file_path().empty()) return false;
223 
224         if (!output_file_.Open(g_env->output_file_path(), g_env->codec())) {
225             printf("[ERR] Failed to open file: %s\n", g_env->output_file_path().c_str());
226             return false;
227         }
228         if (!output_file_.WriteHeader(g_env->visible_size(), g_env->requested_framerate(), 0)) {
229             printf("[ERR] Failed to write file header\n");
230             return false;
231         }
232 
233         return true;
234     }
235 
CalculateAverageBitrate(size_t num_frames,unsigned int framerate) const236     double CalculateAverageBitrate(size_t num_frames, unsigned int framerate) const {
237         return 1.0f * total_output_buffer_size_ * 8 / num_frames * framerate;
238     }
239 
240     // The wrapper of the mediacodec encoder.
241     std::unique_ptr<MediaCodecEncoder> encoder_;
242 
243     // The output file to write the encoded video bitstream.
244     OutputFile output_file_;
245     // Used to accumulate the output buffer size.
246     size_t total_output_buffer_size_;
247 };
248 
249 class LatencyRecorder {
250 public:
OnEncodeInputBuffer(uint64_t time_us)251     void OnEncodeInputBuffer(uint64_t time_us) {
252         auto res = start_times_.insert(std::make_pair(time_us, GetNowUs()));
253         ASSERT_TRUE(res.second);
254     }
255 
OnOutputBufferReady(const uint8_t *,const AMediaCodecBufferInfo & info)256     void OnOutputBufferReady(const uint8_t* /* data */, const AMediaCodecBufferInfo& info) {
257         // Ignore the CSD buffer and the empty EOS buffer.
258         if (!(info.flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) && info.size != 0)
259             end_times_[info.presentationTimeUs] = GetNowUs();
260     }
261 
PrintResult() const262     void PrintResult() const {
263         std::vector<int64_t> latency_times;
264         for (auto const& start_kv : start_times_) {
265             auto end_it = end_times_.find(start_kv.first);
266             ASSERT_TRUE(end_it != end_times_.end());
267             latency_times.push_back(end_it->second - start_kv.second);
268         }
269         std::sort(latency_times.begin(), latency_times.end());
270 
271         for (int percentile : kLoggedLatencyPercentiles) {
272             size_t index =
273                     static_cast<size_t>(std::ceil(0.01f * percentile * latency_times.size())) - 1;
274             printf("Encode latency for the %dth percentile: %" PRId64 " us\n", percentile,
275                    latency_times[index]);
276         }
277     }
278 
279 private:
280     // Measured time of enqueueing input buffers and dequeueing output buffers.
281     // The key is the timestamp of the frame.
282     std::map<uint64_t, int64_t> start_times_;
283     std::map<uint64_t, int64_t> end_times_;
284 };
285 
TEST_F(C2VideoEncoderE2ETest,TestSimpleEncode)286 TEST_F(C2VideoEncoderE2ETest, TestSimpleEncode) {
287     // Write the output buffers to file.
288     if (CreateOutputFile()) {
289         encoder_->SetOutputBufferReadyCb(std::bind(&C2VideoEncoderE2ETest::WriteOutputBufferToFile,
290                                                    this, g_env->codec(), std::placeholders::_1,
291                                                    std::placeholders::_2));
292     }
293     encoder_->set_run_at_fps(g_env->run_at_fps());
294     if (g_env->num_encoded_frames()) encoder_->set_num_encoded_frames(g_env->num_encoded_frames());
295 
296     EXPECT_TRUE(encoder_->Encode());
297 }
298 
TEST_F(C2VideoEncoderE2ETest,TestBitrate)299 TEST_F(C2VideoEncoderE2ETest, TestBitrate) {
300     // Ensure the number of encoded frames is enough for bitrate test case.
301     encoder_->set_num_encoded_frames(
302             std::max(encoder_->num_encoded_frames(), kMinNumEncodedFrames));
303 
304     // Accumulate the size of the output buffers.
305     total_output_buffer_size_ = 0;
306     encoder_->SetOutputBufferReadyCb(std::bind(&C2VideoEncoderE2ETest::AccumulateOutputBufferSize,
307                                                this, std::placeholders::_1, std::placeholders::_2));
308 
309     // TODO(akahuang): Verify bitrate switch at the middle of stream.
310     EXPECT_TRUE(encoder_->Encode());
311 
312     double measured_bitrate =
313             CalculateAverageBitrate(encoder_->num_encoded_frames(), g_env->requested_framerate());
314     EXPECT_NEAR(measured_bitrate, g_env->requested_bitrate(),
315                 kBitrateTolerance * g_env->requested_bitrate());
316 }
317 
TEST_F(C2VideoEncoderE2ETest,PerfFPS)318 TEST_F(C2VideoEncoderE2ETest, PerfFPS) {
319     FPSCalculator fps_calculator;
320     auto callback = [&fps_calculator](const uint8_t* /* data */,
321                                       const AMediaCodecBufferInfo& /* info */) {
322         ASSERT_TRUE(fps_calculator.RecordFrameTimeDiff());
323     };
324 
325     // Record frame time differences of the output buffers.
326     encoder_->SetOutputBufferReadyCb(callback);
327 
328     EXPECT_TRUE(encoder_->Encode());
329 
330     double measured_fps = fps_calculator.CalculateFPS();
331     printf("Measured encoder FPS: %.4f\n", measured_fps);
332 }
333 
TEST_F(C2VideoEncoderE2ETest,PerfLatency)334 TEST_F(C2VideoEncoderE2ETest, PerfLatency) {
335     LatencyRecorder recorder;
336     encoder_->SetEncodeInputBufferCb(
337             std::bind(&LatencyRecorder::OnEncodeInputBuffer, &recorder, std::placeholders::_1));
338     encoder_->SetOutputBufferReadyCb(std::bind(&LatencyRecorder::OnOutputBufferReady, &recorder,
339                                                std::placeholders::_1, std::placeholders::_2));
340     encoder_->set_run_at_fps(true);
341 
342     EXPECT_TRUE(encoder_->Encode());
343 
344     recorder.PrintResult();
345 }
346 
347 }  // namespace android
348 
GetOption(int argc,char ** argv,android::CmdlineArgs * args)349 bool GetOption(int argc, char** argv, android::CmdlineArgs* args) {
350     const char* const optstring = "t:rn:";
351     static const struct option opts[] = {
352             {"test_stream_data", required_argument, nullptr, 't'},
353             {"run_at_fps", no_argument, nullptr, 'r'},
354             {"num_encoded_frames", required_argument, nullptr, 'n'},
355             {"use_sw_encoder", no_argument, nullptr, 's'},
356             {nullptr, 0, nullptr, 0},
357     };
358 
359     int opt;
360     while ((opt = getopt_long(argc, argv, optstring, opts, nullptr)) != -1) {
361         switch (opt) {
362         case 't':
363             args->test_stream_data = optarg;
364             break;
365         case 'r':
366             args->run_at_fps = true;
367             break;
368         case 'n':
369             args->num_encoded_frames = static_cast<size_t>(atoi(optarg));
370             break;
371         case 's':
372             args->use_sw_encoder = true;
373             break;
374         default:
375             printf("[WARN] Unknown option: getopt_long() returned code 0x%x.\n", opt);
376             break;
377         }
378     }
379 
380     if (args->test_stream_data.empty()) {
381         printf("[ERR] Please assign test stream data by --test_stream_data\n");
382         return false;
383     }
384     return true;
385 }
386 
RunEncoderTests(char ** test_args,int test_args_count,android::ConfigureCallback * cb)387 int RunEncoderTests(char** test_args, int test_args_count, android::ConfigureCallback* cb) {
388     android::CmdlineArgs args;
389     if (!GetOption(test_args_count, test_args, &args)) return EXIT_FAILURE;
390 
391     android::g_env = reinterpret_cast<android::C2VideoEncoderTestEnvironment*>(
392             testing::AddGlobalTestEnvironment(
393                     new android::C2VideoEncoderTestEnvironment(args, cb)));
394     testing::InitGoogleTest(&test_args_count, test_args);
395     return RUN_ALL_TESTS();
396 }
397