blob: 6521112bbc1b8b8aea4f740ab451b1598195af1d [file] [log] [blame]
/*
* 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/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 {
using Psnr = VideoCodecStats::Frame::Psnr;
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);
return psnr;
}
} // namespace
VideoCodecAnalyzer::VideoCodecAnalyzer(
rtc::TaskQueue& task_queue,
ReferenceVideoSource* reference_video_source)
: task_queue_(task_queue),
reference_video_source_(reference_video_source),
num_frames_(0) {
sequence_checker_.Detach();
}
void VideoCodecAnalyzer::StartEncode(const VideoFrame& input_frame) {
int64_t encode_start_us = rtc::TimeMicros();
task_queue_.PostTask(
[this, timestamp_rtp = input_frame.timestamp(), encode_start_us]() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_CHECK(frame_num_.find(timestamp_rtp) == frame_num_.end());
frame_num_[timestamp_rtp] = num_frames_++;
int frame_num = frame_num_[timestamp_rtp];
VideoCodecStats::Frame* fs =
stats_.AddFrame(frame_num, timestamp_rtp, /*spatial_idx=*/0);
fs->encode_start = Timestamp::Micros(encode_start_us);
});
}
void VideoCodecAnalyzer::FinishEncode(const EncodedImage& frame) {
int64_t encode_finished_us = rtc::TimeMicros();
task_queue_.PostTask([this, timestamp_rtp = frame.Timestamp(),
spatial_idx = frame.SpatialIndex().value_or(0),
temporal_idx = frame.TemporalIndex().value_or(0),
width = frame._encodedWidth,
height = frame._encodedHeight,
frame_type = frame._frameType,
size_bytes = frame.size(), qp = frame.qp_,
encode_finished_us]() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
if (spatial_idx > 0) {
VideoCodecStats::Frame* fs0 =
stats_.GetFrame(timestamp_rtp, /*spatial_idx=*/0);
VideoCodecStats::Frame* fs =
stats_.AddFrame(fs0->frame_num, timestamp_rtp, spatial_idx);
fs->encode_start = fs0->encode_start;
}
VideoCodecStats::Frame* fs = stats_.GetFrame(timestamp_rtp, spatial_idx);
fs->spatial_idx = spatial_idx;
fs->temporal_idx = temporal_idx;
fs->width = width;
fs->height = height;
fs->size_bytes = static_cast<int>(size_bytes);
fs->qp = qp;
fs->keyframe = frame_type == VideoFrameType::kVideoFrameKey;
fs->encode_time = Timestamp::Micros(encode_finished_us) - fs->encode_start;
fs->encoded = true;
});
}
void VideoCodecAnalyzer::StartDecode(const EncodedImage& frame) {
int64_t decode_start_us = rtc::TimeMicros();
task_queue_.PostTask([this, timestamp_rtp = frame.Timestamp(),
spatial_idx = frame.SpatialIndex().value_or(0),
size_bytes = frame.size(), decode_start_us]() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
VideoCodecStats::Frame* fs = stats_.GetFrame(timestamp_rtp, spatial_idx);
if (fs == nullptr) {
if (frame_num_.find(timestamp_rtp) == frame_num_.end()) {
frame_num_[timestamp_rtp] = num_frames_++;
}
int frame_num = frame_num_[timestamp_rtp];
fs = stats_.AddFrame(frame_num, timestamp_rtp, spatial_idx);
fs->spatial_idx = spatial_idx;
fs->size_bytes = size_bytes;
}
fs->decode_start = Timestamp::Micros(decode_start_us);
});
}
void VideoCodecAnalyzer::FinishDecode(const VideoFrame& frame,
int spatial_idx) {
int64_t decode_finished_us = rtc::TimeMicros();
task_queue_.PostTask([this, timestamp_rtp = frame.timestamp(), spatial_idx,
width = frame.width(), height = frame.height(),
decode_finished_us]() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
VideoCodecStats::Frame* fs = stats_.GetFrame(timestamp_rtp, spatial_idx);
fs->decode_time = Timestamp::Micros(decode_finished_us) - fs->decode_start;
if (!fs->encoded) {
fs->width = width;
fs->height = height;
}
fs->decoded = 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);
VideoCodecStats::Frame* fs =
this->stats_.GetFrame(timestamp_rtp, spatial_idx);
fs->psnr = psnr;
});
}
}
std::unique_ptr<VideoCodecStats> VideoCodecAnalyzer::GetStats() {
std::unique_ptr<VideoCodecStats> stats;
rtc::Event ready;
task_queue_.PostTask([this, &stats, &ready]() mutable {
RTC_DCHECK_RUN_ON(&sequence_checker_);
stats.reset(new VideoCodecStatsImpl(stats_));
ready.Set();
});
ready.Wait(rtc::Event::kForever);
return stats;
}
} // namespace test
} // namespace webrtc