mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 13:50:40 +01:00

This feature is not needed in video codec testing framework. In WebRTC video codecs never deal with packet loss. Packet loss is handled by jitter buffer which prevents passing of incomplete frames to decoder. Bug: webrtc:8768 Change-Id: I211cf51d913bec6a1f935e30691661d428ebd3b6 Reviewed-on: https://webrtc-review.googlesource.com/40740 Commit-Queue: Sergey Silkin <ssilkin@webrtc.org> Reviewed-by: Stefan Holmer <stefan@webrtc.org> Reviewed-by: Rasmus Brandt <brandtr@webrtc.org> Reviewed-by: Åsa Persson <asapersson@webrtc.org> Cr-Commit-Position: refs/heads/master@{#21722}
728 lines
28 KiB
C++
728 lines
28 KiB
C++
/*
|
|
* 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
|