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

This is a reland of 1880c7162b
Original change's description:
> Updated analysis in videoprocessor.
>
> - Run analysis after all frames are processed. Before part of it was
> done at bitrate change points;
> - Analysis is done for whole stream as well as for each rate update
> interval;
> - Changed units from number of frames to time units for some metrics
> and thresholds. E.g. 'num frames to hit tagret bitrate' is changed to
> 'time to reach target bitrate, sec';
> - Changed data type of FrameStatistic::max_nalu_length (renamed to
> max_nalu_size_bytes) from rtc::Optional to size_t. There it no need to
> use such advanced data type in such low level data structure.
>
> Bug: webrtc:8524
> Change-Id: Ic9f6eab5b15ee12a80324b1f9c101de1bf3c702f
> Reviewed-on: https://webrtc-review.googlesource.com/31901
> Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
> Reviewed-by: Stefan Holmer <stefan@webrtc.org>
> Reviewed-by: Åsa Persson <asapersson@webrtc.org>
> Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
> Cr-Commit-Position: refs/heads/master@{#21653}
TBR=brandtr@webrtc.org, stefan@webrtc.org
Bug: webrtc:8524
Change-Id: Ie0ad7790689422ffa61da294967fc492a13b75ae
Reviewed-on: https://webrtc-review.googlesource.com/40202
Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
Reviewed-by: Sergey Silkin <ssilkin@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21668}
731 lines
28 KiB
C++
731 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_));
|
|
packet_manipulator_.reset(new PacketManipulatorImpl(
|
|
&packet_reader_, config_.networking_config, false));
|
|
|
|
rtc::Event sync_event(false, false);
|
|
task_queue->PostTask([this, &sync_event]() {
|
|
processor_ = rtc::MakeUnique<VideoProcessor>(
|
|
encoder_.get(), decoder_.get(), analysis_frame_reader_.get(),
|
|
packet_manipulator_.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);
|
|
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
|