| /* |
| * 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 <algorithm> |
| #include <utility> |
| |
| #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<webrtc::H264::NaluIndex> 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<double>(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<RateProfile>& rate_profiles, |
| const std::vector<RateControlThresholds>* rc_thresholds, |
| const std::vector<QualityThresholds>* 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<const int>(rate_profiles[0].target_kbps), |
| static_cast<const int>(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<RateProfile>& 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<int>(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<RateProfile>& rate_profiles, |
| const std::vector<RateControlThresholds>* rc_thresholds, |
| const std::vector<QualityThresholds>* 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<size_t>( |
| 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<FrameStatistic> 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<FrameStatistic> 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<FrameStatistic> 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<FrameStatistic> 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<VideoEncoderFactory> encoder_factory; |
| if (config_.hw_encoder) { |
| #if defined(WEBRTC_ANDROID) |
| JNIEnv* env = jni::AttachCurrentThreadIfNeeded(); |
| jni::ScopedJavaLocalRef<jclass> factory_class = |
| jni::GetClass(env, "org/webrtc/HardwareVideoEncoderFactory"); |
| jmethodID factory_constructor = env->GetMethodID( |
| factory_class.obj(), "<init>", "(Lorg/webrtc/EglBase$Context;ZZ)V"); |
| jni::ScopedJavaLocalRef<jobject> 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<webrtc::jni::VideoEncoderFactoryWrapper>( |
| 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<InternalEncoderFactory>(); |
| } |
| |
| std::unique_ptr<VideoDecoderFactory> decoder_factory; |
| if (config_.hw_decoder) { |
| #if defined(WEBRTC_ANDROID) |
| JNIEnv* env = jni::AttachCurrentThreadIfNeeded(); |
| jni::ScopedJavaLocalRef<jclass> factory_class = |
| jni::GetClass(env, "org/webrtc/HardwareVideoDecoderFactory"); |
| jmethodID factory_constructor = env->GetMethodID( |
| factory_class.obj(), "<init>", "(Lorg/webrtc/EglBase$Context;)V"); |
| jni::ScopedJavaLocalRef<jobject> factory_object( |
| env, env->NewObject(factory_class.obj(), factory_constructor, |
| nullptr /* shared_context */)); |
| decoder_factory = rtc::MakeUnique<webrtc::jni::VideoDecoderFactoryWrapper>( |
| 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<InternalDecoderFactory>(); |
| } |
| |
| const SdpVideoFormat format = CreateSdpVideoFormat(config_); |
| encoder_ = encoder_factory->CreateVideoEncoder(format); |
| decoder_ = decoder_factory->CreateVideoDecoder(format); |
| |
| if (config_.sw_fallback_encoder) { |
| encoder_ = rtc::MakeUnique<VideoEncoderSoftwareFallbackWrapper>( |
| InternalEncoderFactory().CreateVideoEncoder(format), |
| std::move(encoder_)); |
| } |
| if (config_.sw_fallback_decoder) { |
| decoder_ = rtc::MakeUnique<VideoDecoderSoftwareFallbackWrapper>( |
| 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<VideoProcessor>( |
| 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<FrameStatistic>& 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<FrameStatistic>& stats) const { |
| for (auto& frame_stat : stats) { |
| printf("%s\n", frame_stat.ToString().c_str()); |
| } |
| } |
| |
| } // namespace test |
| } // namespace webrtc |