blob: 863cf1f36a41d7f175e0991c7f26f367489304cb [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 {
20
21VideoQualityAnalyzer::VideoQualityAnalyzer(
Sebastian Jansson52de8b02019-01-16 17:25:44 +010022 std::unique_ptr<RtcEventLogOutput> writer,
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +010023 std::function<void(const VideoFrameQualityInfo&)> frame_info_handler)
Sebastian Jansson52de8b02019-01-16 17:25:44 +010024 : writer_(std::move(writer)), task_queue_("VideoAnalyzer") {
25 if (writer_) {
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +010026 PrintHeaders();
27 frame_info_handlers_.push_back(
28 [this](const VideoFrameQualityInfo& info) { PrintFrameInfo(info); });
29 }
30 if (frame_info_handler)
31 frame_info_handlers_.push_back(frame_info_handler);
32}
33
34VideoQualityAnalyzer::~VideoQualityAnalyzer() {
35 rtc::Event event;
36 task_queue_.PostTask([&event] { event.Set(); });
37 event.Wait(rtc::Event::kForever);
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +010038}
39
40void VideoQualityAnalyzer::OnCapturedFrame(const VideoFrame& frame) {
41 VideoFrame copy = frame;
42 task_queue_.PostTask([this, copy] {
43 if (!first_capture_ntp_time_ms_)
44 first_capture_ntp_time_ms_ = copy.ntp_time_ms();
45 captured_frames_.push_back(std::move(copy));
46 });
47}
48
49void VideoQualityAnalyzer::OnDecodedFrame(const VideoFrame& frame) {
50 VideoFrame decoded = frame;
51 RTC_CHECK(frame.ntp_time_ms());
52 RTC_CHECK(frame.timestamp());
53 task_queue_.PostTask([this, decoded] {
54 // If first frame never is received, this value will be wrong. However, that
55 // is something that is very unlikely to happen.
56 if (!first_decode_rtp_timestamp_)
57 first_decode_rtp_timestamp_ = decoded.timestamp();
58 RTC_CHECK(!captured_frames_.empty());
59 int64_t decoded_capture_time_ms = DecodedFrameCaptureTimeOffsetMs(decoded);
60 while (CapturedFrameCaptureTimeOffsetMs(captured_frames_.front()) <
61 decoded_capture_time_ms) {
62 VideoFrame lost = std::move(captured_frames_.front());
63 captured_frames_.pop_front();
64 VideoFrameQualityInfo lost_info =
65 VideoFrameQualityInfo{Timestamp::us(lost.timestamp_us()),
66 Timestamp::PlusInfinity(),
67 Timestamp::PlusInfinity(),
68 lost.width(),
69 lost.height(),
70 NAN};
71 for (auto& handler : frame_info_handlers_)
72 handler(lost_info);
73 RTC_CHECK(!captured_frames_.empty());
74 }
75 RTC_CHECK(!captured_frames_.empty());
76 RTC_CHECK(CapturedFrameCaptureTimeOffsetMs(captured_frames_.front()) ==
77 DecodedFrameCaptureTimeOffsetMs(decoded));
78 VideoFrame captured = std::move(captured_frames_.front());
79 captured_frames_.pop_front();
80
81 VideoFrameQualityInfo decoded_info =
82 VideoFrameQualityInfo{Timestamp::us(captured.timestamp_us()),
83 Timestamp::ms(decoded.timestamp() / 90.0),
84 Timestamp::ms(decoded.render_time_ms()),
85 decoded.width(),
86 decoded.height(),
87 I420PSNR(&captured, &decoded)};
88 for (auto& handler : frame_info_handlers_)
89 handler(decoded_info);
90 });
91}
92
93bool VideoQualityAnalyzer::Active() const {
94 return !frame_info_handlers_.empty();
95}
96
97int64_t VideoQualityAnalyzer::DecodedFrameCaptureTimeOffsetMs(
98 const VideoFrame& decoded) const {
99 // Assumes that the underlying resolution is ms.
100 // Note that we intentinally allow wraparound. The code is incorrect for
101 // durations of more than UINT32_MAX/90 ms.
102 RTC_DCHECK(first_decode_rtp_timestamp_);
103 return (decoded.timestamp() - *first_decode_rtp_timestamp_) / 90;
104}
105
106int64_t VideoQualityAnalyzer::CapturedFrameCaptureTimeOffsetMs(
107 const VideoFrame& captured) const {
108 RTC_DCHECK(first_capture_ntp_time_ms_);
109 return captured.ntp_time_ms() - *first_capture_ntp_time_ms_;
110}
111
112void VideoQualityAnalyzer::PrintHeaders() {
Sebastian Jansson52de8b02019-01-16 17:25:44 +0100113 writer_->Write("capt recv_capt render width height psnr\n");
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +0100114}
115
116void VideoQualityAnalyzer::PrintFrameInfo(const VideoFrameQualityInfo& sample) {
Sebastian Jansson52de8b02019-01-16 17:25:44 +0100117 LogWriteFormat(writer_.get(), "%.3f %.3f %.3f %i %i %.3f\n",
118 sample.capture_time.seconds<double>(),
119 sample.received_capture_time.seconds<double>(),
120 sample.render_time.seconds<double>(), sample.width,
121 sample.height, sample.psnr);
Sebastian Jansson9a4f38e2018-12-19 13:14:41 +0100122}
123
124void VideoQualityStats::HandleFrameInfo(VideoFrameQualityInfo sample) {
125 total++;
126 if (sample.render_time.IsInfinite()) {
127 ++lost;
128 } else {
129 ++valid;
130 end_to_end_seconds.AddSample(
131 (sample.render_time - sample.capture_time).seconds<double>());
132 psnr.AddSample(sample.psnr);
133 }
134}
135
136ForwardingCapturedFrameTap::ForwardingCapturedFrameTap(
137 const Clock* clock,
138 VideoQualityAnalyzer* analyzer,
139 rtc::VideoSourceInterface<VideoFrame>* source)
140 : clock_(clock), analyzer_(analyzer), source_(source) {}
141
142ForwardingCapturedFrameTap::~ForwardingCapturedFrameTap() {}
143
144void ForwardingCapturedFrameTap::OnFrame(const VideoFrame& frame) {
145 RTC_CHECK(sink_);
146 VideoFrame copy = frame;
147 if (frame.ntp_time_ms() == 0)
148 copy.set_ntp_time_ms(clock_->CurrentNtpInMilliseconds());
149 copy.set_timestamp(copy.ntp_time_ms() * 90);
150 analyzer_->OnCapturedFrame(copy);
151 sink_->OnFrame(copy);
152}
153void ForwardingCapturedFrameTap::OnDiscardedFrame() {
154 RTC_CHECK(sink_);
155 discarded_count_++;
156 sink_->OnDiscardedFrame();
157}
158
159void ForwardingCapturedFrameTap::AddOrUpdateSink(
160 VideoSinkInterface<VideoFrame>* sink,
161 const rtc::VideoSinkWants& wants) {
162 sink_ = sink;
163 source_->AddOrUpdateSink(this, wants);
164}
165void ForwardingCapturedFrameTap::RemoveSink(
166 VideoSinkInterface<VideoFrame>* sink) {
167 source_->RemoveSink(this);
168 sink_ = nullptr;
169}
170
171DecodedFrameTap::DecodedFrameTap(VideoQualityAnalyzer* analyzer)
172 : analyzer_(analyzer) {}
173
174void DecodedFrameTap::OnFrame(const VideoFrame& frame) {
175 analyzer_->OnDecodedFrame(frame);
176}
177
178} // namespace test
179} // namespace webrtc