webrtc/modules/video_coding/codecs/test/videocodec_test_fixture_impl.cc
Ilya Nikolaevskiy 03d909634b Ensure that the first active layer isn't disabled by too low input resolution
If e.g. CPU adaptation reduces input video size too much, video pipeline would
reduce the number of used simulcast streams/spatial layers. This may result in
disabled video if some streams are disabled by Rtp encoding parameters API.

Bug: webrtc:11319
Change-Id: Id7f157255599dcb6f494129b83477cda4bea982a
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/168480
Reviewed-by: Evan Shrubsole <eshr@google.com>
Commit-Queue: Ilya Nikolaevskiy <ilnik@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30498}
2020-02-11 14:57:51 +00:00

747 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/videocodec_test_fixture_impl.h"
#include <stdint.h>
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/video/video_bitrate_allocation.h"
#include "api/video_codecs/sdp_video_format.h"
#include "api/video_codecs/video_codec.h"
#include "api/video_codecs/video_decoder.h"
#include "api/video_codecs/video_encoder_config.h"
#include "common_video/h264/h264_common.h"
#include "media/base/h264_profile_level_id.h"
#include "media/base/media_constants.h"
#include "media/engine/internal_decoder_factory.h"
#include "media/engine/internal_encoder_factory.h"
#include "media/engine/simulcast.h"
#include "modules/video_coding/codecs/h264/include/h264_globals.h"
#include "modules/video_coding/codecs/vp9/svc_config.h"
#include "modules/video_coding/utility/ivf_file_writer.h"
#include "rtc_base/checks.h"
#include "rtc_base/cpu_time.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/time_utils.h"
#include "system_wrappers/include/cpu_info.h"
#include "system_wrappers/include/sleep.h"
#include "test/gtest.h"
#include "test/testsupport/file_utils.h"
#include "test/testsupport/frame_writer.h"
#include "test/testsupport/perf_test.h"
#include "test/video_codec_settings.h"
namespace webrtc {
namespace test {
using VideoStatistics = VideoCodecTestStats::VideoStatistics;
namespace {
const int kBaseKeyFrameInterval = 3000;
const double kBitratePriority = 1.0;
const int kMaxFramerateFps = 30;
const int kMaxQp = 56;
void ConfigureSimulcast(VideoCodec* codec_settings) {
const std::vector<webrtc::VideoStream> streams = cricket::GetSimulcastConfig(
/*min_layer=*/1, codec_settings->numberOfSimulcastStreams,
codec_settings->width, codec_settings->height, kBitratePriority, kMaxQp,
/* is_screenshare = */ false, true);
for (size_t i = 0; i < streams.size(); ++i) {
SimulcastStream* ss = &codec_settings->simulcastStream[i];
ss->width = static_cast<uint16_t>(streams[i].width);
ss->height = static_cast<uint16_t>(streams[i].height);
ss->numberOfTemporalLayers =
static_cast<unsigned char>(*streams[i].num_temporal_layers);
ss->maxBitrate = streams[i].max_bitrate_bps / 1000;
ss->targetBitrate = streams[i].target_bitrate_bps / 1000;
ss->minBitrate = streams[i].min_bitrate_bps / 1000;
ss->qpMax = streams[i].max_qp;
ss->active = true;
}
}
void ConfigureSvc(VideoCodec* codec_settings) {
RTC_CHECK_EQ(kVideoCodecVP9, codec_settings->codecType);
const std::vector<SpatialLayer> layers = GetSvcConfig(
codec_settings->width, codec_settings->height, kMaxFramerateFps,
/*min_spatial_layers=*/1, codec_settings->VP9()->numberOfSpatialLayers,
codec_settings->VP9()->numberOfTemporalLayers,
/* is_screen_sharing = */ false);
ASSERT_EQ(codec_settings->VP9()->numberOfSpatialLayers, layers.size())
<< "GetSvcConfig returned fewer spatial layers than configured.";
for (size_t i = 0; i < layers.size(); ++i) {
codec_settings->spatialLayers[i] = layers[i];
}
}
std::string CodecSpecificToString(const VideoCodec& codec) {
char buf[1024];
rtc::SimpleStringBuilder ss(buf);
switch (codec.codecType) {
case kVideoCodecVP8:
ss << "complexity: " << static_cast<int>(codec.VP8().complexity);
ss << "\nnum_temporal_layers: "
<< static_cast<int>(codec.VP8().numberOfTemporalLayers);
ss << "\ndenoising: " << codec.VP8().denoisingOn;
ss << "\nautomatic_resize: " << codec.VP8().automaticResizeOn;
ss << "\nframe_dropping: " << codec.VP8().frameDroppingOn;
ss << "\nkey_frame_interval: " << codec.VP8().keyFrameInterval;
break;
case kVideoCodecVP9:
ss << "complexity: " << static_cast<int>(codec.VP9().complexity);
ss << "\nnum_temporal_layers: "
<< static_cast<int>(codec.VP9().numberOfTemporalLayers);
ss << "\nnum_spatial_layers: "
<< static_cast<int>(codec.VP9().numberOfSpatialLayers);
ss << "\ndenoising: " << codec.VP9().denoisingOn;
ss << "\nframe_dropping: " << codec.VP9().frameDroppingOn;
ss << "\nkey_frame_interval: " << codec.VP9().keyFrameInterval;
ss << "\nadaptive_qp_mode: " << codec.VP9().adaptiveQpMode;
ss << "\nautomatic_resize: " << codec.VP9().automaticResizeOn;
ss << "\nflexible_mode: " << codec.VP9().flexibleMode;
break;
case kVideoCodecH264:
ss << "frame_dropping: " << codec.H264().frameDroppingOn;
ss << "\nkey_frame_interval: " << codec.H264().keyFrameInterval;
break;
default:
break;
}
return ss.str();
}
bool RunEncodeInRealTime(const VideoCodecTestFixtureImpl::Config& config) {
if (config.measure_cpu || config.encode_in_real_time) {
return true;
}
return false;
}
std::string FilenameWithParams(
const VideoCodecTestFixtureImpl::Config& config) {
return config.filename + "_" + config.CodecName() + "_" +
std::to_string(config.codec_settings.startBitrate);
}
} // namespace
VideoCodecTestFixtureImpl::Config::Config() = default;
void VideoCodecTestFixtureImpl::Config::SetCodecSettings(
std::string codec_name,
size_t num_simulcast_streams,
size_t num_spatial_layers,
size_t num_temporal_layers,
bool denoising_on,
bool frame_dropper_on,
bool spatial_resize_on,
size_t width,
size_t height) {
this->codec_name = codec_name;
VideoCodecType codec_type = PayloadStringToCodecType(codec_name);
webrtc::test::CodecSettings(codec_type, &codec_settings);
// TODO(brandtr): Move the setting of |width| and |height| to the tests, and
// DCHECK that they are set before initializing the codec instead.
codec_settings.width = static_cast<uint16_t>(width);
codec_settings.height = static_cast<uint16_t>(height);
RTC_CHECK(num_simulcast_streams >= 1 &&
num_simulcast_streams <= kMaxSimulcastStreams);
RTC_CHECK(num_spatial_layers >= 1 && num_spatial_layers <= kMaxSpatialLayers);
RTC_CHECK(num_temporal_layers >= 1 &&
num_temporal_layers <= kMaxTemporalStreams);
// Simulcast is only available with VP8.
RTC_CHECK(num_simulcast_streams < 2 || codec_type == kVideoCodecVP8);
// Spatial scalability is only available with VP9.
RTC_CHECK(num_spatial_layers < 2 || codec_type == kVideoCodecVP9);
// Some base code requires numberOfSimulcastStreams to be set to zero
// when simulcast is not used.
codec_settings.numberOfSimulcastStreams =
num_simulcast_streams <= 1 ? 0
: static_cast<uint8_t>(num_simulcast_streams);
switch (codec_settings.codecType) {
case kVideoCodecVP8:
codec_settings.VP8()->numberOfTemporalLayers =
static_cast<uint8_t>(num_temporal_layers);
codec_settings.VP8()->denoisingOn = denoising_on;
codec_settings.VP8()->automaticResizeOn = spatial_resize_on;
codec_settings.VP8()->frameDroppingOn = frame_dropper_on;
codec_settings.VP8()->keyFrameInterval = kBaseKeyFrameInterval;
break;
case kVideoCodecVP9:
codec_settings.VP9()->numberOfTemporalLayers =
static_cast<uint8_t>(num_temporal_layers);
codec_settings.VP9()->denoisingOn = denoising_on;
codec_settings.VP9()->frameDroppingOn = frame_dropper_on;
codec_settings.VP9()->keyFrameInterval = kBaseKeyFrameInterval;
codec_settings.VP9()->automaticResizeOn = spatial_resize_on;
codec_settings.VP9()->numberOfSpatialLayers =
static_cast<uint8_t>(num_spatial_layers);
break;
case kVideoCodecH264:
codec_settings.H264()->frameDroppingOn = frame_dropper_on;
codec_settings.H264()->keyFrameInterval = kBaseKeyFrameInterval;
break;
default:
break;
}
if (codec_settings.numberOfSimulcastStreams > 1) {
ConfigureSimulcast(&codec_settings);
} else if (codec_settings.codecType == kVideoCodecVP9 &&
codec_settings.VP9()->numberOfSpatialLayers > 1) {
ConfigureSvc(&codec_settings);
}
}
size_t VideoCodecTestFixtureImpl::Config::NumberOfCores() const {
return use_single_core ? 1 : CpuInfo::DetectNumberOfCores();
}
size_t VideoCodecTestFixtureImpl::Config::NumberOfTemporalLayers() const {
if (codec_settings.codecType == kVideoCodecVP8) {
return codec_settings.VP8().numberOfTemporalLayers;
} else if (codec_settings.codecType == kVideoCodecVP9) {
return codec_settings.VP9().numberOfTemporalLayers;
} else {
return 1;
}
}
size_t VideoCodecTestFixtureImpl::Config::NumberOfSpatialLayers() const {
if (codec_settings.codecType == kVideoCodecVP9) {
return codec_settings.VP9().numberOfSpatialLayers;
} else {
return 1;
}
}
size_t VideoCodecTestFixtureImpl::Config::NumberOfSimulcastStreams() const {
return codec_settings.numberOfSimulcastStreams;
}
std::string VideoCodecTestFixtureImpl::Config::ToString() const {
std::string codec_type = CodecTypeToPayloadString(codec_settings.codecType);
rtc::StringBuilder ss;
ss << "test_name: " << test_name;
ss << "\nfilename: " << filename;
ss << "\nnum_frames: " << num_frames;
ss << "\nmax_payload_size_bytes: " << max_payload_size_bytes;
ss << "\ndecode: " << decode;
ss << "\nuse_single_core: " << use_single_core;
ss << "\nmeasure_cpu: " << measure_cpu;
ss << "\nnum_cores: " << NumberOfCores();
ss << "\ncodec_type: " << codec_type;
ss << "\n\n--> codec_settings";
ss << "\nwidth: " << codec_settings.width;
ss << "\nheight: " << codec_settings.height;
ss << "\nmax_framerate_fps: " << codec_settings.maxFramerate;
ss << "\nstart_bitrate_kbps: " << codec_settings.startBitrate;
ss << "\nmax_bitrate_kbps: " << codec_settings.maxBitrate;
ss << "\nmin_bitrate_kbps: " << codec_settings.minBitrate;
ss << "\nmax_qp: " << codec_settings.qpMax;
ss << "\nnum_simulcast_streams: "
<< static_cast<int>(codec_settings.numberOfSimulcastStreams);
ss << "\n\n--> codec_settings." << codec_type;
ss << "\n" << CodecSpecificToString(codec_settings);
if (codec_settings.numberOfSimulcastStreams > 1) {
for (int i = 0; i < codec_settings.numberOfSimulcastStreams; ++i) {
ss << "\n\n--> codec_settings.simulcastStream[" << i << "]";
const SimulcastStream& simulcast_stream =
codec_settings.simulcastStream[i];
ss << "\nwidth: " << simulcast_stream.width;
ss << "\nheight: " << simulcast_stream.height;
ss << "\nnum_temporal_layers: "
<< static_cast<int>(simulcast_stream.numberOfTemporalLayers);
ss << "\nmin_bitrate_kbps: " << simulcast_stream.minBitrate;
ss << "\ntarget_bitrate_kbps: " << simulcast_stream.targetBitrate;
ss << "\nmax_bitrate_kbps: " << simulcast_stream.maxBitrate;
ss << "\nmax_qp: " << simulcast_stream.qpMax;
ss << "\nactive: " << simulcast_stream.active;
}
}
ss << "\n";
return ss.Release();
}
std::string VideoCodecTestFixtureImpl::Config::CodecName() const {
std::string name = codec_name;
if (name.empty()) {
name = CodecTypeToPayloadString(codec_settings.codecType);
}
if (codec_settings.codecType == kVideoCodecH264) {
if (h264_codec_settings.profile == H264::kProfileConstrainedHigh) {
return name + "-CHP";
} else {
RTC_DCHECK_EQ(h264_codec_settings.profile,
H264::kProfileConstrainedBaseline);
return name + "-CBP";
}
}
return name;
}
// TODO(kthelgason): Move this out of the test fixture impl and
// make available as a shared utility class.
void VideoCodecTestFixtureImpl::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.data(), encoded_frame.size());
for (const webrtc::H264::NaluIndex& index : nalu_indices) {
webrtc::H264::NaluType nalu_type = webrtc::H264::ParseNaluType(
encoded_frame.data()[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 == VideoFrameType::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 == VideoFrameType::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 VideoCodecTestFixtureImpl::CpuProcessTime final {
public:
explicit CpuProcessTime(const Config& 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) {
RTC_LOG(LS_INFO) << "cpu_usage_percent: "
<< GetUsagePercent() / config_.NumberOfCores();
}
}
private:
double GetUsagePercent() const {
return static_cast<double>(cpu_time_) / wallclock_time_ * 100.0;
}
const Config config_;
int64_t cpu_time_ = 0;
int64_t wallclock_time_ = 0;
};
VideoCodecTestFixtureImpl::VideoCodecTestFixtureImpl(Config config)
: encoder_factory_(std::make_unique<InternalEncoderFactory>()),
decoder_factory_(std::make_unique<InternalDecoderFactory>()),
config_(config) {}
VideoCodecTestFixtureImpl::VideoCodecTestFixtureImpl(
Config config,
std::unique_ptr<VideoDecoderFactory> decoder_factory,
std::unique_ptr<VideoEncoderFactory> encoder_factory)
: encoder_factory_(std::move(encoder_factory)),
decoder_factory_(std::move(decoder_factory)),
config_(config) {}
VideoCodecTestFixtureImpl::~VideoCodecTestFixtureImpl() = default;
// Processes all frames in the clip and verifies the result.
void VideoCodecTestFixtureImpl::RunTest(
const std::vector<RateProfile>& rate_profiles,
const std::vector<RateControlThresholds>* rc_thresholds,
const std::vector<QualityThresholds>* quality_thresholds,
const BitstreamThresholds* bs_thresholds) {
RTC_DCHECK(!rate_profiles.empty());
// To emulate operation on a production VideoStreamEncoder, we call the
// codecs on a task queue.
TaskQueueForTest task_queue("VidProc TQ");
SetUpAndInitObjects(&task_queue, rate_profiles[0].target_kbps,
rate_profiles[0].input_fps);
PrintSettings(&task_queue);
ProcessAllFrames(&task_queue, rate_profiles);
ReleaseAndCloseObjects(&task_queue);
AnalyzeAllFrames(rate_profiles, rc_thresholds, quality_thresholds,
bs_thresholds);
}
void VideoCodecTestFixtureImpl::ProcessAllFrames(
TaskQueueForTest* task_queue,
const std::vector<RateProfile>& rate_profiles) {
// Set initial rates.
auto rate_profile = rate_profiles.begin();
task_queue->PostTask([this, rate_profile] {
processor_->SetRates(rate_profile->target_kbps, rate_profile->input_fps);
});
cpu_process_time_->Start();
for (size_t frame_num = 0; frame_num < config_.num_frames; ++frame_num) {
auto next_rate_profile = std::next(rate_profile);
if (next_rate_profile != rate_profiles.end() &&
frame_num == next_rate_profile->frame_num) {
rate_profile = next_rate_profile;
task_queue->PostTask([this, rate_profile] {
processor_->SetRates(rate_profile->target_kbps,
rate_profile->input_fps);
});
}
task_queue->PostTask([this] { processor_->ProcessFrame(); });
if (RunEncodeInRealTime(config_)) {
// Roughly pace the frames.
const int frame_duration_ms =
std::ceil(rtc::kNumMillisecsPerSec / rate_profile->input_fps);
SleepMs(frame_duration_ms);
}
}
// Wait until we know that the last frame has been sent for encode.
task_queue->SendTask([] {}, RTC_FROM_HERE);
// Give the VideoProcessor pipeline some time to process the last frame,
// and then release the codecs.
SleepMs(1 * rtc::kNumMillisecsPerSec);
cpu_process_time_->Stop();
}
void VideoCodecTestFixtureImpl::AnalyzeAllFrames(
const std::vector<RateProfile>& rate_profiles,
const std::vector<RateControlThresholds>* rc_thresholds,
const std::vector<QualityThresholds>* quality_thresholds,
const BitstreamThresholds* bs_thresholds) {
for (size_t rate_profile_idx = 0; rate_profile_idx < rate_profiles.size();
++rate_profile_idx) {
const size_t first_frame_num = rate_profiles[rate_profile_idx].frame_num;
const size_t last_frame_num =
rate_profile_idx + 1 < rate_profiles.size()
? rate_profiles[rate_profile_idx + 1].frame_num - 1
: config_.num_frames - 1;
RTC_CHECK(last_frame_num >= first_frame_num);
VideoStatistics send_stat = stats_.SliceAndCalcAggregatedVideoStatistic(
first_frame_num, last_frame_num);
RTC_LOG(LS_INFO) << "==> Send stats";
RTC_LOG(LS_INFO) << send_stat.ToString("send_") << "\n";
std::vector<VideoStatistics> layer_stats =
stats_.SliceAndCalcLayerVideoStatistic(first_frame_num, last_frame_num);
RTC_LOG(LS_INFO) << "==> Receive stats";
for (const auto& layer_stat : layer_stats) {
RTC_LOG(LS_INFO) << layer_stat.ToString("recv_") << "\n";
// For perf dashboard.
char modifier_buf[256];
rtc::SimpleStringBuilder modifier(modifier_buf);
modifier << "_r" << rate_profile_idx << "_sl" << layer_stat.spatial_idx;
auto PrintResultHelper = [&modifier, this](const std::string& measurement,
double value,
const std::string& units) {
PrintResult(measurement, modifier.str(), config_.test_name, value,
units, /*important=*/false);
};
if (layer_stat.temporal_idx == config_.NumberOfTemporalLayers() - 1) {
PrintResultHelper("enc_speed", layer_stat.enc_speed_fps, "fps");
PrintResultHelper("avg_key_frame_size",
layer_stat.avg_key_frame_size_bytes, "bytes");
PrintResultHelper("num_key_frames", layer_stat.num_key_frames,
"frames");
printf("\n");
}
modifier << "tl" << layer_stat.temporal_idx;
PrintResultHelper("dec_speed", layer_stat.dec_speed_fps, "fps");
PrintResultHelper("avg_delta_frame_size",
layer_stat.avg_delta_frame_size_bytes, "bytes");
PrintResultHelper("bitrate", layer_stat.bitrate_kbps, "kbps");
PrintResultHelper("framerate", layer_stat.framerate_fps, "fps");
PrintResultHelper("avg_psnr_y", layer_stat.avg_psnr_y, "dB");
PrintResultHelper("avg_psnr_u", layer_stat.avg_psnr_u, "dB");
PrintResultHelper("avg_psnr_v", layer_stat.avg_psnr_v, "dB");
PrintResultHelper("min_psnr_yuv", layer_stat.min_psnr, "dB");
PrintResultHelper("avg_qp", layer_stat.avg_qp, "");
printf("\n");
if (layer_stat.temporal_idx == config_.NumberOfTemporalLayers() - 1) {
printf("\n");
}
}
const RateControlThresholds* rc_threshold =
rc_thresholds ? &(*rc_thresholds)[rate_profile_idx] : nullptr;
const QualityThresholds* quality_threshold =
quality_thresholds ? &(*quality_thresholds)[rate_profile_idx] : nullptr;
VerifyVideoStatistic(send_stat, rc_threshold, quality_threshold,
bs_thresholds,
rate_profiles[rate_profile_idx].target_kbps,
rate_profiles[rate_profile_idx].input_fps);
}
if (config_.print_frame_level_stats) {
RTC_LOG(LS_INFO) << "==> Frame stats";
std::vector<VideoCodecTestStats::FrameStatistics> frame_stats =
stats_.GetFrameStatistics();
for (const auto& frame_stat : frame_stats) {
RTC_LOG(LS_INFO) << frame_stat.ToString();
}
}
cpu_process_time_->Print();
}
void VideoCodecTestFixtureImpl::VerifyVideoStatistic(
const VideoStatistics& video_stat,
const RateControlThresholds* rc_thresholds,
const QualityThresholds* quality_thresholds,
const BitstreamThresholds* bs_thresholds,
size_t target_bitrate_kbps,
double input_framerate_fps) {
if (rc_thresholds) {
const float bitrate_mismatch_percent =
100 * std::fabs(1.0f * video_stat.bitrate_kbps - target_bitrate_kbps) /
target_bitrate_kbps;
const float framerate_mismatch_percent =
100 * std::fabs(video_stat.framerate_fps - input_framerate_fps) /
input_framerate_fps;
EXPECT_LE(bitrate_mismatch_percent,
rc_thresholds->max_avg_bitrate_mismatch_percent);
EXPECT_LE(video_stat.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(video_stat.avg_delay_sec,
rc_thresholds->max_avg_buffer_level_sec);
EXPECT_LE(video_stat.max_key_frame_delay_sec,
rc_thresholds->max_max_key_frame_delay_sec);
EXPECT_LE(video_stat.max_delta_frame_delay_sec,
rc_thresholds->max_max_delta_frame_delay_sec);
EXPECT_LE(video_stat.num_spatial_resizes,
rc_thresholds->max_num_spatial_resizes);
EXPECT_LE(video_stat.num_key_frames, rc_thresholds->max_num_key_frames);
}
if (quality_thresholds) {
EXPECT_GT(video_stat.avg_psnr, quality_thresholds->min_avg_psnr);
EXPECT_GT(video_stat.min_psnr, quality_thresholds->min_min_psnr);
// SSIM calculation is not optimized and thus it is disabled in real-time
// mode.
if (!config_.encode_in_real_time) {
EXPECT_GT(video_stat.avg_ssim, quality_thresholds->min_avg_ssim);
EXPECT_GT(video_stat.min_ssim, quality_thresholds->min_min_ssim);
}
}
if (bs_thresholds) {
EXPECT_LE(video_stat.max_nalu_size_bytes,
bs_thresholds->max_max_nalu_size_bytes);
}
}
void VideoCodecTestFixtureImpl::CreateEncoderAndDecoder() {
SdpVideoFormat::Parameters params;
if (config_.codec_settings.codecType == kVideoCodecH264) {
const char* packetization_mode =
config_.h264_codec_settings.packetization_mode ==
H264PacketizationMode::NonInterleaved
? "1"
: "0";
params = {{cricket::kH264FmtpProfileLevelId,
*H264::ProfileLevelIdToString(H264::ProfileLevelId(
config_.h264_codec_settings.profile, H264::kLevel3_1))},
{cricket::kH264FmtpPacketizationMode, packetization_mode}};
} else {
params = {};
}
SdpVideoFormat format(config_.codec_name, params);
encoder_ = encoder_factory_->CreateVideoEncoder(format);
EXPECT_TRUE(encoder_) << "Encoder not successfully created.";
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)));
}
for (const auto& decoder : decoders_) {
EXPECT_TRUE(decoder) << "Decoder not successfully created.";
}
}
void VideoCodecTestFixtureImpl::DestroyEncoderAndDecoder() {
decoders_.clear();
encoder_.reset();
}
VideoCodecTestStats& VideoCodecTestFixtureImpl::GetStats() {
return stats_;
}
void VideoCodecTestFixtureImpl::SetUpAndInitObjects(
TaskQueueForTest* task_queue,
size_t initial_bitrate_kbps,
double initial_framerate_fps) {
config_.codec_settings.minBitrate = 0;
config_.codec_settings.startBitrate = static_cast<int>(initial_bitrate_kbps);
config_.codec_settings.maxFramerate = std::ceil(initial_framerate_fps);
// Create file objects for quality analysis.
source_frame_reader_.reset(
new YuvFrameReaderImpl(config_.filepath, config_.codec_settings.width,
config_.codec_settings.height));
EXPECT_TRUE(source_frame_reader_->Init());
RTC_DCHECK(encoded_frame_writers_.empty());
RTC_DCHECK(decoded_frame_writers_.empty());
if (config_.visualization_params.save_encoded_ivf ||
config_.visualization_params.save_decoded_y4m) {
const size_t num_simulcast_or_spatial_layers = std::max(
config_.NumberOfSimulcastStreams(), config_.NumberOfSpatialLayers());
const size_t num_temporal_layers = config_.NumberOfTemporalLayers();
for (size_t simulcast_svc_idx = 0;
simulcast_svc_idx < num_simulcast_or_spatial_layers;
++simulcast_svc_idx) {
const std::string output_filename_base = JoinFilename(
config_.output_path, FilenameWithParams(config_) + "_sl" +
std::to_string(simulcast_svc_idx));
if (config_.visualization_params.save_encoded_ivf) {
for (size_t temporal_idx = 0; temporal_idx < num_temporal_layers;
++temporal_idx) {
const std::string output_file_path = output_filename_base + "tl" +
std::to_string(temporal_idx) +
".ivf";
FileWrapper ivf_file = FileWrapper::OpenWriteOnly(output_file_path);
const VideoProcessor::LayerKey layer_key(simulcast_svc_idx,
temporal_idx);
encoded_frame_writers_[layer_key] =
IvfFileWriter::Wrap(std::move(ivf_file), /*byte_limit=*/0);
}
}
if (config_.visualization_params.save_decoded_y4m) {
FrameWriter* decoded_frame_writer = new Y4mFrameWriterImpl(
output_filename_base + ".y4m", config_.codec_settings.width,
config_.codec_settings.height, config_.codec_settings.maxFramerate);
EXPECT_TRUE(decoded_frame_writer->Init());
decoded_frame_writers_.push_back(
std::unique_ptr<FrameWriter>(decoded_frame_writer));
}
}
}
stats_.Clear();
cpu_process_time_.reset(new CpuProcessTime(config_));
task_queue->SendTask(
[this]() {
CreateEncoderAndDecoder();
processor_ = std::make_unique<VideoProcessor>(
encoder_.get(), &decoders_, source_frame_reader_.get(), config_,
&stats_, &encoded_frame_writers_,
decoded_frame_writers_.empty() ? nullptr : &decoded_frame_writers_);
},
RTC_FROM_HERE);
}
void VideoCodecTestFixtureImpl::ReleaseAndCloseObjects(
TaskQueueForTest* task_queue) {
task_queue->SendTask(
[this]() {
processor_.reset();
// The VideoProcessor must be destroyed before the codecs.
DestroyEncoderAndDecoder();
},
RTC_FROM_HERE);
source_frame_reader_->Close();
// Close visualization files.
for (auto& encoded_frame_writer : encoded_frame_writers_) {
EXPECT_TRUE(encoded_frame_writer.second->Close());
}
encoded_frame_writers_.clear();
for (auto& decoded_frame_writer : decoded_frame_writers_) {
decoded_frame_writer->Close();
}
decoded_frame_writers_.clear();
}
void VideoCodecTestFixtureImpl::PrintSettings(
TaskQueueForTest* task_queue) const {
RTC_LOG(LS_INFO) << "==> Config";
RTC_LOG(LS_INFO) << config_.ToString();
RTC_LOG(LS_INFO) << "==> Codec names";
std::string encoder_name;
std::string decoder_name;
task_queue->SendTask(
[this, &encoder_name, &decoder_name] {
encoder_name = encoder_->GetEncoderInfo().implementation_name;
decoder_name = decoders_.at(0)->ImplementationName();
},
RTC_FROM_HERE);
RTC_LOG(LS_INFO) << "enc_impl_name: " << encoder_name;
RTC_LOG(LS_INFO) << "dec_impl_name: " << decoder_name;
}
} // namespace test
} // namespace webrtc