| /* |
| * 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 <memory> |
| #include <utility> |
| |
| #if defined(WEBRTC_ANDROID) |
| #include "modules/video_coding/codecs/test/android_codec_factory_helper.h" |
| #elif defined(WEBRTC_IOS) |
| #include "modules/video_coding/codecs/test/objc_codec_factory_helper.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(&task_queue); |
| |
| 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) { |
| FrameStatistic superframe_stat = |
| *stats_.at(target_spatial_layer_number).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) { |
| const FrameStatistic* frame_stat = |
| stats_.at(spatial_layer_number).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 of extracted interval is bitrate of the highest |
| // spatial and temporal layer. |
| target_bitrate_kbps = |
| std::max(target_bitrate_kbps, superframe_stat.target_bitrate_kbps); |
| |
| 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) |
| encoder_factory = CreateAndroidEncoderFactory(); |
| #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) |
| decoder_factory = CreateAndroidDecoderFactory(); |
| #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); |
| |
| const size_t num_simulcast_or_spatial_layers = std::max( |
| config_.NumberOfSimulcastStreams(), config_.NumberOfSpatialLayers()); |
| |
| for (size_t i = 0; i < num_simulcast_or_spatial_layers; ++i) { |
| decoders_.push_back(std::unique_ptr<VideoDecoder>( |
| decoder_factory->CreateVideoDecoder(format))); |
| } |
| |
| if (config_.sw_fallback_encoder) { |
| encoder_ = rtc::MakeUnique<VideoEncoderSoftwareFallbackWrapper>( |
| InternalEncoderFactory().CreateVideoEncoder(format), |
| std::move(encoder_)); |
| } |
| if (config_.sw_fallback_decoder) { |
| for (auto& decoder : decoders_) { |
| decoder = rtc::MakeUnique<VideoDecoderSoftwareFallbackWrapper>( |
| InternalDecoderFactory().CreateVideoDecoder(format), |
| std::move(decoder)); |
| } |
| } |
| |
| EXPECT_TRUE(encoder_) << "Encoder not successfully created."; |
| |
| for (const auto& decoder : decoders_) { |
| EXPECT_TRUE(decoder) << "Decoder not successfully created."; |
| } |
| } |
| |
| void VideoProcessorIntegrationTest::DestroyEncoderAndDecoder() { |
| encoder_.reset(); |
| decoders_.clear(); |
| } |
| |
| 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. |
| source_frame_reader_.reset(new YuvFrameReaderImpl( |
| config_.input_filename, config_.codec_settings.width, |
| config_.codec_settings.height)); |
| EXPECT_TRUE(source_frame_reader_->Init()); |
| |
| const size_t num_simulcast_or_spatial_layers = std::max( |
| config_.NumberOfSimulcastStreams(), config_.NumberOfSpatialLayers()); |
| |
| if (visualization_params) { |
| for (size_t simulcast_svc_idx = 0; |
| simulcast_svc_idx < num_simulcast_or_spatial_layers; |
| ++simulcast_svc_idx) { |
| const std::string output_filename_base = |
| OutputPath() + config_.FilenameWithParams() + "_" + |
| std::to_string(simulcast_svc_idx); |
| |
| if (visualization_params->save_encoded_ivf) { |
| rtc::File post_encode_file = |
| rtc::File::Create(output_filename_base + ".ivf"); |
| encoded_frame_writers_.push_back( |
| IvfFileWriter::Wrap(std::move(post_encode_file), 0)); |
| } |
| |
| if (visualization_params->save_decoded_y4m) { |
| FrameWriter* decoded_frame_writer = new Y4mFrameWriterImpl( |
| output_filename_base + ".y4m", config_.codec_settings.width, |
| config_.codec_settings.height, initial_framerate_fps); |
| EXPECT_TRUE(decoded_frame_writer->Init()); |
| decoded_frame_writers_.push_back( |
| std::unique_ptr<FrameWriter>(decoded_frame_writer)); |
| } |
| } |
| } |
| |
| stats_.resize(num_simulcast_or_spatial_layers); |
| |
| 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(), &decoders_, source_frame_reader_.get(), config_, |
| &stats_, |
| encoded_frame_writers_.empty() ? nullptr : &encoded_frame_writers_, |
| decoded_frame_writers_.empty() ? nullptr : &decoded_frame_writers_); |
| 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(); |
| |
| source_frame_reader_->Close(); |
| |
| // Close visualization files. |
| for (auto& encoded_frame_writer : encoded_frame_writers_) { |
| EXPECT_TRUE(encoded_frame_writer->Close()); |
| } |
| for (auto& decoded_frame_writer : decoded_frame_writers_) { |
| decoded_frame_writer->Close(); |
| } |
| } |
| |
| void VideoProcessorIntegrationTest::PrintSettings( |
| rtc::TaskQueue* task_queue) const { |
| printf("VideoProcessor settings\n==\n"); |
| printf(" Total # of frames : %d", |
| source_frame_reader_->NumberOfFrames()); |
| printf("%s\n", config_.ToString().c_str()); |
| |
| printf("VideoProcessorIntegrationTest settings\n==\n"); |
| std::string encoder_name; |
| std::string decoder_name; |
| rtc::Event sync_event(false, false); |
| task_queue->PostTask([this, &encoder_name, &decoder_name, &sync_event] { |
| encoder_name = encoder_->ImplementationName(); |
| decoder_name = decoders_.at(0)->ImplementationName(); |
| sync_event.Set(); |
| }); |
| sync_event.Wait(rtc::Event::kForever); |
| printf(" Encoder implementation name: %s\n", encoder_name.c_str()); |
| printf(" Decoder implementation name: %s\n", decoder_name.c_str()); |
| if (encoder_name == decoder_name) { |
| printf(" Codec implementation name : %s_%s\n", config_.CodecName().c_str(), |
| encoder_name.c_str()); |
| } |
| 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("Frame width : %zu\n", |
| last_successfully_decoded_frame.decoded_width); |
| printf("Frame height : %zu\n", |
| last_successfully_decoded_frame.decoded_height); |
| 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("Encoded framerate : %f fps\n", encoded_framerate_fps); |
| printf("Decoded 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("Encoding framerate : %f fps\n", |
| 1000000 / encoding_time_us.Mean()); |
| printf("Decoding framerate : %f fps\n", |
| 1000000 / 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 (const auto& frame_stat : stats) { |
| printf("%s\n", frame_stat.ToString().c_str()); |
| } |
| } |
| |
| } // namespace test |
| } // namespace webrtc |