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",
|
||||
"call_client.cc",
|
||||
"call_client.h",
|
||||
"call_client.h",
|
||||
"column_printer.cc",
|
||||
"column_printer.h",
|
||||
"hardware_codecs.cc",
|
||||
"hardware_codecs.h",
|
||||
"network_node.cc",
|
||||
"network_node.h",
|
||||
"quality_info.h",
|
||||
"quality_stats.cc",
|
||||
"quality_stats.h",
|
||||
"scenario.cc",
|
||||
"scenario.h",
|
||||
"scenario_config.cc",
|
||||
@ -90,6 +94,7 @@ if (rtc_include_tests) {
|
||||
"../../system_wrappers",
|
||||
"../../system_wrappers:field_trial",
|
||||
"../../video",
|
||||
"//third_party/abseil-cpp/absl/memory",
|
||||
"//third_party/abseil-cpp/absl/types:optional",
|
||||
]
|
||||
if (is_android) {
|
||||
@ -125,4 +130,25 @@ if (rtc_include_tests) {
|
||||
"//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,
|
||||
std::string name,
|
||||
std::string log_filename,
|
||||
CallClientConfig config)
|
||||
: clock_(clock),
|
||||
name_(name),
|
||||
network_controller_factory_(log_filename, config.transport),
|
||||
fake_audio_setup_(InitAudio()),
|
||||
call_(CreateCall(config,
|
||||
&network_controller_factory_,
|
||||
fake_audio_setup_.audio_state)),
|
||||
transport_(clock_, call_.get()),
|
||||
header_parser_(RtpHeaderParser::Create()) {
|
||||
} // namespace test
|
||||
header_parser_(RtpHeaderParser::Create()) {}
|
||||
|
||||
CallClient::~CallClient() {
|
||||
delete header_parser_;
|
||||
|
@ -58,7 +58,10 @@ struct CallClientFakeAudio {
|
||||
// stream session.
|
||||
class CallClient : public NetworkReceiverInterface {
|
||||
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);
|
||||
|
||||
~CallClient();
|
||||
@ -89,6 +92,7 @@ class CallClient : public NetworkReceiverInterface {
|
||||
void AddExtensions(std::vector<RtpExtension> extensions);
|
||||
|
||||
Clock* clock_;
|
||||
const std::string name_;
|
||||
LoggingNetworkControllerFactory network_controller_factory_;
|
||||
CallClientFakeAudio fake_audio_setup_;
|
||||
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) {
|
||||
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()) {
|
||||
Every(config.transport.state_log_interval, [this, client]() {
|
||||
client->network_controller_factory_.LogCongestionControllerStats(Now());
|
||||
@ -266,8 +267,12 @@ VideoStreamPair* Scenario::CreateVideoStream(
|
||||
VideoStreamPair* Scenario::CreateVideoStream(
|
||||
std::pair<CallClient*, CallClient*> clients,
|
||||
VideoStreamConfig config) {
|
||||
video_streams_.emplace_back(
|
||||
new VideoStreamPair(clients.first, clients.second, config));
|
||||
std::string quality_log_file_name;
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "api/units/time_delta.h"
|
||||
#include "common_types.h" // NOLINT(build/include)
|
||||
#include "test/frame_generator.h"
|
||||
#include "test/scenario/quality_info.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
@ -138,6 +139,10 @@ struct VideoStreamConfig {
|
||||
struct Renderer {
|
||||
enum Type { kFake } type = kFake;
|
||||
};
|
||||
struct analyzer {
|
||||
bool log_to_file = false;
|
||||
std::function<void(const VideoFrameQualityInfo&)> frame_quality_handler;
|
||||
} analyzer;
|
||||
};
|
||||
|
||||
struct AudioStreamConfig {
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "api/test/video/function_video_encoder_factory.h"
|
||||
#include "api/video/builtin_video_bitrate_allocator_factory.h"
|
||||
#include "media/base/mediaconstants.h"
|
||||
@ -175,7 +176,8 @@ VideoEncoderConfig CreateVideoEncoderConfig(VideoStreamConfig config) {
|
||||
|
||||
SendVideoStream::SendVideoStream(CallClient* sender,
|
||||
VideoStreamConfig config,
|
||||
Transport* send_transport)
|
||||
Transport* send_transport,
|
||||
VideoQualityAnalyzer* analyzer)
|
||||
: sender_(sender), config_(config) {
|
||||
for (size_t i = 0; i < config.encoder.num_simulcast_streams; ++i) {
|
||||
ssrcs_.push_back(sender->GetNextVideoSsrc());
|
||||
@ -244,9 +246,20 @@ SendVideoStream::SendVideoStream(CallClient* sender,
|
||||
|
||||
send_stream_ = sender_->call_->CreateVideoSendStream(
|
||||
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(),
|
||||
config.encoder.degradation_preference);
|
||||
if (analyzer->Active()) {
|
||||
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() {
|
||||
@ -282,7 +295,6 @@ void SendVideoStream::SetCaptureFramerate(int framerate) {
|
||||
RTC_CHECK(frame_generator_)
|
||||
<< "Framerate change only implemented for generators";
|
||||
frame_generator_->ChangeFramerate(framerate);
|
||||
|
||||
}
|
||||
|
||||
VideoSendStream::Stats SendVideoStream::GetStats() const {
|
||||
@ -311,9 +323,14 @@ ReceiveVideoStream::ReceiveVideoStream(CallClient* receiver,
|
||||
VideoStreamConfig config,
|
||||
SendVideoStream* send_stream,
|
||||
size_t chosen_stream,
|
||||
Transport* feedback_transport)
|
||||
Transport* feedback_transport,
|
||||
VideoQualityAnalyzer* analyzer)
|
||||
: 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);
|
||||
recv_config.rtp.remb = !config.stream.packet_feedback;
|
||||
recv_config.rtp.transport_cc = config.stream.packet_feedback;
|
||||
@ -384,14 +401,17 @@ VideoStreamPair::~VideoStreamPair() = default;
|
||||
|
||||
VideoStreamPair::VideoStreamPair(CallClient* sender,
|
||||
CallClient* receiver,
|
||||
VideoStreamConfig config)
|
||||
VideoStreamConfig config,
|
||||
std::string quality_log_file_name)
|
||||
: 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,
|
||||
config,
|
||||
&send_stream_,
|
||||
/*chosen_stream=*/0,
|
||||
&receiver->transport_) {}
|
||||
&receiver->transport_,
|
||||
&analyzer_) {}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "test/scenario/call_client.h"
|
||||
#include "test/scenario/column_printer.h"
|
||||
#include "test/scenario/network_node.h"
|
||||
#include "test/scenario/quality_stats.h"
|
||||
#include "test/scenario/scenario_config.h"
|
||||
#include "test/test_video_capturer.h"
|
||||
|
||||
@ -43,7 +44,8 @@ class SendVideoStream {
|
||||
// Handles RTCP feedback for this stream.
|
||||
SendVideoStream(CallClient* sender,
|
||||
VideoStreamConfig config,
|
||||
Transport* send_transport);
|
||||
Transport* send_transport,
|
||||
VideoQualityAnalyzer* analyzer);
|
||||
|
||||
rtc::CriticalSection crit_;
|
||||
std::vector<uint32_t> ssrcs_;
|
||||
@ -55,6 +57,7 @@ class SendVideoStream {
|
||||
std::vector<test::FakeEncoder*> fake_encoders_ RTC_GUARDED_BY(crit_);
|
||||
std::unique_ptr<VideoBitrateAllocatorFactory> bitrate_allocator_factory_;
|
||||
std::unique_ptr<TestVideoCapturer> video_capturer_;
|
||||
std::unique_ptr<ForwardingCapturedFrameTap> frame_tap_;
|
||||
FrameGeneratorCapturer* frame_generator_ = nullptr;
|
||||
int next_local_network_id_ = 0;
|
||||
int next_remote_network_id_ = 0;
|
||||
@ -74,11 +77,12 @@ class ReceiveVideoStream {
|
||||
VideoStreamConfig config,
|
||||
SendVideoStream* send_stream,
|
||||
size_t chosen_stream,
|
||||
Transport* feedback_transport);
|
||||
Transport* feedback_transport,
|
||||
VideoQualityAnalyzer* analyzer);
|
||||
|
||||
VideoReceiveStream* receive_stream_ = nullptr;
|
||||
FlexfecReceiveStream* flecfec_stream_ = nullptr;
|
||||
std::unique_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> renderer_;
|
||||
std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>> renderer_;
|
||||
CallClient* const receiver_;
|
||||
const VideoStreamConfig config_;
|
||||
std::unique_ptr<VideoDecoderFactory> decoder_factory_;
|
||||
@ -93,15 +97,18 @@ class VideoStreamPair {
|
||||
~VideoStreamPair();
|
||||
SendVideoStream* send() { return &send_stream_; }
|
||||
ReceiveVideoStream* receive() { return &receive_stream_; }
|
||||
VideoQualityAnalyzer* analyzer() { return &analyzer_; }
|
||||
|
||||
private:
|
||||
friend class Scenario;
|
||||
VideoStreamPair(CallClient* sender,
|
||||
CallClient* receiver,
|
||||
VideoStreamConfig config);
|
||||
VideoStreamConfig config,
|
||||
std::string quality_log_file_name);
|
||||
|
||||
const VideoStreamConfig config_;
|
||||
|
||||
VideoQualityAnalyzer analyzer_;
|
||||
SendVideoStream send_stream_;
|
||||
ReceiveVideoStream receive_stream_;
|
||||
};
|
||||
|
Reference in New Issue
Block a user