• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "modules/video_coding/codecs/test/videocodec_test_fixture_impl.h"
12 
13 #include <stdint.h>
14 #include <stdio.h>
15 
16 #include <algorithm>
17 #include <cmath>
18 #include <memory>
19 #include <string>
20 #include <utility>
21 #include <vector>
22 
23 #include "absl/types/optional.h"
24 #include "api/array_view.h"
25 #include "api/video/video_bitrate_allocation.h"
26 #include "api/video_codecs/sdp_video_format.h"
27 #include "api/video_codecs/video_codec.h"
28 #include "api/video_codecs/video_decoder.h"
29 #include "api/video_codecs/video_encoder_config.h"
30 #include "common_video/h264/h264_common.h"
31 #include "media/base/h264_profile_level_id.h"
32 #include "media/base/media_constants.h"
33 #include "media/engine/internal_decoder_factory.h"
34 #include "media/engine/internal_encoder_factory.h"
35 #include "media/engine/simulcast.h"
36 #include "modules/video_coding/codecs/h264/include/h264_globals.h"
37 #include "modules/video_coding/codecs/vp9/svc_config.h"
38 #include "modules/video_coding/utility/ivf_file_writer.h"
39 #include "rtc_base/checks.h"
40 #include "rtc_base/cpu_time.h"
41 #include "rtc_base/logging.h"
42 #include "rtc_base/strings/string_builder.h"
43 #include "rtc_base/time_utils.h"
44 #include "system_wrappers/include/cpu_info.h"
45 #include "system_wrappers/include/sleep.h"
46 #include "test/gtest.h"
47 #include "test/testsupport/file_utils.h"
48 #include "test/testsupport/frame_writer.h"
49 #include "test/testsupport/perf_test.h"
50 #include "test/video_codec_settings.h"
51 
52 namespace webrtc {
53 namespace test {
54 
55 using VideoStatistics = VideoCodecTestStats::VideoStatistics;
56 
57 namespace {
58 const int kBaseKeyFrameInterval = 3000;
59 const double kBitratePriority = 1.0;
60 const int kMaxFramerateFps = 30;
61 const int kMaxQp = 56;
62 
ConfigureSimulcast(VideoCodec * codec_settings)63 void ConfigureSimulcast(VideoCodec* codec_settings) {
64   const std::vector<webrtc::VideoStream> streams = cricket::GetSimulcastConfig(
65       /*min_layer=*/1, codec_settings->numberOfSimulcastStreams,
66       codec_settings->width, codec_settings->height, kBitratePriority, kMaxQp,
67       /* is_screenshare = */ false, true);
68 
69   for (size_t i = 0; i < streams.size(); ++i) {
70     SimulcastStream* ss = &codec_settings->simulcastStream[i];
71     ss->width = static_cast<uint16_t>(streams[i].width);
72     ss->height = static_cast<uint16_t>(streams[i].height);
73     ss->numberOfTemporalLayers =
74         static_cast<unsigned char>(*streams[i].num_temporal_layers);
75     ss->maxBitrate = streams[i].max_bitrate_bps / 1000;
76     ss->targetBitrate = streams[i].target_bitrate_bps / 1000;
77     ss->minBitrate = streams[i].min_bitrate_bps / 1000;
78     ss->qpMax = streams[i].max_qp;
79     ss->active = true;
80   }
81 }
82 
ConfigureSvc(VideoCodec * codec_settings)83 void ConfigureSvc(VideoCodec* codec_settings) {
84   RTC_CHECK_EQ(kVideoCodecVP9, codec_settings->codecType);
85 
86   const std::vector<SpatialLayer> layers = GetSvcConfig(
87       codec_settings->width, codec_settings->height, kMaxFramerateFps,
88       /*first_active_layer=*/0, codec_settings->VP9()->numberOfSpatialLayers,
89       codec_settings->VP9()->numberOfTemporalLayers,
90       /* is_screen_sharing = */ false);
91   ASSERT_EQ(codec_settings->VP9()->numberOfSpatialLayers, layers.size())
92       << "GetSvcConfig returned fewer spatial layers than configured.";
93 
94   for (size_t i = 0; i < layers.size(); ++i) {
95     codec_settings->spatialLayers[i] = layers[i];
96   }
97 }
98 
CodecSpecificToString(const VideoCodec & codec)99 std::string CodecSpecificToString(const VideoCodec& codec) {
100   char buf[1024];
101   rtc::SimpleStringBuilder ss(buf);
102   switch (codec.codecType) {
103     case kVideoCodecVP8:
104       ss << "complexity: " << static_cast<int>(codec.VP8().complexity);
105       ss << "\nnum_temporal_layers: "
106          << static_cast<int>(codec.VP8().numberOfTemporalLayers);
107       ss << "\ndenoising: " << codec.VP8().denoisingOn;
108       ss << "\nautomatic_resize: " << codec.VP8().automaticResizeOn;
109       ss << "\nframe_dropping: " << codec.VP8().frameDroppingOn;
110       ss << "\nkey_frame_interval: " << codec.VP8().keyFrameInterval;
111       break;
112     case kVideoCodecVP9:
113       ss << "complexity: " << static_cast<int>(codec.VP9().complexity);
114       ss << "\nnum_temporal_layers: "
115          << static_cast<int>(codec.VP9().numberOfTemporalLayers);
116       ss << "\nnum_spatial_layers: "
117          << static_cast<int>(codec.VP9().numberOfSpatialLayers);
118       ss << "\ndenoising: " << codec.VP9().denoisingOn;
119       ss << "\nframe_dropping: " << codec.VP9().frameDroppingOn;
120       ss << "\nkey_frame_interval: " << codec.VP9().keyFrameInterval;
121       ss << "\nadaptive_qp_mode: " << codec.VP9().adaptiveQpMode;
122       ss << "\nautomatic_resize: " << codec.VP9().automaticResizeOn;
123       ss << "\nflexible_mode: " << codec.VP9().flexibleMode;
124       break;
125     case kVideoCodecH264:
126       ss << "frame_dropping: " << codec.H264().frameDroppingOn;
127       ss << "\nkey_frame_interval: " << codec.H264().keyFrameInterval;
128       break;
129     default:
130       break;
131   }
132   return ss.str();
133 }
134 
RunEncodeInRealTime(const VideoCodecTestFixtureImpl::Config & config)135 bool RunEncodeInRealTime(const VideoCodecTestFixtureImpl::Config& config) {
136   if (config.measure_cpu || config.encode_in_real_time) {
137     return true;
138   }
139   return false;
140 }
141 
FilenameWithParams(const VideoCodecTestFixtureImpl::Config & config)142 std::string FilenameWithParams(
143     const VideoCodecTestFixtureImpl::Config& config) {
144   return config.filename + "_" + config.CodecName() + "_" +
145          std::to_string(config.codec_settings.startBitrate);
146 }
147 
148 }  // namespace
149 
150 VideoCodecTestFixtureImpl::Config::Config() = default;
151 
SetCodecSettings(std::string codec_name,size_t num_simulcast_streams,size_t num_spatial_layers,size_t num_temporal_layers,bool denoising_on,bool frame_dropper_on,bool spatial_resize_on,size_t width,size_t height)152 void VideoCodecTestFixtureImpl::Config::SetCodecSettings(
153     std::string codec_name,
154     size_t num_simulcast_streams,
155     size_t num_spatial_layers,
156     size_t num_temporal_layers,
157     bool denoising_on,
158     bool frame_dropper_on,
159     bool spatial_resize_on,
160     size_t width,
161     size_t height) {
162   this->codec_name = codec_name;
163   VideoCodecType codec_type = PayloadStringToCodecType(codec_name);
164   webrtc::test::CodecSettings(codec_type, &codec_settings);
165 
166   // TODO(brandtr): Move the setting of |width| and |height| to the tests, and
167   // DCHECK that they are set before initializing the codec instead.
168   codec_settings.width = static_cast<uint16_t>(width);
169   codec_settings.height = static_cast<uint16_t>(height);
170 
171   RTC_CHECK(num_simulcast_streams >= 1 &&
172             num_simulcast_streams <= kMaxSimulcastStreams);
173   RTC_CHECK(num_spatial_layers >= 1 && num_spatial_layers <= kMaxSpatialLayers);
174   RTC_CHECK(num_temporal_layers >= 1 &&
175             num_temporal_layers <= kMaxTemporalStreams);
176 
177   // Simulcast is only available with VP8.
178   RTC_CHECK(num_simulcast_streams < 2 || codec_type == kVideoCodecVP8);
179 
180   // Spatial scalability is only available with VP9.
181   RTC_CHECK(num_spatial_layers < 2 || codec_type == kVideoCodecVP9);
182 
183   // Some base code requires numberOfSimulcastStreams to be set to zero
184   // when simulcast is not used.
185   codec_settings.numberOfSimulcastStreams =
186       num_simulcast_streams <= 1 ? 0
187                                  : static_cast<uint8_t>(num_simulcast_streams);
188 
189   switch (codec_settings.codecType) {
190     case kVideoCodecVP8:
191       codec_settings.VP8()->numberOfTemporalLayers =
192           static_cast<uint8_t>(num_temporal_layers);
193       codec_settings.VP8()->denoisingOn = denoising_on;
194       codec_settings.VP8()->automaticResizeOn = spatial_resize_on;
195       codec_settings.VP8()->frameDroppingOn = frame_dropper_on;
196       codec_settings.VP8()->keyFrameInterval = kBaseKeyFrameInterval;
197       break;
198     case kVideoCodecVP9:
199       codec_settings.VP9()->numberOfTemporalLayers =
200           static_cast<uint8_t>(num_temporal_layers);
201       codec_settings.VP9()->denoisingOn = denoising_on;
202       codec_settings.VP9()->frameDroppingOn = frame_dropper_on;
203       codec_settings.VP9()->keyFrameInterval = kBaseKeyFrameInterval;
204       codec_settings.VP9()->automaticResizeOn = spatial_resize_on;
205       codec_settings.VP9()->numberOfSpatialLayers =
206           static_cast<uint8_t>(num_spatial_layers);
207       break;
208     case kVideoCodecAV1:
209       codec_settings.qpMax = 63;
210       break;
211     case kVideoCodecH264:
212       codec_settings.H264()->frameDroppingOn = frame_dropper_on;
213       codec_settings.H264()->keyFrameInterval = kBaseKeyFrameInterval;
214       break;
215     default:
216       break;
217   }
218 
219   if (codec_settings.numberOfSimulcastStreams > 1) {
220     ConfigureSimulcast(&codec_settings);
221   } else if (codec_settings.codecType == kVideoCodecVP9 &&
222              codec_settings.VP9()->numberOfSpatialLayers > 1) {
223     ConfigureSvc(&codec_settings);
224   }
225 }
226 
NumberOfCores() const227 size_t VideoCodecTestFixtureImpl::Config::NumberOfCores() const {
228   return use_single_core ? 1 : CpuInfo::DetectNumberOfCores();
229 }
230 
NumberOfTemporalLayers() const231 size_t VideoCodecTestFixtureImpl::Config::NumberOfTemporalLayers() const {
232   if (codec_settings.codecType == kVideoCodecVP8) {
233     return codec_settings.VP8().numberOfTemporalLayers;
234   } else if (codec_settings.codecType == kVideoCodecVP9) {
235     return codec_settings.VP9().numberOfTemporalLayers;
236   } else {
237     return 1;
238   }
239 }
240 
NumberOfSpatialLayers() const241 size_t VideoCodecTestFixtureImpl::Config::NumberOfSpatialLayers() const {
242   if (codec_settings.codecType == kVideoCodecVP9) {
243     return codec_settings.VP9().numberOfSpatialLayers;
244   } else {
245     return 1;
246   }
247 }
248 
NumberOfSimulcastStreams() const249 size_t VideoCodecTestFixtureImpl::Config::NumberOfSimulcastStreams() const {
250   return codec_settings.numberOfSimulcastStreams;
251 }
252 
ToString() const253 std::string VideoCodecTestFixtureImpl::Config::ToString() const {
254   std::string codec_type = CodecTypeToPayloadString(codec_settings.codecType);
255   rtc::StringBuilder ss;
256   ss << "test_name: " << test_name;
257   ss << "\nfilename: " << filename;
258   ss << "\nnum_frames: " << num_frames;
259   ss << "\nmax_payload_size_bytes: " << max_payload_size_bytes;
260   ss << "\ndecode: " << decode;
261   ss << "\nuse_single_core: " << use_single_core;
262   ss << "\nmeasure_cpu: " << measure_cpu;
263   ss << "\nnum_cores: " << NumberOfCores();
264   ss << "\ncodec_type: " << codec_type;
265   ss << "\n\n--> codec_settings";
266   ss << "\nwidth: " << codec_settings.width;
267   ss << "\nheight: " << codec_settings.height;
268   ss << "\nmax_framerate_fps: " << codec_settings.maxFramerate;
269   ss << "\nstart_bitrate_kbps: " << codec_settings.startBitrate;
270   ss << "\nmax_bitrate_kbps: " << codec_settings.maxBitrate;
271   ss << "\nmin_bitrate_kbps: " << codec_settings.minBitrate;
272   ss << "\nmax_qp: " << codec_settings.qpMax;
273   ss << "\nnum_simulcast_streams: "
274      << static_cast<int>(codec_settings.numberOfSimulcastStreams);
275   ss << "\n\n--> codec_settings." << codec_type;
276   ss << "\n" << CodecSpecificToString(codec_settings);
277   if (codec_settings.numberOfSimulcastStreams > 1) {
278     for (int i = 0; i < codec_settings.numberOfSimulcastStreams; ++i) {
279       ss << "\n\n--> codec_settings.simulcastStream[" << i << "]";
280       const SimulcastStream& simulcast_stream =
281           codec_settings.simulcastStream[i];
282       ss << "\nwidth: " << simulcast_stream.width;
283       ss << "\nheight: " << simulcast_stream.height;
284       ss << "\nnum_temporal_layers: "
285          << static_cast<int>(simulcast_stream.numberOfTemporalLayers);
286       ss << "\nmin_bitrate_kbps: " << simulcast_stream.minBitrate;
287       ss << "\ntarget_bitrate_kbps: " << simulcast_stream.targetBitrate;
288       ss << "\nmax_bitrate_kbps: " << simulcast_stream.maxBitrate;
289       ss << "\nmax_qp: " << simulcast_stream.qpMax;
290       ss << "\nactive: " << simulcast_stream.active;
291     }
292   }
293   ss << "\n";
294   return ss.Release();
295 }
296 
CodecName() const297 std::string VideoCodecTestFixtureImpl::Config::CodecName() const {
298   std::string name = codec_name;
299   if (name.empty()) {
300     name = CodecTypeToPayloadString(codec_settings.codecType);
301   }
302   if (codec_settings.codecType == kVideoCodecH264) {
303     if (h264_codec_settings.profile == H264::kProfileConstrainedHigh) {
304       return name + "-CHP";
305     } else {
306       RTC_DCHECK_EQ(h264_codec_settings.profile,
307                     H264::kProfileConstrainedBaseline);
308       return name + "-CBP";
309     }
310   }
311   return name;
312 }
313 
314 // TODO(kthelgason): Move this out of the test fixture impl and
315 // make available as a shared utility class.
CheckEncodedFrame(webrtc::VideoCodecType codec,const EncodedImage & encoded_frame) const316 void VideoCodecTestFixtureImpl::H264KeyframeChecker::CheckEncodedFrame(
317     webrtc::VideoCodecType codec,
318     const EncodedImage& encoded_frame) const {
319   EXPECT_EQ(kVideoCodecH264, codec);
320   bool contains_sps = false;
321   bool contains_pps = false;
322   bool contains_idr = false;
323   const std::vector<webrtc::H264::NaluIndex> nalu_indices =
324       webrtc::H264::FindNaluIndices(encoded_frame.data(), encoded_frame.size());
325   for (const webrtc::H264::NaluIndex& index : nalu_indices) {
326     webrtc::H264::NaluType nalu_type = webrtc::H264::ParseNaluType(
327         encoded_frame.data()[index.payload_start_offset]);
328     if (nalu_type == webrtc::H264::NaluType::kSps) {
329       contains_sps = true;
330     } else if (nalu_type == webrtc::H264::NaluType::kPps) {
331       contains_pps = true;
332     } else if (nalu_type == webrtc::H264::NaluType::kIdr) {
333       contains_idr = true;
334     }
335   }
336   if (encoded_frame._frameType == VideoFrameType::kVideoFrameKey) {
337     EXPECT_TRUE(contains_sps) << "Keyframe should contain SPS.";
338     EXPECT_TRUE(contains_pps) << "Keyframe should contain PPS.";
339     EXPECT_TRUE(contains_idr) << "Keyframe should contain IDR.";
340   } else if (encoded_frame._frameType == VideoFrameType::kVideoFrameDelta) {
341     EXPECT_FALSE(contains_sps) << "Delta frame should not contain SPS.";
342     EXPECT_FALSE(contains_pps) << "Delta frame should not contain PPS.";
343     EXPECT_FALSE(contains_idr) << "Delta frame should not contain IDR.";
344   } else {
345     RTC_NOTREACHED();
346   }
347 }
348 
349 class VideoCodecTestFixtureImpl::CpuProcessTime final {
350  public:
CpuProcessTime(const Config & config)351   explicit CpuProcessTime(const Config& config) : config_(config) {}
~CpuProcessTime()352   ~CpuProcessTime() {}
353 
Start()354   void Start() {
355     if (config_.measure_cpu) {
356       cpu_time_ -= rtc::GetProcessCpuTimeNanos();
357       wallclock_time_ -= rtc::SystemTimeNanos();
358     }
359   }
Stop()360   void Stop() {
361     if (config_.measure_cpu) {
362       cpu_time_ += rtc::GetProcessCpuTimeNanos();
363       wallclock_time_ += rtc::SystemTimeNanos();
364     }
365   }
Print() const366   void Print() const {
367     if (config_.measure_cpu) {
368       RTC_LOG(LS_INFO) << "cpu_usage_percent: "
369                        << GetUsagePercent() / config_.NumberOfCores();
370     }
371   }
372 
373  private:
GetUsagePercent() const374   double GetUsagePercent() const {
375     return static_cast<double>(cpu_time_) / wallclock_time_ * 100.0;
376   }
377 
378   const Config config_;
379   int64_t cpu_time_ = 0;
380   int64_t wallclock_time_ = 0;
381 };
382 
VideoCodecTestFixtureImpl(Config config)383 VideoCodecTestFixtureImpl::VideoCodecTestFixtureImpl(Config config)
384     : encoder_factory_(std::make_unique<InternalEncoderFactory>()),
385       decoder_factory_(std::make_unique<InternalDecoderFactory>()),
386       config_(config) {}
387 
VideoCodecTestFixtureImpl(Config config,std::unique_ptr<VideoDecoderFactory> decoder_factory,std::unique_ptr<VideoEncoderFactory> encoder_factory)388 VideoCodecTestFixtureImpl::VideoCodecTestFixtureImpl(
389     Config config,
390     std::unique_ptr<VideoDecoderFactory> decoder_factory,
391     std::unique_ptr<VideoEncoderFactory> encoder_factory)
392     : encoder_factory_(std::move(encoder_factory)),
393       decoder_factory_(std::move(decoder_factory)),
394       config_(config) {}
395 
396 VideoCodecTestFixtureImpl::~VideoCodecTestFixtureImpl() = default;
397 
398 // Processes all frames in the clip and verifies the result.
RunTest(const std::vector<RateProfile> & rate_profiles,const std::vector<RateControlThresholds> * rc_thresholds,const std::vector<QualityThresholds> * quality_thresholds,const BitstreamThresholds * bs_thresholds)399 void VideoCodecTestFixtureImpl::RunTest(
400     const std::vector<RateProfile>& rate_profiles,
401     const std::vector<RateControlThresholds>* rc_thresholds,
402     const std::vector<QualityThresholds>* quality_thresholds,
403     const BitstreamThresholds* bs_thresholds) {
404   RTC_DCHECK(!rate_profiles.empty());
405 
406   // To emulate operation on a production VideoStreamEncoder, we call the
407   // codecs on a task queue.
408   TaskQueueForTest task_queue("VidProc TQ");
409 
410   SetUpAndInitObjects(&task_queue, rate_profiles[0].target_kbps,
411                       rate_profiles[0].input_fps);
412   PrintSettings(&task_queue);
413   ProcessAllFrames(&task_queue, rate_profiles);
414   ReleaseAndCloseObjects(&task_queue);
415 
416   AnalyzeAllFrames(rate_profiles, rc_thresholds, quality_thresholds,
417                    bs_thresholds);
418 }
419 
ProcessAllFrames(TaskQueueForTest * task_queue,const std::vector<RateProfile> & rate_profiles)420 void VideoCodecTestFixtureImpl::ProcessAllFrames(
421     TaskQueueForTest* task_queue,
422     const std::vector<RateProfile>& rate_profiles) {
423   // Set initial rates.
424   auto rate_profile = rate_profiles.begin();
425   task_queue->PostTask([this, rate_profile] {
426     processor_->SetRates(rate_profile->target_kbps, rate_profile->input_fps);
427   });
428 
429   cpu_process_time_->Start();
430 
431   for (size_t frame_num = 0; frame_num < config_.num_frames; ++frame_num) {
432     auto next_rate_profile = std::next(rate_profile);
433     if (next_rate_profile != rate_profiles.end() &&
434         frame_num == next_rate_profile->frame_num) {
435       rate_profile = next_rate_profile;
436       task_queue->PostTask([this, rate_profile] {
437         processor_->SetRates(rate_profile->target_kbps,
438                              rate_profile->input_fps);
439       });
440     }
441 
442     task_queue->PostTask([this] { processor_->ProcessFrame(); });
443 
444     if (RunEncodeInRealTime(config_)) {
445       // Roughly pace the frames.
446       const int frame_duration_ms =
447           std::ceil(rtc::kNumMillisecsPerSec / rate_profile->input_fps);
448       SleepMs(frame_duration_ms);
449     }
450   }
451 
452   // Wait until we know that the last frame has been sent for encode.
453   task_queue->SendTask([] {}, RTC_FROM_HERE);
454 
455   // Give the VideoProcessor pipeline some time to process the last frame,
456   // and then release the codecs.
457   SleepMs(1 * rtc::kNumMillisecsPerSec);
458   cpu_process_time_->Stop();
459 }
460 
AnalyzeAllFrames(const std::vector<RateProfile> & rate_profiles,const std::vector<RateControlThresholds> * rc_thresholds,const std::vector<QualityThresholds> * quality_thresholds,const BitstreamThresholds * bs_thresholds)461 void VideoCodecTestFixtureImpl::AnalyzeAllFrames(
462     const std::vector<RateProfile>& rate_profiles,
463     const std::vector<RateControlThresholds>* rc_thresholds,
464     const std::vector<QualityThresholds>* quality_thresholds,
465     const BitstreamThresholds* bs_thresholds) {
466 
467   for (size_t rate_profile_idx = 0; rate_profile_idx < rate_profiles.size();
468        ++rate_profile_idx) {
469     const size_t first_frame_num = rate_profiles[rate_profile_idx].frame_num;
470     const size_t last_frame_num =
471         rate_profile_idx + 1 < rate_profiles.size()
472             ? rate_profiles[rate_profile_idx + 1].frame_num - 1
473             : config_.num_frames - 1;
474     RTC_CHECK(last_frame_num >= first_frame_num);
475 
476     VideoStatistics send_stat = stats_.SliceAndCalcAggregatedVideoStatistic(
477         first_frame_num, last_frame_num);
478     RTC_LOG(LS_INFO) << "==> Send stats";
479     RTC_LOG(LS_INFO) << send_stat.ToString("send_") << "\n";
480 
481     std::vector<VideoStatistics> layer_stats =
482         stats_.SliceAndCalcLayerVideoStatistic(first_frame_num, last_frame_num);
483     RTC_LOG(LS_INFO) << "==> Receive stats";
484     for (const auto& layer_stat : layer_stats) {
485       RTC_LOG(LS_INFO) << layer_stat.ToString("recv_") << "\n";
486 
487       // For perf dashboard.
488       char modifier_buf[256];
489       rtc::SimpleStringBuilder modifier(modifier_buf);
490       modifier << "_r" << rate_profile_idx << "_sl" << layer_stat.spatial_idx;
491 
492       auto PrintResultHelper = [&modifier, this](const std::string& measurement,
493                                                  double value,
494                                                  const std::string& units) {
495         PrintResult(measurement, modifier.str(), config_.test_name, value,
496                     units, /*important=*/false);
497       };
498 
499       if (layer_stat.temporal_idx == config_.NumberOfTemporalLayers() - 1) {
500         PrintResultHelper("enc_speed", layer_stat.enc_speed_fps, "fps");
501         PrintResultHelper("avg_key_frame_size",
502                           layer_stat.avg_key_frame_size_bytes, "bytes");
503         PrintResultHelper("num_key_frames", layer_stat.num_key_frames,
504                           "frames");
505         printf("\n");
506       }
507 
508       modifier << "tl" << layer_stat.temporal_idx;
509       PrintResultHelper("dec_speed", layer_stat.dec_speed_fps, "fps");
510       PrintResultHelper("avg_delta_frame_size",
511                         layer_stat.avg_delta_frame_size_bytes, "bytes");
512       PrintResultHelper("bitrate", layer_stat.bitrate_kbps, "kbps");
513       PrintResultHelper("framerate", layer_stat.framerate_fps, "fps");
514       PrintResultHelper("avg_psnr_y", layer_stat.avg_psnr_y, "dB");
515       PrintResultHelper("avg_psnr_u", layer_stat.avg_psnr_u, "dB");
516       PrintResultHelper("avg_psnr_v", layer_stat.avg_psnr_v, "dB");
517       PrintResultHelper("min_psnr_yuv", layer_stat.min_psnr, "dB");
518       PrintResultHelper("avg_qp", layer_stat.avg_qp, "");
519       printf("\n");
520       if (layer_stat.temporal_idx == config_.NumberOfTemporalLayers() - 1) {
521         printf("\n");
522       }
523     }
524 
525     const RateControlThresholds* rc_threshold =
526         rc_thresholds ? &(*rc_thresholds)[rate_profile_idx] : nullptr;
527     const QualityThresholds* quality_threshold =
528         quality_thresholds ? &(*quality_thresholds)[rate_profile_idx] : nullptr;
529 
530     VerifyVideoStatistic(send_stat, rc_threshold, quality_threshold,
531                          bs_thresholds,
532                          rate_profiles[rate_profile_idx].target_kbps,
533                          rate_profiles[rate_profile_idx].input_fps);
534   }
535 
536   if (config_.print_frame_level_stats) {
537     RTC_LOG(LS_INFO) << "==> Frame stats";
538     std::vector<VideoCodecTestStats::FrameStatistics> frame_stats =
539         stats_.GetFrameStatistics();
540     for (const auto& frame_stat : frame_stats) {
541       RTC_LOG(LS_INFO) << frame_stat.ToString();
542     }
543   }
544 
545   cpu_process_time_->Print();
546 }
547 
VerifyVideoStatistic(const VideoStatistics & video_stat,const RateControlThresholds * rc_thresholds,const QualityThresholds * quality_thresholds,const BitstreamThresholds * bs_thresholds,size_t target_bitrate_kbps,double input_framerate_fps)548 void VideoCodecTestFixtureImpl::VerifyVideoStatistic(
549     const VideoStatistics& video_stat,
550     const RateControlThresholds* rc_thresholds,
551     const QualityThresholds* quality_thresholds,
552     const BitstreamThresholds* bs_thresholds,
553     size_t target_bitrate_kbps,
554     double input_framerate_fps) {
555   if (rc_thresholds) {
556     const float bitrate_mismatch_percent =
557         100 * std::fabs(1.0f * video_stat.bitrate_kbps - target_bitrate_kbps) /
558         target_bitrate_kbps;
559     const float framerate_mismatch_percent =
560         100 * std::fabs(video_stat.framerate_fps - input_framerate_fps) /
561         input_framerate_fps;
562     EXPECT_LE(bitrate_mismatch_percent,
563               rc_thresholds->max_avg_bitrate_mismatch_percent);
564     EXPECT_LE(video_stat.time_to_reach_target_bitrate_sec,
565               rc_thresholds->max_time_to_reach_target_bitrate_sec);
566     EXPECT_LE(framerate_mismatch_percent,
567               rc_thresholds->max_avg_framerate_mismatch_percent);
568     EXPECT_LE(video_stat.avg_delay_sec,
569               rc_thresholds->max_avg_buffer_level_sec);
570     EXPECT_LE(video_stat.max_key_frame_delay_sec,
571               rc_thresholds->max_max_key_frame_delay_sec);
572     EXPECT_LE(video_stat.max_delta_frame_delay_sec,
573               rc_thresholds->max_max_delta_frame_delay_sec);
574     EXPECT_LE(video_stat.num_spatial_resizes,
575               rc_thresholds->max_num_spatial_resizes);
576     EXPECT_LE(video_stat.num_key_frames, rc_thresholds->max_num_key_frames);
577   }
578 
579   if (quality_thresholds) {
580     EXPECT_GT(video_stat.avg_psnr, quality_thresholds->min_avg_psnr);
581     EXPECT_GT(video_stat.min_psnr, quality_thresholds->min_min_psnr);
582 
583     // SSIM calculation is not optimized and thus it is disabled in real-time
584     // mode.
585     if (!config_.encode_in_real_time) {
586       EXPECT_GT(video_stat.avg_ssim, quality_thresholds->min_avg_ssim);
587       EXPECT_GT(video_stat.min_ssim, quality_thresholds->min_min_ssim);
588     }
589   }
590 
591   if (bs_thresholds) {
592     EXPECT_LE(video_stat.max_nalu_size_bytes,
593               bs_thresholds->max_max_nalu_size_bytes);
594   }
595 }
596 
CreateEncoderAndDecoder()597 void VideoCodecTestFixtureImpl::CreateEncoderAndDecoder() {
598   SdpVideoFormat::Parameters params;
599   if (config_.codec_settings.codecType == kVideoCodecH264) {
600     const char* packetization_mode =
601         config_.h264_codec_settings.packetization_mode ==
602                 H264PacketizationMode::NonInterleaved
603             ? "1"
604             : "0";
605     params = {{cricket::kH264FmtpProfileLevelId,
606                *H264::ProfileLevelIdToString(H264::ProfileLevelId(
607                    config_.h264_codec_settings.profile, H264::kLevel3_1))},
608               {cricket::kH264FmtpPacketizationMode, packetization_mode}};
609   } else {
610     params = {};
611   }
612   SdpVideoFormat format(config_.codec_name, params);
613 
614   encoder_ = encoder_factory_->CreateVideoEncoder(format);
615   EXPECT_TRUE(encoder_) << "Encoder not successfully created.";
616 
617   const size_t num_simulcast_or_spatial_layers = std::max(
618       config_.NumberOfSimulcastStreams(), config_.NumberOfSpatialLayers());
619   for (size_t i = 0; i < num_simulcast_or_spatial_layers; ++i) {
620     decoders_.push_back(std::unique_ptr<VideoDecoder>(
621         decoder_factory_->CreateVideoDecoder(format)));
622   }
623 
624   for (const auto& decoder : decoders_) {
625     EXPECT_TRUE(decoder) << "Decoder not successfully created.";
626   }
627 }
628 
DestroyEncoderAndDecoder()629 void VideoCodecTestFixtureImpl::DestroyEncoderAndDecoder() {
630   decoders_.clear();
631   encoder_.reset();
632 }
633 
GetStats()634 VideoCodecTestStats& VideoCodecTestFixtureImpl::GetStats() {
635   return stats_;
636 }
637 
SetUpAndInitObjects(TaskQueueForTest * task_queue,size_t initial_bitrate_kbps,double initial_framerate_fps)638 void VideoCodecTestFixtureImpl::SetUpAndInitObjects(
639     TaskQueueForTest* task_queue,
640     size_t initial_bitrate_kbps,
641     double initial_framerate_fps) {
642   config_.codec_settings.minBitrate = 0;
643   config_.codec_settings.startBitrate = static_cast<int>(initial_bitrate_kbps);
644   config_.codec_settings.maxFramerate = std::ceil(initial_framerate_fps);
645 
646   // Create file objects for quality analysis.
647   source_frame_reader_.reset(
648       new YuvFrameReaderImpl(config_.filepath, config_.codec_settings.width,
649                              config_.codec_settings.height));
650   EXPECT_TRUE(source_frame_reader_->Init());
651 
652   RTC_DCHECK(encoded_frame_writers_.empty());
653   RTC_DCHECK(decoded_frame_writers_.empty());
654 
655   if (config_.visualization_params.save_encoded_ivf ||
656       config_.visualization_params.save_decoded_y4m) {
657     const size_t num_simulcast_or_spatial_layers = std::max(
658         config_.NumberOfSimulcastStreams(), config_.NumberOfSpatialLayers());
659     const size_t num_temporal_layers = config_.NumberOfTemporalLayers();
660     for (size_t simulcast_svc_idx = 0;
661          simulcast_svc_idx < num_simulcast_or_spatial_layers;
662          ++simulcast_svc_idx) {
663       const std::string output_filename_base = JoinFilename(
664           config_.output_path, FilenameWithParams(config_) + "_sl" +
665                                    std::to_string(simulcast_svc_idx));
666 
667       if (config_.visualization_params.save_encoded_ivf) {
668         for (size_t temporal_idx = 0; temporal_idx < num_temporal_layers;
669              ++temporal_idx) {
670           const std::string output_file_path = output_filename_base + "tl" +
671                                                std::to_string(temporal_idx) +
672                                                ".ivf";
673           FileWrapper ivf_file = FileWrapper::OpenWriteOnly(output_file_path);
674 
675           const VideoProcessor::LayerKey layer_key(simulcast_svc_idx,
676                                                    temporal_idx);
677           encoded_frame_writers_[layer_key] =
678               IvfFileWriter::Wrap(std::move(ivf_file), /*byte_limit=*/0);
679         }
680       }
681 
682       if (config_.visualization_params.save_decoded_y4m) {
683         FrameWriter* decoded_frame_writer = new Y4mFrameWriterImpl(
684             output_filename_base + ".y4m", config_.codec_settings.width,
685             config_.codec_settings.height, config_.codec_settings.maxFramerate);
686         EXPECT_TRUE(decoded_frame_writer->Init());
687         decoded_frame_writers_.push_back(
688             std::unique_ptr<FrameWriter>(decoded_frame_writer));
689       }
690     }
691   }
692 
693   stats_.Clear();
694 
695   cpu_process_time_.reset(new CpuProcessTime(config_));
696 
697   task_queue->SendTask(
698       [this]() {
699         CreateEncoderAndDecoder();
700         processor_ = std::make_unique<VideoProcessor>(
701             encoder_.get(), &decoders_, source_frame_reader_.get(), config_,
702             &stats_, &encoded_frame_writers_,
703             decoded_frame_writers_.empty() ? nullptr : &decoded_frame_writers_);
704       },
705       RTC_FROM_HERE);
706 }
707 
ReleaseAndCloseObjects(TaskQueueForTest * task_queue)708 void VideoCodecTestFixtureImpl::ReleaseAndCloseObjects(
709     TaskQueueForTest* task_queue) {
710   task_queue->SendTask(
711       [this]() {
712         processor_.reset();
713         // The VideoProcessor must be destroyed before the codecs.
714         DestroyEncoderAndDecoder();
715       },
716       RTC_FROM_HERE);
717 
718   source_frame_reader_->Close();
719 
720   // Close visualization files.
721   for (auto& encoded_frame_writer : encoded_frame_writers_) {
722     EXPECT_TRUE(encoded_frame_writer.second->Close());
723   }
724   encoded_frame_writers_.clear();
725   for (auto& decoded_frame_writer : decoded_frame_writers_) {
726     decoded_frame_writer->Close();
727   }
728   decoded_frame_writers_.clear();
729 }
730 
PrintSettings(TaskQueueForTest * task_queue) const731 void VideoCodecTestFixtureImpl::PrintSettings(
732     TaskQueueForTest* task_queue) const {
733   RTC_LOG(LS_INFO) << "==> Config";
734   RTC_LOG(LS_INFO) << config_.ToString();
735 
736   RTC_LOG(LS_INFO) << "==> Codec names";
737   std::string encoder_name;
738   std::string decoder_name;
739   task_queue->SendTask(
740       [this, &encoder_name, &decoder_name] {
741         encoder_name = encoder_->GetEncoderInfo().implementation_name;
742         decoder_name = decoders_.at(0)->ImplementationName();
743       },
744       RTC_FROM_HERE);
745   RTC_LOG(LS_INFO) << "enc_impl_name: " << encoder_name;
746   RTC_LOG(LS_INFO) << "dec_impl_name: " << decoder_name;
747 }
748 
749 }  // namespace test
750 }  // namespace webrtc
751