| /* | |
| * 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 |