diff --git a/api/BUILD.gn b/api/BUILD.gn index 33a6b0aaa6..138d855b45 100644 --- a/api/BUILD.gn +++ b/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", diff --git a/api/test/video_codec_stats.h b/api/test/video_codec_stats.h new file mode 100644 index 0000000000..b1dfee8b75 --- /dev/null +++ b/api/test/video_codec_stats.h @@ -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 +#include + +#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 first_frame; + absl::optional last_frame; + absl::optional spatial_idx; + absl::optional 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 qp = absl::nullopt; + absl::optional 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 = 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 Slice( + absl::optional 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& frames, + absl::optional bitrate = absl::nullopt, + absl::optional 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_ diff --git a/api/test/video_codec_tester.h b/api/test/video_codec_tester.h index 0eaaa1b895..b2ce88c340 100644 --- a/api/test/video_codec_tester.h +++ b/api/test/video_codec_tester.h @@ -14,7 +14,8 @@ #include #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 RunDecodeTest( + virtual std::unique_ptr RunDecodeTest( std::unique_ptr video_source, std::unique_ptr 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 RunEncodeTest( + virtual std::unique_ptr RunEncodeTest( std::unique_ptr video_source, std::unique_ptr 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 RunEncodeDecodeTest( + virtual std::unique_ptr RunEncodeDecodeTest( std::unique_ptr video_source, std::unique_ptr encoder, std::unique_ptr decoder, diff --git a/api/test/videocodec_test_fixture.h b/api/test/videocodec_test_fixture.h index dbf20993e2..8e66f72b91 100644 --- a/api/test/videocodec_test_fixture.h +++ b/api/test/videocodec_test_fixture.h @@ -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 { diff --git a/api/test/videocodec_test_stats.h b/api/test/videocodec_test_stats.h index 12c60638db..d620d31f12 100644 --- a/api/test/videocodec_test_stats.h +++ b/api/test/videocodec_test_stats.h @@ -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. diff --git a/modules/video_coding/BUILD.gn b/modules/video_coding/BUILD.gn index fe63804b19..e2384bbeb2 100644 --- a/modules/video_coding/BUILD.gn +++ b/modules/video_coding/BUILD.gn @@ -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", diff --git a/modules/video_coding/codecs/test/video_codec_analyzer.cc b/modules/video_coding/codecs/test/video_codec_analyzer.cc index 50af417bcf..6521112bbc 100644 --- a/modules/video_coding/codecs/test/video_codec_analyzer.cc +++ b/modules/video_coding/codecs/test/video_codec_analyzer.cc @@ -13,7 +13,6 @@ #include #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(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_++; + } + 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_ns = decode_start_ns; + + 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(psnr.y); - fs->psnr_u = static_cast(psnr.u); - fs->psnr_v = static_cast(psnr.v); - fs->psnr = static_cast(psnr.yuv); - fs->quality_analysis_successful = true; + VideoCodecStats::Frame* fs = + this->stats_.GetFrame(timestamp_rtp, spatial_idx); + fs->psnr = psnr; }); } } -std::unique_ptr VideoCodecAnalyzer::GetStats() { - std::unique_ptr stats; +std::unique_ptr VideoCodecAnalyzer::GetStats() { + std::unique_ptr 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); diff --git a/modules/video_coding/codecs/test/video_codec_analyzer.h b/modules/video_coding/codecs/test/video_codec_analyzer.h index 63a864e810..8cb1b542d6 100644 --- a/modules/video_coding/codecs/test/video_codec_analyzer.h +++ b/modules/video_coding/codecs/test/video_codec_analyzer.h @@ -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 #include #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 GetStats(); + std::unique_ptr 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 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_; }; diff --git a/modules/video_coding/codecs/test/video_codec_analyzer_unittest.cc b/modules/video_coding/codecs/test/video_codec_analyzer_unittest.cc index 3f9de6dac2..133a60ff36 100644 --- a/modules/video_coding/codecs/test/video_codec_analyzer_unittest.cc +++ b/modules/video_coding/codecs/test/video_codec_analyzer_unittest.cc @@ -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(fs[0].decoded_width), decoded_frame.width()); - EXPECT_EQ(static_cast(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 diff --git a/modules/video_coding/codecs/test/video_codec_stats_impl.cc b/modules/video_coding/codecs/test/video_codec_stats_impl.cc new file mode 100644 index 0000000000..f56debfa28 --- /dev/null +++ b/modules/video_coding/codecs/test/video_codec_stats_impl.cc @@ -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 + +#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 VideoCodecStatsImpl::Slice( + absl::optional filter) const { + std::vector 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& frames, + absl::optional bitrate, + absl::optional framerate) const { + std::vector superframes = Merge(frames); + + Stream stream; + stream.num_frames = static_cast(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(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 VideoCodecStatsImpl::Merge( + const std::vector& frames) const { + std::vector superframes; + // Map from frame_num to index in `superframes` vector. + std::map 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(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 diff --git a/modules/video_coding/codecs/test/video_codec_stats_impl.h b/modules/video_coding/codecs/test/video_codec_stats_impl.h new file mode 100644 index 0000000000..951f6e0b3d --- /dev/null +++ b/modules/video_coding/codecs/test/video_codec_stats_impl.h @@ -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 +#include +#include + +#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 Slice( + absl::optional filter = absl::nullopt) const override; + + Stream Aggregate( + const std::vector& frames, + absl::optional bitrate = absl::nullopt, + absl::optional 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 Merge(const std::vector& frames) const; + + // Map from RTP timestamp to frame number (`Frame::frame_num`). + std::map frame_num_; + + std::map frames_; +}; + +} // namespace test +} // namespace webrtc + +#endif // MODULES_VIDEO_CODING_CODECS_TEST_VIDEO_CODEC_STATS_IMPL_H_ diff --git a/modules/video_coding/codecs/test/video_codec_stats_impl_unittest.cc b/modules/video_coding/codecs/test/video_codec_stats_impl_unittest.cc new file mode 100644 index 0000000000..f55ca3bbc5 --- /dev/null +++ b/modules/video_coding/codecs/test/video_codec_stats_impl_unittest.cc @@ -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 expected; +}; + +class VideoCodecStatsSlicingTest + : public ::testing::TestWithParam { + 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 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 { + 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 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 diff --git a/modules/video_coding/codecs/test/video_codec_test.cc b/modules/video_coding/codecs/test/video_codec_test.cc index bd4c8e07f2..d2ad7431ef 100644 --- a/modules/video_coding/codecs/test/video_codec_test.cc +++ b/modules/video_coding/codecs/test/video_codec_test.cc @@ -43,7 +43,6 @@ namespace test { namespace { using ::testing::Combine; using ::testing::Values; -using Layer = std::pair; 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 bitrate; + std::map 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 stats = tester_->RunEncodeDecodeTest( + std::unique_ptr 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 frames = stats->Slice(slicer); + VideoCodecStats::Stream stream = stats->Aggregate(frames); + EXPECT_GE(stream.psnr.y.GetAverage(), test_params_.quality_expectations.min_apsnr_y); } } diff --git a/modules/video_coding/codecs/test/video_codec_tester_impl.cc b/modules/video_coding/codecs/test/video_codec_tester_impl.cc index 3000c1adee..09c5978cfc 100644 --- a/modules/video_coding/codecs/test/video_codec_tester_impl.cc +++ b/modules/video_coding/codecs/test/video_codec_tester_impl.cc @@ -171,9 +171,11 @@ 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 VideoCodecTesterImpl::RunDecodeTest( +std::unique_ptr VideoCodecTesterImpl::RunDecodeTest( std::unique_ptr video_source, std::unique_ptr decoder, const DecoderSettings& decoder_settings) { @@ -266,7 +268,7 @@ std::unique_ptr VideoCodecTesterImpl::RunDecodeTest( return perf_analyzer.GetStats(); } -std::unique_ptr VideoCodecTesterImpl::RunEncodeTest( +std::unique_ptr VideoCodecTesterImpl::RunEncodeTest( std::unique_ptr video_source, std::unique_ptr encoder, const EncoderSettings& encoder_settings) { @@ -290,7 +292,7 @@ std::unique_ptr VideoCodecTesterImpl::RunEncodeTest( return perf_analyzer.GetStats(); } -std::unique_ptr VideoCodecTesterImpl::RunEncodeDecodeTest( +std::unique_ptr VideoCodecTesterImpl::RunEncodeDecodeTest( std::unique_ptr video_source, std::unique_ptr encoder, std::unique_ptr decoder, diff --git a/modules/video_coding/codecs/test/video_codec_tester_impl.h b/modules/video_coding/codecs/test/video_codec_tester_impl.h index b64adeb882..4ac61eef56 100644 --- a/modules/video_coding/codecs/test/video_codec_tester_impl.h +++ b/modules/video_coding/codecs/test/video_codec_tester_impl.h @@ -25,17 +25,17 @@ class VideoCodecTesterImpl : public VideoCodecTester { VideoCodecTesterImpl(); explicit VideoCodecTesterImpl(TaskQueueFactory* task_queue_factory); - std::unique_ptr RunDecodeTest( + std::unique_ptr RunDecodeTest( std::unique_ptr video_source, std::unique_ptr decoder, const DecoderSettings& decoder_settings) override; - std::unique_ptr RunEncodeTest( + std::unique_ptr RunEncodeTest( std::unique_ptr video_source, std::unique_ptr encoder, const EncoderSettings& encoder_settings) override; - std::unique_ptr RunEncodeDecodeTest( + std::unique_ptr RunEncodeDecodeTest( std::unique_ptr video_source, std::unique_ptr encoder, std::unique_ptr decoder, diff --git a/modules/video_coding/codecs/test/video_codec_tester_impl_unittest.cc b/modules/video_coding/codecs/test/video_codec_tester_impl_unittest.cc index 29fb006fb5..409e813e90 100644 --- a/modules/video_coding/codecs/test/video_codec_tester_impl_unittest.cc +++ b/modules/video_coding/codecs/test/video_codec_tester_impl_unittest.cc @@ -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); } } diff --git a/modules/video_coding/codecs/test/videoprocessor.h b/modules/video_coding/codecs/test/videoprocessor.h index 0a5fdf8622..502fa3d0fa 100644 --- a/modules/video_coding/codecs/test/videoprocessor.h +++ b/modules/video_coding/codecs/test/videoprocessor.h @@ -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>;