Adds optional video quality metrics to scenario tests.
Bug: webrtc:9510 Change-Id: I448e7156cc8f56930f58c4d25bd167df83a2ba85 Reviewed-on: https://webrtc-review.googlesource.com/c/114885 Commit-Queue: Sebastian Jansson <srte@webrtc.org> Reviewed-by: Erik Språng <sprang@webrtc.org> Cr-Commit-Position: refs/heads/master@{#26065}
This commit is contained in:

committed by
Commit Bot

parent
54fa02486a
commit
9a4f38ec5c
@ -16,12 +16,16 @@ if (rtc_include_tests) {
|
|||||||
"audio_stream.h",
|
"audio_stream.h",
|
||||||
"call_client.cc",
|
"call_client.cc",
|
||||||
"call_client.h",
|
"call_client.h",
|
||||||
|
"call_client.h",
|
||||||
"column_printer.cc",
|
"column_printer.cc",
|
||||||
"column_printer.h",
|
"column_printer.h",
|
||||||
"hardware_codecs.cc",
|
"hardware_codecs.cc",
|
||||||
"hardware_codecs.h",
|
"hardware_codecs.h",
|
||||||
"network_node.cc",
|
"network_node.cc",
|
||||||
"network_node.h",
|
"network_node.h",
|
||||||
|
"quality_info.h",
|
||||||
|
"quality_stats.cc",
|
||||||
|
"quality_stats.h",
|
||||||
"scenario.cc",
|
"scenario.cc",
|
||||||
"scenario.h",
|
"scenario.h",
|
||||||
"scenario_config.cc",
|
"scenario_config.cc",
|
||||||
@ -90,6 +94,7 @@ if (rtc_include_tests) {
|
|||||||
"../../system_wrappers",
|
"../../system_wrappers",
|
||||||
"../../system_wrappers:field_trial",
|
"../../system_wrappers:field_trial",
|
||||||
"../../video",
|
"../../video",
|
||||||
|
"//third_party/abseil-cpp/absl/memory",
|
||||||
"//third_party/abseil-cpp/absl/types:optional",
|
"//third_party/abseil-cpp/absl/types:optional",
|
||||||
]
|
]
|
||||||
if (is_android) {
|
if (is_android) {
|
||||||
@ -125,4 +130,25 @@ if (rtc_include_tests) {
|
|||||||
"//third_party/abseil-cpp/absl/memory",
|
"//third_party/abseil-cpp/absl/memory",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
rtc_source_set("scenario_slow_tests") {
|
||||||
|
testonly = true
|
||||||
|
sources = [
|
||||||
|
"quality_stats_unittest.cc",
|
||||||
|
]
|
||||||
|
if (!build_with_chromium && is_clang) {
|
||||||
|
suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
|
||||||
|
}
|
||||||
|
deps = [
|
||||||
|
":scenario",
|
||||||
|
"../../logging:mocks",
|
||||||
|
"../../rtc_base:checks",
|
||||||
|
"../../rtc_base:rtc_base_approved",
|
||||||
|
"../../system_wrappers",
|
||||||
|
"../../system_wrappers:field_trial",
|
||||||
|
"../../test:field_trial",
|
||||||
|
"../../test:test_support",
|
||||||
|
"//testing/gmock",
|
||||||
|
"//third_party/abseil-cpp/absl/memory",
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,17 +134,18 @@ TimeDelta LoggingNetworkControllerFactory::GetProcessInterval() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CallClient::CallClient(Clock* clock,
|
CallClient::CallClient(Clock* clock,
|
||||||
|
std::string name,
|
||||||
std::string log_filename,
|
std::string log_filename,
|
||||||
CallClientConfig config)
|
CallClientConfig config)
|
||||||
: clock_(clock),
|
: clock_(clock),
|
||||||
|
name_(name),
|
||||||
network_controller_factory_(log_filename, config.transport),
|
network_controller_factory_(log_filename, config.transport),
|
||||||
fake_audio_setup_(InitAudio()),
|
fake_audio_setup_(InitAudio()),
|
||||||
call_(CreateCall(config,
|
call_(CreateCall(config,
|
||||||
&network_controller_factory_,
|
&network_controller_factory_,
|
||||||
fake_audio_setup_.audio_state)),
|
fake_audio_setup_.audio_state)),
|
||||||
transport_(clock_, call_.get()),
|
transport_(clock_, call_.get()),
|
||||||
header_parser_(RtpHeaderParser::Create()) {
|
header_parser_(RtpHeaderParser::Create()) {}
|
||||||
} // namespace test
|
|
||||||
|
|
||||||
CallClient::~CallClient() {
|
CallClient::~CallClient() {
|
||||||
delete header_parser_;
|
delete header_parser_;
|
||||||
|
@ -58,7 +58,10 @@ struct CallClientFakeAudio {
|
|||||||
// stream session.
|
// stream session.
|
||||||
class CallClient : public NetworkReceiverInterface {
|
class CallClient : public NetworkReceiverInterface {
|
||||||
public:
|
public:
|
||||||
CallClient(Clock* clock, std::string log_filename, CallClientConfig config);
|
CallClient(Clock* clock,
|
||||||
|
std::string name,
|
||||||
|
std::string log_filename,
|
||||||
|
CallClientConfig config);
|
||||||
RTC_DISALLOW_COPY_AND_ASSIGN(CallClient);
|
RTC_DISALLOW_COPY_AND_ASSIGN(CallClient);
|
||||||
|
|
||||||
~CallClient();
|
~CallClient();
|
||||||
@ -89,6 +92,7 @@ class CallClient : public NetworkReceiverInterface {
|
|||||||
void AddExtensions(std::vector<RtpExtension> extensions);
|
void AddExtensions(std::vector<RtpExtension> extensions);
|
||||||
|
|
||||||
Clock* clock_;
|
Clock* clock_;
|
||||||
|
const std::string name_;
|
||||||
LoggingNetworkControllerFactory network_controller_factory_;
|
LoggingNetworkControllerFactory network_controller_factory_;
|
||||||
CallClientFakeAudio fake_audio_setup_;
|
CallClientFakeAudio fake_audio_setup_;
|
||||||
std::unique_ptr<Call> call_;
|
std::unique_ptr<Call> call_;
|
||||||
|
27
test/scenario/quality_info.h
Normal file
27
test/scenario/quality_info.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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 TEST_SCENARIO_QUALITY_INFO_H_
|
||||||
|
#define TEST_SCENARIO_QUALITY_INFO_H_
|
||||||
|
|
||||||
|
#include "api/units/timestamp.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
namespace test {
|
||||||
|
struct VideoFrameQualityInfo {
|
||||||
|
Timestamp capture_time;
|
||||||
|
Timestamp received_capture_time;
|
||||||
|
Timestamp render_time;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
double psnr;
|
||||||
|
};
|
||||||
|
} // namespace test
|
||||||
|
} // namespace webrtc
|
||||||
|
#endif // TEST_SCENARIO_QUALITY_INFO_H_
|
183
test/scenario/quality_stats.cc
Normal file
183
test/scenario/quality_stats.cc
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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 "test/scenario/quality_stats.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "common_video/libyuv/include/webrtc_libyuv.h"
|
||||||
|
#include "rtc_base/checks.h"
|
||||||
|
#include "rtc_base/event.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
VideoQualityAnalyzer::VideoQualityAnalyzer(
|
||||||
|
std::string filename_or_empty,
|
||||||
|
std::function<void(const VideoFrameQualityInfo&)> frame_info_handler)
|
||||||
|
: task_queue_("VideoAnalyzer") {
|
||||||
|
if (!filename_or_empty.empty()) {
|
||||||
|
output_file_ = fopen(filename_or_empty.c_str(), "w");
|
||||||
|
RTC_CHECK(output_file_);
|
||||||
|
PrintHeaders();
|
||||||
|
frame_info_handlers_.push_back(
|
||||||
|
[this](const VideoFrameQualityInfo& info) { PrintFrameInfo(info); });
|
||||||
|
}
|
||||||
|
if (frame_info_handler)
|
||||||
|
frame_info_handlers_.push_back(frame_info_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoQualityAnalyzer::~VideoQualityAnalyzer() {
|
||||||
|
rtc::Event event;
|
||||||
|
task_queue_.PostTask([&event] { event.Set(); });
|
||||||
|
event.Wait(rtc::Event::kForever);
|
||||||
|
if (output_file_)
|
||||||
|
fclose(output_file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoQualityAnalyzer::OnCapturedFrame(const VideoFrame& frame) {
|
||||||
|
VideoFrame copy = frame;
|
||||||
|
task_queue_.PostTask([this, copy] {
|
||||||
|
if (!first_capture_ntp_time_ms_)
|
||||||
|
first_capture_ntp_time_ms_ = copy.ntp_time_ms();
|
||||||
|
captured_frames_.push_back(std::move(copy));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoQualityAnalyzer::OnDecodedFrame(const VideoFrame& frame) {
|
||||||
|
VideoFrame decoded = frame;
|
||||||
|
RTC_CHECK(frame.ntp_time_ms());
|
||||||
|
RTC_CHECK(frame.timestamp());
|
||||||
|
task_queue_.PostTask([this, decoded] {
|
||||||
|
// If first frame never is received, this value will be wrong. However, that
|
||||||
|
// is something that is very unlikely to happen.
|
||||||
|
if (!first_decode_rtp_timestamp_)
|
||||||
|
first_decode_rtp_timestamp_ = decoded.timestamp();
|
||||||
|
RTC_CHECK(!captured_frames_.empty());
|
||||||
|
int64_t decoded_capture_time_ms = DecodedFrameCaptureTimeOffsetMs(decoded);
|
||||||
|
while (CapturedFrameCaptureTimeOffsetMs(captured_frames_.front()) <
|
||||||
|
decoded_capture_time_ms) {
|
||||||
|
VideoFrame lost = std::move(captured_frames_.front());
|
||||||
|
captured_frames_.pop_front();
|
||||||
|
VideoFrameQualityInfo lost_info =
|
||||||
|
VideoFrameQualityInfo{Timestamp::us(lost.timestamp_us()),
|
||||||
|
Timestamp::PlusInfinity(),
|
||||||
|
Timestamp::PlusInfinity(),
|
||||||
|
lost.width(),
|
||||||
|
lost.height(),
|
||||||
|
NAN};
|
||||||
|
for (auto& handler : frame_info_handlers_)
|
||||||
|
handler(lost_info);
|
||||||
|
RTC_CHECK(!captured_frames_.empty());
|
||||||
|
}
|
||||||
|
RTC_CHECK(!captured_frames_.empty());
|
||||||
|
RTC_CHECK(CapturedFrameCaptureTimeOffsetMs(captured_frames_.front()) ==
|
||||||
|
DecodedFrameCaptureTimeOffsetMs(decoded));
|
||||||
|
VideoFrame captured = std::move(captured_frames_.front());
|
||||||
|
captured_frames_.pop_front();
|
||||||
|
|
||||||
|
VideoFrameQualityInfo decoded_info =
|
||||||
|
VideoFrameQualityInfo{Timestamp::us(captured.timestamp_us()),
|
||||||
|
Timestamp::ms(decoded.timestamp() / 90.0),
|
||||||
|
Timestamp::ms(decoded.render_time_ms()),
|
||||||
|
decoded.width(),
|
||||||
|
decoded.height(),
|
||||||
|
I420PSNR(&captured, &decoded)};
|
||||||
|
for (auto& handler : frame_info_handlers_)
|
||||||
|
handler(decoded_info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoQualityAnalyzer::Active() const {
|
||||||
|
return !frame_info_handlers_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t VideoQualityAnalyzer::DecodedFrameCaptureTimeOffsetMs(
|
||||||
|
const VideoFrame& decoded) const {
|
||||||
|
// Assumes that the underlying resolution is ms.
|
||||||
|
// Note that we intentinally allow wraparound. The code is incorrect for
|
||||||
|
// durations of more than UINT32_MAX/90 ms.
|
||||||
|
RTC_DCHECK(first_decode_rtp_timestamp_);
|
||||||
|
return (decoded.timestamp() - *first_decode_rtp_timestamp_) / 90;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t VideoQualityAnalyzer::CapturedFrameCaptureTimeOffsetMs(
|
||||||
|
const VideoFrame& captured) const {
|
||||||
|
RTC_DCHECK(first_capture_ntp_time_ms_);
|
||||||
|
return captured.ntp_time_ms() - *first_capture_ntp_time_ms_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoQualityAnalyzer::PrintHeaders() {
|
||||||
|
fprintf(output_file_, "capt recv_capt render width height psnr\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoQualityAnalyzer::PrintFrameInfo(const VideoFrameQualityInfo& sample) {
|
||||||
|
fprintf(output_file_, "%.3f %.3f %.3f %i %i %.3f\n",
|
||||||
|
sample.capture_time.seconds<double>(),
|
||||||
|
sample.received_capture_time.seconds<double>(),
|
||||||
|
sample.render_time.seconds<double>(), sample.width, sample.height,
|
||||||
|
sample.psnr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoQualityStats::HandleFrameInfo(VideoFrameQualityInfo sample) {
|
||||||
|
total++;
|
||||||
|
if (sample.render_time.IsInfinite()) {
|
||||||
|
++lost;
|
||||||
|
} else {
|
||||||
|
++valid;
|
||||||
|
end_to_end_seconds.AddSample(
|
||||||
|
(sample.render_time - sample.capture_time).seconds<double>());
|
||||||
|
psnr.AddSample(sample.psnr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ForwardingCapturedFrameTap::ForwardingCapturedFrameTap(
|
||||||
|
const Clock* clock,
|
||||||
|
VideoQualityAnalyzer* analyzer,
|
||||||
|
rtc::VideoSourceInterface<VideoFrame>* source)
|
||||||
|
: clock_(clock), analyzer_(analyzer), source_(source) {}
|
||||||
|
|
||||||
|
ForwardingCapturedFrameTap::~ForwardingCapturedFrameTap() {}
|
||||||
|
|
||||||
|
void ForwardingCapturedFrameTap::OnFrame(const VideoFrame& frame) {
|
||||||
|
RTC_CHECK(sink_);
|
||||||
|
VideoFrame copy = frame;
|
||||||
|
if (frame.ntp_time_ms() == 0)
|
||||||
|
copy.set_ntp_time_ms(clock_->CurrentNtpInMilliseconds());
|
||||||
|
copy.set_timestamp(copy.ntp_time_ms() * 90);
|
||||||
|
analyzer_->OnCapturedFrame(copy);
|
||||||
|
sink_->OnFrame(copy);
|
||||||
|
}
|
||||||
|
void ForwardingCapturedFrameTap::OnDiscardedFrame() {
|
||||||
|
RTC_CHECK(sink_);
|
||||||
|
discarded_count_++;
|
||||||
|
sink_->OnDiscardedFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForwardingCapturedFrameTap::AddOrUpdateSink(
|
||||||
|
VideoSinkInterface<VideoFrame>* sink,
|
||||||
|
const rtc::VideoSinkWants& wants) {
|
||||||
|
sink_ = sink;
|
||||||
|
source_->AddOrUpdateSink(this, wants);
|
||||||
|
}
|
||||||
|
void ForwardingCapturedFrameTap::RemoveSink(
|
||||||
|
VideoSinkInterface<VideoFrame>* sink) {
|
||||||
|
source_->RemoveSink(this);
|
||||||
|
sink_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedFrameTap::DecodedFrameTap(VideoQualityAnalyzer* analyzer)
|
||||||
|
: analyzer_(analyzer) {}
|
||||||
|
|
||||||
|
void DecodedFrameTap::OnFrame(const VideoFrame& frame) {
|
||||||
|
analyzer_->OnDecodedFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace webrtc
|
110
test/scenario/quality_stats.h
Normal file
110
test/scenario/quality_stats.h
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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 TEST_SCENARIO_QUALITY_STATS_H_
|
||||||
|
#define TEST_SCENARIO_QUALITY_STATS_H_
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/types/optional.h"
|
||||||
|
#include "api/units/timestamp.h"
|
||||||
|
#include "api/video/video_frame.h"
|
||||||
|
#include "api/video/video_sink_interface.h"
|
||||||
|
#include "api/video/video_source_interface.h"
|
||||||
|
#include "rtc_base/task_queue.h"
|
||||||
|
#include "rtc_base/timeutils.h"
|
||||||
|
#include "system_wrappers/include/clock.h"
|
||||||
|
#include "test/scenario/quality_info.h"
|
||||||
|
#include "test/scenario/scenario_config.h"
|
||||||
|
#include "test/statistics.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
class VideoQualityAnalyzer {
|
||||||
|
public:
|
||||||
|
VideoQualityAnalyzer(
|
||||||
|
std::string filename_or_empty,
|
||||||
|
std::function<void(const VideoFrameQualityInfo&)> frame_info_handler);
|
||||||
|
~VideoQualityAnalyzer();
|
||||||
|
void OnCapturedFrame(const VideoFrame& frame);
|
||||||
|
void OnDecodedFrame(const VideoFrame& frame);
|
||||||
|
void Synchronize();
|
||||||
|
bool Active() const;
|
||||||
|
const Clock* clock();
|
||||||
|
|
||||||
|
private:
|
||||||
|
int64_t DecodedFrameCaptureTimeOffsetMs(const VideoFrame& decoded) const;
|
||||||
|
int64_t CapturedFrameCaptureTimeOffsetMs(const VideoFrame& captured) const;
|
||||||
|
void PrintHeaders();
|
||||||
|
void PrintFrameInfo(const VideoFrameQualityInfo& sample);
|
||||||
|
std::vector<std::function<void(const VideoFrameQualityInfo&)>>
|
||||||
|
frame_info_handlers_;
|
||||||
|
std::deque<VideoFrame> captured_frames_;
|
||||||
|
absl::optional<int64_t> first_capture_ntp_time_ms_;
|
||||||
|
absl::optional<uint32_t> first_decode_rtp_timestamp_;
|
||||||
|
FILE* output_file_ = nullptr;
|
||||||
|
rtc::TaskQueue task_queue_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VideoQualityStats {
|
||||||
|
int total = 0;
|
||||||
|
int valid = 0;
|
||||||
|
int lost = 0;
|
||||||
|
Statistics end_to_end_seconds;
|
||||||
|
Statistics frame_size;
|
||||||
|
Statistics psnr;
|
||||||
|
Statistics ssim;
|
||||||
|
|
||||||
|
void HandleFrameInfo(VideoFrameQualityInfo sample);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ForwardingCapturedFrameTap
|
||||||
|
: public rtc::VideoSinkInterface<VideoFrame>,
|
||||||
|
public rtc::VideoSourceInterface<VideoFrame> {
|
||||||
|
public:
|
||||||
|
ForwardingCapturedFrameTap(const Clock* clock,
|
||||||
|
VideoQualityAnalyzer* analyzer,
|
||||||
|
rtc::VideoSourceInterface<VideoFrame>* source);
|
||||||
|
ForwardingCapturedFrameTap(ForwardingCapturedFrameTap&) = delete;
|
||||||
|
ForwardingCapturedFrameTap& operator=(ForwardingCapturedFrameTap&) = delete;
|
||||||
|
~ForwardingCapturedFrameTap();
|
||||||
|
|
||||||
|
// VideoSinkInterface interface
|
||||||
|
void OnFrame(const VideoFrame& frame) override;
|
||||||
|
void OnDiscardedFrame() override;
|
||||||
|
|
||||||
|
// VideoSourceInterface interface
|
||||||
|
void AddOrUpdateSink(VideoSinkInterface<VideoFrame>* sink,
|
||||||
|
const rtc::VideoSinkWants& wants) override;
|
||||||
|
void RemoveSink(VideoSinkInterface<VideoFrame>* sink) override;
|
||||||
|
VideoFrame PopFrame();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Clock* clock_;
|
||||||
|
VideoQualityAnalyzer* const analyzer_;
|
||||||
|
rtc::VideoSourceInterface<VideoFrame>* const source_;
|
||||||
|
VideoSinkInterface<VideoFrame>* sink_;
|
||||||
|
int discarded_count_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DecodedFrameTap : public rtc::VideoSinkInterface<VideoFrame> {
|
||||||
|
public:
|
||||||
|
explicit DecodedFrameTap(VideoQualityAnalyzer* analyzer);
|
||||||
|
// VideoSinkInterface interface
|
||||||
|
void OnFrame(const VideoFrame& frame) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
VideoQualityAnalyzer* const analyzer_;
|
||||||
|
};
|
||||||
|
} // namespace test
|
||||||
|
} // namespace webrtc
|
||||||
|
#endif // TEST_SCENARIO_QUALITY_STATS_H_
|
62
test/scenario/quality_stats_unittest.cc
Normal file
62
test/scenario/quality_stats_unittest.cc
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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 "test/gtest.h"
|
||||||
|
#include "test/scenario/scenario.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
namespace test {
|
||||||
|
namespace {
|
||||||
|
VideoStreamConfig AnalyzerVideoConfig(VideoQualityStats* stats) {
|
||||||
|
VideoStreamConfig config;
|
||||||
|
config.encoder.codec = VideoStreamConfig::Encoder::Codec::kVideoCodecVP8;
|
||||||
|
config.encoder.implementation =
|
||||||
|
VideoStreamConfig::Encoder::Implementation::kSoftware;
|
||||||
|
config.analyzer.frame_quality_handler = [stats](VideoFrameQualityInfo info) {
|
||||||
|
stats->HandleFrameInfo(info);
|
||||||
|
};
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(ScenarioAnalyzerTest, PsnrIsHighWhenNetworkIsGood) {
|
||||||
|
VideoQualityStats stats;
|
||||||
|
{
|
||||||
|
Scenario s;
|
||||||
|
NetworkNodeConfig good_network;
|
||||||
|
good_network.simulation.bandwidth = DataRate::kbps(1000);
|
||||||
|
auto route = s.CreateRoutes(s.CreateClient("caller", CallClientConfig()),
|
||||||
|
{s.CreateSimulationNode(good_network)},
|
||||||
|
s.CreateClient("callee", CallClientConfig()),
|
||||||
|
{s.CreateSimulationNode(NetworkNodeConfig())});
|
||||||
|
s.CreateVideoStream(route->forward(), AnalyzerVideoConfig(&stats));
|
||||||
|
s.RunFor(TimeDelta::seconds(1));
|
||||||
|
}
|
||||||
|
EXPECT_GT(stats.psnr.Mean(), 46);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ScenarioAnalyzerTest, PsnrIsLowWhenNetworkIsBad) {
|
||||||
|
VideoQualityStats stats;
|
||||||
|
{
|
||||||
|
Scenario s;
|
||||||
|
NetworkNodeConfig bad_network;
|
||||||
|
bad_network.simulation.bandwidth = DataRate::kbps(100);
|
||||||
|
bad_network.simulation.loss_rate = 0.02;
|
||||||
|
auto route = s.CreateRoutes(s.CreateClient("caller", CallClientConfig()),
|
||||||
|
{s.CreateSimulationNode(bad_network)},
|
||||||
|
s.CreateClient("callee", CallClientConfig()),
|
||||||
|
{s.CreateSimulationNode(NetworkNodeConfig())});
|
||||||
|
|
||||||
|
s.CreateVideoStream(route->forward(), AnalyzerVideoConfig(&stats));
|
||||||
|
s.RunFor(TimeDelta::seconds(2));
|
||||||
|
}
|
||||||
|
EXPECT_LT(stats.psnr.Mean(), 40);
|
||||||
|
}
|
||||||
|
} // namespace test
|
||||||
|
} // namespace webrtc
|
@ -107,7 +107,8 @@ StatesPrinter* Scenario::CreatePrinter(std::string name,
|
|||||||
|
|
||||||
CallClient* Scenario::CreateClient(std::string name, CallClientConfig config) {
|
CallClient* Scenario::CreateClient(std::string name, CallClientConfig config) {
|
||||||
RTC_DCHECK(real_time_mode_);
|
RTC_DCHECK(real_time_mode_);
|
||||||
CallClient* client = new CallClient(clock_, GetFullPathOrEmpty(name), config);
|
CallClient* client =
|
||||||
|
new CallClient(clock_, name, GetFullPathOrEmpty(name), config);
|
||||||
if (config.transport.state_log_interval.IsFinite()) {
|
if (config.transport.state_log_interval.IsFinite()) {
|
||||||
Every(config.transport.state_log_interval, [this, client]() {
|
Every(config.transport.state_log_interval, [this, client]() {
|
||||||
client->network_controller_factory_.LogCongestionControllerStats(Now());
|
client->network_controller_factory_.LogCongestionControllerStats(Now());
|
||||||
@ -266,8 +267,12 @@ VideoStreamPair* Scenario::CreateVideoStream(
|
|||||||
VideoStreamPair* Scenario::CreateVideoStream(
|
VideoStreamPair* Scenario::CreateVideoStream(
|
||||||
std::pair<CallClient*, CallClient*> clients,
|
std::pair<CallClient*, CallClient*> clients,
|
||||||
VideoStreamConfig config) {
|
VideoStreamConfig config) {
|
||||||
video_streams_.emplace_back(
|
std::string quality_log_file_name;
|
||||||
new VideoStreamPair(clients.first, clients.second, config));
|
if (config.analyzer.log_to_file)
|
||||||
|
quality_log_file_name =
|
||||||
|
GetFullPathOrEmpty(clients.first->name_ + ".video_quality.txt");
|
||||||
|
video_streams_.emplace_back(new VideoStreamPair(
|
||||||
|
clients.first, clients.second, config, quality_log_file_name));
|
||||||
return video_streams_.back().get();
|
return video_streams_.back().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "api/units/time_delta.h"
|
#include "api/units/time_delta.h"
|
||||||
#include "common_types.h" // NOLINT(build/include)
|
#include "common_types.h" // NOLINT(build/include)
|
||||||
#include "test/frame_generator.h"
|
#include "test/frame_generator.h"
|
||||||
|
#include "test/scenario/quality_info.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace test {
|
namespace test {
|
||||||
@ -138,6 +139,10 @@ struct VideoStreamConfig {
|
|||||||
struct Renderer {
|
struct Renderer {
|
||||||
enum Type { kFake } type = kFake;
|
enum Type { kFake } type = kFake;
|
||||||
};
|
};
|
||||||
|
struct analyzer {
|
||||||
|
bool log_to_file = false;
|
||||||
|
std::function<void(const VideoFrameQualityInfo&)> frame_quality_handler;
|
||||||
|
} analyzer;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AudioStreamConfig {
|
struct AudioStreamConfig {
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
#include "api/test/video/function_video_encoder_factory.h"
|
#include "api/test/video/function_video_encoder_factory.h"
|
||||||
#include "api/video/builtin_video_bitrate_allocator_factory.h"
|
#include "api/video/builtin_video_bitrate_allocator_factory.h"
|
||||||
#include "media/base/mediaconstants.h"
|
#include "media/base/mediaconstants.h"
|
||||||
@ -175,7 +176,8 @@ VideoEncoderConfig CreateVideoEncoderConfig(VideoStreamConfig config) {
|
|||||||
|
|
||||||
SendVideoStream::SendVideoStream(CallClient* sender,
|
SendVideoStream::SendVideoStream(CallClient* sender,
|
||||||
VideoStreamConfig config,
|
VideoStreamConfig config,
|
||||||
Transport* send_transport)
|
Transport* send_transport,
|
||||||
|
VideoQualityAnalyzer* analyzer)
|
||||||
: sender_(sender), config_(config) {
|
: sender_(sender), config_(config) {
|
||||||
for (size_t i = 0; i < config.encoder.num_simulcast_streams; ++i) {
|
for (size_t i = 0; i < config.encoder.num_simulcast_streams; ++i) {
|
||||||
ssrcs_.push_back(sender->GetNextVideoSsrc());
|
ssrcs_.push_back(sender->GetNextVideoSsrc());
|
||||||
@ -244,9 +246,20 @@ SendVideoStream::SendVideoStream(CallClient* sender,
|
|||||||
|
|
||||||
send_stream_ = sender_->call_->CreateVideoSendStream(
|
send_stream_ = sender_->call_->CreateVideoSendStream(
|
||||||
std::move(send_config), std::move(encoder_config));
|
std::move(send_config), std::move(encoder_config));
|
||||||
|
std::vector<std::function<void(const VideoFrameQualityInfo&)> >
|
||||||
|
frame_info_handlers;
|
||||||
|
if (config.analyzer.frame_quality_handler)
|
||||||
|
frame_info_handlers.push_back(config.analyzer.frame_quality_handler);
|
||||||
|
|
||||||
send_stream_->SetSource(video_capturer_.get(),
|
if (analyzer->Active()) {
|
||||||
config.encoder.degradation_preference);
|
frame_tap_.reset(new ForwardingCapturedFrameTap(sender_->clock_, analyzer,
|
||||||
|
video_capturer_.get()));
|
||||||
|
send_stream_->SetSource(frame_tap_.get(),
|
||||||
|
config.encoder.degradation_preference);
|
||||||
|
} else {
|
||||||
|
send_stream_->SetSource(video_capturer_.get(),
|
||||||
|
config.encoder.degradation_preference);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SendVideoStream::~SendVideoStream() {
|
SendVideoStream::~SendVideoStream() {
|
||||||
@ -282,7 +295,6 @@ void SendVideoStream::SetCaptureFramerate(int framerate) {
|
|||||||
RTC_CHECK(frame_generator_)
|
RTC_CHECK(frame_generator_)
|
||||||
<< "Framerate change only implemented for generators";
|
<< "Framerate change only implemented for generators";
|
||||||
frame_generator_->ChangeFramerate(framerate);
|
frame_generator_->ChangeFramerate(framerate);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoSendStream::Stats SendVideoStream::GetStats() const {
|
VideoSendStream::Stats SendVideoStream::GetStats() const {
|
||||||
@ -311,9 +323,14 @@ ReceiveVideoStream::ReceiveVideoStream(CallClient* receiver,
|
|||||||
VideoStreamConfig config,
|
VideoStreamConfig config,
|
||||||
SendVideoStream* send_stream,
|
SendVideoStream* send_stream,
|
||||||
size_t chosen_stream,
|
size_t chosen_stream,
|
||||||
Transport* feedback_transport)
|
Transport* feedback_transport,
|
||||||
|
VideoQualityAnalyzer* analyzer)
|
||||||
: receiver_(receiver), config_(config) {
|
: receiver_(receiver), config_(config) {
|
||||||
renderer_ = absl::make_unique<FakeVideoRenderer>();
|
if (analyzer->Active()) {
|
||||||
|
renderer_ = absl::make_unique<DecodedFrameTap>(analyzer);
|
||||||
|
} else {
|
||||||
|
renderer_ = absl::make_unique<FakeVideoRenderer>();
|
||||||
|
}
|
||||||
VideoReceiveStream::Config recv_config(feedback_transport);
|
VideoReceiveStream::Config recv_config(feedback_transport);
|
||||||
recv_config.rtp.remb = !config.stream.packet_feedback;
|
recv_config.rtp.remb = !config.stream.packet_feedback;
|
||||||
recv_config.rtp.transport_cc = config.stream.packet_feedback;
|
recv_config.rtp.transport_cc = config.stream.packet_feedback;
|
||||||
@ -384,14 +401,17 @@ VideoStreamPair::~VideoStreamPair() = default;
|
|||||||
|
|
||||||
VideoStreamPair::VideoStreamPair(CallClient* sender,
|
VideoStreamPair::VideoStreamPair(CallClient* sender,
|
||||||
CallClient* receiver,
|
CallClient* receiver,
|
||||||
VideoStreamConfig config)
|
VideoStreamConfig config,
|
||||||
|
std::string quality_log_file_name)
|
||||||
: config_(config),
|
: config_(config),
|
||||||
send_stream_(sender, config, &sender->transport_),
|
analyzer_(quality_log_file_name, config.analyzer.frame_quality_handler),
|
||||||
|
send_stream_(sender, config, &sender->transport_, &analyzer_),
|
||||||
receive_stream_(receiver,
|
receive_stream_(receiver,
|
||||||
config,
|
config,
|
||||||
&send_stream_,
|
&send_stream_,
|
||||||
/*chosen_stream=*/0,
|
/*chosen_stream=*/0,
|
||||||
&receiver->transport_) {}
|
&receiver->transport_,
|
||||||
|
&analyzer_) {}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "test/scenario/call_client.h"
|
#include "test/scenario/call_client.h"
|
||||||
#include "test/scenario/column_printer.h"
|
#include "test/scenario/column_printer.h"
|
||||||
#include "test/scenario/network_node.h"
|
#include "test/scenario/network_node.h"
|
||||||
|
#include "test/scenario/quality_stats.h"
|
||||||
#include "test/scenario/scenario_config.h"
|
#include "test/scenario/scenario_config.h"
|
||||||
#include "test/test_video_capturer.h"
|
#include "test/test_video_capturer.h"
|
||||||
|
|
||||||
@ -43,7 +44,8 @@ class SendVideoStream {
|
|||||||
// Handles RTCP feedback for this stream.
|
// Handles RTCP feedback for this stream.
|
||||||
SendVideoStream(CallClient* sender,
|
SendVideoStream(CallClient* sender,
|
||||||
VideoStreamConfig config,
|
VideoStreamConfig config,
|
||||||
Transport* send_transport);
|
Transport* send_transport,
|
||||||
|
VideoQualityAnalyzer* analyzer);
|
||||||
|
|
||||||
rtc::CriticalSection crit_;
|
rtc::CriticalSection crit_;
|
||||||
std::vector<uint32_t> ssrcs_;
|
std::vector<uint32_t> ssrcs_;
|
||||||
@ -55,6 +57,7 @@ class SendVideoStream {
|
|||||||
std::vector<test::FakeEncoder*> fake_encoders_ RTC_GUARDED_BY(crit_);
|
std::vector<test::FakeEncoder*> fake_encoders_ RTC_GUARDED_BY(crit_);
|
||||||
std::unique_ptr<VideoBitrateAllocatorFactory> bitrate_allocator_factory_;
|
std::unique_ptr<VideoBitrateAllocatorFactory> bitrate_allocator_factory_;
|
||||||
std::unique_ptr<TestVideoCapturer> video_capturer_;
|
std::unique_ptr<TestVideoCapturer> video_capturer_;
|
||||||
|
std::unique_ptr<ForwardingCapturedFrameTap> frame_tap_;
|
||||||
FrameGeneratorCapturer* frame_generator_ = nullptr;
|
FrameGeneratorCapturer* frame_generator_ = nullptr;
|
||||||
int next_local_network_id_ = 0;
|
int next_local_network_id_ = 0;
|
||||||
int next_remote_network_id_ = 0;
|
int next_remote_network_id_ = 0;
|
||||||
@ -74,11 +77,12 @@ class ReceiveVideoStream {
|
|||||||
VideoStreamConfig config,
|
VideoStreamConfig config,
|
||||||
SendVideoStream* send_stream,
|
SendVideoStream* send_stream,
|
||||||
size_t chosen_stream,
|
size_t chosen_stream,
|
||||||
Transport* feedback_transport);
|
Transport* feedback_transport,
|
||||||
|
VideoQualityAnalyzer* analyzer);
|
||||||
|
|
||||||
VideoReceiveStream* receive_stream_ = nullptr;
|
VideoReceiveStream* receive_stream_ = nullptr;
|
||||||
FlexfecReceiveStream* flecfec_stream_ = nullptr;
|
FlexfecReceiveStream* flecfec_stream_ = nullptr;
|
||||||
std::unique_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> renderer_;
|
std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>> renderer_;
|
||||||
CallClient* const receiver_;
|
CallClient* const receiver_;
|
||||||
const VideoStreamConfig config_;
|
const VideoStreamConfig config_;
|
||||||
std::unique_ptr<VideoDecoderFactory> decoder_factory_;
|
std::unique_ptr<VideoDecoderFactory> decoder_factory_;
|
||||||
@ -93,15 +97,18 @@ class VideoStreamPair {
|
|||||||
~VideoStreamPair();
|
~VideoStreamPair();
|
||||||
SendVideoStream* send() { return &send_stream_; }
|
SendVideoStream* send() { return &send_stream_; }
|
||||||
ReceiveVideoStream* receive() { return &receive_stream_; }
|
ReceiveVideoStream* receive() { return &receive_stream_; }
|
||||||
|
VideoQualityAnalyzer* analyzer() { return &analyzer_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class Scenario;
|
friend class Scenario;
|
||||||
VideoStreamPair(CallClient* sender,
|
VideoStreamPair(CallClient* sender,
|
||||||
CallClient* receiver,
|
CallClient* receiver,
|
||||||
VideoStreamConfig config);
|
VideoStreamConfig config,
|
||||||
|
std::string quality_log_file_name);
|
||||||
|
|
||||||
const VideoStreamConfig config_;
|
const VideoStreamConfig config_;
|
||||||
|
|
||||||
|
VideoQualityAnalyzer analyzer_;
|
||||||
SendVideoStream send_stream_;
|
SendVideoStream send_stream_;
|
||||||
ReceiveVideoStream receive_stream_;
|
ReceiveVideoStream receive_stream_;
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user