Sebastian Jansson | 9a4f38e | 2018-12-19 13:14:41 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2018 The WebRTC project authors. All Rights Reserved. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license |
| 5 | * that can be found in the LICENSE file in the root of the source |
| 6 | * tree. An additional intellectual property rights grant can be found |
| 7 | * in the file PATENTS. All contributing project authors may |
| 8 | * be found in the AUTHORS file in the root of the source tree. |
| 9 | */ |
| 10 | #include "test/scenario/quality_stats.h" |
| 11 | |
| 12 | #include <utility> |
| 13 | |
| 14 | #include "common_video/libyuv/include/webrtc_libyuv.h" |
| 15 | #include "rtc_base/checks.h" |
| 16 | #include "rtc_base/event.h" |
| 17 | |
| 18 | namespace webrtc { |
| 19 | namespace test { |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 20 | namespace { |
| 21 | constexpr int kThumbWidth = 96; |
| 22 | constexpr int kThumbHeight = 96; |
| 23 | } // namespace |
Sebastian Jansson | 9a4f38e | 2018-12-19 13:14:41 +0100 | [diff] [blame] | 24 | |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 25 | VideoFrameMatcher::VideoFrameMatcher( |
| 26 | std::vector<std::function<void(const VideoFramePair&)> > |
| 27 | frame_pair_handlers) |
| 28 | : frame_pair_handlers_(frame_pair_handlers), task_queue_("VideoAnalyzer") {} |
Sebastian Jansson | 9a4f38e | 2018-12-19 13:14:41 +0100 | [diff] [blame] | 29 | |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 30 | VideoFrameMatcher::~VideoFrameMatcher() { |
Sebastian Jansson | 123f345 | 2019-03-13 11:22:52 +0100 | [diff] [blame] | 31 | task_queue_.SendTask([] {}); |
Sebastian Jansson | 9a4f38e | 2018-12-19 13:14:41 +0100 | [diff] [blame] | 32 | } |
| 33 | |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 34 | void VideoFrameMatcher::RegisterLayer(int layer_id) { |
| 35 | task_queue_.PostTask([this, layer_id] { layers_[layer_id] = VideoLayer(); }); |
| 36 | } |
| 37 | |
| 38 | void VideoFrameMatcher::OnCapturedFrame(const VideoFrame& frame, |
| 39 | Timestamp at_time) { |
| 40 | CapturedFrame captured; |
| 41 | captured.id = next_capture_id_++; |
| 42 | captured.capture_time = at_time; |
| 43 | captured.frame = frame.video_frame_buffer(); |
| 44 | captured.thumb = ScaleVideoFrameBuffer(*frame.video_frame_buffer()->ToI420(), |
| 45 | kThumbWidth, kThumbHeight), |
| 46 | task_queue_.PostTask([this, captured]() { |
| 47 | for (auto& layer : layers_) { |
| 48 | CapturedFrame copy = captured; |
| 49 | if (layer.second.last_decode) { |
| 50 | copy.best_score = I420SSE(*captured.thumb->GetI420(), |
| 51 | *layer.second.last_decode->thumb->GetI420()); |
| 52 | copy.best_decode = layer.second.last_decode; |
| 53 | } |
| 54 | layer.second.captured_frames.push_back(std::move(copy)); |
| 55 | } |
Sebastian Jansson | 9a4f38e | 2018-12-19 13:14:41 +0100 | [diff] [blame] | 56 | }); |
| 57 | } |
| 58 | |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 59 | void VideoFrameMatcher::OnDecodedFrame(const VideoFrame& frame, |
| 60 | Timestamp render_time, |
| 61 | int layer_id) { |
| 62 | rtc::scoped_refptr<DecodedFrame> decoded(new DecodedFrame{}); |
| 63 | decoded->render_time = render_time; |
| 64 | decoded->frame = frame.video_frame_buffer(); |
| 65 | decoded->thumb = ScaleVideoFrameBuffer(*frame.video_frame_buffer()->ToI420(), |
| 66 | kThumbWidth, kThumbHeight); |
| 67 | decoded->render_time = render_time; |
| 68 | |
| 69 | task_queue_.PostTask([this, decoded, layer_id] { |
| 70 | auto& layer = layers_[layer_id]; |
| 71 | decoded->id = layer.next_decoded_id++; |
| 72 | layer.last_decode = decoded; |
| 73 | for (auto& captured : layer.captured_frames) { |
| 74 | double score = |
| 75 | I420SSE(*captured.thumb->GetI420(), *decoded->thumb->GetI420()); |
| 76 | if (score < captured.best_score) { |
| 77 | captured.best_score = score; |
| 78 | captured.best_decode = decoded; |
| 79 | captured.matched = false; |
| 80 | } else { |
| 81 | captured.matched = true; |
| 82 | } |
| 83 | } |
| 84 | while (!layer.captured_frames.empty() && |
| 85 | layer.captured_frames.front().matched) { |
| 86 | HandleMatch(layer.captured_frames.front(), layer_id); |
| 87 | layer.captured_frames.pop_front(); |
| 88 | } |
Sebastian Jansson | 9a4f38e | 2018-12-19 13:14:41 +0100 | [diff] [blame] | 89 | }); |
| 90 | } |
| 91 | |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 92 | bool VideoFrameMatcher::Active() const { |
| 93 | return !frame_pair_handlers_.empty(); |
Sebastian Jansson | 9a4f38e | 2018-12-19 13:14:41 +0100 | [diff] [blame] | 94 | } |
| 95 | |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 96 | void VideoFrameMatcher::Finalize() { |
| 97 | for (auto& layer : layers_) { |
| 98 | while (!layer.second.captured_frames.empty()) { |
| 99 | HandleMatch(layer.second.captured_frames.front(), layer.first); |
| 100 | layer.second.captured_frames.pop_front(); |
| 101 | } |
Sebastian Jansson | 9a4f38e | 2018-12-19 13:14:41 +0100 | [diff] [blame] | 102 | } |
| 103 | } |
| 104 | |
| 105 | ForwardingCapturedFrameTap::ForwardingCapturedFrameTap( |
Sebastian Jansson | aa01f27 | 2019-01-30 11:28:59 +0100 | [diff] [blame] | 106 | Clock* clock, |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 107 | VideoFrameMatcher* matcher, |
Sebastian Jansson | 9a4f38e | 2018-12-19 13:14:41 +0100 | [diff] [blame] | 108 | rtc::VideoSourceInterface<VideoFrame>* source) |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 109 | : clock_(clock), matcher_(matcher), source_(source) {} |
Sebastian Jansson | 9a4f38e | 2018-12-19 13:14:41 +0100 | [diff] [blame] | 110 | |
| 111 | ForwardingCapturedFrameTap::~ForwardingCapturedFrameTap() {} |
| 112 | |
| 113 | void ForwardingCapturedFrameTap::OnFrame(const VideoFrame& frame) { |
| 114 | RTC_CHECK(sink_); |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 115 | matcher_->OnCapturedFrame(frame, Timestamp::ms(clock_->TimeInMilliseconds())); |
| 116 | sink_->OnFrame(frame); |
Sebastian Jansson | 9a4f38e | 2018-12-19 13:14:41 +0100 | [diff] [blame] | 117 | } |
| 118 | void ForwardingCapturedFrameTap::OnDiscardedFrame() { |
| 119 | RTC_CHECK(sink_); |
| 120 | discarded_count_++; |
| 121 | sink_->OnDiscardedFrame(); |
| 122 | } |
| 123 | |
| 124 | void ForwardingCapturedFrameTap::AddOrUpdateSink( |
| 125 | VideoSinkInterface<VideoFrame>* sink, |
| 126 | const rtc::VideoSinkWants& wants) { |
| 127 | sink_ = sink; |
| 128 | source_->AddOrUpdateSink(this, wants); |
| 129 | } |
| 130 | void ForwardingCapturedFrameTap::RemoveSink( |
| 131 | VideoSinkInterface<VideoFrame>* sink) { |
| 132 | source_->RemoveSink(this); |
| 133 | sink_ = nullptr; |
| 134 | } |
| 135 | |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 136 | DecodedFrameTap::DecodedFrameTap(VideoFrameMatcher* matcher, int layer_id) |
| 137 | : matcher_(matcher), layer_id_(layer_id) { |
| 138 | matcher_->RegisterLayer(layer_id_); |
| 139 | } |
Sebastian Jansson | 9a4f38e | 2018-12-19 13:14:41 +0100 | [diff] [blame] | 140 | |
| 141 | void DecodedFrameTap::OnFrame(const VideoFrame& frame) { |
Sebastian Jansson | cf2df2f | 2019-04-02 11:51:28 +0200 | [diff] [blame] | 142 | matcher_->OnDecodedFrame(frame, Timestamp::ms(frame.render_time_ms()), |
| 143 | layer_id_); |
| 144 | } |
| 145 | |
| 146 | VideoQualityAnalyzer::VideoQualityAnalyzer( |
| 147 | VideoQualityAnalyzerConfig config, |
| 148 | std::unique_ptr<RtcEventLogOutput> writer) |
| 149 | : config_(config), writer_(std::move(writer)) { |
| 150 | if (writer_) { |
| 151 | PrintHeaders(); |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | VideoQualityAnalyzer::~VideoQualityAnalyzer() = default; |
| 156 | |
| 157 | void VideoQualityAnalyzer::PrintHeaders() { |
| 158 | writer_->Write( |
| 159 | "capture_time render_time capture_width capture_height render_width " |
| 160 | "render_height psnr\n"); |
| 161 | } |
| 162 | |
| 163 | std::function<void(const VideoFramePair&)> VideoQualityAnalyzer::Handler() { |
| 164 | return [this](VideoFramePair pair) { HandleFramePair(pair); }; |
| 165 | } |
| 166 | |
| 167 | void VideoQualityAnalyzer::HandleFramePair(VideoFramePair sample) { |
| 168 | double psnr = NAN; |
| 169 | RTC_CHECK(sample.captured); |
| 170 | ++stats_.captures_count; |
| 171 | if (!sample.decoded) { |
| 172 | ++stats_.lost_count; |
| 173 | } else { |
| 174 | psnr = I420PSNR(*sample.captured->ToI420(), *sample.decoded->ToI420()); |
| 175 | ++stats_.valid_count; |
| 176 | stats_.end_to_end_seconds.AddSample( |
| 177 | (sample.render_time - sample.capture_time).seconds<double>()); |
| 178 | stats_.psnr.AddSample(psnr); |
| 179 | } |
| 180 | if (writer_) { |
| 181 | LogWriteFormat(writer_.get(), "%.3f %.3f %.3f %i %i %i %i %.3f\n", |
| 182 | sample.capture_time.seconds<double>(), |
| 183 | sample.render_time.seconds<double>(), |
| 184 | sample.captured->width(), sample.captured->height(), |
| 185 | sample.decoded->width(), sample.decoded->height(), psnr); |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | VideoQualityStats VideoQualityAnalyzer::stats() const { |
| 190 | return stats_; |
Sebastian Jansson | 9a4f38e | 2018-12-19 13:14:41 +0100 | [diff] [blame] | 191 | } |
| 192 | |
| 193 | } // namespace test |
| 194 | } // namespace webrtc |