/* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/video_coding/codecs/test/videoprocessor_integrationtest.h" #include #include #if defined(WEBRTC_ANDROID) #include "modules/video_coding/codecs/test/android_test_initializer.h" #include "sdk/android/src/jni/class_loader.h" #include "sdk/android/src/jni/videodecoderfactorywrapper.h" #include "sdk/android/src/jni/videoencoderfactorywrapper.h" #elif defined(WEBRTC_IOS) #include "modules/video_coding/codecs/test/objc_codec_h264_test.h" #endif #include "common_types.h" // NOLINT(build/include) #include "media/base/h264_profile_level_id.h" #include "media/engine/internaldecoderfactory.h" #include "media/engine/internalencoderfactory.h" #include "media/engine/videodecodersoftwarefallbackwrapper.h" #include "media/engine/videoencodersoftwarefallbackwrapper.h" #include "modules/video_coding/codecs/vp8/include/vp8_common_types.h" #include "modules/video_coding/include/video_codec_interface.h" #include "modules/video_coding/include/video_coding.h" #include "rtc_base/checks.h" #include "rtc_base/cpu_time.h" #include "rtc_base/event.h" #include "rtc_base/file.h" #include "rtc_base/ptr_util.h" #include "system_wrappers/include/sleep.h" #include "test/statistics.h" #include "test/testsupport/fileutils.h" #include "test/testsupport/metrics/video_metrics.h" namespace webrtc { namespace test { namespace { const int kRtpClockRateHz = 90000; const int kMaxBitrateMismatchPercent = 20; bool RunEncodeInRealTime(const TestConfig& config) { if (config.measure_cpu) { return true; } #if defined(WEBRTC_ANDROID) // In order to not overwhelm the OpenMAX buffers in the Android MediaCodec. return (config.hw_encoder || config.hw_decoder); #else return false; #endif } SdpVideoFormat CreateSdpVideoFormat(const TestConfig& config) { switch (config.codec_settings.codecType) { case kVideoCodecVP8: return SdpVideoFormat(cricket::kVp8CodecName); case kVideoCodecVP9: return SdpVideoFormat(cricket::kVp9CodecName); case kVideoCodecH264: { const char* packetization_mode = config.h264_codec_settings.packetization_mode == H264PacketizationMode::NonInterleaved ? "1" : "0"; return SdpVideoFormat( cricket::kH264CodecName, {{cricket::kH264FmtpProfileLevelId, *H264::ProfileLevelIdToString(H264::ProfileLevelId( config.h264_codec_settings.profile, H264::kLevel3_1))}, {cricket::kH264FmtpPacketizationMode, packetization_mode}}); } default: RTC_NOTREACHED(); return SdpVideoFormat(""); } } } // namespace void VideoProcessorIntegrationTest::H264KeyframeChecker::CheckEncodedFrame( webrtc::VideoCodecType codec, const EncodedImage& encoded_frame) const { EXPECT_EQ(kVideoCodecH264, codec); bool contains_sps = false; bool contains_pps = false; bool contains_idr = false; const std::vector nalu_indices = webrtc::H264::FindNaluIndices(encoded_frame._buffer, encoded_frame._length); for (const webrtc::H264::NaluIndex& index : nalu_indices) { webrtc::H264::NaluType nalu_type = webrtc::H264::ParseNaluType( encoded_frame._buffer[index.payload_start_offset]); if (nalu_type == webrtc::H264::NaluType::kSps) { contains_sps = true; } else if (nalu_type == webrtc::H264::NaluType::kPps) { contains_pps = true; } else if (nalu_type == webrtc::H264::NaluType::kIdr) { contains_idr = true; } } if (encoded_frame._frameType == kVideoFrameKey) { EXPECT_TRUE(contains_sps) << "Keyframe should contain SPS."; EXPECT_TRUE(contains_pps) << "Keyframe should contain PPS."; EXPECT_TRUE(contains_idr) << "Keyframe should contain IDR."; } else if (encoded_frame._frameType == kVideoFrameDelta) { EXPECT_FALSE(contains_sps) << "Delta frame should not contain SPS."; EXPECT_FALSE(contains_pps) << "Delta frame should not contain PPS."; EXPECT_FALSE(contains_idr) << "Delta frame should not contain IDR."; } else { RTC_NOTREACHED(); } } class VideoProcessorIntegrationTest::CpuProcessTime final { public: explicit CpuProcessTime(const TestConfig& config) : config_(config) {} ~CpuProcessTime() {} void Start() { if (config_.measure_cpu) { cpu_time_ -= rtc::GetProcessCpuTimeNanos(); wallclock_time_ -= rtc::SystemTimeNanos(); } } void Stop() { if (config_.measure_cpu) { cpu_time_ += rtc::GetProcessCpuTimeNanos(); wallclock_time_ += rtc::SystemTimeNanos(); } } void Print() const { if (config_.measure_cpu) { printf("CPU usage %%: %f\n", GetUsagePercent() / config_.NumberOfCores()); printf("\n"); } } private: double GetUsagePercent() const { return static_cast(cpu_time_) / wallclock_time_ * 100.0; } const TestConfig config_; int64_t cpu_time_ = 0; int64_t wallclock_time_ = 0; }; VideoProcessorIntegrationTest::VideoProcessorIntegrationTest() { #if defined(WEBRTC_ANDROID) InitializeAndroidObjects(); #endif } VideoProcessorIntegrationTest::~VideoProcessorIntegrationTest() = default; // Processes all frames in the clip and verifies the result. void VideoProcessorIntegrationTest::ProcessFramesAndMaybeVerify( const std::vector& rate_profiles, const std::vector* rc_thresholds, const std::vector* quality_thresholds, const BitstreamThresholds* bs_thresholds, const VisualizationParams* visualization_params) { RTC_DCHECK(!rate_profiles.empty()); // The Android HW codec needs to be run on a task queue, so we simply always // run the test on a task queue. rtc::TaskQueue task_queue("VidProc TQ"); SetUpAndInitObjects( &task_queue, static_cast(rate_profiles[0].target_kbps), static_cast(rate_profiles[0].input_fps), visualization_params); PrintSettings(); ProcessAllFrames(&task_queue, rate_profiles); ReleaseAndCloseObjects(&task_queue); AnalyzeAllFrames(rate_profiles, rc_thresholds, quality_thresholds, bs_thresholds); } void VideoProcessorIntegrationTest::ProcessAllFrames( rtc::TaskQueue* task_queue, const std::vector& rate_profiles) { // Process all frames. size_t rate_update_index = 0; // Set initial rates. task_queue->PostTask([this, &rate_profiles, rate_update_index] { processor_->SetRates(rate_profiles[rate_update_index].target_kbps, rate_profiles[rate_update_index].input_fps); }); cpu_process_time_->Start(); for (size_t frame_number = 0; frame_number < config_.num_frames; ++frame_number) { if (frame_number == rate_profiles[rate_update_index].frame_index_rate_update) { ++rate_update_index; RTC_DCHECK_GT(rate_profiles.size(), rate_update_index); task_queue->PostTask([this, &rate_profiles, rate_update_index] { processor_->SetRates(rate_profiles[rate_update_index].target_kbps, rate_profiles[rate_update_index].input_fps); }); } task_queue->PostTask([this] { processor_->ProcessFrame(); }); if (RunEncodeInRealTime(config_)) { // Roughly pace the frames. size_t frame_duration_ms = rtc::kNumMillisecsPerSec / rate_profiles[rate_update_index].input_fps; SleepMs(static_cast(frame_duration_ms)); } } rtc::Event sync_event(false, false); task_queue->PostTask([&sync_event] { sync_event.Set(); }); sync_event.Wait(rtc::Event::kForever); // Give the VideoProcessor pipeline some time to process the last frame, // and then release the codecs. if (config_.hw_encoder || config_.hw_decoder) { SleepMs(1 * rtc::kNumMillisecsPerSec); } cpu_process_time_->Stop(); } void VideoProcessorIntegrationTest::AnalyzeAllFrames( const std::vector& rate_profiles, const std::vector* rc_thresholds, const std::vector* quality_thresholds, const BitstreamThresholds* bs_thresholds) { const bool is_svc = config_.NumberOfSpatialLayers() > 1; const size_t number_of_simulcast_or_spatial_layers = std::max(std::size_t{1}, std::max(config_.NumberOfSpatialLayers(), static_cast( config_.codec_settings.numberOfSimulcastStreams))); const size_t number_of_temporal_layers = config_.NumberOfTemporalLayers(); printf("Rate control statistics\n==\n"); for (size_t rate_update_index = 0; rate_update_index < rate_profiles.size(); ++rate_update_index) { const size_t first_frame_number = (rate_update_index == 0) ? 0 : rate_profiles[rate_update_index - 1].frame_index_rate_update; const size_t last_frame_number = rate_profiles[rate_update_index].frame_index_rate_update - 1; RTC_CHECK(last_frame_number >= first_frame_number); const size_t number_of_frames = last_frame_number - first_frame_number + 1; const float input_duration_sec = 1.0 * number_of_frames / rate_profiles[rate_update_index].input_fps; std::vector overall_stats = ExtractLayerStats(number_of_simulcast_or_spatial_layers - 1, number_of_temporal_layers - 1, first_frame_number, last_frame_number, true); printf("Rate update #%zu:\n", rate_update_index); const RateControlThresholds* rc_threshold = rc_thresholds ? &(*rc_thresholds)[rate_update_index] : nullptr; const QualityThresholds* quality_threshold = quality_thresholds ? &(*quality_thresholds)[rate_update_index] : nullptr; AnalyzeAndPrintStats( overall_stats, rate_profiles[rate_update_index].target_kbps, rate_profiles[rate_update_index].input_fps, input_duration_sec, rc_threshold, quality_threshold, bs_thresholds); if (config_.print_frame_level_stats) { PrintFrameLevelStats(overall_stats); } for (size_t spatial_layer_number = 0; spatial_layer_number < number_of_simulcast_or_spatial_layers; ++spatial_layer_number) { for (size_t temporal_layer_number = 0; temporal_layer_number < number_of_temporal_layers; ++temporal_layer_number) { std::vector layer_stats = ExtractLayerStats(spatial_layer_number, temporal_layer_number, first_frame_number, last_frame_number, is_svc); const size_t target_bitrate_kbps = layer_stats[0].target_bitrate_kbps; const float target_framerate_fps = 1.0 * rate_profiles[rate_update_index].input_fps / (1 << (number_of_temporal_layers - temporal_layer_number - 1)); printf("Spatial %zu temporal %zu:\n", spatial_layer_number, temporal_layer_number); AnalyzeAndPrintStats(layer_stats, target_bitrate_kbps, target_framerate_fps, input_duration_sec, nullptr, nullptr, nullptr); if (config_.print_frame_level_stats) { PrintFrameLevelStats(layer_stats); } } } } cpu_process_time_->Print(); } std::vector VideoProcessorIntegrationTest::ExtractLayerStats( size_t target_spatial_layer_number, size_t target_temporal_layer_number, size_t first_frame_number, size_t last_frame_number, bool combine_layers_stats) { size_t target_bitrate_kbps = 0; std::vector layer_stats; for (size_t frame_number = first_frame_number; frame_number <= last_frame_number; ++frame_number) { // TODO(ssilkin): Add layering support // FrameStatistic superframe_stat = // *stats_[target_spatial_layer_number].GetFrame(frame_number); FrameStatistic superframe_stat = *stats_.GetFrame(frame_number); const size_t tl_idx = superframe_stat.temporal_layer_idx; if (tl_idx <= target_temporal_layer_number) { if (combine_layers_stats) { for (size_t spatial_layer_number = 0; spatial_layer_number < target_spatial_layer_number; ++spatial_layer_number) { // TODO(ssilkin): Add layering support // const FrameStatistic* frame_stat = // stats_[spatial_layer_number].GetFrame(frame_number); const FrameStatistic* frame_stat = stats_.GetFrame(frame_number); superframe_stat.encoded_frame_size_bytes += frame_stat->encoded_frame_size_bytes; superframe_stat.encode_time_us = std::max( superframe_stat.encode_time_us, frame_stat->encode_time_us); superframe_stat.decode_time_us = std::max( superframe_stat.decode_time_us, frame_stat->decode_time_us); } } target_bitrate_kbps = std::max(target_bitrate_kbps, superframe_stat.target_bitrate_kbps); if (superframe_stat.encoding_successful) { RTC_CHECK(superframe_stat.target_bitrate_kbps <= target_bitrate_kbps || tl_idx == target_temporal_layer_number); RTC_CHECK(superframe_stat.target_bitrate_kbps == target_bitrate_kbps || tl_idx < target_temporal_layer_number); } layer_stats.push_back(superframe_stat); } } for (auto& frame_stat : layer_stats) { frame_stat.target_bitrate_kbps = target_bitrate_kbps; } return layer_stats; } void VideoProcessorIntegrationTest::CreateEncoderAndDecoder() { std::unique_ptr encoder_factory; if (config_.hw_encoder) { #if defined(WEBRTC_ANDROID) JNIEnv* env = jni::AttachCurrentThreadIfNeeded(); jni::ScopedJavaLocalRef factory_class = jni::GetClass(env, "org/webrtc/HardwareVideoEncoderFactory"); jmethodID factory_constructor = env->GetMethodID( factory_class.obj(), "", "(Lorg/webrtc/EglBase$Context;ZZ)V"); jni::ScopedJavaLocalRef factory_object( env, env->NewObject(factory_class.obj(), factory_constructor, nullptr /* shared_context */, false /* enable_intel_vp8_encoder */, true /* enable_h264_high_profile */)); encoder_factory = rtc::MakeUnique( env, factory_object); #elif defined(WEBRTC_IOS) EXPECT_EQ(kVideoCodecH264, config_.codec_settings.codecType) << "iOS HW codecs only support H264."; encoder_factory = CreateObjCEncoderFactory(); #else RTC_NOTREACHED() << "Only support HW encoder on Android and iOS."; #endif } else { encoder_factory = rtc::MakeUnique(); } std::unique_ptr decoder_factory; if (config_.hw_decoder) { #if defined(WEBRTC_ANDROID) JNIEnv* env = jni::AttachCurrentThreadIfNeeded(); jni::ScopedJavaLocalRef factory_class = jni::GetClass(env, "org/webrtc/HardwareVideoDecoderFactory"); jmethodID factory_constructor = env->GetMethodID( factory_class.obj(), "", "(Lorg/webrtc/EglBase$Context;)V"); jni::ScopedJavaLocalRef factory_object( env, env->NewObject(factory_class.obj(), factory_constructor, nullptr /* shared_context */)); decoder_factory = rtc::MakeUnique( env, factory_object); #elif defined(WEBRTC_IOS) EXPECT_EQ(kVideoCodecH264, config_.codec_settings.codecType) << "iOS HW codecs only support H264."; decoder_factory = CreateObjCDecoderFactory(); #else RTC_NOTREACHED() << "Only support HW decoder on Android and iOS."; #endif } else { decoder_factory = rtc::MakeUnique(); } const SdpVideoFormat format = CreateSdpVideoFormat(config_); encoder_ = encoder_factory->CreateVideoEncoder(format); decoder_ = decoder_factory->CreateVideoDecoder(format); if (config_.sw_fallback_encoder) { encoder_ = rtc::MakeUnique( InternalEncoderFactory().CreateVideoEncoder(format), std::move(encoder_)); } if (config_.sw_fallback_decoder) { decoder_ = rtc::MakeUnique( InternalDecoderFactory().CreateVideoDecoder(format), std::move(decoder_)); } EXPECT_TRUE(encoder_) << "Encoder not successfully created."; EXPECT_TRUE(decoder_) << "Decoder not successfully created."; } void VideoProcessorIntegrationTest::DestroyEncoderAndDecoder() { encoder_.reset(); decoder_.reset(); } void VideoProcessorIntegrationTest::SetUpAndInitObjects( rtc::TaskQueue* task_queue, const int initial_bitrate_kbps, const int initial_framerate_fps, const VisualizationParams* visualization_params) { CreateEncoderAndDecoder(); config_.codec_settings.minBitrate = 0; config_.codec_settings.startBitrate = initial_bitrate_kbps; config_.codec_settings.maxFramerate = initial_framerate_fps; // Create file objects for quality analysis. analysis_frame_reader_.reset(new YuvFrameReaderImpl( config_.input_filename, config_.codec_settings.width, config_.codec_settings.height)); analysis_frame_writer_.reset(new YuvFrameWriterImpl( config_.output_filename, config_.codec_settings.width, config_.codec_settings.height)); EXPECT_TRUE(analysis_frame_reader_->Init()); EXPECT_TRUE(analysis_frame_writer_->Init()); if (visualization_params) { const std::string output_filename_base = OutputPath() + config_.FilenameWithParams(); if (visualization_params->save_encoded_ivf) { rtc::File post_encode_file = rtc::File::Create(output_filename_base + ".ivf"); encoded_frame_writer_ = IvfFileWriter::Wrap(std::move(post_encode_file), 0); } if (visualization_params->save_decoded_y4m) { decoded_frame_writer_.reset(new Y4mFrameWriterImpl( output_filename_base + ".y4m", config_.codec_settings.width, config_.codec_settings.height, initial_framerate_fps)); EXPECT_TRUE(decoded_frame_writer_->Init()); } } cpu_process_time_.reset(new CpuProcessTime(config_)); rtc::Event sync_event(false, false); task_queue->PostTask([this, &sync_event]() { processor_ = rtc::MakeUnique( encoder_.get(), decoder_.get(), analysis_frame_reader_.get(), config_, &stats_, encoded_frame_writer_.get(), decoded_frame_writer_.get()); sync_event.Set(); }); sync_event.Wait(rtc::Event::kForever); } void VideoProcessorIntegrationTest::ReleaseAndCloseObjects( rtc::TaskQueue* task_queue) { rtc::Event sync_event(false, false); task_queue->PostTask([this, &sync_event]() { processor_.reset(); sync_event.Set(); }); sync_event.Wait(rtc::Event::kForever); // The VideoProcessor must be destroyed before the codecs. DestroyEncoderAndDecoder(); analysis_frame_reader_->Close(); // Close visualization files. if (encoded_frame_writer_) { EXPECT_TRUE(encoded_frame_writer_->Close()); } if (decoded_frame_writer_) { decoded_frame_writer_->Close(); } } void VideoProcessorIntegrationTest::PrintSettings() const { printf("VideoProcessor settings\n==\n"); printf(" Total # of frames : %d", analysis_frame_reader_->NumberOfFrames()); printf("%s\n", config_.ToString().c_str()); printf("VideoProcessorIntegrationTest settings\n==\n"); const char* encoder_name = encoder_->ImplementationName(); printf(" Encoder implementation name: %s\n", encoder_name); const char* decoder_name = decoder_->ImplementationName(); printf(" Decoder implementation name: %s\n", decoder_name); if (strcmp(encoder_name, decoder_name) == 0) { printf(" Codec implementation name : %s_%s\n", config_.CodecName().c_str(), encoder_name); } printf("\n"); } void VideoProcessorIntegrationTest::AnalyzeAndPrintStats( const std::vector& stats, const float target_bitrate_kbps, const float target_framerate_fps, const float input_duration_sec, const RateControlThresholds* rc_thresholds, const QualityThresholds* quality_thresholds, const BitstreamThresholds* bs_thresholds) { const size_t num_input_frames = stats.size(); size_t num_dropped_frames = 0; size_t num_decoded_frames = 0; size_t num_spatial_resizes = 0; size_t num_key_frames = 0; size_t max_nalu_size_bytes = 0; size_t encoded_bytes = 0; float buffer_level_kbits = 0.0; float time_to_reach_target_bitrate_sec = -1.0; Statistics buffer_level_sec; Statistics key_frame_size_bytes; Statistics delta_frame_size_bytes; Statistics encoding_time_us; Statistics decoding_time_us; Statistics psnr; Statistics ssim; Statistics qp; FrameStatistic last_successfully_decoded_frame(0, 0); for (size_t frame_idx = 0; frame_idx < stats.size(); ++frame_idx) { const FrameStatistic& frame_stat = stats[frame_idx]; const float time_since_first_input_sec = frame_idx == 0 ? 0.0 : 1.0 * (frame_stat.rtp_timestamp - stats[0].rtp_timestamp) / kRtpClockRateHz; const float time_since_last_input_sec = frame_idx == 0 ? 0.0 : 1.0 * (frame_stat.rtp_timestamp - stats[frame_idx - 1].rtp_timestamp) / kRtpClockRateHz; // Testing framework uses constant input framerate. This guarantees even // sampling, which is important, of buffer level. buffer_level_kbits -= time_since_last_input_sec * target_bitrate_kbps; buffer_level_kbits = std::max(0.0f, buffer_level_kbits); buffer_level_kbits += 8.0 * frame_stat.encoded_frame_size_bytes / 1000; buffer_level_sec.AddSample(buffer_level_kbits / target_bitrate_kbps); encoded_bytes += frame_stat.encoded_frame_size_bytes; if (frame_stat.encoded_frame_size_bytes == 0) { ++num_dropped_frames; } else { if (frame_stat.frame_type == kVideoFrameKey) { key_frame_size_bytes.AddSample(frame_stat.encoded_frame_size_bytes); ++num_key_frames; } else { delta_frame_size_bytes.AddSample(frame_stat.encoded_frame_size_bytes); } encoding_time_us.AddSample(frame_stat.encode_time_us); qp.AddSample(frame_stat.qp); max_nalu_size_bytes = std::max(max_nalu_size_bytes, frame_stat.max_nalu_size_bytes); } if (frame_stat.decoding_successful) { psnr.AddSample(frame_stat.psnr); ssim.AddSample(frame_stat.ssim); if (num_decoded_frames > 0) { if (last_successfully_decoded_frame.decoded_width != frame_stat.decoded_width || last_successfully_decoded_frame.decoded_height != frame_stat.decoded_height) { ++num_spatial_resizes; } } decoding_time_us.AddSample(frame_stat.decode_time_us); last_successfully_decoded_frame = frame_stat; ++num_decoded_frames; } if (time_to_reach_target_bitrate_sec < 0 && frame_idx > 0) { const float curr_bitrate_kbps = (8.0 * encoded_bytes / 1000) / time_since_first_input_sec; const float bitrate_mismatch_percent = 100 * std::fabs(curr_bitrate_kbps - target_bitrate_kbps) / target_bitrate_kbps; if (bitrate_mismatch_percent < kMaxBitrateMismatchPercent) { time_to_reach_target_bitrate_sec = time_since_first_input_sec; } } } const float encoded_bitrate_kbps = 8 * encoded_bytes / input_duration_sec / 1000; const float bitrate_mismatch_percent = 100 * std::fabs(encoded_bitrate_kbps - target_bitrate_kbps) / target_bitrate_kbps; const size_t num_encoded_frames = num_input_frames - num_dropped_frames; const float encoded_framerate_fps = num_encoded_frames / input_duration_sec; const float decoded_framerate_fps = num_decoded_frames / input_duration_sec; const float framerate_mismatch_percent = 100 * std::fabs(decoded_framerate_fps - target_framerate_fps) / target_framerate_fps; const float max_key_frame_delay_sec = 8 * key_frame_size_bytes.Max() / 1000 / target_bitrate_kbps; const float max_delta_frame_delay_sec = 8 * delta_frame_size_bytes.Max() / 1000 / target_bitrate_kbps; printf("Target bitrate : %f kbps\n", target_bitrate_kbps); printf("Encoded bitrate : %f kbps\n", encoded_bitrate_kbps); printf("Bitrate mismatch : %f %%\n", bitrate_mismatch_percent); printf("Time to reach target bitrate : %f sec\n", time_to_reach_target_bitrate_sec); printf("Target framerate : %f fps\n", target_framerate_fps); printf("Encoding framerate : %f fps\n", encoded_framerate_fps); printf("Decoding framerate : %f fps\n", decoded_framerate_fps); printf("Frame encoding time : %f us\n", encoding_time_us.Mean()); printf("Frame decoding time : %f us\n", decoding_time_us.Mean()); printf("Framerate mismatch percent : %f %%\n", framerate_mismatch_percent); printf("Avg buffer level : %f sec\n", buffer_level_sec.Mean()); printf("Max key frame delay : %f sec\n", max_key_frame_delay_sec); printf("Max delta frame delay : %f sec\n", max_delta_frame_delay_sec); printf("Avg key frame size : %f bytes\n", key_frame_size_bytes.Mean()); printf("Avg delta frame size : %f bytes\n", delta_frame_size_bytes.Mean()); printf("Avg QP : %f\n", qp.Mean()); printf("Avg PSNR : %f dB\n", psnr.Mean()); printf("Min PSNR : %f dB\n", psnr.Min()); printf("Avg SSIM : %f\n", ssim.Mean()); printf("Min SSIM : %f\n", ssim.Min()); printf("# input frames : %zu\n", num_input_frames); printf("# encoded frames : %zu\n", num_encoded_frames); printf("# decoded frames : %zu\n", num_decoded_frames); printf("# dropped frames : %zu\n", num_dropped_frames); printf("# key frames : %zu\n", num_key_frames); printf("# encoded bytes : %zu\n", encoded_bytes); printf("# spatial resizes : %zu\n", num_spatial_resizes); if (rc_thresholds) { EXPECT_LE(bitrate_mismatch_percent, rc_thresholds->max_avg_bitrate_mismatch_percent); EXPECT_LE(time_to_reach_target_bitrate_sec, rc_thresholds->max_time_to_reach_target_bitrate_sec); EXPECT_LE(framerate_mismatch_percent, rc_thresholds->max_avg_framerate_mismatch_percent); EXPECT_LE(buffer_level_sec.Mean(), rc_thresholds->max_avg_buffer_level_sec); EXPECT_LE(max_key_frame_delay_sec, rc_thresholds->max_max_key_frame_delay_sec); EXPECT_LE(max_delta_frame_delay_sec, rc_thresholds->max_max_delta_frame_delay_sec); EXPECT_LE(num_spatial_resizes, rc_thresholds->max_num_spatial_resizes); EXPECT_LE(num_key_frames, rc_thresholds->max_num_key_frames); } if (quality_thresholds) { EXPECT_GT(psnr.Mean(), quality_thresholds->min_avg_psnr); EXPECT_GT(psnr.Min(), quality_thresholds->min_min_psnr); EXPECT_GT(ssim.Mean(), quality_thresholds->min_avg_ssim); EXPECT_GT(ssim.Min(), quality_thresholds->min_min_ssim); } if (bs_thresholds) { EXPECT_LE(max_nalu_size_bytes, bs_thresholds->max_max_nalu_size_bytes); } } void VideoProcessorIntegrationTest::PrintFrameLevelStats( const std::vector& stats) const { for (auto& frame_stat : stats) { printf("%s\n", frame_stat.ToString().c_str()); } } } // namespace test } // namespace webrtc