webrtc/modules/video_coding/codecs/test/video_codec_analyzer.cc
Sergey Silkin 2e1a9a4ae0 Add video codec tester.
This tester is an improved version of VideoProcessor and VideoCodecTestFixture and will eventually replace them.

The tester provides better separation between codecs and testing logic. Its knowledge about codecs is limited to frame encode/decode calls and frame ready callbacks. Instantiation and configuration of codecs are the test responsibilities.

Other differences:
- Run encoding and decoding in separate threads
- Run quality analysis in a separate thread
- Reference frame buffering is moved into video source (which re-read frames from the file).
- Make it possible to run decode-only tests

This CL is MVP implementation: it adds only 1 test (video_codec_test.cc, ConstantRate/EncodeDecodeTest) and the test is disabled for now.

Bug: b/261160916
Change-Id: Ida24a2fca1b1496237fa695c812084877c76379f
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/283525
Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38901}
2022-12-15 14:32:53 +00:00

186 lines
7 KiB
C++

/*
* Copyright (c) 2022 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/video_codec_analyzer.h"
#include <memory>
#include "api/task_queue/default_task_queue_factory.h"
#include "api/test/video_codec_tester.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_codec_constants.h"
#include "api/video/video_frame.h"
#include "rtc_base/checks.h"
#include "rtc_base/event.h"
#include "rtc_base/time_utils.h"
#include "third_party/libyuv/include/libyuv/compare.h"
namespace webrtc {
namespace test {
namespace {
struct Psnr {
double y;
double u;
double v;
double yuv;
};
Psnr CalcPsnr(const I420BufferInterface& ref_buffer,
const I420BufferInterface& dec_buffer) {
RTC_CHECK_EQ(ref_buffer.width(), dec_buffer.width());
RTC_CHECK_EQ(ref_buffer.height(), dec_buffer.height());
uint64_t sse_y = libyuv::ComputeSumSquareErrorPlane(
dec_buffer.DataY(), dec_buffer.StrideY(), ref_buffer.DataY(),
ref_buffer.StrideY(), dec_buffer.width(), dec_buffer.height());
uint64_t sse_u = libyuv::ComputeSumSquareErrorPlane(
dec_buffer.DataU(), dec_buffer.StrideU(), ref_buffer.DataU(),
ref_buffer.StrideU(), dec_buffer.width() / 2, dec_buffer.height() / 2);
uint64_t sse_v = libyuv::ComputeSumSquareErrorPlane(
dec_buffer.DataV(), dec_buffer.StrideV(), ref_buffer.DataV(),
ref_buffer.StrideV(), dec_buffer.width() / 2, dec_buffer.height() / 2);
int num_y_samples = dec_buffer.width() * dec_buffer.height();
Psnr psnr;
psnr.y = libyuv::SumSquareErrorToPsnr(sse_y, num_y_samples);
psnr.u = libyuv::SumSquareErrorToPsnr(sse_u, num_y_samples / 4);
psnr.v = libyuv::SumSquareErrorToPsnr(sse_v, num_y_samples / 4);
psnr.yuv = libyuv::SumSquareErrorToPsnr(sse_y + sse_u + sse_v,
num_y_samples + num_y_samples / 2);
return psnr;
}
} // namespace
VideoCodecAnalyzer::VideoCodecAnalyzer(
rtc::TaskQueue& task_queue,
ReferenceVideoSource* reference_video_source)
: task_queue_(task_queue), reference_video_source_(reference_video_source) {
sequence_checker_.Detach();
}
void VideoCodecAnalyzer::StartEncode(const VideoFrame& input_frame) {
int64_t encode_started_ns = rtc::TimeNanos();
task_queue_.PostTask(
[this, timestamp_rtp = input_frame.timestamp(), encode_started_ns]() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
VideoCodecTestStats::FrameStatistics* fs =
stats_.GetOrAddFrame(timestamp_rtp, /*spatial_idx=*/0);
fs->encode_start_ns = encode_started_ns;
});
}
void VideoCodecAnalyzer::FinishEncode(const EncodedImage& frame) {
int64_t encode_finished_ns = rtc::TimeNanos();
task_queue_.PostTask([this, timestamp_rtp = frame.Timestamp(),
spatial_idx = frame.SpatialIndex().value_or(0),
temporal_idx = frame.TemporalIndex().value_or(0),
frame_type = frame._frameType, qp = frame.qp_,
frame_size_bytes = frame.size(), encode_finished_ns]() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
VideoCodecTestStats::FrameStatistics* fs =
stats_.GetOrAddFrame(timestamp_rtp, spatial_idx);
VideoCodecTestStats::FrameStatistics* fs_base =
stats_.GetOrAddFrame(timestamp_rtp, 0);
fs->encode_start_ns = fs_base->encode_start_ns;
fs->spatial_idx = spatial_idx;
fs->temporal_idx = temporal_idx;
fs->frame_type = frame_type;
fs->qp = qp;
fs->encode_time_us = (encode_finished_ns - fs->encode_start_ns) /
rtc::kNumNanosecsPerMicrosec;
fs->length_bytes = frame_size_bytes;
fs->encoding_successful = true;
});
}
void VideoCodecAnalyzer::StartDecode(const EncodedImage& frame) {
int64_t decode_start_ns = rtc::TimeNanos();
task_queue_.PostTask([this, timestamp_rtp = frame.Timestamp(),
spatial_idx = frame.SpatialIndex().value_or(0),
frame_size_bytes = frame.size(), decode_start_ns]() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
VideoCodecTestStats::FrameStatistics* fs =
stats_.GetOrAddFrame(timestamp_rtp, spatial_idx);
if (fs->length_bytes == 0) {
// In encode-decode test the frame size is set in EncodeFinished. In
// decode-only test set it here.
fs->length_bytes = frame_size_bytes;
}
fs->decode_start_ns = decode_start_ns;
});
}
void VideoCodecAnalyzer::FinishDecode(const VideoFrame& frame,
int spatial_idx) {
int64_t decode_finished_ns = rtc::TimeNanos();
task_queue_.PostTask([this, timestamp_rtp = frame.timestamp(), spatial_idx,
width = frame.width(), height = frame.height(),
decode_finished_ns]() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
VideoCodecTestStats::FrameStatistics* fs =
stats_.GetFrameWithTimestamp(timestamp_rtp, spatial_idx);
fs->decode_time_us = (decode_finished_ns - fs->decode_start_ns) /
rtc::kNumNanosecsPerMicrosec;
fs->decoded_width = width;
fs->decoded_height = height;
fs->decoding_successful = true;
});
if (reference_video_source_ != nullptr) {
// Copy hardware-backed frame into main memory to release output buffers
// which number may be limited in hardware decoders.
rtc::scoped_refptr<I420BufferInterface> decoded_buffer =
frame.video_frame_buffer()->ToI420();
task_queue_.PostTask([this, decoded_buffer,
timestamp_rtp = frame.timestamp(), spatial_idx]() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
VideoFrame ref_frame = reference_video_source_->GetFrame(
timestamp_rtp, {.width = decoded_buffer->width(),
.height = decoded_buffer->height()});
rtc::scoped_refptr<I420BufferInterface> ref_buffer =
ref_frame.video_frame_buffer()->ToI420();
Psnr psnr = CalcPsnr(*decoded_buffer, *ref_buffer);
VideoCodecTestStats::FrameStatistics* fs =
this->stats_.GetFrameWithTimestamp(timestamp_rtp, spatial_idx);
fs->psnr_y = static_cast<float>(psnr.y);
fs->psnr_u = static_cast<float>(psnr.u);
fs->psnr_v = static_cast<float>(psnr.v);
fs->psnr = static_cast<float>(psnr.yuv);
fs->quality_analysis_successful = true;
});
}
}
std::unique_ptr<VideoCodecTestStats> VideoCodecAnalyzer::GetStats() {
std::unique_ptr<VideoCodecTestStats> stats;
rtc::Event ready;
task_queue_.PostTask([this, &stats, &ready]() mutable {
RTC_DCHECK_RUN_ON(&sequence_checker_);
stats.reset(new VideoCodecTestStatsImpl(stats_));
ready.Set();
});
ready.Wait(rtc::Event::kForever);
return stats;
}
} // namespace test
} // namespace webrtc