Revert of Adding support for simulcast and spatial layers into VideoQualityTest (patchset #10 id:180001 of https://codereview.webrtc.org/1353263005/ )

Reason for revert:
Temporarily reverting as this causes some issues with perf tests. Especially tests with packet loss no longer works.

Original issue's description:
> Adding support for simulcast and spatial layers into VideoQualityTest
>
> The CL includes several changes:
> - Adding flags describing the streams and spatial layers.
> - Reorganizing the order of the flags, to make them easier to maintain.
> - Adding a member .params_ to VideoQualityAnalyzer.
>     (instead of passing it to every member function manually)
> - Updating VideoAnalyzer to support simulcast.
>     (select appropriate ssrc and fix timestamps which are sometimes increased by 1)
> - VP9EncoderImpl already had code for automatic calculation of bitrate for each layer.
>     Changing to first read bitrates and resolution ratios from the flags, if specified.
>     If not specified, reverting to the old code are setting the values automatically.
> - Changing the parameters in LayerFilteringTransport, replacing
>     xx_discard_thresholds with selected_xx, to make it easier to use for the end user.
>
> Committed: https://crrev.com/87f83a9a27d657731ccb54025bc04ccad0da136e
> Cr-Commit-Position: refs/heads/master@{#10215}

TBR=pbos@webrtc.org,mflodman@webrtc.org,ivica@webrtc.org
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true

Review URL: https://codereview.webrtc.org/1397363002

Cr-Commit-Position: refs/heads/master@{#10252}
This commit is contained in:
sprang
2015-10-12 06:33:21 -07:00
committed by Commit bot
parent d940880e7a
commit 7a975f75e7
12 changed files with 280 additions and 696 deletions

View File

@ -548,7 +548,6 @@ enum RawVideoType
enum { kConfigParameterSize = 128}; enum { kConfigParameterSize = 128};
enum { kPayloadNameSize = 32}; enum { kPayloadNameSize = 32};
enum { kMaxSimulcastStreams = 4}; enum { kMaxSimulcastStreams = 4};
enum { kMaxSpatialLayers = 5};
enum { kMaxTemporalStreams = 4}; enum { kMaxTemporalStreams = 4};
enum VideoCodecComplexity enum VideoCodecComplexity
@ -678,13 +677,6 @@ struct SimulcastStream {
} }
}; };
struct SpatialLayer {
int scaling_factor_num;
int scaling_factor_den;
int target_bitrate_bps;
// TODO(ivica): Add max_quantizer and min_quantizer?
};
enum VideoCodecMode { enum VideoCodecMode {
kRealtimeVideo, kRealtimeVideo,
kScreensharing kScreensharing
@ -711,7 +703,6 @@ struct VideoCodec {
unsigned int qpMax; unsigned int qpMax;
unsigned char numberOfSimulcastStreams; unsigned char numberOfSimulcastStreams;
SimulcastStream simulcastStream[kMaxSimulcastStreams]; SimulcastStream simulcastStream[kMaxSimulcastStreams];
SpatialLayer spatialLayers[kMaxSpatialLayers];
VideoCodecMode mode; VideoCodecMode mode;

View File

@ -104,7 +104,6 @@ struct VideoEncoderConfig {
std::string ToString() const; std::string ToString() const;
std::vector<VideoStream> streams; std::vector<VideoStream> streams;
std::vector<SpatialLayer> spatial_layers;
ContentType content_type; ContentType content_type;
void* encoder_specific_settings; void* encoder_specific_settings;

View File

@ -112,72 +112,42 @@ int VP9EncoderImpl::Release() {
return WEBRTC_VIDEO_CODEC_OK; return WEBRTC_VIDEO_CODEC_OK;
} }
bool VP9EncoderImpl::ExplicitlyConfiguredSpatialLayers() const {
// We check target_bitrate_bps of the 0th layer to see if the spatial layers
// (i.e. bitrates) were explicitly configured.
return num_spatial_layers_ > 1 &&
codec_.spatialLayers[0].target_bitrate_bps > 0;
}
bool VP9EncoderImpl::SetSvcRates() { bool VP9EncoderImpl::SetSvcRates() {
float rate_ratio[VPX_MAX_LAYERS] = {0};
float total = 0;
uint8_t i = 0; uint8_t i = 0;
if (ExplicitlyConfiguredSpatialLayers()) { for (i = 0; i < num_spatial_layers_; ++i) {
if (num_temporal_layers_ > 1) { if (svc_internal_.svc_params.scaling_factor_num[i] <= 0 ||
LOG(LS_ERROR) << "Multiple temporal layers when manually specifying " svc_internal_.svc_params.scaling_factor_den[i] <= 0) {
"spatial layers not implemented yet!";
return false; return false;
} }
int total_bitrate_bps = 0; rate_ratio[i] = static_cast<float>(
for (i = 0; i < num_spatial_layers_; ++i) svc_internal_.svc_params.scaling_factor_num[i]) /
total_bitrate_bps += codec_.spatialLayers[i].target_bitrate_bps; svc_internal_.svc_params.scaling_factor_den[i];
// If total bitrate differs now from what has been specified at the total += rate_ratio[i];
// beginning, update the bitrates in the same ratio as before. }
for (i = 0; i < num_spatial_layers_; ++i) {
config_->ss_target_bitrate[i] =
config_->layer_target_bitrate[i] = static_cast<int>(
static_cast<int64_t>(config_->rc_target_bitrate) *
codec_.spatialLayers[i].target_bitrate_bps / total_bitrate_bps);
}
} else {
float rate_ratio[VPX_MAX_LAYERS] = {0};
float total = 0;
for (i = 0; i < num_spatial_layers_; ++i) { for (i = 0; i < num_spatial_layers_; ++i) {
if (svc_internal_.svc_params.scaling_factor_num[i] <= 0 || config_->ss_target_bitrate[i] = static_cast<unsigned int>(
svc_internal_.svc_params.scaling_factor_den[i] <= 0) { config_->rc_target_bitrate * rate_ratio[i] / total);
LOG(LS_ERROR) << "Scaling factors not specified!"; if (num_temporal_layers_ == 1) {
return false; config_->layer_target_bitrate[i] = config_->ss_target_bitrate[i];
} } else if (num_temporal_layers_ == 2) {
rate_ratio[i] = static_cast<float>( config_->layer_target_bitrate[i * num_temporal_layers_] =
svc_internal_.svc_params.scaling_factor_num[i]) / config_->ss_target_bitrate[i] * 2 / 3;
svc_internal_.svc_params.scaling_factor_den[i]; config_->layer_target_bitrate[i * num_temporal_layers_ + 1] =
total += rate_ratio[i]; config_->ss_target_bitrate[i];
} } else if (num_temporal_layers_ == 3) {
config_->layer_target_bitrate[i * num_temporal_layers_] =
for (i = 0; i < num_spatial_layers_; ++i) { config_->ss_target_bitrate[i] / 2;
config_->ss_target_bitrate[i] = static_cast<unsigned int>( config_->layer_target_bitrate[i * num_temporal_layers_ + 1] =
config_->rc_target_bitrate * rate_ratio[i] / total); config_->layer_target_bitrate[i * num_temporal_layers_] +
if (num_temporal_layers_ == 1) { (config_->ss_target_bitrate[i] / 4);
config_->layer_target_bitrate[i] = config_->ss_target_bitrate[i]; config_->layer_target_bitrate[i * num_temporal_layers_ + 2] =
} else if (num_temporal_layers_ == 2) { config_->ss_target_bitrate[i];
config_->layer_target_bitrate[i * num_temporal_layers_] = } else {
config_->ss_target_bitrate[i] * 2 / 3; return false;
config_->layer_target_bitrate[i * num_temporal_layers_ + 1] =
config_->ss_target_bitrate[i];
} else if (num_temporal_layers_ == 3) {
config_->layer_target_bitrate[i * num_temporal_layers_] =
config_->ss_target_bitrate[i] / 2;
config_->layer_target_bitrate[i * num_temporal_layers_ + 1] =
config_->layer_target_bitrate[i * num_temporal_layers_] +
(config_->ss_target_bitrate[i] / 4);
config_->layer_target_bitrate[i * num_temporal_layers_ + 2] =
config_->ss_target_bitrate[i];
} else {
LOG(LS_ERROR) << "Unsupported number of temporal layers: "
<< num_temporal_layers_;
return false;
}
} }
} }
@ -379,24 +349,14 @@ int VP9EncoderImpl::NumberOfThreads(int width,
int VP9EncoderImpl::InitAndSetControlSettings(const VideoCodec* inst) { int VP9EncoderImpl::InitAndSetControlSettings(const VideoCodec* inst) {
config_->ss_number_layers = num_spatial_layers_; config_->ss_number_layers = num_spatial_layers_;
if (ExplicitlyConfiguredSpatialLayers()) { int scaling_factor_num = 256;
for (int i = 0; i < num_spatial_layers_; ++i) { for (int i = num_spatial_layers_ - 1; i >= 0; --i) {
const auto &layer = codec_.spatialLayers[i]; svc_internal_.svc_params.max_quantizers[i] = config_->rc_max_quantizer;
svc_internal_.svc_params.max_quantizers[i] = config_->rc_max_quantizer; svc_internal_.svc_params.min_quantizers[i] = config_->rc_min_quantizer;
svc_internal_.svc_params.min_quantizers[i] = config_->rc_min_quantizer; // 1:2 scaling in each dimension.
svc_internal_.svc_params.scaling_factor_num[i] = layer.scaling_factor_num; svc_internal_.svc_params.scaling_factor_num[i] = scaling_factor_num;
svc_internal_.svc_params.scaling_factor_den[i] = layer.scaling_factor_den; svc_internal_.svc_params.scaling_factor_den[i] = 256;
} scaling_factor_num /= 2;
} else {
int scaling_factor_num = 256;
for (int i = num_spatial_layers_ - 1; i >= 0; --i) {
svc_internal_.svc_params.max_quantizers[i] = config_->rc_max_quantizer;
svc_internal_.svc_params.min_quantizers[i] = config_->rc_min_quantizer;
// 1:2 scaling in each dimension.
svc_internal_.svc_params.scaling_factor_num[i] = scaling_factor_num;
svc_internal_.svc_params.scaling_factor_den[i] = 256;
scaling_factor_num /= 2;
}
} }
if (!SetSvcRates()) { if (!SetSvcRates()) {

View File

@ -56,7 +56,6 @@ class VP9EncoderImpl : public VP9Encoder {
const vpx_codec_cx_pkt& pkt, const vpx_codec_cx_pkt& pkt,
uint32_t timestamp); uint32_t timestamp);
bool ExplicitlyConfiguredSpatialLayers() const;
bool SetSvcRates(); bool SetSvcRates();
virtual int GetEncodedLayerFrame(const vpx_codec_cx_pkt* pkt); virtual int GetEncodedLayerFrame(const vpx_codec_cx_pkt* pkt);

View File

@ -23,18 +23,13 @@ LayerFilteringTransport::LayerFilteringTransport(
const FakeNetworkPipe::Config& config, const FakeNetworkPipe::Config& config,
uint8_t vp8_video_payload_type, uint8_t vp8_video_payload_type,
uint8_t vp9_video_payload_type, uint8_t vp9_video_payload_type,
int selected_tl, uint8_t tl_discard_threshold,
int selected_sl) uint8_t sl_discard_threshold)
: test::DirectTransport(config), : test::DirectTransport(config),
vp8_video_payload_type_(vp8_video_payload_type), vp8_video_payload_type_(vp8_video_payload_type),
vp9_video_payload_type_(vp9_video_payload_type), vp9_video_payload_type_(vp9_video_payload_type),
selected_tl_(selected_tl), tl_discard_threshold_(tl_discard_threshold),
selected_sl_(selected_sl), sl_discard_threshold_(sl_discard_threshold) {
discarded_last_packet_(false) {
}
bool LayerFilteringTransport::DiscardedLastPacket() const {
return discarded_last_packet_;
} }
uint16_t LayerFilteringTransport::NextSequenceNumber(uint32_t ssrc) { uint16_t LayerFilteringTransport::NextSequenceNumber(uint32_t ssrc) {
@ -47,7 +42,7 @@ uint16_t LayerFilteringTransport::NextSequenceNumber(uint32_t ssrc) {
bool LayerFilteringTransport::SendRtp(const uint8_t* packet, bool LayerFilteringTransport::SendRtp(const uint8_t* packet,
size_t length, size_t length,
const PacketOptions& options) { const PacketOptions& options) {
if (selected_tl_ == -1 && selected_sl_ == -1) { if (tl_discard_threshold_ == 0 && sl_discard_threshold_ == 0) {
// Nothing to change, forward the packet immediately. // Nothing to change, forward the packet immediately.
return test::DirectTransport::SendRtp(packet, length, options); return test::DirectTransport::SendRtp(packet, length, options);
} }
@ -70,24 +65,23 @@ bool LayerFilteringTransport::SendRtp(const uint8_t* packet,
RtpDepacketizer::Create(is_vp8 ? kRtpVideoVp8 : kRtpVideoVp9)); RtpDepacketizer::Create(is_vp8 ? kRtpVideoVp8 : kRtpVideoVp9));
RtpDepacketizer::ParsedPayload parsed_payload; RtpDepacketizer::ParsedPayload parsed_payload;
if (depacketizer->Parse(&parsed_payload, payload, payload_data_length)) { if (depacketizer->Parse(&parsed_payload, payload, payload_data_length)) {
const int temporal_idx = static_cast<int>( const uint8_t temporalIdx =
is_vp8 ? parsed_payload.type.Video.codecHeader.VP8.temporalIdx is_vp8 ? parsed_payload.type.Video.codecHeader.VP8.temporalIdx
: parsed_payload.type.Video.codecHeader.VP9.temporal_idx); : parsed_payload.type.Video.codecHeader.VP9.temporal_idx;
const int spatial_idx = static_cast<int>( const uint8_t spatialIdx =
is_vp8 ? kNoSpatialIdx is_vp8 ? kNoSpatialIdx
: parsed_payload.type.Video.codecHeader.VP9.spatial_idx); : parsed_payload.type.Video.codecHeader.VP9.spatial_idx;
if (selected_sl_ >= 0 && if (sl_discard_threshold_ > 0 &&
spatial_idx == selected_sl_ && spatialIdx == sl_discard_threshold_ - 1 &&
parsed_payload.type.Video.codecHeader.VP9.end_of_frame) { parsed_payload.type.Video.codecHeader.VP9.end_of_frame) {
// This layer is now the last in the superframe. // This layer is now the last in the superframe.
set_marker_bit = true; set_marker_bit = true;
} }
if ((selected_tl_ >= 0 && temporal_idx != kNoTemporalIdx && if ((tl_discard_threshold_ > 0 && temporalIdx != kNoTemporalIdx &&
temporal_idx > selected_tl_) || temporalIdx >= tl_discard_threshold_) ||
(selected_sl_ >= 0 && spatial_idx != kNoSpatialIdx && (sl_discard_threshold_ > 0 && spatialIdx != kNoSpatialIdx &&
spatial_idx > selected_sl_)) { spatialIdx >= sl_discard_threshold_)) {
discarded_last_packet_ = true; return true; // Discard the packet.
return true;
} }
} else { } else {
RTC_NOTREACHED() << "Parse error"; RTC_NOTREACHED() << "Parse error";

View File

@ -24,9 +24,8 @@ class LayerFilteringTransport : public test::DirectTransport {
LayerFilteringTransport(const FakeNetworkPipe::Config& config, LayerFilteringTransport(const FakeNetworkPipe::Config& config,
uint8_t vp8_video_payload_type, uint8_t vp8_video_payload_type,
uint8_t vp9_video_payload_type, uint8_t vp9_video_payload_type,
int selected_tl, uint8_t tl_discard_threshold,
int selected_sl); uint8_t sl_discard_threshold);
bool DiscardedLastPacket() const;
bool SendRtp(const uint8_t* data, bool SendRtp(const uint8_t* data,
size_t length, size_t length,
const PacketOptions& options) override; const PacketOptions& options) override;
@ -36,13 +35,12 @@ class LayerFilteringTransport : public test::DirectTransport {
// Used to distinguish between VP8 and VP9. // Used to distinguish between VP8 and VP9.
const uint8_t vp8_video_payload_type_; const uint8_t vp8_video_payload_type_;
const uint8_t vp9_video_payload_type_; const uint8_t vp9_video_payload_type_;
// Discard or invalidate all temporal/spatial layers with id greater than the // Discard all temporal/spatial layers with id greater or equal the
// selected one. -1 to disable filtering. // threshold. 0 to disable.
const int selected_tl_; const uint8_t tl_discard_threshold_;
const int selected_sl_; const uint8_t sl_discard_threshold_;
// Current sequence number for each SSRC separately. // Current sequence number for each SSRC separately.
std::map<uint32_t, uint16_t> current_seq_nums_; std::map<uint32_t, uint16_t> current_seq_nums_;
bool discarded_last_packet_;
}; };
} // namespace test } // namespace test

View File

@ -23,15 +23,6 @@ class FullStackTest : public VideoQualityTest {
} }
}; };
// VideoQualityTest::Params params = {
// { ... }, // Common.
// { ... }, // Video-specific settings.
// { ... }, // Screenshare-specific settings.
// { ... }, // Analyzer settings.
// pipe, // FakeNetworkPipe::Config
// { ... }, // Spatial scalability.
// logs // bool
// };
TEST_F(FullStackTest, ParisQcifWithoutPacketLoss) { TEST_F(FullStackTest, ParisQcifWithoutPacketLoss) {
VideoQualityTest::Params paris_qcif = { VideoQualityTest::Params paris_qcif = {
@ -129,16 +120,16 @@ TEST_F(FullStackTest, ForemanCif1000kbps100msLimitedQueue) {
TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL) { TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL) {
VideoQualityTest::Params screenshare = { VideoQualityTest::Params screenshare = {
{1850, 1110, 5, 50000, 200000, 2000000, "VP8", 2, 1, 400000}, {1850, 1110, 5, 50000, 200000, 2000000, "VP8", 2, 400000},
{}, {}, // Video-specific.
{true, 10}, {true, 10}, // Screenshare-specific.
{"screenshare_slides", 0.0, 0.0, kFullStackTestDurationSecs}}; {"screenshare_slides", 0.0, 0.0, kFullStackTestDurationSecs}};
RunTest(screenshare); RunTest(screenshare);
} }
TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL_Scroll) { TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL_Scroll) {
VideoQualityTest::Params config = { VideoQualityTest::Params config = {
{1850, 1110 / 2, 5, 50000, 200000, 2000000, "VP8", 2, 1, 400000}, {1850, 1110 / 2, 5, 50000, 200000, 2000000, "VP8", 2, 400000},
{}, {},
{true, 10, 2}, {true, 10, 2},
{"screenshare_slides_scrolling", 0.0, 0.0, kFullStackTestDurationSecs}}; {"screenshare_slides_scrolling", 0.0, 0.0, kFullStackTestDurationSecs}};
@ -147,7 +138,7 @@ TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL_Scroll) {
TEST_F(FullStackTest, ScreenshareSlidesVP9_2TL) { TEST_F(FullStackTest, ScreenshareSlidesVP9_2TL) {
VideoQualityTest::Params screenshare = { VideoQualityTest::Params screenshare = {
{1850, 1110, 5, 50000, 200000, 2000000, "VP9", 2, 1, 400000}, {1850, 1110, 5, 50000, 200000, 2000000, "VP9", 2, 400000},
{}, {},
{true, 10}, {true, 10},
{"screenshare_slides_vp9_2tl", 0.0, 0.0, kFullStackTestDurationSecs}}; {"screenshare_slides_vp9_2tl", 0.0, 0.0, kFullStackTestDurationSecs}};

View File

@ -20,7 +20,6 @@
namespace webrtc { namespace webrtc {
namespace flags { namespace flags {
// Flags common with video loopback, with different default values.
DEFINE_int32(width, 1850, "Video width (crops source)."); DEFINE_int32(width, 1850, "Video width (crops source).");
size_t Width() { size_t Width() {
return static_cast<size_t>(FLAGS_width); return static_cast<size_t>(FLAGS_width);
@ -36,6 +35,21 @@ int Fps() {
return static_cast<int>(FLAGS_fps); 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."); DEFINE_int32(min_bitrate, 50, "Call and stream min bitrate in kbps.");
int MinBitrateKbps() { int MinBitrateKbps() {
return static_cast<int>(FLAGS_min_bitrate); return static_cast<int>(FLAGS_min_bitrate);
@ -57,40 +71,26 @@ int MaxBitrateKbps() {
} }
DEFINE_int32(num_temporal_layers, 2, "Number of temporal layers to use."); DEFINE_int32(num_temporal_layers, 2, "Number of temporal layers to use.");
int NumTemporalLayers() { size_t NumTemporalLayers() {
return static_cast<int>(FLAGS_num_temporal_layers); return static_cast<size_t>(FLAGS_num_temporal_layers);
}
// 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( DEFINE_int32(
duration, tl_discard_threshold,
0, 0,
"Duration of the test in seconds. If 0, rendered will be shown instead."); "Discard TLs with id greater or equal the threshold. 0 to disable.");
int DurationSecs() { size_t TLDiscardThreshold() {
return static_cast<int>(FLAGS_duration); return static_cast<size_t>(FLAGS_tl_discard_threshold);
} }
DEFINE_string(output_filename, "", "Target graph data filename."); DEFINE_int32(min_transmit_bitrate, 400, "Min transmit bitrate incl. padding.");
std::string OutputFilename() { int MinTransmitBitrateKbps() {
return static_cast<std::string>(FLAGS_output_filename); return FLAGS_min_transmit_bitrate;
} }
DEFINE_string( DEFINE_string(codec, "VP8", "Video codec to use.");
graph_title, "", "If empty, title will be generated automatically."); std::string Codec() {
std::string GraphTitle() { return static_cast<std::string>(FLAGS_codec);
return static_cast<std::string>(FLAGS_graph_title);
} }
DEFINE_int32(loss_percent, 0, "Percentage of packets randomly lost."); DEFINE_int32(loss_percent, 0, "Percentage of packets randomly lost.");
@ -124,51 +124,21 @@ int StdPropagationDelayMs() {
return static_cast<int>(FLAGS_std_propagation_delay_ms); 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(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_bool(send_side_bwe, true, "Use send-side bandwidth estimation");
DEFINE_string( DEFINE_string(
@ -178,28 +148,6 @@ DEFINE_string(
"E.g. running with --force_fieldtrials=WebRTC-FooFeature/Enable/" "E.g. running with --force_fieldtrials=WebRTC-FooFeature/Enable/"
" will assign the group Enable to field trial WebRTC-FooFeature. Multiple " " will assign the group Enable to field trial WebRTC-FooFeature. Multiple "
"trials are separated by \"/\""); "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 } // namespace flags
void Loopback() { void Loopback() {
@ -219,31 +167,20 @@ void Loopback() {
{flags::Width(), flags::Height(), flags::Fps(), {flags::Width(), flags::Height(), flags::Fps(),
flags::MinBitrateKbps() * 1000, flags::TargetBitrateKbps() * 1000, flags::MinBitrateKbps() * 1000, flags::TargetBitrateKbps() * 1000,
flags::MaxBitrateKbps() * 1000, flags::Codec(), flags::MaxBitrateKbps() * 1000, flags::Codec(),
flags::NumTemporalLayers(), flags::SelectedTL(), flags::NumTemporalLayers(), flags::MinTransmitBitrateKbps() * 1000,
flags::MinTransmitBitrateKbps() * 1000, call_bitrate_config, call_bitrate_config, flags::TLDiscardThreshold(),
flags::FLAGS_send_side_bwe}, flags::FLAGS_send_side_bwe},
{}, // Video specific. {}, // Video specific.
{true, flags::SlideChangeInterval(), flags::ScrollDuration()}, {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, pipe_config,
flags::FLAGS_logs}; 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(&params, stream_descriptors,
flags::SelectedStream(), flags::NumSpatialLayers(), flags::SelectedSL(),
SL_descriptors);
VideoQualityTest test; VideoQualityTest test;
if (flags::DurationSecs()) if (flags::OutputFilename().empty())
test.RunWithAnalyzer(params);
else
test.RunWithVideoRenderer(params); test.RunWithVideoRenderer(params);
else
test.RunWithAnalyzer(params);
} }
} // namespace webrtc } // namespace webrtc

View File

@ -20,7 +20,6 @@
namespace webrtc { namespace webrtc {
namespace flags { namespace flags {
// Flags common with screenshare loopback, with different default values.
DEFINE_int32(width, 640, "Video width."); DEFINE_int32(width, 640, "Video width.");
size_t Width() { size_t Width() {
return static_cast<size_t>(FLAGS_width); return static_cast<size_t>(FLAGS_width);
@ -56,45 +55,11 @@ int MaxBitrateKbps() {
return static_cast<int>(FLAGS_max_bitrate); 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."); DEFINE_string(codec, "VP8", "Video codec to use.");
std::string Codec() { std::string Codec() {
return static_cast<std::string>(FLAGS_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."); DEFINE_int32(loss_percent, 0, "Percentage of packets randomly lost.");
int LossPercent() { int LossPercent() {
return static_cast<int>(FLAGS_loss_percent); return static_cast<int>(FLAGS_loss_percent);
@ -126,53 +91,8 @@ int StdPropagationDelayMs() {
return static_cast<int>(FLAGS_std_propagation_delay_ms); 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(logs, false, "print logs to stderr");
DEFINE_bool(send_side_bwe, true, "Use send-side bandwidth estimation");
DEFINE_string( DEFINE_string(
force_fieldtrials, force_fieldtrials,
"", "",
@ -181,7 +101,21 @@ DEFINE_string(
" will assign the group Enable to field trial WebRTC-FooFeature. Multiple " " will assign the group Enable to field trial WebRTC-FooFeature. Multiple "
"trials are separated by \"/\""); "trials are separated by \"/\"");
// Video-specific flags. 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);
}
DEFINE_string(clip, DEFINE_string(clip,
"", "",
"Name of the clip to show. If empty, using chroma generator."); "Name of the clip to show. If empty, using chroma generator.");
@ -189,6 +123,21 @@ std::string Clip() {
return static_cast<std::string>(FLAGS_clip); 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 } // namespace flags
void Loopback() { void Loopback() {
@ -204,35 +153,27 @@ void Loopback() {
call_bitrate_config.start_bitrate_bps = flags::StartBitrateKbps() * 1000; call_bitrate_config.start_bitrate_bps = flags::StartBitrateKbps() * 1000;
call_bitrate_config.max_bitrate_bps = flags::MaxBitrateKbps() * 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{ VideoQualityTest::Params params{
{flags::Width(), flags::Height(), flags::Fps(), {flags::Width(), flags::Height(), flags::Fps(),
flags::MinBitrateKbps() * 1000, flags::TargetBitrateKbps() * 1000, flags::MinBitrateKbps() * 1000, flags::TargetBitrateKbps() * 1000,
flags::MaxBitrateKbps() * 1000, flags::Codec(), flags::MaxBitrateKbps() * 1000, flags::Codec(),
flags::NumTemporalLayers(), flags::SelectedTL(), flags::NumTemporalLayers(),
0, // No min transmit bitrate. 0, // No min transmit bitrate.
call_bitrate_config, flags::FLAGS_send_side_bwe}, call_bitrate_config, flags::TLDiscardThreshold(),
{flags::Clip()}, flags::FLAGS_send_side_bwe},
{clip},
{}, // Screenshare specific. {}, // Screenshare specific.
{"video", 0.0, 0.0, flags::DurationSecs(), flags::OutputFilename(), {graph_title, 0.0, 0.0, flags::DurationSecs(), flags::OutputFilename()},
flags::GraphTitle()},
pipe_config, pipe_config,
flags::FLAGS_logs}; 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(&params, stream_descriptors,
flags::SelectedStream(), flags::NumSpatialLayers(), flags::SelectedSL(),
SL_descriptors);
VideoQualityTest test; VideoQualityTest test;
if (flags::DurationSecs()) if (flags::OutputFilename().empty())
test.RunWithAnalyzer(params);
else
test.RunWithVideoRenderer(params); test.RunWithVideoRenderer(params);
else
test.RunWithAnalyzer(params);
} }
} // namespace webrtc } // namespace webrtc

View File

@ -12,7 +12,6 @@
#include <algorithm> #include <algorithm>
#include <deque> #include <deque>
#include <map> #include <map>
#include <sstream>
#include <vector> #include <vector>
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
@ -46,22 +45,18 @@ class VideoAnalyzer : public PacketReceiver,
public EncodedFrameObserver, public EncodedFrameObserver,
public EncodingTimeObserver { public EncodingTimeObserver {
public: public:
VideoAnalyzer(test::LayerFilteringTransport* transport, VideoAnalyzer(Transport* transport,
const std::string& test_label, const std::string& test_label,
double avg_psnr_threshold, double avg_psnr_threshold,
double avg_ssim_threshold, double avg_ssim_threshold,
int duration_frames, 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), : input_(nullptr),
transport_(transport), transport_(transport),
receiver_(nullptr), receiver_(nullptr),
send_stream_(nullptr), send_stream_(nullptr),
test_label_(test_label), test_label_(test_label),
graph_data_output_file_(graph_data_output_file), graph_data_output_file_(graph_data_output_file),
graph_title_(graph_title),
ssrc_to_analyze_(ssrc_to_analyze),
frames_to_process_(duration_frames), frames_to_process_(duration_frames),
frames_recorded_(0), frames_recorded_(0),
frames_processed_(0), frames_processed_(0),
@ -156,9 +151,6 @@ class VideoAnalyzer : public PacketReceiver,
RTPHeader header; RTPHeader header;
parser->Parse(packet, length, &header); parser->Parse(packet, length, &header);
int64_t current_time =
Clock::GetRealTimeClock()->CurrentNtpInMilliseconds();
bool result = transport_->SendRtp(packet, length, options);
{ {
rtc::CritScope lock(&crit_); rtc::CritScope lock(&crit_);
if (rtp_timestamp_delta_ == 0) { if (rtp_timestamp_delta_ == 0) {
@ -166,14 +158,13 @@ class VideoAnalyzer : public PacketReceiver,
first_send_frame_.Reset(); first_send_frame_.Reset();
} }
uint32_t timestamp = header.timestamp - rtp_timestamp_delta_; uint32_t timestamp = header.timestamp - rtp_timestamp_delta_;
send_times_[timestamp] = current_time; send_times_[timestamp] =
if (!transport_->DiscardedLastPacket() && Clock::GetRealTimeClock()->CurrentNtpInMilliseconds();
header.ssrc == ssrc_to_analyze_) { encoded_frame_sizes_[timestamp] +=
encoded_frame_sizes_[timestamp] += length - (header.headerLength + header.paddingLength);
length - (header.headerLength + header.paddingLength);
}
} }
return result;
return transport_->SendRtp(packet, length, options);
} }
bool SendRtcp(const uint8_t* packet, size_t length) override { bool SendRtcp(const uint8_t* packet, size_t length) override {
@ -203,11 +194,6 @@ class VideoAnalyzer : public PacketReceiver,
VideoFrame reference_frame = frames_.front(); VideoFrame reference_frame = frames_.front();
frames_.pop_front(); frames_.pop_front();
assert(!reference_frame.IsZeroSize()); 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); EXPECT_EQ(reference_frame.timestamp(), send_timestamp);
assert(reference_frame.timestamp() == send_timestamp); assert(reference_frame.timestamp() == send_timestamp);
@ -261,7 +247,7 @@ class VideoAnalyzer : public PacketReceiver,
} }
VideoCaptureInput* input_; VideoCaptureInput* input_;
test::LayerFilteringTransport* transport_; Transport* transport_;
PacketReceiver* receiver_; PacketReceiver* receiver_;
VideoSendStream* send_stream_; VideoSendStream* send_stream_;
@ -336,13 +322,8 @@ class VideoAnalyzer : public PacketReceiver,
int64_t recv_time_ms = recv_times_[reference.timestamp()]; int64_t recv_time_ms = recv_times_[reference.timestamp()];
recv_times_.erase(reference.timestamp()); recv_times_.erase(reference.timestamp());
// TODO(ivica): Make this work for > 2 streams. size_t encoded_size = encoded_frame_sizes_[reference.timestamp()];
auto it = encoded_frame_sizes_.find(reference.timestamp()); encoded_frame_sizes_.erase(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 reference_copy;
VideoFrame render_copy; VideoFrame render_copy;
@ -530,7 +511,7 @@ class VideoAnalyzer : public PacketReceiver,
return A.input_time_ms < B.input_time_ms; return A.input_time_ms < B.input_time_ms;
}); });
fprintf(out, "%s\n", graph_title_.c_str()); fprintf(out, "%s\n", test_label_.c_str());
fprintf(out, "%" PRIuS "\n", samples_.size()); fprintf(out, "%" PRIuS "\n", samples_.size());
fprintf(out, fprintf(out,
"dropped " "dropped "
@ -568,8 +549,6 @@ class VideoAnalyzer : public PacketReceiver,
const std::string test_label_; const std::string test_label_;
FILE* const graph_data_output_file_; 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::vector<Sample> samples_ GUARDED_BY(comparison_lock_);
std::map<int64_t, int> samples_encode_time_ms_ 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_); test::Statistics sender_time_ GUARDED_BY(comparison_lock_);
@ -609,173 +588,30 @@ class VideoAnalyzer : public PacketReceiver,
const rtc::scoped_ptr<EventWrapper> done_; const rtc::scoped_ptr<EventWrapper> done_;
}; };
VideoQualityTest::VideoQualityTest() : clock_(Clock::GetRealTimeClock()) {} 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::TestBody() {}
std::string VideoQualityTest::GenerateGraphTitle() const { void VideoQualityTest::SetupFullStack(const Params& params,
std::stringstream ss; Transport* send_transport,
ss << params_.common.codec; Transport* recv_transport) {
ss << " (" << params_.common.target_bitrate_bps / 1000 << "kbps"; if (params.logs)
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;
// 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_LT(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;
std::vector<int> v = VideoQualityTest::ParseCSV(descriptor);
VideoStream stream(VideoQualityTest::DefaultVideoStream(*params));
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); trace_to_stderr_.reset(new test::TraceToStderr);
size_t num_streams = params_.ss.streams.size(); CreateSendConfig(1, send_transport);
CreateSendConfig(num_streams, send_transport);
int payload_type; int payload_type;
if (params_.common.codec == "VP8") { if (params.common.codec == "VP8") {
encoder_.reset(VideoEncoder::Create(VideoEncoder::kVp8)); encoder_.reset(VideoEncoder::Create(VideoEncoder::kVp8));
payload_type = kPayloadTypeVP8; payload_type = kPayloadTypeVP8;
} else if (params_.common.codec == "VP9") { } else if (params.common.codec == "VP9") {
encoder_.reset(VideoEncoder::Create(VideoEncoder::kVp9)); encoder_.reset(VideoEncoder::Create(VideoEncoder::kVp9));
payload_type = kPayloadTypeVP9; payload_type = kPayloadTypeVP9;
} else { } else {
@ -783,15 +619,15 @@ void VideoQualityTest::SetupCommon(Transport* send_transport,
return; return;
} }
send_config_.encoder_settings.encoder = encoder_.get(); 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_.encoder_settings.payload_type = payload_type;
send_config_.rtp.nack.rtp_history_ms = kNackRtpHistoryMs; send_config_.rtp.nack.rtp_history_ms = kNackRtpHistoryMs;
send_config_.rtp.rtx.ssrcs.push_back(kSendRtxSsrcs[0]);
send_config_.rtp.rtx.payload_type = kSendRtxPayloadType; 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(); send_config_.rtp.extensions.clear();
if (params_.common.send_side_bwe) { if (params.common.send_side_bwe) {
send_config_.rtp.extensions.push_back(RtpExtension( send_config_.rtp.extensions.push_back(RtpExtension(
RtpExtension::kTransportSequenceNumber, kTransportSeqExtensionId)); RtpExtension::kTransportSequenceNumber, kTransportSeqExtensionId));
} else { } else {
@ -799,41 +635,49 @@ void VideoQualityTest::SetupCommon(Transport* send_transport,
RtpExtension(RtpExtension::kAbsSendTime, kAbsSendTimeExtensionId)); RtpExtension(RtpExtension::kAbsSendTime, kAbsSendTimeExtensionId));
} }
encoder_config_.min_transmit_bitrate_bps = params_.common.min_transmit_bps; // Automatically fill out streams[0] with params.
encoder_config_.streams = params_.ss.streams; VideoStream* stream = &encoder_config_.streams[0];
encoder_config_.spatial_layers = params_.ss.spatial_layers; 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);
}
CreateMatchingReceiveConfigs(recv_transport); CreateMatchingReceiveConfigs(recv_transport);
for (size_t i = 0; i < num_streams; ++i) { receive_configs_[0].rtp.nack.rtp_history_ms = kNackRtpHistoryMs;
receive_configs_[i].rtp.nack.rtp_history_ms = kNackRtpHistoryMs; receive_configs_[0].rtp.rtx[kSendRtxPayloadType].ssrc = kSendRtxSsrcs[0];
receive_configs_[i].rtp.rtx[kSendRtxPayloadType].ssrc = kSendRtxSsrcs[i]; receive_configs_[0].rtp.rtx[kSendRtxPayloadType].payload_type =
receive_configs_[i].rtp.rtx[kSendRtxPayloadType].payload_type = kSendRtxPayloadType;
kSendRtxPayloadType;
} encoder_config_.min_transmit_bitrate_bps = params.common.min_transmit_bps;
} }
void VideoQualityTest::SetupScreenshare() { void VideoQualityTest::SetupScreenshare(const Params& params) {
RTC_CHECK(params_.screenshare.enabled); RTC_CHECK(params.screenshare.enabled);
// Fill out codec settings. // Fill out codec settings.
encoder_config_.content_type = VideoEncoderConfig::ContentType::kScreen; 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 = VideoEncoder::GetDefaultVp8Settings();
codec_settings_.VP8.denoisingOn = false; codec_settings_.VP8.denoisingOn = false;
codec_settings_.VP8.frameDroppingOn = false; codec_settings_.VP8.frameDroppingOn = false;
codec_settings_.VP8.numberOfTemporalLayers = 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; 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 = VideoEncoder::GetDefaultVp9Settings();
codec_settings_.VP9.denoisingOn = false; codec_settings_.VP9.denoisingOn = false;
codec_settings_.VP9.frameDroppingOn = false; codec_settings_.VP9.frameDroppingOn = false;
codec_settings_.VP9.numberOfTemporalLayers = 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; encoder_config_.encoder_specific_settings = &codec_settings_.VP9;
codec_settings_.VP9.numberOfSpatialLayers =
static_cast<unsigned char>(params_.ss.num_spatial_layers);
} }
// Setup frame generator. // Setup frame generator.
@ -845,127 +689,105 @@ void VideoQualityTest::SetupScreenshare() {
slides.push_back(test::ResourcePath("photo_1850_1110", "yuv")); slides.push_back(test::ResourcePath("photo_1850_1110", "yuv"));
slides.push_back(test::ResourcePath("difficult_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. // Cycle image every slide_change_interval seconds.
frame_generator_.reset(test::FrameGenerator::CreateFromYuvFile( frame_generator_.reset(test::FrameGenerator::CreateFromYuvFile(
slides, kWidth, kHeight, slides, kWidth, kHeight,
params_.screenshare.slide_change_interval * params_.common.fps)); params.screenshare.slide_change_interval * params.common.fps));
} else { } else {
RTC_CHECK_LE(params_.common.width, kWidth); RTC_CHECK_LE(params.common.width, kWidth);
RTC_CHECK_LE(params_.common.height, kHeight); RTC_CHECK_LE(params.common.height, kHeight);
RTC_CHECK_GT(params_.screenshare.slide_change_interval, 0); RTC_CHECK_GT(params.screenshare.slide_change_interval, 0);
const int kPauseDurationMs = (params_.screenshare.slide_change_interval - const int kPauseDurationMs = (params.screenshare.slide_change_interval -
params_.screenshare.scroll_duration) * 1000; params.screenshare.scroll_duration) * 1000;
RTC_CHECK_LE(params_.screenshare.scroll_duration, RTC_CHECK_LE(params.screenshare.scroll_duration,
params_.screenshare.slide_change_interval); params.screenshare.slide_change_interval);
frame_generator_.reset( if (params.screenshare.scroll_duration) {
test::FrameGenerator::CreateScrollingInputFromYuvFiles( frame_generator_.reset(
clock_, slides, kWidth, kHeight, params_.common.width, test::FrameGenerator::CreateScrollingInputFromYuvFiles(
params_.common.height, params_.screenshare.scroll_duration * 1000, clock_, slides, kWidth, kHeight, params.common.width,
kPauseDurationMs)); 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));
}
} }
} }
void VideoQualityTest::CreateCapturer(VideoCaptureInput* input) { void VideoQualityTest::CreateCapturer(const Params& params,
if (params_.screenshare.enabled) { VideoCaptureInput* input) {
if (params.screenshare.enabled) {
test::FrameGeneratorCapturer *frame_generator_capturer = test::FrameGeneratorCapturer *frame_generator_capturer =
new test::FrameGeneratorCapturer( 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()); EXPECT_TRUE(frame_generator_capturer->Init());
capturer_.reset(frame_generator_capturer); capturer_.reset(frame_generator_capturer);
} else { } else {
if (params_.video.clip_name.empty()) { if (params.video.clip_name.empty()) {
capturer_.reset(test::VideoCapturer::Create( capturer_.reset(test::VideoCapturer::Create(
input, params_.common.width, params_.common.height, input, params.common.width, params.common.height, params.common.fps,
params_.common.fps, clock_)); clock_));
} else { } else {
capturer_.reset(test::FrameGeneratorCapturer::CreateFromYuvFile( capturer_.reset(test::FrameGeneratorCapturer::CreateFromYuvFile(
input, test::ResourcePath(params_.video.clip_name, "yuv"), input, test::ResourcePath(params.video.clip_name, "yuv"),
params_.common.width, params_.common.height, params_.common.fps, params.common.width, params.common.height, params.common.fps,
clock_)); clock_));
ASSERT_TRUE(capturer_.get() != nullptr) 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?"; << ".yuv. Is this resource file present?";
} }
} }
} }
void VideoQualityTest::RunWithAnalyzer(const Params& _params) { void VideoQualityTest::RunWithAnalyzer(const Params& params) {
params_ = _params;
// TODO(ivica): Merge with RunWithRenderer and use a flag / argument to // TODO(ivica): Merge with RunWithRenderer and use a flag / argument to
// differentiate between the analyzer and the renderer case. // differentiate between the analyzer and the renderer case.
CheckParams(); ValidateParams(params);
FILE* graph_data_output_file = nullptr; 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 = 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) RTC_CHECK(graph_data_output_file != nullptr)
<< "Can't open the file " << "Can't open the file "
<< params_.analyzer.graph_data_output_filename << "!"; << params.analyzer.graph_data_output_filename << "!";
} }
test::LayerFilteringTransport send_transport( test::LayerFilteringTransport send_transport(
params_.pipe, kPayloadTypeVP8, kPayloadTypeVP9, params.pipe, kPayloadTypeVP8, kPayloadTypeVP9,
params_.common.selected_tl, params_.ss.selected_sl); static_cast<uint8_t>(params.common.tl_discard_threshold), 0);
test::DirectTransport recv_transport(params_.pipe); test::DirectTransport recv_transport(params.pipe);
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( VideoAnalyzer analyzer(
&send_transport, params_.analyzer.test_label, &send_transport, params.analyzer.test_label,
disable_quality_check ? -1.1 : params_.analyzer.avg_psnr_threshold, params.analyzer.avg_psnr_threshold, params.analyzer.avg_ssim_threshold,
disable_quality_check ? -1.1 : params_.analyzer.avg_ssim_threshold, params.analyzer.test_durations_secs * params.common.fps,
params_.analyzer.test_durations_secs * params_.common.fps, graph_data_output_file);
graph_data_output_file, graph_title,
kSendSsrcs[params_.ss.selected_stream]);
Call::Config call_config; Call::Config call_config;
call_config.bitrate_config = params_.common.call_bitrate_config; call_config.bitrate_config = params.common.call_bitrate_config;
CreateCalls(call_config, call_config); CreateCalls(call_config, call_config);
analyzer.SetReceiver(receiver_call_->Receiver()); analyzer.SetReceiver(receiver_call_->Receiver());
send_transport.SetReceiver(&analyzer); send_transport.SetReceiver(&analyzer);
recv_transport.SetReceiver(sender_call_->Receiver()); recv_transport.SetReceiver(sender_call_->Receiver());
SetupCommon(&analyzer, &recv_transport); SetupFullStack(params, &analyzer, &recv_transport);
send_config_.encoding_time_observer = &analyzer; send_config_.encoding_time_observer = &analyzer;
receive_configs_[params_.ss.selected_stream].renderer = &analyzer; receive_configs_[0].renderer = &analyzer;
for (auto& config : receive_configs_) for (auto& config : receive_configs_)
config.pre_decode_callback = &analyzer; config.pre_decode_callback = &analyzer;
if (params_.screenshare.enabled) if (params.screenshare.enabled)
SetupScreenshare(); SetupScreenshare(params);
CreateStreams(); CreateStreams();
analyzer.input_ = send_stream_->Input(); analyzer.input_ = send_stream_->Input();
analyzer.send_stream_ = send_stream_; analyzer.send_stream_ = send_stream_;
CreateCapturer(&analyzer); CreateCapturer(params, &analyzer);
send_stream_->Start(); send_stream_->Start();
for (size_t i = 0; i < receive_streams_.size(); ++i) for (size_t i = 0; i < receive_streams_.size(); ++i)
@ -988,52 +810,41 @@ void VideoQualityTest::RunWithAnalyzer(const Params& _params) {
fclose(graph_data_output_file); fclose(graph_data_output_file);
} }
void VideoQualityTest::RunWithVideoRenderer(const Params& _params) { void VideoQualityTest::RunWithVideoRenderer(const Params& params) {
params_ = _params; ValidateParams(params);
CheckParams();
rtc::scoped_ptr<test::VideoRenderer> local_preview( rtc::scoped_ptr<test::VideoRenderer> local_preview(
test::VideoRenderer::Create("Local Preview", params_.common.width, test::VideoRenderer::Create("Local Preview", params.common.width,
params_.common.height)); 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( rtc::scoped_ptr<test::VideoRenderer> loopback_video(
test::VideoRenderer::Create( test::VideoRenderer::Create("Loopback Video", params.common.width,
title, params_.ss.streams[stream_id].width, params.common.height));
params_.ss.streams[stream_id].height));
// TODO(ivica): Remove bitrate_config and use the default Call::Config(), to // TODO(ivica): Remove bitrate_config and use the default Call::Config(), to
// match the full stack tests. // match the full stack tests.
Call::Config call_config; 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)); rtc::scoped_ptr<Call> call(Call::Create(call_config));
test::LayerFilteringTransport transport( test::LayerFilteringTransport transport(
params_.pipe, kPayloadTypeVP8, kPayloadTypeVP9, params.pipe, kPayloadTypeVP8, kPayloadTypeVP9,
params_.common.selected_tl, params_.ss.selected_sl); static_cast<uint8_t>(params.common.tl_discard_threshold), 0);
// TODO(ivica): Use two calls to be able to merge with RunWithAnalyzer or at // 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 // least share as much code as possible. That way this test would also match
// the full stack tests better. // the full stack tests better.
transport.SetReceiver(call->Receiver()); transport.SetReceiver(call->Receiver());
SetupCommon(&transport, &transport); SetupFullStack(params, &transport, &transport);
send_config_.local_renderer = local_preview.get(); send_config_.local_renderer = local_preview.get();
receive_configs_[stream_id].renderer = loopback_video.get(); receive_configs_[0].renderer = loopback_video.get();
if (params_.screenshare.enabled) if (params.screenshare.enabled)
SetupScreenshare(); SetupScreenshare(params);
send_stream_ = call->CreateVideoSendStream(send_config_, encoder_config_); send_stream_ = call->CreateVideoSendStream(send_config_, encoder_config_);
VideoReceiveStream* receive_stream = VideoReceiveStream* receive_stream =
call->CreateVideoReceiveStream(receive_configs_[stream_id]); call->CreateVideoReceiveStream(receive_configs_[0]);
CreateCapturer(send_stream_->Input()); CreateCapturer(params, send_stream_->Input());
receive_stream->Start(); receive_stream->Start();
send_stream_->Start(); send_stream_->Start();

View File

@ -33,11 +33,11 @@ class VideoQualityTest : public test::CallTest {
int target_bitrate_bps; int target_bitrate_bps;
int max_bitrate_bps; int max_bitrate_bps;
std::string codec; std::string codec;
int num_temporal_layers; size_t num_temporal_layers;
int selected_tl;
int min_transmit_bps; int min_transmit_bps;
Call::Config::BitrateConfig call_bitrate_config; Call::Config::BitrateConfig call_bitrate_config;
size_t tl_discard_threshold;
bool send_side_bwe; bool send_side_bwe;
} common; } common;
struct { // Video-specific settings. struct { // Video-specific settings.
@ -50,55 +50,30 @@ class VideoQualityTest : public test::CallTest {
} screenshare; } screenshare;
struct { // Analyzer settings. struct { // Analyzer settings.
std::string test_label; std::string test_label;
double avg_psnr_threshold; // (*) double avg_psnr_threshold;
double avg_ssim_threshold; // (*) double avg_ssim_threshold;
int test_durations_secs; int test_durations_secs;
std::string graph_data_output_filename; std::string graph_data_output_filename;
std::string graph_title;
} analyzer; } analyzer;
FakeNetworkPipe::Config pipe; FakeNetworkPipe::Config pipe;
bool logs; 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(); VideoQualityTest();
void RunWithAnalyzer(const Params& params); void RunWithAnalyzer(const Params& params);
void RunWithVideoRenderer(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: protected:
// No-op implementation to be able to instantiate this class from non-TEST_F // No-op implementation to be able to instantiate this class from non-TEST_F
// locations. // locations.
void TestBody() override; void TestBody() override;
// Helper methods accessing only params_. void CreateCapturer(const Params& params, VideoCaptureInput* input);
std::string GenerateGraphTitle() const; void ValidateParams(const Params& params);
void CheckParams(); void SetupFullStack(const Params& params,
Transport* send_transport,
// Helper static methods. Transport* recv_transport);
static VideoStream DefaultVideoStream(const Params& params); void SetupScreenshare(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. // We need a more general capturer than the FrameGeneratorCapturer.
rtc::scoped_ptr<test::VideoCapturer> capturer_; rtc::scoped_ptr<test::VideoCapturer> capturer_;
@ -107,8 +82,6 @@ class VideoQualityTest : public test::CallTest {
rtc::scoped_ptr<VideoEncoder> encoder_; rtc::scoped_ptr<VideoEncoder> encoder_;
VideoCodecUnion codec_settings_; VideoCodecUnion codec_settings_;
Clock* const clock_; Clock* const clock_;
Params params_;
}; };
} // namespace webrtc } // namespace webrtc

View File

@ -310,16 +310,6 @@ bool VideoSendStream::ReconfigureVideoEncoder(
static_cast<unsigned char>(streams.size()); static_cast<unsigned char>(streams.size());
video_codec.minBitrate = streams[0].min_bitrate_bps / 1000; video_codec.minBitrate = streams[0].min_bitrate_bps / 1000;
RTC_DCHECK_LE(streams.size(), static_cast<size_t>(kMaxSimulcastStreams)); 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) { for (size_t i = 0; i < streams.size(); ++i) {
SimulcastStream* sim_stream = &video_codec.simulcastStream[i]; SimulcastStream* sim_stream = &video_codec.simulcastStream[i];
RTC_DCHECK_GT(streams[i].width, 0u); RTC_DCHECK_GT(streams[i].width, 0u);