mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 05:40:42 +01:00
Refactor video codec testing stats
This CL introduces VideoCodecStats and VideoCodecStatsImpl which provide baseline functionalities for storing, slicing and aggregation of encoded and/or decoded video frame statistics. To facilitate metrics logging (not implemented yet), SamplesStatsCounter is used for stream parameters. VideoCodecStats/VideoCodecStatsImpl will replace existing VideoCodecTestStats/VideoCodecTestStatsImpl. Bug: b/261160916, webrtc:14852 Change-Id: I0f96ce1ed9be3aee2a702804612524676c9882fd Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/291323 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@{#39248}
This commit is contained in:
parent
97d1c34769
commit
6c60f72a6b
17 changed files with 699 additions and 128 deletions
16
api/BUILD.gn
16
api/BUILD.gn
|
@ -1003,6 +1003,20 @@ if (rtc_include_tests) {
|
|||
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
|
||||
}
|
||||
|
||||
rtc_library("video_codec_stats_api") {
|
||||
visibility = [ "*" ]
|
||||
testonly = true
|
||||
sources = [ "test/video_codec_stats.h" ]
|
||||
deps = [
|
||||
"../api/numerics:numerics",
|
||||
"../api/units:data_rate",
|
||||
"../api/units:frequency",
|
||||
"test/metrics:metric",
|
||||
"test/metrics:metrics_logger",
|
||||
]
|
||||
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
|
||||
}
|
||||
|
||||
rtc_library("videocodec_test_fixture_api") {
|
||||
visibility = [ "*" ]
|
||||
testonly = true
|
||||
|
@ -1019,7 +1033,7 @@ if (rtc_include_tests) {
|
|||
testonly = true
|
||||
sources = [ "test/video_codec_tester.h" ]
|
||||
deps = [
|
||||
":videocodec_test_stats_api",
|
||||
":video_codec_stats_api",
|
||||
"../modules/video_coding/svc:scalability_mode_util",
|
||||
"video:encoded_image",
|
||||
"video:resolution",
|
||||
|
|
117
api/test/video_codec_stats.h
Normal file
117
api/test/video_codec_stats.h
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
#ifndef API_TEST_VIDEO_CODEC_STATS_H_
|
||||
#define API_TEST_VIDEO_CODEC_STATS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/numerics/samples_stats_counter.h"
|
||||
#include "api/test/metrics/metric.h"
|
||||
#include "api/test/metrics/metrics_logger.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/frequency.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
// Interface for encoded and/or decoded video frame and stream statistics.
|
||||
class VideoCodecStats {
|
||||
public:
|
||||
// Filter for slicing frames.
|
||||
struct Filter {
|
||||
absl::optional<int> first_frame;
|
||||
absl::optional<int> last_frame;
|
||||
absl::optional<int> spatial_idx;
|
||||
absl::optional<int> temporal_idx;
|
||||
};
|
||||
|
||||
struct Frame {
|
||||
int frame_num = 0;
|
||||
uint32_t timestamp_rtp = 0;
|
||||
|
||||
int spatial_idx = 0;
|
||||
int temporal_idx = 0;
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int size_bytes = 0;
|
||||
bool keyframe = false;
|
||||
absl::optional<int> qp = absl::nullopt;
|
||||
absl::optional<int> base_spatial_idx = absl::nullopt;
|
||||
|
||||
Timestamp encode_start = Timestamp::Zero();
|
||||
TimeDelta encode_time = TimeDelta::Zero();
|
||||
Timestamp decode_start = Timestamp::Zero();
|
||||
TimeDelta decode_time = TimeDelta::Zero();
|
||||
|
||||
struct Psnr {
|
||||
double y = 0.0;
|
||||
double u = 0.0;
|
||||
double v = 0.0;
|
||||
};
|
||||
absl::optional<Psnr> psnr = absl::nullopt;
|
||||
|
||||
bool encoded = false;
|
||||
bool decoded = false;
|
||||
};
|
||||
|
||||
struct Stream {
|
||||
int num_frames = 0;
|
||||
int num_keyframes = 0;
|
||||
|
||||
SamplesStatsCounter width;
|
||||
SamplesStatsCounter height;
|
||||
SamplesStatsCounter size_bytes;
|
||||
SamplesStatsCounter qp;
|
||||
|
||||
SamplesStatsCounter encode_time_us;
|
||||
SamplesStatsCounter decode_time_us;
|
||||
|
||||
DataRate bitrate = DataRate::Zero();
|
||||
Frequency framerate = Frequency::Zero();
|
||||
int bitrate_mismatch_pct = 0;
|
||||
int framerate_mismatch_pct = 0;
|
||||
SamplesStatsCounter transmission_time_us;
|
||||
|
||||
struct Psnr {
|
||||
SamplesStatsCounter y;
|
||||
SamplesStatsCounter u;
|
||||
SamplesStatsCounter v;
|
||||
} psnr;
|
||||
};
|
||||
|
||||
virtual ~VideoCodecStats() = default;
|
||||
|
||||
// Returns frames from interval, spatial and temporal layer specified by given
|
||||
// `filter`.
|
||||
virtual std::vector<Frame> Slice(
|
||||
absl::optional<Filter> filter = absl::nullopt) const = 0;
|
||||
|
||||
// Returns video statistics aggregated for given `frames`. If `bitrate` is
|
||||
// provided, also performs rate control analysis. If `framerate` is provided,
|
||||
// also calculates frame rate mismatch.
|
||||
virtual Stream Aggregate(
|
||||
const std::vector<Frame>& frames,
|
||||
absl::optional<DataRate> bitrate = absl::nullopt,
|
||||
absl::optional<Frequency> framerate = absl::nullopt) const = 0;
|
||||
|
||||
// Logs `Stream` metrics to provided `MetricsLogger`.
|
||||
virtual void LogMetrics(MetricsLogger* logger,
|
||||
const Stream& stream,
|
||||
std::string test_case_name) const = 0;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // API_TEST_VIDEO_CODEC_STATS_H_
|
|
@ -14,7 +14,8 @@
|
|||
#include <memory>
|
||||
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include "api/test/videocodec_test_stats.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/test/video_codec_stats.h"
|
||||
#include "api/video/encoded_image.h"
|
||||
#include "api/video/resolution.h"
|
||||
#include "api/video/video_frame.h"
|
||||
|
@ -104,7 +105,7 @@ class VideoCodecTester {
|
|||
// Pulls coded video frames from `video_source` and passes them to `decoder`.
|
||||
// Returns `VideoCodecTestStats` object that contains collected per-frame
|
||||
// metrics.
|
||||
virtual std::unique_ptr<VideoCodecTestStats> RunDecodeTest(
|
||||
virtual std::unique_ptr<VideoCodecStats> RunDecodeTest(
|
||||
std::unique_ptr<CodedVideoSource> video_source,
|
||||
std::unique_ptr<Decoder> decoder,
|
||||
const DecoderSettings& decoder_settings) = 0;
|
||||
|
@ -112,7 +113,7 @@ class VideoCodecTester {
|
|||
// Pulls raw video frames from `video_source` and passes them to `encoder`.
|
||||
// Returns `VideoCodecTestStats` object that contains collected per-frame
|
||||
// metrics.
|
||||
virtual std::unique_ptr<VideoCodecTestStats> RunEncodeTest(
|
||||
virtual std::unique_ptr<VideoCodecStats> RunEncodeTest(
|
||||
std::unique_ptr<RawVideoSource> video_source,
|
||||
std::unique_ptr<Encoder> encoder,
|
||||
const EncoderSettings& encoder_settings) = 0;
|
||||
|
@ -120,7 +121,7 @@ class VideoCodecTester {
|
|||
// Pulls raw video frames from `video_source`, passes them to `encoder` and
|
||||
// then passes encoded frames to `decoder`. Returns `VideoCodecTestStats`
|
||||
// object that contains collected per-frame metrics.
|
||||
virtual std::unique_ptr<VideoCodecTestStats> RunEncodeDecodeTest(
|
||||
virtual std::unique_ptr<VideoCodecStats> RunEncodeDecodeTest(
|
||||
std::unique_ptr<RawVideoSource> video_source,
|
||||
std::unique_ptr<Encoder> encoder,
|
||||
std::unique_ptr<Decoder> decoder,
|
||||
|
|
|
@ -54,6 +54,7 @@ struct BitstreamThresholds {
|
|||
};
|
||||
|
||||
// NOTE: This class is still under development and may change without notice.
|
||||
// TODO(webrtc:14852): Deprecated in favor VideoCodecTester.
|
||||
class VideoCodecTestFixture {
|
||||
public:
|
||||
class EncodedFrameChecker {
|
||||
|
|
|
@ -27,6 +27,7 @@ namespace webrtc {
|
|||
namespace test {
|
||||
|
||||
// Statistics for a sequence of processed frames. This class is not thread safe.
|
||||
// TODO(webrtc:14852): Deprecated in favor VideoCodecStats.
|
||||
class VideoCodecTestStats {
|
||||
public:
|
||||
// Statistics for one processed frame.
|
||||
|
|
|
@ -878,6 +878,8 @@ if (rtc_include_tests) {
|
|||
sources = [
|
||||
"codecs/test/video_codec_analyzer.cc",
|
||||
"codecs/test/video_codec_analyzer.h",
|
||||
"codecs/test/video_codec_stats_impl.cc",
|
||||
"codecs/test/video_codec_stats_impl.h",
|
||||
"codecs/test/video_codec_unittest.cc",
|
||||
"codecs/test/video_codec_unittest.h",
|
||||
"codecs/test/videoprocessor.cc",
|
||||
|
@ -896,10 +898,13 @@ if (rtc_include_tests) {
|
|||
"../../api:frame_generator_api",
|
||||
"../../api:scoped_refptr",
|
||||
"../../api:sequence_checker",
|
||||
"../../api:video_codec_stats_api",
|
||||
"../../api:video_codec_tester_api",
|
||||
"../../api:videocodec_test_fixture_api",
|
||||
"../../api/numerics:numerics",
|
||||
"../../api/task_queue",
|
||||
"../../api/task_queue:default_task_queue_factory",
|
||||
"../../api/test/metrics:global_metrics_logger_and_exporter",
|
||||
"../../api/video:builtin_video_bitrate_allocator_factory",
|
||||
"../../api/video:encoded_image",
|
||||
"../../api/video:resolution",
|
||||
|
@ -1037,6 +1042,8 @@ if (rtc_include_tests) {
|
|||
deps = [
|
||||
"../../api:videocodec_test_stats_api",
|
||||
"../../api/numerics",
|
||||
"../../api/test/metrics:global_metrics_logger_and_exporter",
|
||||
"../../api/test/metrics:metric",
|
||||
"../../rtc_base:checks",
|
||||
"../../rtc_base:rtc_numerics",
|
||||
"../../rtc_base:stringutils",
|
||||
|
@ -1162,6 +1169,7 @@ if (rtc_include_tests) {
|
|||
sources = [
|
||||
"chain_diff_calculator_unittest.cc",
|
||||
"codecs/test/video_codec_analyzer_unittest.cc",
|
||||
"codecs/test/video_codec_stats_impl_unittest.cc",
|
||||
"codecs/test/video_codec_tester_impl_unittest.cc",
|
||||
"codecs/test/videocodec_test_fixture_config_unittest.cc",
|
||||
"codecs/test/videocodec_test_stats_impl_unittest.cc",
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#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"
|
||||
|
@ -26,13 +25,7 @@ namespace webrtc {
|
|||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
struct Psnr {
|
||||
double y;
|
||||
double u;
|
||||
double v;
|
||||
double yuv;
|
||||
};
|
||||
using Psnr = VideoCodecStats::Frame::Psnr;
|
||||
|
||||
Psnr CalcPsnr(const I420BufferInterface& ref_buffer,
|
||||
const I420BufferInterface& dec_buffer) {
|
||||
|
@ -56,8 +49,7 @@ Psnr CalcPsnr(const I420BufferInterface& ref_buffer,
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -66,80 +58,101 @@ Psnr CalcPsnr(const I420BufferInterface& ref_buffer,
|
|||
VideoCodecAnalyzer::VideoCodecAnalyzer(
|
||||
rtc::TaskQueue& task_queue,
|
||||
ReferenceVideoSource* reference_video_source)
|
||||
: task_queue_(task_queue), reference_video_source_(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_started_ns = rtc::TimeNanos();
|
||||
int64_t encode_start_us = rtc::TimeMicros();
|
||||
task_queue_.PostTask(
|
||||
[this, timestamp_rtp = input_frame.timestamp(), encode_started_ns]() {
|
||||
[this, timestamp_rtp = input_frame.timestamp(), encode_start_us]() {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
VideoCodecTestStats::FrameStatistics* fs =
|
||||
stats_.GetOrAddFrame(timestamp_rtp, /*spatial_idx=*/0);
|
||||
fs->encode_start_ns = encode_started_ns;
|
||||
|
||||
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_ns = rtc::TimeNanos();
|
||||
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),
|
||||
frame_type = frame._frameType, qp = frame.qp_,
|
||||
frame_size_bytes = frame.size(), encode_finished_ns]() {
|
||||
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_);
|
||||
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;
|
||||
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->frame_type = frame_type;
|
||||
fs->width = width;
|
||||
fs->height = height;
|
||||
fs->size_bytes = static_cast<int>(size_bytes);
|
||||
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;
|
||||
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_ns = rtc::TimeNanos();
|
||||
int64_t decode_start_us = rtc::TimeMicros();
|
||||
task_queue_.PostTask([this, timestamp_rtp = frame.Timestamp(),
|
||||
spatial_idx = frame.SpatialIndex().value_or(0),
|
||||
frame_size_bytes = frame.size(), decode_start_ns]() {
|
||||
size_bytes = frame.size(), decode_start_us]() {
|
||||
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;
|
||||
|
||||
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_++;
|
||||
}
|
||||
fs->decode_start_ns = decode_start_ns;
|
||||
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_ns = rtc::TimeNanos();
|
||||
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_ns]() {
|
||||
decode_finished_us]() {
|
||||
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;
|
||||
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) {
|
||||
|
@ -158,24 +171,20 @@ void VideoCodecAnalyzer::FinishDecode(const VideoFrame& frame,
|
|||
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;
|
||||
VideoCodecStats::Frame* fs =
|
||||
this->stats_.GetFrame(timestamp_rtp, spatial_idx);
|
||||
fs->psnr = psnr;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<VideoCodecTestStats> VideoCodecAnalyzer::GetStats() {
|
||||
std::unique_ptr<VideoCodecTestStats> stats;
|
||||
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 VideoCodecTestStatsImpl(stats_));
|
||||
stats.reset(new VideoCodecStatsImpl(stats_));
|
||||
ready.Set();
|
||||
});
|
||||
ready.Wait(rtc::Event::kForever);
|
||||
|
|
|
@ -11,14 +11,16 @@
|
|||
#ifndef MODULES_VIDEO_CODING_CODECS_TEST_VIDEO_CODEC_ANALYZER_H_
|
||||
#define MODULES_VIDEO_CODING_CODECS_TEST_VIDEO_CODEC_ANALYZER_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/sequence_checker.h"
|
||||
#include "api/test/video_codec_tester.h"
|
||||
#include "api/video/encoded_image.h"
|
||||
#include "api/video/resolution.h"
|
||||
#include "api/video/video_frame.h"
|
||||
#include "modules/video_coding/codecs/test/videocodec_test_stats_impl.h"
|
||||
#include "modules/video_coding/codecs/test/video_codec_stats_impl.h"
|
||||
#include "rtc_base/synchronization/mutex.h"
|
||||
#include "rtc_base/system/no_unique_address.h"
|
||||
#include "rtc_base/task_queue_for_test.h"
|
||||
|
@ -50,12 +52,21 @@ class VideoCodecAnalyzer {
|
|||
|
||||
void FinishDecode(const VideoFrame& frame, int spatial_idx);
|
||||
|
||||
std::unique_ptr<VideoCodecTestStats> GetStats();
|
||||
std::unique_ptr<VideoCodecStats> GetStats();
|
||||
|
||||
protected:
|
||||
rtc::TaskQueue& task_queue_;
|
||||
|
||||
ReferenceVideoSource* const reference_video_source_;
|
||||
VideoCodecTestStatsImpl stats_ RTC_GUARDED_BY(sequence_checker_);
|
||||
|
||||
VideoCodecStatsImpl stats_ RTC_GUARDED_BY(sequence_checker_);
|
||||
|
||||
// Map from RTP timestamp to frame number.
|
||||
std::map<uint32_t, int> frame_num_ RTC_GUARDED_BY(sequence_checker_);
|
||||
|
||||
// Processed frames counter.
|
||||
int num_frames_ RTC_GUARDED_BY(sequence_checker_);
|
||||
|
||||
RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_;
|
||||
};
|
||||
|
||||
|
|
|
@ -22,9 +22,10 @@ namespace test {
|
|||
namespace {
|
||||
using ::testing::Return;
|
||||
using ::testing::Values;
|
||||
using Psnr = VideoCodecStats::Frame::Psnr;
|
||||
|
||||
const size_t kTimestamp = 3000;
|
||||
const size_t kSpatialIdx = 2;
|
||||
const uint32_t kTimestamp = 3000;
|
||||
const int kSpatialIdx = 2;
|
||||
|
||||
class MockReferenceVideoSource
|
||||
: public VideoCodecAnalyzer::ReferenceVideoSource {
|
||||
|
@ -57,17 +58,17 @@ EncodedImage CreateEncodedImage(uint32_t timestamp_rtp, int spatial_idx = 0) {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
TEST(VideoCodecAnalyzerTest, EncodeStartedCreatesFrameStats) {
|
||||
TEST(VideoCodecAnalyzerTest, StartEncode) {
|
||||
TaskQueueForTest task_queue;
|
||||
VideoCodecAnalyzer analyzer(task_queue);
|
||||
analyzer.StartEncode(CreateVideoFrame(kTimestamp));
|
||||
|
||||
auto fs = analyzer.GetStats()->GetFrameStatistics();
|
||||
auto fs = analyzer.GetStats()->Slice();
|
||||
EXPECT_EQ(1u, fs.size());
|
||||
EXPECT_EQ(fs[0].rtp_timestamp, kTimestamp);
|
||||
EXPECT_EQ(fs[0].timestamp_rtp, kTimestamp);
|
||||
}
|
||||
|
||||
TEST(VideoCodecAnalyzerTest, EncodeFinishedUpdatesFrameStats) {
|
||||
TEST(VideoCodecAnalyzerTest, FinishEncode) {
|
||||
TaskQueueForTest task_queue;
|
||||
VideoCodecAnalyzer analyzer(task_queue);
|
||||
analyzer.StartEncode(CreateVideoFrame(kTimestamp));
|
||||
|
@ -75,47 +76,35 @@ TEST(VideoCodecAnalyzerTest, EncodeFinishedUpdatesFrameStats) {
|
|||
EncodedImage encoded_frame = CreateEncodedImage(kTimestamp, kSpatialIdx);
|
||||
analyzer.FinishEncode(encoded_frame);
|
||||
|
||||
auto fs = analyzer.GetStats()->GetFrameStatistics();
|
||||
auto fs = analyzer.GetStats()->Slice();
|
||||
EXPECT_EQ(2u, fs.size());
|
||||
EXPECT_TRUE(fs[1].encoding_successful);
|
||||
EXPECT_EQ(kSpatialIdx, fs[1].spatial_idx);
|
||||
}
|
||||
|
||||
TEST(VideoCodecAnalyzerTest, DecodeStartedNoFrameStatsCreatesFrameStats) {
|
||||
TEST(VideoCodecAnalyzerTest, StartDecode) {
|
||||
TaskQueueForTest task_queue;
|
||||
VideoCodecAnalyzer analyzer(task_queue);
|
||||
analyzer.StartDecode(CreateEncodedImage(kTimestamp, kSpatialIdx));
|
||||
|
||||
auto fs = analyzer.GetStats()->GetFrameStatistics();
|
||||
auto fs = analyzer.GetStats()->Slice();
|
||||
EXPECT_EQ(1u, fs.size());
|
||||
EXPECT_EQ(fs[0].rtp_timestamp, kTimestamp);
|
||||
EXPECT_EQ(kTimestamp, fs[0].timestamp_rtp);
|
||||
}
|
||||
|
||||
TEST(VideoCodecAnalyzerTest, DecodeStartedFrameStatsExistsReusesFrameStats) {
|
||||
TaskQueueForTest task_queue;
|
||||
VideoCodecAnalyzer analyzer(task_queue);
|
||||
analyzer.StartEncode(CreateVideoFrame(kTimestamp));
|
||||
analyzer.StartDecode(CreateEncodedImage(kTimestamp, /*spatial_idx=*/0));
|
||||
|
||||
auto fs = analyzer.GetStats()->GetFrameStatistics();
|
||||
EXPECT_EQ(1u, fs.size());
|
||||
}
|
||||
|
||||
TEST(VideoCodecAnalyzerTest, DecodeFinishedUpdatesFrameStats) {
|
||||
TEST(VideoCodecAnalyzerTest, FinishDecode) {
|
||||
TaskQueueForTest task_queue;
|
||||
VideoCodecAnalyzer analyzer(task_queue);
|
||||
analyzer.StartDecode(CreateEncodedImage(kTimestamp, kSpatialIdx));
|
||||
VideoFrame decoded_frame = CreateVideoFrame(kTimestamp);
|
||||
analyzer.FinishDecode(decoded_frame, kSpatialIdx);
|
||||
|
||||
auto fs = analyzer.GetStats()->GetFrameStatistics();
|
||||
auto fs = analyzer.GetStats()->Slice();
|
||||
EXPECT_EQ(1u, fs.size());
|
||||
|
||||
EXPECT_TRUE(fs[0].decoding_successful);
|
||||
EXPECT_EQ(static_cast<int>(fs[0].decoded_width), decoded_frame.width());
|
||||
EXPECT_EQ(static_cast<int>(fs[0].decoded_height), decoded_frame.height());
|
||||
EXPECT_EQ(decoded_frame.width(), fs[0].width);
|
||||
EXPECT_EQ(decoded_frame.height(), fs[0].height);
|
||||
}
|
||||
|
||||
TEST(VideoCodecAnalyzerTest, DecodeFinishedComputesPsnr) {
|
||||
TEST(VideoCodecAnalyzerTest, ReferenceVideoSource) {
|
||||
TaskQueueForTest task_queue;
|
||||
MockReferenceVideoSource reference_video_source;
|
||||
VideoCodecAnalyzer analyzer(task_queue, &reference_video_source);
|
||||
|
@ -129,12 +118,14 @@ TEST(VideoCodecAnalyzerTest, DecodeFinishedComputesPsnr) {
|
|||
CreateVideoFrame(kTimestamp, /*value_y=*/1, /*value_u=*/2, /*value_v=*/3),
|
||||
kSpatialIdx);
|
||||
|
||||
auto fs = analyzer.GetStats()->GetFrameStatistics();
|
||||
auto fs = analyzer.GetStats()->Slice();
|
||||
EXPECT_EQ(1u, fs.size());
|
||||
EXPECT_TRUE(fs[0].psnr.has_value());
|
||||
|
||||
EXPECT_NEAR(fs[0].psnr_y, 48, 1);
|
||||
EXPECT_NEAR(fs[0].psnr_u, 42, 1);
|
||||
EXPECT_NEAR(fs[0].psnr_v, 38, 1);
|
||||
const Psnr& psnr = *fs[0].psnr;
|
||||
EXPECT_NEAR(psnr.y, 48, 1);
|
||||
EXPECT_NEAR(psnr.u, 42, 1);
|
||||
EXPECT_NEAR(psnr.v, 38, 1);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
|
185
modules/video_coding/codecs/test/video_codec_stats_impl.cc
Normal file
185
modules/video_coding/codecs/test/video_codec_stats_impl.cc
Normal file
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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_stats_impl.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "api/numerics/samples_stats_counter.h"
|
||||
#include "api/test/metrics/global_metrics_logger_and_exporter.h"
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
namespace {
|
||||
using Frame = VideoCodecStats::Frame;
|
||||
using Stream = VideoCodecStats::Stream;
|
||||
|
||||
constexpr Frequency k90kHz = Frequency::Hertz(90000);
|
||||
} // namespace
|
||||
|
||||
std::vector<Frame> VideoCodecStatsImpl::Slice(
|
||||
absl::optional<Filter> filter) const {
|
||||
std::vector<Frame> frames;
|
||||
for (const auto& [frame_id, f] : frames_) {
|
||||
if (filter.has_value()) {
|
||||
if (filter->first_frame.has_value() &&
|
||||
f.frame_num < *filter->first_frame) {
|
||||
continue;
|
||||
}
|
||||
if (filter->last_frame.has_value() && f.frame_num > *filter->last_frame) {
|
||||
continue;
|
||||
}
|
||||
if (filter->spatial_idx.has_value() &&
|
||||
f.spatial_idx != *filter->spatial_idx) {
|
||||
continue;
|
||||
}
|
||||
if (filter->temporal_idx.has_value() &&
|
||||
f.temporal_idx > *filter->temporal_idx) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
frames.push_back(f);
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
Stream VideoCodecStatsImpl::Aggregate(
|
||||
const std::vector<Frame>& frames,
|
||||
absl::optional<DataRate> bitrate,
|
||||
absl::optional<Frequency> framerate) const {
|
||||
std::vector<Frame> superframes = Merge(frames);
|
||||
|
||||
Stream stream;
|
||||
stream.num_frames = static_cast<int>(superframes.size());
|
||||
|
||||
for (const auto& f : superframes) {
|
||||
Timestamp time = Timestamp::Micros((f.timestamp_rtp / k90kHz).us());
|
||||
// TODO(webrtc:14852): Add AddSample(double value, Timestamp time) method to
|
||||
// SamplesStatsCounter.
|
||||
stream.decode_time_us.AddSample(SamplesStatsCounter::StatsSample(
|
||||
{.value = static_cast<double>(f.decode_time.us()), .time = time}));
|
||||
|
||||
if (f.psnr) {
|
||||
stream.psnr.y.AddSample(
|
||||
SamplesStatsCounter::StatsSample({.value = f.psnr->y, .time = time}));
|
||||
stream.psnr.u.AddSample(
|
||||
SamplesStatsCounter::StatsSample({.value = f.psnr->u, .time = time}));
|
||||
stream.psnr.v.AddSample(
|
||||
SamplesStatsCounter::StatsSample({.value = f.psnr->v, .time = time}));
|
||||
}
|
||||
|
||||
if (f.keyframe) {
|
||||
++stream.num_keyframes;
|
||||
}
|
||||
|
||||
// TODO(webrtc:14852): Aggregate other metrics.
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
void VideoCodecStatsImpl::LogMetrics(MetricsLogger* logger,
|
||||
const Stream& stream,
|
||||
std::string test_case_name) const {
|
||||
logger->LogMetric("width", test_case_name, stream.width, Unit::kCount,
|
||||
webrtc::test::ImprovementDirection::kBiggerIsBetter);
|
||||
// TODO(webrtc:14852): Log other metrics.
|
||||
}
|
||||
|
||||
Frame* VideoCodecStatsImpl::AddFrame(int frame_num,
|
||||
uint32_t timestamp_rtp,
|
||||
int spatial_idx) {
|
||||
Frame frame;
|
||||
frame.frame_num = frame_num;
|
||||
frame.timestamp_rtp = timestamp_rtp;
|
||||
frame.spatial_idx = spatial_idx;
|
||||
|
||||
FrameId frame_id;
|
||||
frame_id.frame_num = frame_num;
|
||||
frame_id.spatial_idx = spatial_idx;
|
||||
|
||||
RTC_CHECK(frames_.find(frame_id) == frames_.end())
|
||||
<< "Frame with frame_num=" << frame_num
|
||||
<< " and spatial_idx=" << spatial_idx << " already exists";
|
||||
|
||||
frames_[frame_id] = frame;
|
||||
|
||||
if (frame_num_.find(timestamp_rtp) == frame_num_.end()) {
|
||||
frame_num_[timestamp_rtp] = frame_num;
|
||||
}
|
||||
|
||||
return &frames_[frame_id];
|
||||
}
|
||||
|
||||
Frame* VideoCodecStatsImpl::GetFrame(uint32_t timestamp_rtp, int spatial_idx) {
|
||||
if (frame_num_.find(timestamp_rtp) == frame_num_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FrameId frame_id;
|
||||
frame_id.frame_num = frame_num_[timestamp_rtp];
|
||||
frame_id.spatial_idx = spatial_idx;
|
||||
|
||||
if (frames_.find(frame_id) == frames_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &frames_[frame_id];
|
||||
}
|
||||
|
||||
std::vector<Frame> VideoCodecStatsImpl::Merge(
|
||||
const std::vector<Frame>& frames) const {
|
||||
std::vector<Frame> superframes;
|
||||
// Map from frame_num to index in `superframes` vector.
|
||||
std::map<int, int> index;
|
||||
|
||||
for (const auto& f : frames) {
|
||||
if (f.encoded == false && f.decoded == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index.find(f.frame_num) == index.end()) {
|
||||
index[f.frame_num] = static_cast<int>(superframes.size());
|
||||
superframes.push_back(f);
|
||||
continue;
|
||||
}
|
||||
|
||||
Frame& sf = superframes[index[f.frame_num]];
|
||||
|
||||
sf.width = std::max(sf.width, f.width);
|
||||
sf.height = std::max(sf.height, f.height);
|
||||
sf.size_bytes += f.size_bytes;
|
||||
sf.keyframe |= f.keyframe;
|
||||
|
||||
sf.encode_time = std::max(sf.encode_time, f.encode_time);
|
||||
sf.decode_time += f.decode_time;
|
||||
|
||||
if (f.spatial_idx > sf.spatial_idx) {
|
||||
if (f.qp) {
|
||||
sf.qp = f.qp;
|
||||
}
|
||||
if (f.psnr) {
|
||||
sf.psnr = f.psnr;
|
||||
}
|
||||
}
|
||||
|
||||
sf.spatial_idx = std::max(sf.spatial_idx, f.spatial_idx);
|
||||
sf.temporal_idx = std::max(sf.temporal_idx, f.temporal_idx);
|
||||
|
||||
sf.encoded |= f.encoded;
|
||||
sf.decoded |= f.decoded;
|
||||
}
|
||||
|
||||
return superframes;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
77
modules/video_coding/codecs/test/video_codec_stats_impl.h
Normal file
77
modules/video_coding/codecs/test/video_codec_stats_impl.h
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_VIDEO_CODING_CODECS_TEST_VIDEO_CODEC_STATS_IMPL_H_
|
||||
#define MODULES_VIDEO_CODING_CODECS_TEST_VIDEO_CODEC_STATS_IMPL_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/test/video_codec_stats.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
// Implementation of `VideoCodecStats`. This class is not thread-safe.
|
||||
class VideoCodecStatsImpl : public VideoCodecStats {
|
||||
public:
|
||||
std::vector<Frame> Slice(
|
||||
absl::optional<Filter> filter = absl::nullopt) const override;
|
||||
|
||||
Stream Aggregate(
|
||||
const std::vector<Frame>& frames,
|
||||
absl::optional<DataRate> bitrate = absl::nullopt,
|
||||
absl::optional<Frequency> framerate = absl::nullopt) const override;
|
||||
|
||||
void LogMetrics(MetricsLogger* logger,
|
||||
const Stream& stream,
|
||||
std::string test_case_name) const override;
|
||||
|
||||
// Creates new frame, caches it and returns raw pointer to it.
|
||||
Frame* AddFrame(int frame_num, uint32_t timestamp_rtp, int spatial_idx);
|
||||
|
||||
// Returns raw pointers to requested frame. If frame does not exist, returns
|
||||
// `nullptr`.
|
||||
Frame* GetFrame(uint32_t timestamp_rtp, int spatial_idx);
|
||||
|
||||
private:
|
||||
struct FrameId {
|
||||
int frame_num;
|
||||
int spatial_idx;
|
||||
|
||||
bool operator==(const FrameId& o) const {
|
||||
return frame_num == o.frame_num && spatial_idx == o.spatial_idx;
|
||||
}
|
||||
|
||||
bool operator<(const FrameId& o) const {
|
||||
if (frame_num < o.frame_num)
|
||||
return true;
|
||||
if (spatial_idx < o.spatial_idx)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Merges frame stats from different spatial layers and returns vector of
|
||||
// superframes.
|
||||
std::vector<Frame> Merge(const std::vector<Frame>& frames) const;
|
||||
|
||||
// Map from RTP timestamp to frame number (`Frame::frame_num`).
|
||||
std::map<uint32_t, int> frame_num_;
|
||||
|
||||
std::map<FrameId, Frame> frames_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_VIDEO_CODING_CODECS_TEST_VIDEO_CODEC_STATS_IMPL_H_
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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_stats_impl.h"
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "test/gmock.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
using ::testing::Return;
|
||||
using ::testing::Values;
|
||||
} // namespace
|
||||
|
||||
TEST(VideoCodecStatsImpl, AddFrame) {
|
||||
VideoCodecStatsImpl stats;
|
||||
VideoCodecStatsImpl::Frame* fs =
|
||||
stats.AddFrame(/*frame_num=*/0, /*timestamp_rtp=*/0, /*spatial_idx=*/0);
|
||||
EXPECT_NE(nullptr, fs);
|
||||
fs = stats.GetFrame(/*timestamp_rtp=*/0, /*spatial_idx=*/0);
|
||||
EXPECT_NE(nullptr, fs);
|
||||
}
|
||||
|
||||
TEST(VideoCodecStatsImpl, GetFrame) {
|
||||
VideoCodecStatsImpl stats;
|
||||
stats.AddFrame(/*frame_num=*/0, /*timestamp_rtp=*/0, /*spatial_idx=*/0);
|
||||
VideoCodecStatsImpl::Frame* fs =
|
||||
stats.GetFrame(/*timestamp_rtp=*/0, /*spatial_idx=*/0);
|
||||
EXPECT_NE(nullptr, fs);
|
||||
}
|
||||
|
||||
struct VideoCodecStatsSlicingTestParams {
|
||||
VideoCodecStats::Filter slicer;
|
||||
std::vector<VideoCodecStats::Frame> expected;
|
||||
};
|
||||
|
||||
class VideoCodecStatsSlicingTest
|
||||
: public ::testing::TestWithParam<VideoCodecStatsSlicingTestParams> {
|
||||
public:
|
||||
void SetUp() {
|
||||
// TODO(ssikin): Hard codec 2x2 table would be better.
|
||||
for (int frame_num = 0; frame_num < 2; ++frame_num) {
|
||||
for (int spatial_idx = 0; spatial_idx < 2; ++spatial_idx) {
|
||||
uint32_t timestamp_rtp = 3000 * frame_num;
|
||||
VideoCodecStats::Frame* f =
|
||||
stats_.AddFrame(frame_num, timestamp_rtp, spatial_idx);
|
||||
f->temporal_idx = frame_num;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
VideoCodecStatsImpl stats_;
|
||||
};
|
||||
|
||||
TEST_P(VideoCodecStatsSlicingTest, Slice) {
|
||||
VideoCodecStatsSlicingTestParams test_params = GetParam();
|
||||
std::vector<VideoCodecStats::Frame> frames = stats_.Slice(test_params.slicer);
|
||||
EXPECT_EQ(frames.size(), test_params.expected.size());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(All,
|
||||
VideoCodecStatsSlicingTest,
|
||||
::testing::ValuesIn({VideoCodecStatsSlicingTestParams(
|
||||
{.slicer = {.first_frame = 0, .last_frame = 1},
|
||||
.expected = {{.frame_num = 0},
|
||||
{.frame_num = 1},
|
||||
{.frame_num = 0},
|
||||
{.frame_num = 1}}})}));
|
||||
|
||||
struct VideoCodecStatsAggregationTestParams {
|
||||
VideoCodecStats::Filter slicer;
|
||||
struct Expected {
|
||||
double decode_time_us;
|
||||
} expected;
|
||||
};
|
||||
|
||||
class VideoCodecStatsAggregationTest
|
||||
: public ::testing::TestWithParam<VideoCodecStatsAggregationTestParams> {
|
||||
public:
|
||||
void SetUp() {
|
||||
// TODO(ssikin): Hard codec 2x2 table would be better. Share with
|
||||
// VideoCodecStatsSlicingTest
|
||||
for (int frame_num = 0; frame_num < 2; ++frame_num) {
|
||||
for (int spatial_idx = 0; spatial_idx < 2; ++spatial_idx) {
|
||||
uint32_t timestamp_rtp = 3000 * frame_num;
|
||||
VideoCodecStats::Frame* f =
|
||||
stats_.AddFrame(frame_num, timestamp_rtp, spatial_idx);
|
||||
f->temporal_idx = frame_num;
|
||||
f->decode_time = TimeDelta::Micros(spatial_idx * 10 + frame_num);
|
||||
f->encoded = true;
|
||||
f->decoded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
VideoCodecStatsImpl stats_;
|
||||
};
|
||||
|
||||
TEST_P(VideoCodecStatsAggregationTest, Aggregate) {
|
||||
VideoCodecStatsAggregationTestParams test_params = GetParam();
|
||||
std::vector<VideoCodecStats::Frame> frames = stats_.Slice(test_params.slicer);
|
||||
VideoCodecStats::Stream stream = stats_.Aggregate(frames);
|
||||
EXPECT_EQ(stream.decode_time_us.GetAverage(),
|
||||
test_params.expected.decode_time_us);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
All,
|
||||
VideoCodecStatsAggregationTest,
|
||||
::testing::ValuesIn(
|
||||
{VideoCodecStatsAggregationTestParams(
|
||||
{.slicer = {},
|
||||
.expected = {.decode_time_us = (0.0 + 1.0 + 10.0 + 11.0) / 2}}),
|
||||
// Slicing on frame number
|
||||
VideoCodecStatsAggregationTestParams(
|
||||
{.slicer = {.first_frame = 1, .last_frame = 1},
|
||||
.expected = {.decode_time_us = 1.0 + 11.0}}),
|
||||
// Slice on spatial index
|
||||
VideoCodecStatsAggregationTestParams(
|
||||
{.slicer = {.spatial_idx = 1},
|
||||
.expected = {.decode_time_us = (10.0 + 11.0) / 2}}),
|
||||
// Slice on temporal index
|
||||
VideoCodecStatsAggregationTestParams(
|
||||
{.slicer = {.temporal_idx = 0},
|
||||
.expected = {.decode_time_us = 0.0 + 10.0}})}));
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
|
@ -43,7 +43,6 @@ namespace test {
|
|||
namespace {
|
||||
using ::testing::Combine;
|
||||
using ::testing::Values;
|
||||
using Layer = std::pair<int, int>;
|
||||
|
||||
struct VideoInfo {
|
||||
std::string name;
|
||||
|
@ -56,6 +55,23 @@ struct CodecInfo {
|
|||
std::string decoder;
|
||||
};
|
||||
|
||||
struct LayerId {
|
||||
int spatial_idx;
|
||||
int temporal_idx;
|
||||
|
||||
bool operator==(const LayerId& o) const {
|
||||
return spatial_idx == o.spatial_idx && temporal_idx == o.temporal_idx;
|
||||
}
|
||||
|
||||
bool operator<(const LayerId& o) const {
|
||||
if (spatial_idx < o.spatial_idx)
|
||||
return true;
|
||||
if (temporal_idx < o.temporal_idx)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct EncodingSettings {
|
||||
ScalabilityMode scalability_mode;
|
||||
// Spatial layer resolution.
|
||||
|
@ -63,7 +79,7 @@ struct EncodingSettings {
|
|||
// Top temporal layer frame rate.
|
||||
Frequency framerate;
|
||||
// Bitrate of spatial and temporal layers.
|
||||
std::map<Layer, DataRate> bitrate;
|
||||
std::map<LayerId, DataRate> bitrate;
|
||||
};
|
||||
|
||||
struct EncodingTestSettings {
|
||||
|
@ -94,7 +110,8 @@ const EncodingSettings kQvga64Kbps30Fps = {
|
|||
.scalability_mode = ScalabilityMode::kL1T1,
|
||||
.resolution = {{0, {.width = 320, .height = 180}}},
|
||||
.framerate = Frequency::Hertz(30),
|
||||
.bitrate = {{Layer(0, 0), DataRate::KilobitsPerSec(64)}}};
|
||||
.bitrate = {
|
||||
{{.spatial_idx = 0, .temporal_idx = 0}, DataRate::KilobitsPerSec(64)}}};
|
||||
|
||||
const EncodingTestSettings kConstantRateQvga64Kbps30Fps = {
|
||||
.name = "ConstantRateQvga64Kbps30Fps",
|
||||
|
@ -277,10 +294,10 @@ class TestEncoder : public VideoCodecTester::Encoder,
|
|||
ScalabilityModeToNumSpatialLayers(es.scalability_mode);
|
||||
for (int sidx = 0; sidx < num_spatial_layers; ++sidx) {
|
||||
for (int tidx = 0; tidx < num_temporal_layers; ++tidx) {
|
||||
RTC_CHECK(es.bitrate.find(Layer(sidx, tidx)) != es.bitrate.end())
|
||||
LayerId layer_id = {.spatial_idx = sidx, .temporal_idx = tidx};
|
||||
RTC_CHECK(es.bitrate.find(layer_id) != es.bitrate.end())
|
||||
<< "Bitrate for layer S=" << sidx << " T=" << tidx << " is not set";
|
||||
rc.bitrate.SetBitrate(sidx, tidx,
|
||||
es.bitrate.at(Layer(sidx, tidx)).bps());
|
||||
rc.bitrate.SetBitrate(sidx, tidx, es.bitrate.at(layer_id).bps());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -405,7 +422,7 @@ class EncodeDecodeTest
|
|||
};
|
||||
|
||||
TEST_P(EncodeDecodeTest, DISABLED_TestEncodeDecode) {
|
||||
std::unique_ptr<VideoCodecTestStats> stats = tester_->RunEncodeDecodeTest(
|
||||
std::unique_ptr<VideoCodecStats> stats = tester_->RunEncodeDecodeTest(
|
||||
std::move(video_source_), std::move(encoder_), std::move(decoder_),
|
||||
test_params_.encoder_settings, test_params_.decoder_settings);
|
||||
|
||||
|
@ -415,13 +432,11 @@ TEST_P(EncodeDecodeTest, DISABLED_TestEncodeDecode) {
|
|||
int last_frame = std::next(fs) != frame_settings.end()
|
||||
? std::next(fs)->first - 1
|
||||
: test_params_.encoding_settings.num_frames - 1;
|
||||
|
||||
const EncodingSettings& encoding_settings = fs->second;
|
||||
auto metrics = stats->CalcVideoStatistic(
|
||||
first_frame, last_frame, encoding_settings.bitrate.rbegin()->second,
|
||||
encoding_settings.framerate);
|
||||
|
||||
EXPECT_GE(metrics.avg_psnr_y,
|
||||
VideoCodecStats::Filter slicer = {.first_frame = first_frame,
|
||||
.last_frame = last_frame};
|
||||
std::vector<VideoCodecStats::Frame> frames = stats->Slice(slicer);
|
||||
VideoCodecStats::Stream stream = stats->Aggregate(frames);
|
||||
EXPECT_GE(stream.psnr.y.GetAverage(),
|
||||
test_params_.quality_expectations.min_apsnr_y);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,8 +171,10 @@ class TesterDecoder {
|
|||
task_queue_.PostDelayedTask(
|
||||
[this, frame] {
|
||||
analyzer_->StartDecode(frame);
|
||||
decoder_->Decode(frame, [this](const VideoFrame& decoded_frame) {
|
||||
this->analyzer_->FinishDecode(decoded_frame, /*spatial_idx=*/0);
|
||||
decoder_->Decode(
|
||||
frame, [this, spatial_idx = frame.SpatialIndex().value_or(0)](
|
||||
const VideoFrame& decoded_frame) {
|
||||
this->analyzer_->FinishDecode(decoded_frame, spatial_idx);
|
||||
});
|
||||
},
|
||||
pacer_.Delay(timestamp));
|
||||
|
@ -244,7 +246,7 @@ VideoCodecTesterImpl::VideoCodecTesterImpl(TaskQueueFactory* task_queue_factory)
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<VideoCodecTestStats> VideoCodecTesterImpl::RunDecodeTest(
|
||||
std::unique_ptr<VideoCodecStats> VideoCodecTesterImpl::RunDecodeTest(
|
||||
std::unique_ptr<CodedVideoSource> video_source,
|
||||
std::unique_ptr<Decoder> decoder,
|
||||
const DecoderSettings& decoder_settings) {
|
||||
|
@ -266,7 +268,7 @@ std::unique_ptr<VideoCodecTestStats> VideoCodecTesterImpl::RunDecodeTest(
|
|||
return perf_analyzer.GetStats();
|
||||
}
|
||||
|
||||
std::unique_ptr<VideoCodecTestStats> VideoCodecTesterImpl::RunEncodeTest(
|
||||
std::unique_ptr<VideoCodecStats> VideoCodecTesterImpl::RunEncodeTest(
|
||||
std::unique_ptr<RawVideoSource> video_source,
|
||||
std::unique_ptr<Encoder> encoder,
|
||||
const EncoderSettings& encoder_settings) {
|
||||
|
@ -290,7 +292,7 @@ std::unique_ptr<VideoCodecTestStats> VideoCodecTesterImpl::RunEncodeTest(
|
|||
return perf_analyzer.GetStats();
|
||||
}
|
||||
|
||||
std::unique_ptr<VideoCodecTestStats> VideoCodecTesterImpl::RunEncodeDecodeTest(
|
||||
std::unique_ptr<VideoCodecStats> VideoCodecTesterImpl::RunEncodeDecodeTest(
|
||||
std::unique_ptr<RawVideoSource> video_source,
|
||||
std::unique_ptr<Encoder> encoder,
|
||||
std::unique_ptr<Decoder> decoder,
|
||||
|
|
|
@ -25,17 +25,17 @@ class VideoCodecTesterImpl : public VideoCodecTester {
|
|||
VideoCodecTesterImpl();
|
||||
explicit VideoCodecTesterImpl(TaskQueueFactory* task_queue_factory);
|
||||
|
||||
std::unique_ptr<VideoCodecTestStats> RunDecodeTest(
|
||||
std::unique_ptr<VideoCodecStats> RunDecodeTest(
|
||||
std::unique_ptr<CodedVideoSource> video_source,
|
||||
std::unique_ptr<Decoder> decoder,
|
||||
const DecoderSettings& decoder_settings) override;
|
||||
|
||||
std::unique_ptr<VideoCodecTestStats> RunEncodeTest(
|
||||
std::unique_ptr<VideoCodecStats> RunEncodeTest(
|
||||
std::unique_ptr<RawVideoSource> video_source,
|
||||
std::unique_ptr<Encoder> encoder,
|
||||
const EncoderSettings& encoder_settings) override;
|
||||
|
||||
std::unique_ptr<VideoCodecTestStats> RunEncodeDecodeTest(
|
||||
std::unique_ptr<VideoCodecStats> RunEncodeDecodeTest(
|
||||
std::unique_ptr<RawVideoSource> video_source,
|
||||
std::unique_ptr<Encoder> encoder,
|
||||
std::unique_ptr<Decoder> decoder,
|
||||
|
|
|
@ -172,12 +172,11 @@ TEST_P(VideoCodecTesterImplPacingTest, PaceEncode) {
|
|||
auto fs = tester
|
||||
.RunEncodeTest(std::move(video_source), std::move(encoder),
|
||||
encoder_settings)
|
||||
->GetFrameStatistics();
|
||||
->Slice();
|
||||
ASSERT_EQ(fs.size(), num_frames_);
|
||||
|
||||
for (size_t i = 0; i < fs.size(); ++i) {
|
||||
int encode_start_ms = (fs[i].encode_start_ns - fs[0].encode_start_ns) /
|
||||
rtc::kNumNanosecsPerMillisec;
|
||||
int encode_start_ms = (fs[i].encode_start - fs[0].encode_start).ms();
|
||||
EXPECT_NEAR(encode_start_ms, expected_frame_start_ms_[i], 10);
|
||||
}
|
||||
}
|
||||
|
@ -206,12 +205,11 @@ TEST_P(VideoCodecTesterImplPacingTest, PaceDecode) {
|
|||
auto fs = tester
|
||||
.RunDecodeTest(std::move(video_source), std::move(decoder),
|
||||
decoder_settings)
|
||||
->GetFrameStatistics();
|
||||
->Slice();
|
||||
ASSERT_EQ(fs.size(), num_frames_);
|
||||
|
||||
for (size_t i = 0; i < fs.size(); ++i) {
|
||||
int decode_start_ms = (fs[i].decode_start_ns - fs[0].decode_start_ns) /
|
||||
rtc::kNumNanosecsPerMillisec;
|
||||
int decode_start_ms = (fs[i].decode_start - fs[0].decode_start).ms();
|
||||
EXPECT_NEAR(decode_start_ms, expected_frame_start_ms_[i], 10);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ namespace test {
|
|||
// measure times properly.
|
||||
// The class processes a frame at the time for the configured input file.
|
||||
// It maintains state of where in the source input file the processing is at.
|
||||
// TODO(webrtc:14852): Deprecated in favor VideoCodecTester.
|
||||
class VideoProcessor {
|
||||
public:
|
||||
using VideoDecoderList = std::vector<std::unique_ptr<VideoDecoder>>;
|
||||
|
|
Loading…
Reference in a new issue