blob: dbfd9ff36d4ca90855e39cdc48e34dfa8c2f71d7 [file] [log] [blame]
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +01001/*
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
18namespace webrtc {
19namespace test {
Sebastian Janssoncf2df2f2019-04-02 11:51:28 +020020namespace {
21constexpr int kThumbWidth = 96;
22constexpr int kThumbHeight = 96;
23} // namespace
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +010024
Sebastian Janssoncf2df2f2019-04-02 11:51:28 +020025VideoFrameMatcher::VideoFrameMatcher(
26 std::vector<std::function<void(const VideoFramePair&)> >
27 frame_pair_handlers)
28 : frame_pair_handlers_(frame_pair_handlers), task_queue_("VideoAnalyzer") {}
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +010029
Sebastian Janssoncf2df2f2019-04-02 11:51:28 +020030VideoFrameMatcher::~VideoFrameMatcher() {
Sebastian Jansson123f3452019-03-13 11:22:52 +010031 task_queue_.SendTask([] {});
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +010032}
33
Sebastian Janssoncf2df2f2019-04-02 11:51:28 +020034void VideoFrameMatcher::RegisterLayer(int layer_id) {
35 task_queue_.PostTask([this, layer_id] { layers_[layer_id] = VideoLayer(); });
36}
37
38void 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 Jansson9a4f38e2018-12-19 13:14:41 +010056 });
57}
58
Sebastian Janssoncf2df2f2019-04-02 11:51:28 +020059void 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 Jansson9a4f38e2018-12-19 13:14:41 +010089 });
90}
91
Sebastian Janssoncf2df2f2019-04-02 11:51:28 +020092bool VideoFrameMatcher::Active() const {
93 return !frame_pair_handlers_.empty();
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +010094}
95
Sebastian Janssoncf2df2f2019-04-02 11:51:28 +020096void 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 Jansson9a4f38e2018-12-19 13:14:41 +0100102 }
103}
104
105ForwardingCapturedFrameTap::ForwardingCapturedFrameTap(
Sebastian Janssonaa01f272019-01-30 11:28:59 +0100106 Clock* clock,
Sebastian Janssoncf2df2f2019-04-02 11:51:28 +0200107 VideoFrameMatcher* matcher,
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +0100108 rtc::VideoSourceInterface<VideoFrame>* source)
Sebastian Janssoncf2df2f2019-04-02 11:51:28 +0200109 : clock_(clock), matcher_(matcher), source_(source) {}
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +0100110
111ForwardingCapturedFrameTap::~ForwardingCapturedFrameTap() {}
112
113void ForwardingCapturedFrameTap::OnFrame(const VideoFrame& frame) {
114 RTC_CHECK(sink_);
Sebastian Janssoncf2df2f2019-04-02 11:51:28 +0200115 matcher_->OnCapturedFrame(frame, Timestamp::ms(clock_->TimeInMilliseconds()));
116 sink_->OnFrame(frame);
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +0100117}
118void ForwardingCapturedFrameTap::OnDiscardedFrame() {
119 RTC_CHECK(sink_);
120 discarded_count_++;
121 sink_->OnDiscardedFrame();
122}
123
124void ForwardingCapturedFrameTap::AddOrUpdateSink(
125 VideoSinkInterface<VideoFrame>* sink,
126 const rtc::VideoSinkWants& wants) {
127 sink_ = sink;
128 source_->AddOrUpdateSink(this, wants);
129}
130void ForwardingCapturedFrameTap::RemoveSink(
131 VideoSinkInterface<VideoFrame>* sink) {
132 source_->RemoveSink(this);
133 sink_ = nullptr;
134}
135
Sebastian Janssoncf2df2f2019-04-02 11:51:28 +0200136DecodedFrameTap::DecodedFrameTap(VideoFrameMatcher* matcher, int layer_id)
137 : matcher_(matcher), layer_id_(layer_id) {
138 matcher_->RegisterLayer(layer_id_);
139}
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +0100140
141void DecodedFrameTap::OnFrame(const VideoFrame& frame) {
Sebastian Janssoncf2df2f2019-04-02 11:51:28 +0200142 matcher_->OnDecodedFrame(frame, Timestamp::ms(frame.render_time_ms()),
143 layer_id_);
144}
145
146VideoQualityAnalyzer::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
155VideoQualityAnalyzer::~VideoQualityAnalyzer() = default;
156
157void VideoQualityAnalyzer::PrintHeaders() {
158 writer_->Write(
159 "capture_time render_time capture_width capture_height render_width "
160 "render_height psnr\n");
161}
162
163std::function<void(const VideoFramePair&)> VideoQualityAnalyzer::Handler() {
164 return [this](VideoFramePair pair) { HandleFramePair(pair); };
165}
166
167void 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
189VideoQualityStats VideoQualityAnalyzer::stats() const {
190 return stats_;
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +0100191}
192
193} // namespace test
194} // namespace webrtc