Adding support for simulcast and spatial layers into VideoQualityTest
This is a re-land of https://codereview.webrtc.org/1353263005/
which was reverted because of perf-regressions. Changes since that CL:
* Change LayerFilteringTransport to send a padding packet instead of
dropping it for data that should be filtered out. This prevents
confusion due to changed sequence numbers.
* Changed timing of stats poller thread in VideoAnalyzer. Startup was
racy wrt initializion of send_stream_.
* Minor formatting issues.
PERF NOTE: This change will affect some performance numbers slightly.
In particular, {encode_frame_rate, encode_time_ms,
encode_usage_percent, media_bitrate_bps} will change due to timing
of the measurements.
BUG=
R=pbos@webrtc.org
TBR=mflodman@webrtc.org
Review URL: https://codereview.webrtc.org/1412233003
Cr-Commit-Position: refs/heads/master@{#10483}
diff --git a/webrtc/video/full_stack.cc b/webrtc/video/full_stack.cc
index 8511b82..2810cd6 100644
--- a/webrtc/video/full_stack.cc
+++ b/webrtc/video/full_stack.cc
@@ -23,6 +23,15 @@
}
};
+// VideoQualityTest::Params params = {
+// { ... }, // Common.
+// { ... }, // Video-specific settings.
+// { ... }, // Screenshare-specific settings.
+// { ... }, // Analyzer settings.
+// pipe, // FakeNetworkPipe::Config
+// { ... }, // Spatial scalability.
+// logs // bool
+// };
TEST_F(FullStackTest, ParisQcifWithoutPacketLoss) {
VideoQualityTest::Params paris_qcif = {
@@ -120,16 +129,16 @@
TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL) {
VideoQualityTest::Params screenshare = {
- {1850, 1110, 5, 50000, 200000, 2000000, "VP8", 2, 400000},
- {}, // Video-specific.
- {true, 10}, // Screenshare-specific.
+ {1850, 1110, 5, 50000, 200000, 2000000, "VP8", 2, 1, 400000},
+ {},
+ {true, 10},
{"screenshare_slides", 0.0, 0.0, kFullStackTestDurationSecs}};
RunTest(screenshare);
}
TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL_Scroll) {
VideoQualityTest::Params config = {
- {1850, 1110 / 2, 5, 50000, 200000, 2000000, "VP8", 2, 400000},
+ {1850, 1110 / 2, 5, 50000, 200000, 2000000, "VP8", 2, 1, 400000},
{},
{true, 10, 2},
{"screenshare_slides_scrolling", 0.0, 0.0, kFullStackTestDurationSecs}};
@@ -138,7 +147,7 @@
TEST_F(FullStackTest, ScreenshareSlidesVP9_2TL) {
VideoQualityTest::Params screenshare = {
- {1850, 1110, 5, 50000, 200000, 2000000, "VP9", 2, 400000},
+ {1850, 1110, 5, 50000, 200000, 2000000, "VP9", 2, 1, 400000},
{},
{true, 10},
{"screenshare_slides_vp9_2tl", 0.0, 0.0, kFullStackTestDurationSecs}};
diff --git a/webrtc/video/screenshare_loopback.cc b/webrtc/video/screenshare_loopback.cc
index 9897783..6479aa4e 100644
--- a/webrtc/video/screenshare_loopback.cc
+++ b/webrtc/video/screenshare_loopback.cc
@@ -20,6 +20,7 @@
namespace webrtc {
namespace flags {
+// Flags common with video loopback, with different default values.
DEFINE_int32(width, 1850, "Video width (crops source).");
size_t Width() {
return static_cast<size_t>(FLAGS_width);
@@ -35,21 +36,6 @@
return static_cast<int>(FLAGS_fps);
}
-DEFINE_int32(slide_change_interval,
- 10,
- "Interval (in seconds) between simulated slide changes.");
-int SlideChangeInterval() {
- return static_cast<int>(FLAGS_slide_change_interval);
-}
-
-DEFINE_int32(
- scroll_duration,
- 0,
- "Duration (in seconds) during which a slide will be scrolled into place.");
-int ScrollDuration() {
- return static_cast<int>(FLAGS_scroll_duration);
-}
-
DEFINE_int32(min_bitrate, 50, "Call and stream min bitrate in kbps.");
int MinBitrateKbps() {
return static_cast<int>(FLAGS_min_bitrate);
@@ -71,28 +57,43 @@
}
DEFINE_int32(num_temporal_layers, 2, "Number of temporal layers to use.");
-size_t NumTemporalLayers() {
- return static_cast<size_t>(FLAGS_num_temporal_layers);
+int NumTemporalLayers() {
+ return static_cast<int>(FLAGS_num_temporal_layers);
}
-DEFINE_int32(
- tl_discard_threshold,
- 0,
- "Discard TLs with id greater or equal the threshold. 0 to disable.");
-size_t TLDiscardThreshold() {
- return static_cast<size_t>(FLAGS_tl_discard_threshold);
-}
-
-DEFINE_int32(min_transmit_bitrate, 400, "Min transmit bitrate incl. padding.");
-int MinTransmitBitrateKbps() {
- return FLAGS_min_transmit_bitrate;
-}
-
+// Flags common with video loopback, with equal default values.
DEFINE_string(codec, "VP8", "Video codec to use.");
std::string Codec() {
return static_cast<std::string>(FLAGS_codec);
}
+DEFINE_int32(selected_tl,
+ -1,
+ "Temporal layer to show or analyze. -1 to disable filtering.");
+int SelectedTL() {
+ return static_cast<int>(FLAGS_selected_tl);
+}
+
+DEFINE_int32(
+ duration,
+ 0,
+ "Duration of the test in seconds. If 0, rendered will be shown instead.");
+int DurationSecs() {
+ return static_cast<int>(FLAGS_duration);
+}
+
+DEFINE_string(output_filename, "", "Target graph data filename.");
+std::string OutputFilename() {
+ return static_cast<std::string>(FLAGS_output_filename);
+}
+
+DEFINE_string(graph_title,
+ "",
+ "If empty, title will be generated automatically.");
+std::string GraphTitle() {
+ return static_cast<std::string>(FLAGS_graph_title);
+}
+
DEFINE_int32(loss_percent, 0, "Percentage of packets randomly lost.");
int LossPercent() {
return static_cast<int>(FLAGS_loss_percent);
@@ -124,21 +125,53 @@
return static_cast<int>(FLAGS_std_propagation_delay_ms);
}
+DEFINE_int32(selected_stream, 0, "ID of the stream to show or analyze.");
+int SelectedStream() {
+ return static_cast<int>(FLAGS_selected_stream);
+}
+
+DEFINE_int32(num_spatial_layers, 1, "Number of spatial layers to use.");
+int NumSpatialLayers() {
+ return static_cast<int>(FLAGS_num_spatial_layers);
+}
+
+DEFINE_int32(selected_sl,
+ -1,
+ "Spatial layer to show or analyze. -1 to disable filtering.");
+int SelectedSL() {
+ return static_cast<int>(FLAGS_selected_sl);
+}
+
+DEFINE_string(stream0,
+ "",
+ "Comma separated values describing VideoStream for stream #0.");
+std::string Stream0() {
+ return static_cast<std::string>(FLAGS_stream0);
+}
+
+DEFINE_string(stream1,
+ "",
+ "Comma separated values describing VideoStream for stream #1.");
+std::string Stream1() {
+ return static_cast<std::string>(FLAGS_stream1);
+}
+
+DEFINE_string(sl0,
+ "",
+ "Comma separated values describing SpatialLayer for layer #0.");
+std::string SL0() {
+ return static_cast<std::string>(FLAGS_sl0);
+}
+
+DEFINE_string(sl1,
+ "",
+ "Comma separated values describing SpatialLayer for layer #1.");
+std::string SL1() {
+ return static_cast<std::string>(FLAGS_sl1);
+}
+
DEFINE_bool(logs, false, "print logs to stderr");
-DEFINE_string(
- output_filename,
- "",
- "Name of a target graph data file. If set, no preview will be shown.");
-std::string OutputFilename() {
- return static_cast<std::string>(FLAGS_output_filename);
-}
-
-DEFINE_int32(duration, 60, "Duration of the test in seconds.");
-int DurationSecs() {
- return static_cast<int>(FLAGS_duration);
-}
-
DEFINE_bool(send_side_bwe, true, "Use send-side bandwidth estimation");
DEFINE_string(
@@ -148,6 +181,28 @@
"E.g. running with --force_fieldtrials=WebRTC-FooFeature/Enable/"
" will assign the group Enable to field trial WebRTC-FooFeature. Multiple "
"trials are separated by \"/\"");
+
+// Screenshare-specific flags.
+DEFINE_int32(min_transmit_bitrate, 400, "Min transmit bitrate incl. padding.");
+int MinTransmitBitrateKbps() {
+ return FLAGS_min_transmit_bitrate;
+}
+
+DEFINE_int32(slide_change_interval,
+ 10,
+ "Interval (in seconds) between simulated slide changes.");
+int SlideChangeInterval() {
+ return static_cast<int>(FLAGS_slide_change_interval);
+}
+
+DEFINE_int32(
+ scroll_duration,
+ 0,
+ "Duration (in seconds) during which a slide will be scrolled into place.");
+int ScrollDuration() {
+ return static_cast<int>(FLAGS_scroll_duration);
+}
+
} // namespace flags
void Loopback() {
@@ -167,20 +222,32 @@
{flags::Width(), flags::Height(), flags::Fps(),
flags::MinBitrateKbps() * 1000, flags::TargetBitrateKbps() * 1000,
flags::MaxBitrateKbps() * 1000, flags::Codec(),
- flags::NumTemporalLayers(), flags::MinTransmitBitrateKbps() * 1000,
- call_bitrate_config, flags::TLDiscardThreshold(),
+ flags::NumTemporalLayers(), flags::SelectedTL(),
+ flags::MinTransmitBitrateKbps() * 1000, call_bitrate_config,
flags::FLAGS_send_side_bwe},
{}, // Video specific.
{true, flags::SlideChangeInterval(), flags::ScrollDuration()},
- {"screenshare", 0.0, 0.0, flags::DurationSecs(), flags::OutputFilename()},
+ {"screenshare", 0.0, 0.0, flags::DurationSecs(), flags::OutputFilename(),
+ flags::GraphTitle()},
pipe_config,
flags::FLAGS_logs};
+ std::vector<std::string> stream_descriptors;
+ stream_descriptors.push_back(flags::Stream0());
+ stream_descriptors.push_back(flags::Stream1());
+ std::vector<std::string> SL_descriptors;
+ SL_descriptors.push_back(flags::SL0());
+ SL_descriptors.push_back(flags::SL1());
+ VideoQualityTest::FillScalabilitySettings(
+ ¶ms, stream_descriptors, flags::SelectedStream(),
+ flags::NumSpatialLayers(), flags::SelectedSL(), SL_descriptors);
+
VideoQualityTest test;
- if (flags::OutputFilename().empty())
- test.RunWithVideoRenderer(params);
- else
+ if (flags::DurationSecs()) {
test.RunWithAnalyzer(params);
+ } else {
+ test.RunWithVideoRenderer(params);
+ }
}
} // namespace webrtc
diff --git a/webrtc/video/video_loopback.cc b/webrtc/video/video_loopback.cc
index 0c06f85..2338a84 100644
--- a/webrtc/video/video_loopback.cc
+++ b/webrtc/video/video_loopback.cc
@@ -20,6 +20,7 @@
namespace webrtc {
namespace flags {
+// Flags common with screenshare loopback, with different default values.
DEFINE_int32(width, 640, "Video width.");
size_t Width() {
return static_cast<size_t>(FLAGS_width);
@@ -55,11 +56,46 @@
return static_cast<int>(FLAGS_max_bitrate);
}
+DEFINE_int32(num_temporal_layers,
+ 1,
+ "Number of temporal layers. Set to 1-4 to override.");
+int NumTemporalLayers() {
+ return static_cast<int>(FLAGS_num_temporal_layers);
+}
+
+// Flags common with screenshare loopback, with equal default values.
DEFINE_string(codec, "VP8", "Video codec to use.");
std::string Codec() {
return static_cast<std::string>(FLAGS_codec);
}
+DEFINE_int32(selected_tl,
+ -1,
+ "Temporal layer to show or analyze. -1 to disable filtering.");
+int SelectedTL() {
+ return static_cast<int>(FLAGS_selected_tl);
+}
+
+DEFINE_int32(
+ duration,
+ 0,
+ "Duration of the test in seconds. If 0, rendered will be shown instead.");
+int DurationSecs() {
+ return static_cast<int>(FLAGS_duration);
+}
+
+DEFINE_string(output_filename, "", "Target graph data filename.");
+std::string OutputFilename() {
+ return static_cast<std::string>(FLAGS_output_filename);
+}
+
+DEFINE_string(graph_title,
+ "",
+ "If empty, title will be generated automatically.");
+std::string GraphTitle() {
+ return static_cast<std::string>(FLAGS_graph_title);
+}
+
DEFINE_int32(loss_percent, 0, "Percentage of packets randomly lost.");
int LossPercent() {
return static_cast<int>(FLAGS_loss_percent);
@@ -91,8 +127,55 @@
return static_cast<int>(FLAGS_std_propagation_delay_ms);
}
+DEFINE_int32(selected_stream, 0, "ID of the stream to show or analyze.");
+int SelectedStream() {
+ return static_cast<int>(FLAGS_selected_stream);
+}
+
+DEFINE_int32(num_spatial_layers, 1, "Number of spatial layers to use.");
+int NumSpatialLayers() {
+ return static_cast<int>(FLAGS_num_spatial_layers);
+}
+
+DEFINE_int32(selected_sl,
+ -1,
+ "Spatial layer to show or analyze. -1 to disable filtering.");
+int SelectedSL() {
+ return static_cast<int>(FLAGS_selected_sl);
+}
+
+DEFINE_string(stream0,
+ "",
+ "Comma separated values describing VideoStream for stream #0.");
+std::string Stream0() {
+ return static_cast<std::string>(FLAGS_stream0);
+}
+
+DEFINE_string(stream1,
+ "",
+ "Comma separated values describing VideoStream for stream #1.");
+std::string Stream1() {
+ return static_cast<std::string>(FLAGS_stream1);
+}
+
+DEFINE_string(sl0,
+ "",
+ "Comma separated values describing SpatialLayer for layer #0.");
+std::string SL0() {
+ return static_cast<std::string>(FLAGS_sl0);
+}
+
+DEFINE_string(sl1,
+ "",
+ "Comma separated values describing SpatialLayer for layer #1.");
+std::string SL1() {
+ return static_cast<std::string>(FLAGS_sl1);
+}
+
DEFINE_bool(logs, false, "print logs to stderr");
+DEFINE_bool(send_side_bwe, true, "Use send-side bandwidth estimation");
+
DEFINE_string(
force_fieldtrials,
"",
@@ -101,21 +184,7 @@
" will assign the group Enable to field trial WebRTC-FooFeature. Multiple "
"trials are separated by \"/\"");
-DEFINE_int32(num_temporal_layers,
- 1,
- "Number of temporal layers. Set to 1-4 to override.");
-size_t NumTemporalLayers() {
- return static_cast<size_t>(FLAGS_num_temporal_layers);
-}
-
-DEFINE_int32(
- tl_discard_threshold,
- 0,
- "Discard TLs with id greater or equal the threshold. 0 to disable.");
-size_t TLDiscardThreshold() {
- return static_cast<size_t>(FLAGS_tl_discard_threshold);
-}
-
+// Video-specific flags.
DEFINE_string(clip,
"",
"Name of the clip to show. If empty, using chroma generator.");
@@ -123,21 +192,6 @@
return static_cast<std::string>(FLAGS_clip);
}
-DEFINE_string(
- output_filename,
- "",
- "Name of a target graph data file. If set, no preview will be shown.");
-std::string OutputFilename() {
- return static_cast<std::string>(FLAGS_output_filename);
-}
-
-DEFINE_int32(duration, 60, "Duration of the test in seconds.");
-int DurationSecs() {
- return static_cast<int>(FLAGS_duration);
-}
-
-DEFINE_bool(send_side_bwe, true, "Use send-side bandwidth estimation");
-
} // namespace flags
void Loopback() {
@@ -153,27 +207,36 @@
call_bitrate_config.start_bitrate_bps = flags::StartBitrateKbps() * 1000;
call_bitrate_config.max_bitrate_bps = flags::MaxBitrateKbps() * 1000;
- std::string clip = flags::Clip();
- std::string graph_title = clip.empty() ? "" : "video " + clip;
VideoQualityTest::Params params{
{flags::Width(), flags::Height(), flags::Fps(),
flags::MinBitrateKbps() * 1000, flags::TargetBitrateKbps() * 1000,
flags::MaxBitrateKbps() * 1000, flags::Codec(),
- flags::NumTemporalLayers(),
+ flags::NumTemporalLayers(), flags::SelectedTL(),
0, // No min transmit bitrate.
- call_bitrate_config, flags::TLDiscardThreshold(),
- flags::FLAGS_send_side_bwe},
- {clip},
+ call_bitrate_config, flags::FLAGS_send_side_bwe},
+ {flags::Clip()},
{}, // Screenshare specific.
- {graph_title, 0.0, 0.0, flags::DurationSecs(), flags::OutputFilename()},
+ {"video", 0.0, 0.0, flags::DurationSecs(), flags::OutputFilename(),
+ flags::GraphTitle()},
pipe_config,
flags::FLAGS_logs};
+ std::vector<std::string> stream_descriptors;
+ stream_descriptors.push_back(flags::Stream0());
+ stream_descriptors.push_back(flags::Stream1());
+ std::vector<std::string> SL_descriptors;
+ SL_descriptors.push_back(flags::SL0());
+ SL_descriptors.push_back(flags::SL1());
+ VideoQualityTest::FillScalabilitySettings(
+ ¶ms, stream_descriptors, flags::SelectedStream(),
+ flags::NumSpatialLayers(), flags::SelectedSL(), SL_descriptors);
+
VideoQualityTest test;
- if (flags::OutputFilename().empty())
- test.RunWithVideoRenderer(params);
- else
+ if (flags::DurationSecs()) {
test.RunWithAnalyzer(params);
+ } else {
+ test.RunWithVideoRenderer(params);
+ }
}
} // namespace webrtc
diff --git a/webrtc/video/video_quality_test.cc b/webrtc/video/video_quality_test.cc
index 0f45fa6..333f00d 100644
--- a/webrtc/video/video_quality_test.cc
+++ b/webrtc/video/video_quality_test.cc
@@ -12,6 +12,7 @@
#include <algorithm>
#include <deque>
#include <map>
+#include <sstream>
#include <vector>
#include "testing/gtest/include/gtest/gtest.h"
@@ -21,7 +22,7 @@
#include "webrtc/base/scoped_ptr.h"
#include "webrtc/call.h"
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
-#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
+#include "webrtc/modules/rtp_rtcp/source/rtp_utility.h"
#include "webrtc/system_wrappers/include/cpu_info.h"
#include "webrtc/test/layer_filtering_transport.h"
#include "webrtc/test/run_loop.h"
@@ -43,18 +44,22 @@
public EncodedFrameObserver,
public EncodingTimeObserver {
public:
- VideoAnalyzer(Transport* transport,
+ VideoAnalyzer(test::LayerFilteringTransport* transport,
const std::string& test_label,
double avg_psnr_threshold,
double avg_ssim_threshold,
int duration_frames,
- FILE* graph_data_output_file)
+ FILE* graph_data_output_file,
+ const std::string& graph_title,
+ uint32_t ssrc_to_analyze)
: input_(nullptr),
transport_(transport),
receiver_(nullptr),
send_stream_(nullptr),
test_label_(test_label),
graph_data_output_file_(graph_data_output_file),
+ graph_title_(graph_title),
+ ssrc_to_analyze_(ssrc_to_analyze),
frames_to_process_(duration_frames),
frames_recorded_(0),
frames_processed_(0),
@@ -93,7 +98,6 @@
stats_polling_thread_ =
ThreadWrapper::CreateThread(&PollStatsThread, this, "StatsPoller");
- EXPECT_TRUE(stats_polling_thread_->Start());
}
~VideoAnalyzer() {
@@ -109,9 +113,9 @@
const uint8_t* packet,
size_t length,
const PacketTime& packet_time) override {
- rtc::scoped_ptr<RtpHeaderParser> parser(RtpHeaderParser::Create());
+ RtpUtility::RtpHeaderParser parser(packet, length);
RTPHeader header;
- parser->Parse(packet, length, &header);
+ parser.Parse(header);
{
rtc::CritScope lock(&crit_);
recv_times_[header.timestamp - rtp_timestamp_delta_] =
@@ -145,10 +149,13 @@
bool SendRtp(const uint8_t* packet,
size_t length,
const PacketOptions& options) override {
- rtc::scoped_ptr<RtpHeaderParser> parser(RtpHeaderParser::Create());
+ RtpUtility::RtpHeaderParser parser(packet, length);
RTPHeader header;
- parser->Parse(packet, length, &header);
+ parser.Parse(header);
+ int64_t current_time =
+ Clock::GetRealTimeClock()->CurrentNtpInMilliseconds();
+ bool result = transport_->SendRtp(packet, length, options);
{
rtc::CritScope lock(&crit_);
if (rtp_timestamp_delta_ == 0) {
@@ -156,13 +163,14 @@
first_send_frame_.Reset();
}
uint32_t timestamp = header.timestamp - rtp_timestamp_delta_;
- send_times_[timestamp] =
- Clock::GetRealTimeClock()->CurrentNtpInMilliseconds();
- encoded_frame_sizes_[timestamp] +=
- length - (header.headerLength + header.paddingLength);
+ send_times_[timestamp] = current_time;
+ if (!transport_->DiscardedLastPacket() &&
+ header.ssrc == ssrc_to_analyze_) {
+ encoded_frame_sizes_[timestamp] +=
+ length - (header.headerLength + header.paddingLength);
+ }
}
-
- return transport_->SendRtp(packet, length, options);
+ return result;
}
bool SendRtcp(const uint8_t* packet, size_t length) override {
@@ -192,6 +200,11 @@
VideoFrame reference_frame = frames_.front();
frames_.pop_front();
assert(!reference_frame.IsZeroSize());
+ if (send_timestamp == reference_frame.timestamp() - 1) {
+ // TODO(ivica): Make this work for > 2 streams.
+ // Look at rtp_sender.c:RTPSender::BuildRTPHeader.
+ ++send_timestamp;
+ }
EXPECT_EQ(reference_frame.timestamp(), send_timestamp);
assert(reference_frame.timestamp() == send_timestamp);
@@ -207,6 +220,8 @@
// at time-out check if frames_processed is going up. If so, give it more
// time, otherwise fail. Hopefully this will reduce test flakiness.
+ EXPECT_TRUE(stats_polling_thread_->Start());
+
int last_frames_processed = -1;
EventTypeWrapper eventType;
int iteration = 0;
@@ -245,7 +260,7 @@
}
VideoCaptureInput* input_;
- Transport* const transport_;
+ test::LayerFilteringTransport* const transport_;
PacketReceiver* receiver_;
VideoSendStream* send_stream_;
@@ -320,8 +335,13 @@
int64_t recv_time_ms = recv_times_[reference.timestamp()];
recv_times_.erase(reference.timestamp());
- size_t encoded_size = encoded_frame_sizes_[reference.timestamp()];
- encoded_frame_sizes_.erase(reference.timestamp());
+ // TODO(ivica): Make this work for > 2 streams.
+ auto it = encoded_frame_sizes_.find(reference.timestamp());
+ if (it == encoded_frame_sizes_.end())
+ it = encoded_frame_sizes_.find(reference.timestamp() - 1);
+ size_t encoded_size = it == encoded_frame_sizes_.end() ? 0 : it->second;
+ if (it != encoded_frame_sizes_.end())
+ encoded_frame_sizes_.erase(it);
VideoFrame reference_copy;
VideoFrame render_copy;
@@ -509,7 +529,7 @@
return A.input_time_ms < B.input_time_ms;
});
- fprintf(out, "%s\n", test_label_.c_str());
+ fprintf(out, "%s\n", graph_title_.c_str());
fprintf(out, "%" PRIuS "\n", samples_.size());
fprintf(out,
"dropped "
@@ -547,6 +567,8 @@
const std::string test_label_;
FILE* const graph_data_output_file_;
+ const std::string graph_title_;
+ const uint32_t ssrc_to_analyze_;
std::vector<Sample> samples_ GUARDED_BY(comparison_lock_);
std::map<int64_t, int> samples_encode_time_ms_ GUARDED_BY(comparison_lock_);
test::Statistics sender_time_ GUARDED_BY(comparison_lock_);
@@ -588,28 +610,188 @@
VideoQualityTest::VideoQualityTest() : clock_(Clock::GetRealTimeClock()) {}
-void VideoQualityTest::ValidateParams(const Params& params) {
- RTC_CHECK_GE(params.common.max_bitrate_bps, params.common.target_bitrate_bps);
- RTC_CHECK_GE(params.common.target_bitrate_bps, params.common.min_bitrate_bps);
- RTC_CHECK_LT(params.common.tl_discard_threshold,
- params.common.num_temporal_layers);
-}
-
void VideoQualityTest::TestBody() {}
-void VideoQualityTest::SetupFullStack(const Params& params,
- Transport* send_transport,
- Transport* recv_transport) {
- if (params.logs)
+std::string VideoQualityTest::GenerateGraphTitle() const {
+ std::stringstream ss;
+ ss << params_.common.codec;
+ ss << " (" << params_.common.target_bitrate_bps / 1000 << "kbps";
+ ss << ", " << params_.common.fps << " FPS";
+ if (params_.screenshare.scroll_duration)
+ ss << ", " << params_.screenshare.scroll_duration << "s scroll";
+ if (params_.ss.streams.size() > 1)
+ ss << ", Stream #" << params_.ss.selected_stream;
+ if (params_.ss.num_spatial_layers > 1)
+ ss << ", Layer #" << params_.ss.selected_sl;
+ ss << ")";
+ return ss.str();
+}
+
+void VideoQualityTest::CheckParams() {
+ // Add a default stream in none specified.
+ if (params_.ss.streams.empty())
+ params_.ss.streams.push_back(VideoQualityTest::DefaultVideoStream(params_));
+ if (params_.ss.num_spatial_layers == 0)
+ params_.ss.num_spatial_layers = 1;
+
+ if (params_.pipe.loss_percent != 0 ||
+ params_.pipe.queue_length_packets != 0) {
+ // Since LayerFilteringTransport changes the sequence numbers, we can't
+ // use that feature with pack loss, since the NACK request would end up
+ // retransmitting the wrong packets.
+ RTC_CHECK(params_.ss.selected_sl == -1 ||
+ params_.ss.num_spatial_layers == 1);
+ RTC_CHECK(params_.common.selected_tl == -1 ||
+ params_.common.num_temporal_layers == 1);
+ }
+
+ // TODO(ivica): Should max_bitrate_bps == -1 represent inf max bitrate, as it
+ // does in some parts of the code?
+ RTC_CHECK_GE(params_.common.max_bitrate_bps,
+ params_.common.target_bitrate_bps);
+ RTC_CHECK_GE(params_.common.target_bitrate_bps,
+ params_.common.min_bitrate_bps);
+ RTC_CHECK_LT(params_.common.selected_tl, params_.common.num_temporal_layers);
+ RTC_CHECK_LT(params_.ss.selected_stream, params_.ss.streams.size());
+ for (const VideoStream& stream : params_.ss.streams) {
+ RTC_CHECK_GE(stream.min_bitrate_bps, 0);
+ RTC_CHECK_GE(stream.target_bitrate_bps, stream.min_bitrate_bps);
+ RTC_CHECK_GE(stream.max_bitrate_bps, stream.target_bitrate_bps);
+ RTC_CHECK_EQ(static_cast<int>(stream.temporal_layer_thresholds_bps.size()),
+ params_.common.num_temporal_layers - 1);
+ }
+ // TODO(ivica): Should we check if the sum of all streams/layers is equal to
+ // the total bitrate? We anyway have to update them in the case bitrate
+ // estimator changes the total bitrates.
+ RTC_CHECK_GE(params_.ss.num_spatial_layers, 1);
+ RTC_CHECK_LE(params_.ss.selected_sl, params_.ss.num_spatial_layers);
+ RTC_CHECK(params_.ss.spatial_layers.empty() ||
+ params_.ss.spatial_layers.size() ==
+ static_cast<size_t>(params_.ss.num_spatial_layers));
+ if (params_.common.codec == "VP8") {
+ RTC_CHECK_EQ(params_.ss.num_spatial_layers, 1);
+ } else if (params_.common.codec == "VP9") {
+ RTC_CHECK_EQ(params_.ss.streams.size(), 1u);
+ }
+}
+
+// Static.
+std::vector<int> VideoQualityTest::ParseCSV(const std::string& str) {
+ // Parse comma separated nonnegative integers, where some elements may be
+ // empty. The empty values are replaced with -1.
+ // E.g. "10,-20,,30,40" --> {10, 20, -1, 30,40}
+ // E.g. ",,10,,20," --> {-1, -1, 10, -1, 20, -1}
+ std::vector<int> result;
+ if (str.empty())
+ return result;
+
+ const char* p = str.c_str();
+ int value = -1;
+ int pos;
+ while (*p) {
+ if (*p == ',') {
+ result.push_back(value);
+ value = -1;
+ ++p;
+ continue;
+ }
+ RTC_CHECK_EQ(sscanf(p, "%d%n", &value, &pos), 1)
+ << "Unexpected non-number value.";
+ p += pos;
+ }
+ result.push_back(value);
+ return result;
+}
+
+// Static.
+VideoStream VideoQualityTest::DefaultVideoStream(const Params& params) {
+ VideoStream stream;
+ stream.width = params.common.width;
+ stream.height = params.common.height;
+ stream.max_framerate = params.common.fps;
+ stream.min_bitrate_bps = params.common.min_bitrate_bps;
+ stream.target_bitrate_bps = params.common.target_bitrate_bps;
+ stream.max_bitrate_bps = params.common.max_bitrate_bps;
+ stream.max_qp = 52;
+ if (params.common.num_temporal_layers == 2)
+ stream.temporal_layer_thresholds_bps.push_back(stream.target_bitrate_bps);
+ return stream;
+}
+
+// Static.
+void VideoQualityTest::FillScalabilitySettings(
+ Params* params,
+ const std::vector<std::string>& stream_descriptors,
+ size_t selected_stream,
+ int num_spatial_layers,
+ int selected_sl,
+ const std::vector<std::string>& sl_descriptors) {
+ // Read VideoStream and SpatialLayer elements from a list of comma separated
+ // lists. To use a default value for an element, use -1 or leave empty.
+ // Validity checks performed in CheckParams.
+
+ RTC_CHECK(params->ss.streams.empty());
+ for (auto descriptor : stream_descriptors) {
+ if (descriptor.empty())
+ continue;
+ VideoStream stream = VideoQualityTest::DefaultVideoStream(*params);
+ std::vector<int> v = VideoQualityTest::ParseCSV(descriptor);
+ if (v[0] != -1)
+ stream.width = static_cast<size_t>(v[0]);
+ if (v[1] != -1)
+ stream.height = static_cast<size_t>(v[1]);
+ if (v[2] != -1)
+ stream.max_framerate = v[2];
+ if (v[3] != -1)
+ stream.min_bitrate_bps = v[3];
+ if (v[4] != -1)
+ stream.target_bitrate_bps = v[4];
+ if (v[5] != -1)
+ stream.max_bitrate_bps = v[5];
+ if (v.size() > 6 && v[6] != -1)
+ stream.max_qp = v[6];
+ if (v.size() > 7) {
+ stream.temporal_layer_thresholds_bps.clear();
+ stream.temporal_layer_thresholds_bps.insert(
+ stream.temporal_layer_thresholds_bps.end(), v.begin() + 7, v.end());
+ } else {
+ // Automatic TL thresholds for more than two layers not supported.
+ RTC_CHECK_LE(params->common.num_temporal_layers, 2);
+ }
+ params->ss.streams.push_back(stream);
+ }
+ params->ss.selected_stream = selected_stream;
+
+ params->ss.num_spatial_layers = num_spatial_layers ? num_spatial_layers : 1;
+ params->ss.selected_sl = selected_sl;
+ RTC_CHECK(params->ss.spatial_layers.empty());
+ for (auto descriptor : sl_descriptors) {
+ if (descriptor.empty())
+ continue;
+ std::vector<int> v = VideoQualityTest::ParseCSV(descriptor);
+ RTC_CHECK_GT(v[2], 0);
+
+ SpatialLayer layer;
+ layer.scaling_factor_num = v[0] == -1 ? 1 : v[0];
+ layer.scaling_factor_den = v[1] == -1 ? 1 : v[1];
+ layer.target_bitrate_bps = v[2];
+ params->ss.spatial_layers.push_back(layer);
+ }
+}
+
+void VideoQualityTest::SetupCommon(Transport* send_transport,
+ Transport* recv_transport) {
+ if (params_.logs)
trace_to_stderr_.reset(new test::TraceToStderr);
- CreateSendConfig(1, send_transport);
+ size_t num_streams = params_.ss.streams.size();
+ CreateSendConfig(num_streams, send_transport);
int payload_type;
- if (params.common.codec == "VP8") {
+ if (params_.common.codec == "VP8") {
encoder_.reset(VideoEncoder::Create(VideoEncoder::kVp8));
payload_type = kPayloadTypeVP8;
- } else if (params.common.codec == "VP9") {
+ } else if (params_.common.codec == "VP9") {
encoder_.reset(VideoEncoder::Create(VideoEncoder::kVp9));
payload_type = kPayloadTypeVP9;
} else {
@@ -617,15 +799,15 @@
return;
}
send_config_.encoder_settings.encoder = encoder_.get();
- send_config_.encoder_settings.payload_name = params.common.codec;
+ send_config_.encoder_settings.payload_name = params_.common.codec;
send_config_.encoder_settings.payload_type = payload_type;
-
send_config_.rtp.nack.rtp_history_ms = kNackRtpHistoryMs;
- send_config_.rtp.rtx.ssrcs.push_back(kSendRtxSsrcs[0]);
send_config_.rtp.rtx.payload_type = kSendRtxPayloadType;
+ for (size_t i = 0; i < num_streams; ++i)
+ send_config_.rtp.rtx.ssrcs.push_back(kSendRtxSsrcs[i]);
send_config_.rtp.extensions.clear();
- if (params.common.send_side_bwe) {
+ if (params_.common.send_side_bwe) {
send_config_.rtp.extensions.push_back(
RtpExtension(RtpExtension::kTransportSequenceNumber,
test::kTransportSequenceNumberExtensionId));
@@ -634,49 +816,41 @@
RtpExtension::kAbsSendTime, test::kAbsSendTimeExtensionId));
}
- // Automatically fill out streams[0] with params.
- VideoStream* stream = &encoder_config_.streams[0];
- stream->width = params.common.width;
- stream->height = params.common.height;
- stream->min_bitrate_bps = params.common.min_bitrate_bps;
- stream->target_bitrate_bps = params.common.target_bitrate_bps;
- stream->max_bitrate_bps = params.common.max_bitrate_bps;
- stream->max_framerate = static_cast<int>(params.common.fps);
-
- stream->temporal_layer_thresholds_bps.clear();
- if (params.common.num_temporal_layers > 1) {
- stream->temporal_layer_thresholds_bps.push_back(stream->target_bitrate_bps);
- }
+ encoder_config_.min_transmit_bitrate_bps = params_.common.min_transmit_bps;
+ encoder_config_.streams = params_.ss.streams;
+ encoder_config_.spatial_layers = params_.ss.spatial_layers;
CreateMatchingReceiveConfigs(recv_transport);
- receive_configs_[0].rtp.nack.rtp_history_ms = kNackRtpHistoryMs;
- receive_configs_[0].rtp.rtx[kSendRtxPayloadType].ssrc = kSendRtxSsrcs[0];
- receive_configs_[0].rtp.rtx[kSendRtxPayloadType].payload_type =
- kSendRtxPayloadType;
-
- encoder_config_.min_transmit_bitrate_bps = params.common.min_transmit_bps;
+ for (size_t i = 0; i < num_streams; ++i) {
+ receive_configs_[i].rtp.nack.rtp_history_ms = kNackRtpHistoryMs;
+ receive_configs_[i].rtp.rtx[kSendRtxPayloadType].ssrc = kSendRtxSsrcs[i];
+ receive_configs_[i].rtp.rtx[kSendRtxPayloadType].payload_type =
+ kSendRtxPayloadType;
+ }
}
-void VideoQualityTest::SetupScreenshare(const Params& params) {
- RTC_CHECK(params.screenshare.enabled);
+void VideoQualityTest::SetupScreenshare() {
+ RTC_CHECK(params_.screenshare.enabled);
// Fill out codec settings.
encoder_config_.content_type = VideoEncoderConfig::ContentType::kScreen;
- if (params.common.codec == "VP8") {
+ if (params_.common.codec == "VP8") {
codec_settings_.VP8 = VideoEncoder::GetDefaultVp8Settings();
codec_settings_.VP8.denoisingOn = false;
codec_settings_.VP8.frameDroppingOn = false;
codec_settings_.VP8.numberOfTemporalLayers =
- static_cast<unsigned char>(params.common.num_temporal_layers);
+ static_cast<unsigned char>(params_.common.num_temporal_layers);
encoder_config_.encoder_specific_settings = &codec_settings_.VP8;
- } else if (params.common.codec == "VP9") {
+ } else if (params_.common.codec == "VP9") {
codec_settings_.VP9 = VideoEncoder::GetDefaultVp9Settings();
codec_settings_.VP9.denoisingOn = false;
codec_settings_.VP9.frameDroppingOn = false;
codec_settings_.VP9.numberOfTemporalLayers =
- static_cast<unsigned char>(params.common.num_temporal_layers);
+ static_cast<unsigned char>(params_.common.num_temporal_layers);
encoder_config_.encoder_specific_settings = &codec_settings_.VP9;
+ codec_settings_.VP9.numberOfSpatialLayers =
+ static_cast<unsigned char>(params_.ss.num_spatial_layers);
}
// Setup frame generator.
@@ -688,71 +862,67 @@
slides.push_back(test::ResourcePath("photo_1850_1110", "yuv"));
slides.push_back(test::ResourcePath("difficult_photo_1850_1110", "yuv"));
- if (params.screenshare.scroll_duration == 0) {
+ if (params_.screenshare.scroll_duration == 0) {
// Cycle image every slide_change_interval seconds.
frame_generator_.reset(test::FrameGenerator::CreateFromYuvFile(
slides, kWidth, kHeight,
- params.screenshare.slide_change_interval * params.common.fps));
+ params_.screenshare.slide_change_interval * params_.common.fps));
} else {
- RTC_CHECK_LE(params.common.width, kWidth);
- RTC_CHECK_LE(params.common.height, kHeight);
- RTC_CHECK_GT(params.screenshare.slide_change_interval, 0);
- const int kPauseDurationMs = (params.screenshare.slide_change_interval -
- params.screenshare.scroll_duration) * 1000;
- RTC_CHECK_LE(params.screenshare.scroll_duration,
- params.screenshare.slide_change_interval);
+ RTC_CHECK_LE(params_.common.width, kWidth);
+ RTC_CHECK_LE(params_.common.height, kHeight);
+ RTC_CHECK_GT(params_.screenshare.slide_change_interval, 0);
+ const int kPauseDurationMs = (params_.screenshare.slide_change_interval -
+ params_.screenshare.scroll_duration) *
+ 1000;
+ RTC_CHECK_LE(params_.screenshare.scroll_duration,
+ params_.screenshare.slide_change_interval);
- if (params.screenshare.scroll_duration) {
- frame_generator_.reset(
- test::FrameGenerator::CreateScrollingInputFromYuvFiles(
- clock_, slides, kWidth, kHeight, params.common.width,
- params.common.height, params.screenshare.scroll_duration * 1000,
- kPauseDurationMs));
- } else {
- frame_generator_.reset(test::FrameGenerator::CreateFromYuvFile(
- slides, kWidth, kHeight,
- params.screenshare.slide_change_interval * params.common.fps));
- }
+ frame_generator_.reset(
+ test::FrameGenerator::CreateScrollingInputFromYuvFiles(
+ clock_, slides, kWidth, kHeight, params_.common.width,
+ params_.common.height, params_.screenshare.scroll_duration * 1000,
+ kPauseDurationMs));
}
}
-void VideoQualityTest::CreateCapturer(const Params& params,
- VideoCaptureInput* input) {
- if (params.screenshare.enabled) {
- test::FrameGeneratorCapturer *frame_generator_capturer =
+void VideoQualityTest::CreateCapturer(VideoCaptureInput* input) {
+ if (params_.screenshare.enabled) {
+ test::FrameGeneratorCapturer* frame_generator_capturer =
new test::FrameGeneratorCapturer(
- clock_, input, frame_generator_.release(), params.common.fps);
+ clock_, input, frame_generator_.release(), params_.common.fps);
EXPECT_TRUE(frame_generator_capturer->Init());
capturer_.reset(frame_generator_capturer);
} else {
- if (params.video.clip_name.empty()) {
- capturer_.reset(test::VideoCapturer::Create(
- input, params.common.width, params.common.height, params.common.fps,
- clock_));
+ if (params_.video.clip_name.empty()) {
+ capturer_.reset(test::VideoCapturer::Create(input, params_.common.width,
+ params_.common.height,
+ params_.common.fps, clock_));
} else {
capturer_.reset(test::FrameGeneratorCapturer::CreateFromYuvFile(
- input, test::ResourcePath(params.video.clip_name, "yuv"),
- params.common.width, params.common.height, params.common.fps,
+ input, test::ResourcePath(params_.video.clip_name, "yuv"),
+ params_.common.width, params_.common.height, params_.common.fps,
clock_));
ASSERT_TRUE(capturer_.get() != nullptr)
- << "Could not create capturer for " << params.video.clip_name
+ << "Could not create capturer for " << params_.video.clip_name
<< ".yuv. Is this resource file present?";
}
}
}
void VideoQualityTest::RunWithAnalyzer(const Params& params) {
+ params_ = params;
+
// TODO(ivica): Merge with RunWithRenderer and use a flag / argument to
// differentiate between the analyzer and the renderer case.
- ValidateParams(params);
+ CheckParams();
FILE* graph_data_output_file = nullptr;
- if (!params.analyzer.graph_data_output_filename.empty()) {
+ if (!params_.analyzer.graph_data_output_filename.empty()) {
graph_data_output_file =
- fopen(params.analyzer.graph_data_output_filename.c_str(), "w");
+ fopen(params_.analyzer.graph_data_output_filename.c_str(), "w");
RTC_CHECK(graph_data_output_file != nullptr)
- << "Can't open the file "
- << params.analyzer.graph_data_output_filename << "!";
+ << "Can't open the file " << params_.analyzer.graph_data_output_filename
+ << "!";
}
Call::Config call_config;
@@ -761,33 +931,60 @@
test::LayerFilteringTransport send_transport(
params.pipe, sender_call_.get(), kPayloadTypeVP8, kPayloadTypeVP9,
- static_cast<uint8_t>(params.common.tl_discard_threshold), 0);
+ params.common.selected_tl, params_.ss.selected_sl);
test::DirectTransport recv_transport(params.pipe, receiver_call_.get());
+ std::string graph_title = params_.analyzer.graph_title;
+ if (graph_title.empty())
+ graph_title = VideoQualityTest::GenerateGraphTitle();
+
+ // In the case of different resolutions, the functions calculating PSNR and
+ // SSIM return -1.0, instead of a positive value as usual. VideoAnalyzer
+ // aborts if the average psnr/ssim are below the given threshold, which is
+ // 0.0 by default. Setting the thresholds to -1.1 prevents the unnecessary
+ // abort.
+ VideoStream& selected_stream = params_.ss.streams[params_.ss.selected_stream];
+ int selected_sl = params_.ss.selected_sl != -1
+ ? params_.ss.selected_sl
+ : params_.ss.num_spatial_layers - 1;
+ bool disable_quality_check =
+ selected_stream.width != params_.common.width ||
+ selected_stream.height != params_.common.height ||
+ (!params_.ss.spatial_layers.empty() &&
+ params_.ss.spatial_layers[selected_sl].scaling_factor_num !=
+ params_.ss.spatial_layers[selected_sl].scaling_factor_den);
+ if (disable_quality_check) {
+ fprintf(stderr,
+ "Warning: Calculating PSNR and SSIM for downsized resolution "
+ "not implemented yet! Skipping PSNR and SSIM calculations!");
+ }
+
VideoAnalyzer analyzer(
- &send_transport, params.analyzer.test_label,
- params.analyzer.avg_psnr_threshold, params.analyzer.avg_ssim_threshold,
- params.analyzer.test_durations_secs * params.common.fps,
- graph_data_output_file);
+ &send_transport, params_.analyzer.test_label,
+ disable_quality_check ? -1.1 : params_.analyzer.avg_psnr_threshold,
+ disable_quality_check ? -1.1 : params_.analyzer.avg_ssim_threshold,
+ params_.analyzer.test_durations_secs * params_.common.fps,
+ graph_data_output_file, graph_title,
+ kSendSsrcs[params_.ss.selected_stream]);
analyzer.SetReceiver(receiver_call_->Receiver());
send_transport.SetReceiver(&analyzer);
recv_transport.SetReceiver(sender_call_->Receiver());
- SetupFullStack(params, &analyzer, &recv_transport);
+ SetupCommon(&analyzer, &recv_transport);
send_config_.encoding_time_observer = &analyzer;
- receive_configs_[0].renderer = &analyzer;
+ receive_configs_[params_.ss.selected_stream].renderer = &analyzer;
for (auto& config : receive_configs_)
config.pre_decode_callback = &analyzer;
- if (params.screenshare.enabled)
- SetupScreenshare(params);
+ if (params_.screenshare.enabled)
+ SetupScreenshare();
CreateStreams();
analyzer.input_ = send_stream_->Input();
analyzer.send_stream_ = send_stream_;
- CreateCapturer(params, &analyzer);
+ CreateCapturer(&analyzer);
send_stream_->Start();
for (size_t i = 0; i < receive_streams_.size(); ++i)
@@ -811,40 +1008,49 @@
}
void VideoQualityTest::RunWithVideoRenderer(const Params& params) {
- ValidateParams(params);
+ params_ = params;
+ CheckParams();
rtc::scoped_ptr<test::VideoRenderer> local_preview(
- test::VideoRenderer::Create("Local Preview", params.common.width,
- params.common.height));
+ test::VideoRenderer::Create("Local Preview", params_.common.width,
+ params_.common.height));
+ size_t stream_id = params_.ss.selected_stream;
+ char title[32];
+ if (params_.ss.streams.size() == 1) {
+ sprintf(title, "Loopback Video");
+ } else {
+ sprintf(title, "Loopback Video - Stream #%" PRIuS, stream_id);
+ }
rtc::scoped_ptr<test::VideoRenderer> loopback_video(
- test::VideoRenderer::Create("Loopback Video", params.common.width,
- params.common.height));
+ test::VideoRenderer::Create(title, params_.ss.streams[stream_id].width,
+ params_.ss.streams[stream_id].height));
// TODO(ivica): Remove bitrate_config and use the default Call::Config(), to
// match the full stack tests.
Call::Config call_config;
- call_config.bitrate_config = params.common.call_bitrate_config;
+ call_config.bitrate_config = params_.common.call_bitrate_config;
rtc::scoped_ptr<Call> call(Call::Create(call_config));
test::LayerFilteringTransport transport(
params.pipe, call.get(), kPayloadTypeVP8, kPayloadTypeVP9,
- static_cast<uint8_t>(params.common.tl_discard_threshold), 0);
+ params.common.selected_tl, params_.ss.selected_sl);
// TODO(ivica): Use two calls to be able to merge with RunWithAnalyzer or at
// least share as much code as possible. That way this test would also match
// the full stack tests better.
transport.SetReceiver(call->Receiver());
- SetupFullStack(params, &transport, &transport);
- send_config_.local_renderer = local_preview.get();
- receive_configs_[0].renderer = loopback_video.get();
+ SetupCommon(&transport, &transport);
- if (params.screenshare.enabled)
- SetupScreenshare(params);
+ send_config_.local_renderer = local_preview.get();
+ receive_configs_[stream_id].renderer = loopback_video.get();
+
+ if (params_.screenshare.enabled)
+ SetupScreenshare();
send_stream_ = call->CreateVideoSendStream(send_config_, encoder_config_);
VideoReceiveStream* receive_stream =
- call->CreateVideoReceiveStream(receive_configs_[0]);
- CreateCapturer(params, send_stream_->Input());
+ call->CreateVideoReceiveStream(receive_configs_[stream_id]);
+ CreateCapturer(send_stream_->Input());
receive_stream->Start();
send_stream_->Start();
diff --git a/webrtc/video/video_quality_test.h b/webrtc/video/video_quality_test.h
index 7b62fb3..b88c513 100644
--- a/webrtc/video/video_quality_test.h
+++ b/webrtc/video/video_quality_test.h
@@ -33,11 +33,11 @@
int target_bitrate_bps;
int max_bitrate_bps;
std::string codec;
- size_t num_temporal_layers;
+ int num_temporal_layers;
+ int selected_tl;
int min_transmit_bps;
Call::Config::BitrateConfig call_bitrate_config;
- size_t tl_discard_threshold;
bool send_side_bwe;
} common;
struct { // Video-specific settings.
@@ -50,30 +50,56 @@
} screenshare;
struct { // Analyzer settings.
std::string test_label;
- double avg_psnr_threshold;
- double avg_ssim_threshold;
+ double avg_psnr_threshold; // (*)
+ double avg_ssim_threshold; // (*)
int test_durations_secs;
std::string graph_data_output_filename;
+ std::string graph_title;
} analyzer;
FakeNetworkPipe::Config pipe;
bool logs;
+ struct { // Spatial scalability.
+ std::vector<VideoStream> streams; // If empty, one stream is assumed.
+ size_t selected_stream;
+ int num_spatial_layers;
+ int selected_sl;
+ // If empty, bitrates are generated in VP9Impl automatically.
+ std::vector<SpatialLayer> spatial_layers;
+ } ss;
};
+ // (*) Set to -1.1 if generating graph data for simulcast or SVC and the
+ // selected stream/layer doesn't have the same resolution as the largest
+ // stream/layer (to ignore the PSNR and SSIM calculation errors).
VideoQualityTest();
void RunWithAnalyzer(const Params& params);
void RunWithVideoRenderer(const Params& params);
+ static void FillScalabilitySettings(
+ Params* params,
+ const std::vector<std::string>& stream_descriptors,
+ size_t selected_stream,
+ int num_spatial_layers,
+ int selected_sl,
+ const std::vector<std::string>& sl_descriptors);
+
protected:
// No-op implementation to be able to instantiate this class from non-TEST_F
// locations.
void TestBody() override;
- void CreateCapturer(const Params& params, VideoCaptureInput* input);
- void ValidateParams(const Params& params);
- void SetupFullStack(const Params& params,
- Transport* send_transport,
- Transport* recv_transport);
- void SetupScreenshare(const Params& params);
+ // Helper methods accessing only params_.
+ std::string GenerateGraphTitle() const;
+ void CheckParams();
+
+ // Helper static methods.
+ static VideoStream DefaultVideoStream(const Params& params);
+ static std::vector<int> ParseCSV(const std::string& str);
+
+ // Helper methods for setting up the call.
+ void CreateCapturer(VideoCaptureInput* input);
+ void SetupCommon(Transport* send_transport, Transport* recv_transport);
+ void SetupScreenshare();
// We need a more general capturer than the FrameGeneratorCapturer.
rtc::scoped_ptr<test::VideoCapturer> capturer_;
@@ -82,6 +108,8 @@
rtc::scoped_ptr<VideoEncoder> encoder_;
VideoCodecUnion codec_settings_;
Clock* const clock_;
+
+ Params params_;
};
} // namespace webrtc
diff --git a/webrtc/video/video_send_stream.cc b/webrtc/video/video_send_stream.cc
index 4ec923f..67e8a9c 100644
--- a/webrtc/video/video_send_stream.cc
+++ b/webrtc/video/video_send_stream.cc
@@ -370,6 +370,16 @@
static_cast<unsigned char>(streams.size());
video_codec.minBitrate = streams[0].min_bitrate_bps / 1000;
RTC_DCHECK_LE(streams.size(), static_cast<size_t>(kMaxSimulcastStreams));
+ if (video_codec.codecType == kVideoCodecVP9) {
+ // If the vector is empty, bitrates will be configured automatically.
+ RTC_DCHECK(config.spatial_layers.empty() ||
+ config.spatial_layers.size() ==
+ video_codec.codecSpecific.VP9.numberOfSpatialLayers);
+ RTC_DCHECK_LE(video_codec.codecSpecific.VP9.numberOfSpatialLayers,
+ kMaxSimulcastStreams);
+ for (size_t i = 0; i < config.spatial_layers.size(); ++i)
+ video_codec.spatialLayers[i] = config.spatial_layers[i];
+ }
for (size_t i = 0; i < streams.size(); ++i) {
SimulcastStream* sim_stream = &video_codec.simulcastStream[i];
RTC_DCHECK_GT(streams[i].width, 0u);