On-fly calculation of quality metrics.
Calculation of quality metrics required writing of decoded video to file. There were two drawbacks with that approach. First, frame drops significantly affected metrics because comparison was done against the last decoded frame. Second, simulcast/SVC required writing of multiple files. This might be too much data to dump. On-fly metrics calculation is done in frame decoded callback. Calculation time is excluded from encoding/decoding time. If CPU usage measurement is enabled metrics calculation is disabled since it affects CPU usage. The results are reported in Stats::PrintSummary. Bug: webrtc:8524 Change-Id: Id54fb21f2f95deeb93757afaf46bde7d7ae18dac Reviewed-on: https://webrtc-review.googlesource.com/22560 Commit-Queue: Sergey Silkin <ssilkin@webrtc.org> Reviewed-by: Patrik Höglund <phoglund@webrtc.org> Reviewed-by: Rasmus Brandt <brandtr@webrtc.org> Cr-Commit-Position: refs/heads/master@{#20798}
This commit is contained in:
committed by
Commit Bot
parent
c5975bf25d
commit
64eaa99cfc
@ -42,6 +42,16 @@ bool LessForBitRate(const FrameStatistic& s1, const FrameStatistic& s2) {
|
||||
return s1.bitrate_kbps < s2.bitrate_kbps;
|
||||
}
|
||||
|
||||
bool LessForPsnr(const FrameStatistic& s1, const FrameStatistic& s2) {
|
||||
RTC_DCHECK_NE(s1.frame_number, s2.frame_number);
|
||||
return s1.psnr < s2.psnr;
|
||||
}
|
||||
|
||||
bool LessForSsim(const FrameStatistic& s1, const FrameStatistic& s2) {
|
||||
RTC_DCHECK_NE(s1.frame_number, s2.frame_number);
|
||||
return s1.ssim < s2.ssim;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FrameStatistic* Stats::AddFrame() {
|
||||
@ -77,6 +87,8 @@ void Stats::PrintSummary() const {
|
||||
size_t num_key_frames = 0;
|
||||
size_t num_delta_frames = 0;
|
||||
int num_encode_failures = 0;
|
||||
double total_psnr = 0.0;
|
||||
double total_ssim = 0.0;
|
||||
|
||||
for (const FrameStatistic& stat : stats_) {
|
||||
total_encoding_time_us += stat.encode_time_us;
|
||||
@ -92,6 +104,10 @@ void Stats::PrintSummary() const {
|
||||
if (stat.encode_return_code != 0) {
|
||||
++num_encode_failures;
|
||||
}
|
||||
if (stat.decoding_successful) {
|
||||
total_psnr += stat.psnr;
|
||||
total_ssim += stat.ssim;
|
||||
}
|
||||
}
|
||||
|
||||
// Encoding stats.
|
||||
@ -164,6 +180,24 @@ void Stats::PrintSummary() const {
|
||||
printf(" Max bitrate: %7d kbps (frame %d)\n", frame_it->bitrate_kbps,
|
||||
frame_it->frame_number);
|
||||
|
||||
// Quality.
|
||||
printf("Quality:\n");
|
||||
if (decoded_frames.empty()) {
|
||||
printf("No successfully decoded frames exist in this statistics.\n");
|
||||
} else {
|
||||
frame_it = std::min_element(decoded_frames.begin(), decoded_frames.end(),
|
||||
LessForPsnr);
|
||||
printf(" PSNR min: %f (frame %d)\n", frame_it->psnr,
|
||||
frame_it->frame_number);
|
||||
printf(" PSNR avg: %f\n", total_psnr / decoded_frames.size());
|
||||
|
||||
frame_it = std::min_element(decoded_frames.begin(), decoded_frames.end(),
|
||||
LessForSsim);
|
||||
printf(" SSIM min: %f (frame %d)\n", frame_it->ssim,
|
||||
frame_it->frame_number);
|
||||
printf(" SSIM avg: %f\n", total_ssim / decoded_frames.size());
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
printf("Total encoding time : %7d ms.\n", total_encoding_time_us / 1000);
|
||||
printf("Total decoding time : %7d ms.\n", total_decoding_time_us / 1000);
|
||||
|
||||
@ -50,6 +50,10 @@ struct FrameStatistic {
|
||||
int packets_dropped = 0;
|
||||
size_t total_packets = 0;
|
||||
size_t manipulated_length = 0;
|
||||
|
||||
// Quality.
|
||||
float psnr = 0.0;
|
||||
float ssim = 0.0;
|
||||
};
|
||||
|
||||
// Statistics for a sequence of processed frames. This class is not thread safe.
|
||||
|
||||
@ -99,7 +99,6 @@ void ExtractBufferWithSize(const VideoFrame& image,
|
||||
VideoProcessor::VideoProcessor(webrtc::VideoEncoder* encoder,
|
||||
webrtc::VideoDecoder* decoder,
|
||||
FrameReader* analysis_frame_reader,
|
||||
FrameWriter* analysis_frame_writer,
|
||||
PacketManipulator* packet_manipulator,
|
||||
const TestConfig& config,
|
||||
Stats* stats,
|
||||
@ -113,7 +112,6 @@ VideoProcessor::VideoProcessor(webrtc::VideoEncoder* encoder,
|
||||
decode_callback_(this),
|
||||
packet_manipulator_(packet_manipulator),
|
||||
analysis_frame_reader_(analysis_frame_reader),
|
||||
analysis_frame_writer_(analysis_frame_writer),
|
||||
encoded_frame_writer_(encoded_frame_writer),
|
||||
decoded_frame_writer_(decoded_frame_writer),
|
||||
last_inputed_frame_num_(-1),
|
||||
@ -127,7 +125,6 @@ VideoProcessor::VideoProcessor(webrtc::VideoEncoder* encoder,
|
||||
RTC_DCHECK(decoder);
|
||||
RTC_DCHECK(packet_manipulator);
|
||||
RTC_DCHECK(analysis_frame_reader);
|
||||
RTC_DCHECK(analysis_frame_writer);
|
||||
RTC_DCHECK(stats);
|
||||
|
||||
// Setup required callbacks for the encoder and decoder.
|
||||
@ -158,7 +155,7 @@ VideoProcessor::~VideoProcessor() {
|
||||
|
||||
void VideoProcessor::ProcessFrame() {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
|
||||
++last_inputed_frame_num_;
|
||||
const int frame_number = ++last_inputed_frame_num_;
|
||||
|
||||
// Get frame from file.
|
||||
rtc::scoped_refptr<I420BufferInterface> buffer(
|
||||
@ -167,15 +164,13 @@ void VideoProcessor::ProcessFrame() {
|
||||
// Use the frame number as the basis for timestamp to identify frames. Let the
|
||||
// first timestamp be non-zero, to not make the IvfFileWriter believe that we
|
||||
// want to use capture timestamps in the IVF files.
|
||||
const uint32_t rtp_timestamp = (last_inputed_frame_num_ + 1) *
|
||||
kRtpClockRateHz /
|
||||
const uint32_t rtp_timestamp = (frame_number + 1) * kRtpClockRateHz /
|
||||
config_.codec_settings.maxFramerate;
|
||||
rtp_timestamp_to_frame_num_[rtp_timestamp] = last_inputed_frame_num_;
|
||||
VideoFrame source_frame(buffer, rtp_timestamp, kNoRenderTime,
|
||||
webrtc::kVideoRotation_0);
|
||||
rtp_timestamp_to_frame_num_[rtp_timestamp] = frame_number;
|
||||
input_frames_[frame_number] = rtc::MakeUnique<VideoFrame>(
|
||||
buffer, rtp_timestamp, kNoRenderTime, webrtc::kVideoRotation_0);
|
||||
|
||||
std::vector<FrameType> frame_types =
|
||||
config_.FrameTypeForFrame(last_inputed_frame_num_);
|
||||
std::vector<FrameType> frame_types = config_.FrameTypeForFrame(frame_number);
|
||||
|
||||
// Create frame statistics object used for aggregation at end of test run.
|
||||
FrameStatistic* frame_stat = stats_->AddFrame();
|
||||
@ -184,7 +179,7 @@ void VideoProcessor::ProcessFrame() {
|
||||
// time recordings should wrap the Encode call as tightly as possible.
|
||||
frame_stat->encode_start_ns = rtc::TimeNanos();
|
||||
frame_stat->encode_return_code =
|
||||
encoder_->Encode(source_frame, nullptr, &frame_types);
|
||||
encoder_->Encode(*input_frames_[frame_number], nullptr, &frame_types);
|
||||
}
|
||||
|
||||
void VideoProcessor::SetRates(int bitrate_kbps, int framerate_fps) {
|
||||
@ -222,30 +217,24 @@ void VideoProcessor::FrameEncoded(webrtc::VideoCodecType codec,
|
||||
config_.encoded_frame_checker->CheckEncodedFrame(codec, encoded_image);
|
||||
}
|
||||
|
||||
// Check for dropped frames.
|
||||
const int frame_number =
|
||||
rtp_timestamp_to_frame_num_[encoded_image._timeStamp];
|
||||
|
||||
// Ensure strict monotonicity.
|
||||
RTC_CHECK_GT(frame_number, last_encoded_frame_num_);
|
||||
|
||||
// Check for dropped frames.
|
||||
bool last_frame_missing = false;
|
||||
if (frame_number > 0) {
|
||||
RTC_DCHECK_GE(last_encoded_frame_num_, 0);
|
||||
int num_dropped_from_last_encode =
|
||||
frame_number - last_encoded_frame_num_ - 1;
|
||||
RTC_DCHECK_GE(num_dropped_from_last_encode, 0);
|
||||
RTC_CHECK_GE(rate_update_index_, 0);
|
||||
num_dropped_frames_[rate_update_index_] += num_dropped_from_last_encode;
|
||||
if (num_dropped_from_last_encode > 0) {
|
||||
// For dropped frames, we write out the last decoded frame to avoid
|
||||
// getting out of sync for the computation of PSNR and SSIM.
|
||||
for (int i = 0; i < num_dropped_from_last_encode; i++) {
|
||||
WriteDecodedFrameToFile(&last_decoded_frame_buffer_);
|
||||
}
|
||||
}
|
||||
const FrameStatistic* last_encoded_frame_stat =
|
||||
stats_->GetFrame(last_encoded_frame_num_);
|
||||
last_frame_missing = (last_encoded_frame_stat->manipulated_length == 0);
|
||||
}
|
||||
// Ensure strict monotonicity.
|
||||
RTC_CHECK_GT(frame_number, last_encoded_frame_num_);
|
||||
last_encoded_frame_num_ = frame_number;
|
||||
|
||||
// Update frame statistics.
|
||||
@ -285,18 +274,12 @@ void VideoProcessor::FrameEncoded(webrtc::VideoCodecType codec,
|
||||
frame_stat->decode_return_code =
|
||||
decoder_->Decode(copied_image, last_frame_missing, nullptr);
|
||||
|
||||
if (frame_stat->decode_return_code != WEBRTC_VIDEO_CODEC_OK) {
|
||||
// Write the last successful frame the output file to avoid getting it out
|
||||
// of sync with the source file for SSIM and PSNR comparisons.
|
||||
WriteDecodedFrameToFile(&last_decoded_frame_buffer_);
|
||||
}
|
||||
|
||||
if (encoded_frame_writer_) {
|
||||
RTC_CHECK(encoded_frame_writer_->WriteFrame(encoded_image, codec));
|
||||
}
|
||||
}
|
||||
|
||||
void VideoProcessor::FrameDecoded(const VideoFrame& image) {
|
||||
void VideoProcessor::FrameDecoded(const VideoFrame& decoded_frame) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
|
||||
|
||||
// For the highest measurement accuracy of the decode time, the start/stop
|
||||
@ -304,46 +287,67 @@ void VideoProcessor::FrameDecoded(const VideoFrame& image) {
|
||||
int64_t decode_stop_ns = rtc::TimeNanos();
|
||||
|
||||
// Update frame statistics.
|
||||
const int frame_number = rtp_timestamp_to_frame_num_[image.timestamp()];
|
||||
const int frame_number =
|
||||
rtp_timestamp_to_frame_num_[decoded_frame.timestamp()];
|
||||
FrameStatistic* frame_stat = stats_->GetFrame(frame_number);
|
||||
frame_stat->decoded_width = image.width();
|
||||
frame_stat->decoded_height = image.height();
|
||||
frame_stat->decoded_width = decoded_frame.width();
|
||||
frame_stat->decoded_height = decoded_frame.height();
|
||||
frame_stat->decode_time_us =
|
||||
GetElapsedTimeMicroseconds(frame_stat->decode_start_ns, decode_stop_ns);
|
||||
frame_stat->decoding_successful = true;
|
||||
|
||||
// Ensure strict monotonicity.
|
||||
RTC_CHECK_GT(frame_number, last_decoded_frame_num_);
|
||||
|
||||
// Check if the codecs have resized the frame since previously decoded frame.
|
||||
if (frame_number > 0) {
|
||||
RTC_CHECK_GE(last_decoded_frame_num_, 0);
|
||||
if (decoded_frame_writer_ && last_decoded_frame_num_ >= 0) {
|
||||
// For dropped/lost frames, write out the last decoded frame to make it
|
||||
// look like a freeze at playback.
|
||||
const int num_dropped_frames = frame_number - last_decoded_frame_num_;
|
||||
for (int i = 0; i < num_dropped_frames; i++) {
|
||||
WriteDecodedFrameToFile(&last_decoded_frame_buffer_);
|
||||
}
|
||||
}
|
||||
// TODO(ssilkin): move to FrameEncoded when webm:1474 is implemented.
|
||||
const FrameStatistic* last_decoded_frame_stat =
|
||||
stats_->GetFrame(last_decoded_frame_num_);
|
||||
if (image.width() != last_decoded_frame_stat->decoded_width ||
|
||||
image.height() != last_decoded_frame_stat->decoded_height) {
|
||||
if (decoded_frame.width() != last_decoded_frame_stat->decoded_width ||
|
||||
decoded_frame.height() != last_decoded_frame_stat->decoded_height) {
|
||||
RTC_CHECK_GE(rate_update_index_, 0);
|
||||
++num_spatial_resizes_[rate_update_index_];
|
||||
}
|
||||
}
|
||||
// Ensure strict monotonicity.
|
||||
RTC_CHECK_GT(frame_number, last_decoded_frame_num_);
|
||||
last_decoded_frame_num_ = frame_number;
|
||||
|
||||
// If the frame size is different from the original size, scale back to the
|
||||
// original size. This is needed for the PSNR and SSIM calculations.
|
||||
rtc::Buffer buffer;
|
||||
ExtractBufferWithSize(image, config_.codec_settings.width,
|
||||
config_.codec_settings.height, &buffer);
|
||||
WriteDecodedFrameToFile(&buffer);
|
||||
// Skip quality metrics calculation to not affect CPU usage.
|
||||
if (!config_.measure_cpu) {
|
||||
frame_stat->psnr =
|
||||
I420PSNR(input_frames_[frame_number].get(), &decoded_frame);
|
||||
frame_stat->ssim =
|
||||
I420SSIM(input_frames_[frame_number].get(), &decoded_frame);
|
||||
}
|
||||
|
||||
last_decoded_frame_buffer_ = std::move(buffer);
|
||||
// Delay erasing of input frames by one frame. The current frame might
|
||||
// still be needed for other simulcast stream or spatial layer.
|
||||
const int frame_number_to_erase = frame_number - 1;
|
||||
if (frame_number_to_erase >= 0) {
|
||||
auto input_frame_erase_to =
|
||||
input_frames_.lower_bound(frame_number_to_erase);
|
||||
input_frames_.erase(input_frames_.begin(), input_frame_erase_to);
|
||||
}
|
||||
|
||||
if (decoded_frame_writer_) {
|
||||
ExtractBufferWithSize(decoded_frame, config_.codec_settings.width,
|
||||
config_.codec_settings.height,
|
||||
&last_decoded_frame_buffer_);
|
||||
WriteDecodedFrameToFile(&last_decoded_frame_buffer_);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoProcessor::WriteDecodedFrameToFile(rtc::Buffer* buffer) {
|
||||
RTC_DCHECK_EQ(buffer->size(), analysis_frame_writer_->FrameLength());
|
||||
RTC_CHECK(analysis_frame_writer_->WriteFrame(buffer->data()));
|
||||
if (decoded_frame_writer_) {
|
||||
RTC_DCHECK_EQ(buffer->size(), decoded_frame_writer_->FrameLength());
|
||||
RTC_CHECK(decoded_frame_writer_->WriteFrame(buffer->data()));
|
||||
}
|
||||
RTC_DCHECK_EQ(buffer->size(), decoded_frame_writer_->FrameLength());
|
||||
RTC_CHECK(decoded_frame_writer_->WriteFrame(buffer->data()));
|
||||
}
|
||||
|
||||
bool VideoProcessor::ExcludeFrame(const EncodedImage& encoded_image) {
|
||||
|
||||
@ -62,7 +62,6 @@ class VideoProcessor {
|
||||
VideoProcessor(webrtc::VideoEncoder* encoder,
|
||||
webrtc::VideoDecoder* decoder,
|
||||
FrameReader* analysis_frame_reader,
|
||||
FrameWriter* analysis_frame_writer,
|
||||
PacketManipulator* packet_manipulator,
|
||||
const TestConfig& config,
|
||||
Stats* stats,
|
||||
@ -199,10 +198,16 @@ class VideoProcessor {
|
||||
// Fake network.
|
||||
PacketManipulator* const packet_manipulator_;
|
||||
|
||||
// Input frames. Used as reference at frame quality evaluation.
|
||||
// Async codecs might queue frames. To handle that we keep input frame
|
||||
// and release it after corresponding coded frame is decoded and quality
|
||||
// measurement is done.
|
||||
std::map<int, std::unique_ptr<VideoFrame>> input_frames_
|
||||
RTC_GUARDED_BY(sequence_checker_);
|
||||
|
||||
// These (mandatory) file manipulators are used for, e.g., objective PSNR and
|
||||
// SSIM calculations at the end of a test run.
|
||||
FrameReader* const analysis_frame_reader_;
|
||||
FrameWriter* const analysis_frame_writer_;
|
||||
|
||||
// These (optional) file writers are used to persistently store the encoded
|
||||
// and decoded bitstreams. The purpose is to give the experimenter an option
|
||||
|
||||
@ -51,23 +51,6 @@ const float kInitialBufferSize = 0.5f;
|
||||
const float kOptimalBufferSize = 0.6f;
|
||||
const float kScaleKeyFrameSize = 0.5f;
|
||||
|
||||
void VerifyQuality(const QualityMetricsResult& psnr_result,
|
||||
const QualityMetricsResult& ssim_result,
|
||||
const QualityThresholds& quality_thresholds) {
|
||||
EXPECT_GT(psnr_result.average, quality_thresholds.min_avg_psnr);
|
||||
EXPECT_GT(psnr_result.min, quality_thresholds.min_min_psnr);
|
||||
EXPECT_GT(ssim_result.average, quality_thresholds.min_avg_ssim);
|
||||
EXPECT_GT(ssim_result.min, quality_thresholds.min_min_ssim);
|
||||
}
|
||||
|
||||
void PrintQualityMetrics(const QualityMetricsResult& psnr_result,
|
||||
const QualityMetricsResult& ssim_result) {
|
||||
printf("Quality statistics\n==\n");
|
||||
printf("PSNR avg: %f, min: %f\n", psnr_result.average, psnr_result.min);
|
||||
printf("SSIM avg: %f, min: %f\n", ssim_result.average, ssim_result.min);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
bool RunEncodeInRealTime(const TestConfig& config) {
|
||||
if (config.measure_cpu) {
|
||||
return true;
|
||||
@ -284,10 +267,15 @@ void VideoProcessorIntegrationTest::ProcessFramesAndMaybeVerify(
|
||||
// Calculate and print rate control statistics.
|
||||
rate_update_index = 0;
|
||||
frame_number = 0;
|
||||
quality_ = QualityMetrics();
|
||||
ResetRateControlMetrics(rate_update_index, rate_profiles);
|
||||
while (frame_number < num_frames) {
|
||||
UpdateRateControlMetrics(frame_number);
|
||||
|
||||
if (quality_thresholds) {
|
||||
UpdateQualityMetrics(frame_number);
|
||||
}
|
||||
|
||||
if (bs_thresholds) {
|
||||
VerifyBitstream(frame_number, *bs_thresholds);
|
||||
}
|
||||
@ -310,28 +298,14 @@ void VideoProcessorIntegrationTest::ProcessFramesAndMaybeVerify(
|
||||
VerifyRateControlMetrics(rate_update_index, rc_thresholds, num_dropped_frames,
|
||||
num_spatial_resizes);
|
||||
|
||||
if (quality_thresholds) {
|
||||
VerifyQualityMetrics(*quality_thresholds);
|
||||
}
|
||||
|
||||
// Calculate and print other statistics.
|
||||
EXPECT_EQ(num_frames, static_cast<int>(stats_.size()));
|
||||
stats_.PrintSummary();
|
||||
cpu_process_time_->Print();
|
||||
|
||||
// Calculate and print image quality statistics.
|
||||
// TODO(marpan): Should compute these quality metrics per SetRates update.
|
||||
QualityMetricsResult psnr_result, ssim_result;
|
||||
EXPECT_EQ(0, I420MetricsFromFiles(config_.input_filename.c_str(),
|
||||
config_.output_filename.c_str(),
|
||||
config_.codec_settings.width,
|
||||
config_.codec_settings.height, &psnr_result,
|
||||
&ssim_result));
|
||||
if (quality_thresholds) {
|
||||
VerifyQuality(psnr_result, ssim_result, *quality_thresholds);
|
||||
}
|
||||
PrintQualityMetrics(psnr_result, ssim_result);
|
||||
|
||||
// Remove analysis file.
|
||||
if (remove(config_.output_filename.c_str()) < 0) {
|
||||
fprintf(stderr, "Failed to remove temporary file!\n");
|
||||
}
|
||||
}
|
||||
|
||||
void VideoProcessorIntegrationTest::CreateEncoderAndDecoder() {
|
||||
@ -482,8 +456,8 @@ void VideoProcessorIntegrationTest::SetUpAndInitObjects(
|
||||
task_queue->PostTask([this, &sync_event]() {
|
||||
processor_ = rtc::MakeUnique<VideoProcessor>(
|
||||
encoder_.get(), decoder_.get(), analysis_frame_reader_.get(),
|
||||
analysis_frame_writer_.get(), packet_manipulator_.get(), config_,
|
||||
&stats_, encoded_frame_writer_.get(), decoded_frame_writer_.get());
|
||||
packet_manipulator_.get(), config_, &stats_,
|
||||
encoded_frame_writer_.get(), decoded_frame_writer_.get());
|
||||
sync_event.Set();
|
||||
});
|
||||
sync_event.Wait(rtc::Event::kForever);
|
||||
@ -501,9 +475,7 @@ void VideoProcessorIntegrationTest::ReleaseAndCloseObjects(
|
||||
// The VideoProcessor must be destroyed before the codecs.
|
||||
DestroyEncoderAndDecoder();
|
||||
|
||||
// Close the analysis files before we use them for SSIM/PSNR calculations.
|
||||
analysis_frame_reader_->Close();
|
||||
analysis_frame_writer_->Close();
|
||||
|
||||
// Close visualization files.
|
||||
if (encoded_frame_writer_) {
|
||||
@ -591,6 +563,19 @@ void VideoProcessorIntegrationTest::VerifyRateControlMetrics(
|
||||
}
|
||||
}
|
||||
|
||||
void VideoProcessorIntegrationTest::UpdateQualityMetrics(int frame_number) {
|
||||
FrameStatistic* frame_stat = stats_.GetFrame(frame_number);
|
||||
if (frame_stat->decoding_successful) {
|
||||
++quality_.num_decoded_frames;
|
||||
quality_.total_psnr += frame_stat->psnr;
|
||||
quality_.total_ssim += frame_stat->ssim;
|
||||
if (frame_stat->psnr < quality_.min_psnr)
|
||||
quality_.min_psnr = frame_stat->psnr;
|
||||
if (frame_stat->ssim < quality_.min_ssim)
|
||||
quality_.min_ssim = frame_stat->ssim;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoProcessorIntegrationTest::PrintRateControlMetrics(
|
||||
int rate_update_index,
|
||||
const std::vector<int>& num_dropped_frames,
|
||||
@ -656,6 +641,17 @@ void VideoProcessorIntegrationTest::VerifyBitstream(
|
||||
EXPECT_LE(*(frame_stat->max_nalu_length), bs_thresholds.max_nalu_length);
|
||||
}
|
||||
|
||||
void VideoProcessorIntegrationTest::VerifyQualityMetrics(
|
||||
const QualityThresholds& quality_thresholds) {
|
||||
EXPECT_GT(quality_.num_decoded_frames, 0);
|
||||
EXPECT_GT(quality_.total_psnr / quality_.num_decoded_frames,
|
||||
quality_thresholds.min_avg_psnr);
|
||||
EXPECT_GT(quality_.min_psnr, quality_thresholds.min_min_psnr);
|
||||
EXPECT_GT(quality_.total_ssim / quality_.num_decoded_frames,
|
||||
quality_thresholds.min_avg_ssim);
|
||||
EXPECT_GT(quality_.min_ssim, quality_thresholds.min_min_ssim);
|
||||
}
|
||||
|
||||
// Reset quantities before each encoder rate update.
|
||||
void VideoProcessorIntegrationTest::ResetRateControlMetrics(
|
||||
int rate_update_index,
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#define MODULES_VIDEO_CODING_CODECS_TEST_VIDEOPROCESSOR_INTEGRATIONTEST_H_
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -159,6 +160,14 @@ class VideoProcessorIntegrationTest : public testing::Test {
|
||||
float key_framesize_kbits;
|
||||
};
|
||||
|
||||
struct QualityMetrics {
|
||||
int num_decoded_frames = 0;
|
||||
double total_psnr = 0.0;
|
||||
double total_ssim = 0.0;
|
||||
double min_psnr = std::numeric_limits<double>::max();
|
||||
double min_ssim = std::numeric_limits<double>::max();
|
||||
};
|
||||
|
||||
void CreateEncoderAndDecoder();
|
||||
void DestroyEncoderAndDecoder();
|
||||
void SetUpAndInitObjects(rtc::TaskQueue* task_queue,
|
||||
@ -185,6 +194,9 @@ class VideoProcessorIntegrationTest : public testing::Test {
|
||||
void VerifyBitstream(int frame_number,
|
||||
const BitstreamThresholds& bs_thresholds);
|
||||
|
||||
void UpdateQualityMetrics(int frame_number);
|
||||
void VerifyQualityMetrics(const QualityThresholds& quality_thresholds);
|
||||
|
||||
void PrintSettings() const;
|
||||
|
||||
// Codecs.
|
||||
@ -207,6 +219,8 @@ class VideoProcessorIntegrationTest : public testing::Test {
|
||||
|
||||
// Rates set for every encoder rate update.
|
||||
TargetRates target_;
|
||||
|
||||
QualityMetrics quality_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
|
||||
@ -20,7 +20,6 @@
|
||||
#include "test/gmock.h"
|
||||
#include "test/gtest.h"
|
||||
#include "test/testsupport/mock/mock_frame_reader.h"
|
||||
#include "test/testsupport/mock/mock_frame_writer.h"
|
||||
#include "test/testsupport/packet_reader.h"
|
||||
#include "test/testsupport/unittest_utils.h"
|
||||
#include "test/video_codec_settings.h"
|
||||
@ -55,7 +54,7 @@ class VideoProcessorTest : public testing::Test {
|
||||
.WillRepeatedly(Return(kFrameSize));
|
||||
video_processor_ = rtc::MakeUnique<VideoProcessor>(
|
||||
&encoder_mock_, &decoder_mock_, &frame_reader_mock_,
|
||||
&frame_writer_mock_, &packet_manipulator_mock_, config_, &stats_,
|
||||
&packet_manipulator_mock_, config_, &stats_,
|
||||
nullptr /* encoded_frame_writer */, nullptr /* decoded_frame_writer */);
|
||||
}
|
||||
|
||||
@ -78,7 +77,6 @@ class VideoProcessorTest : public testing::Test {
|
||||
MockVideoEncoder encoder_mock_;
|
||||
MockVideoDecoder decoder_mock_;
|
||||
MockFrameReader frame_reader_mock_;
|
||||
MockFrameWriter frame_writer_mock_;
|
||||
MockPacketManipulator packet_manipulator_mock_;
|
||||
Stats stats_;
|
||||
std::unique_ptr<VideoProcessor> video_processor_;
|
||||
|
||||
Reference in New Issue
Block a user