From a85307737cc9ea3e79b86daf96d455fca4ad1bb4 Mon Sep 17 00:00:00 2001 From: "buildbot@webrtc.org" Date: Wed, 10 Dec 2014 09:01:18 +0000 Subject: [PATCH] (Auto)update libjingle 81702493-> 81755413 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7860 4adac7df-926f-26a2-2b94-8c16560cd09d --- talk/libjingle.gyp | 2 + talk/media/webrtc/simulcast.cc | 423 ++++ talk/media/webrtc/simulcast.h | 108 + talk/media/webrtc/webrtcvideoengine.cc | 122 +- talk/media/webrtc/webrtcvideoengine.h | 1 + talk/media/webrtc/webrtcvideoengine2.cc | 39 +- talk/media/webrtc/webrtcvideoengine2.h | 6 + .../webrtc/webrtcvideoengine2_unittest.cc | 454 ++++ .../webrtc/webrtcvideoengine_unittest.cc | 1904 ++++++++++++++++- 9 files changed, 3045 insertions(+), 14 deletions(-) create mode 100755 talk/media/webrtc/simulcast.cc create mode 100755 talk/media/webrtc/simulcast.h diff --git a/talk/libjingle.gyp b/talk/libjingle.gyp index 5ae312f9d4..2a987f69e7 100755 --- a/talk/libjingle.gyp +++ b/talk/libjingle.gyp @@ -396,6 +396,8 @@ 'media/other/linphonemediaengine.h', 'media/sctp/sctpdataengine.cc', 'media/sctp/sctpdataengine.h', + 'media/webrtc/simulcast.cc', + 'media/webrtc/simulcast.h', 'media/webrtc/webrtccommon.h', 'media/webrtc/webrtcexport.h', 'media/webrtc/webrtcmediaengine.cc', diff --git a/talk/media/webrtc/simulcast.cc b/talk/media/webrtc/simulcast.cc new file mode 100755 index 0000000000..289aacad05 --- /dev/null +++ b/talk/media/webrtc/simulcast.cc @@ -0,0 +1,423 @@ +/* + * libjingle + * Copyright 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/media/base/mediachannel.h" // For VideoOptions +#include "talk/media/base/streamparams.h" +#include "talk/media/webrtc/simulcast.h" +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/common_types.h" // For webrtc::VideoCodec + +namespace cricket { + +struct SimulcastFormat { + int width; + int height; + // The maximum number of simulcast layers can be used for + // resolutions at |widthxheigh|. + size_t max_layers; + // The maximum bitrate for encoding stream at |widthxheight|, when we are + // not sending the next higher spatial stream. + int max_bitrate_kbps[SBM_COUNT]; + // The target bitrate for encoding stream at |widthxheight|, when this layer + // is not the highest layer (i.e., when we are sending another higher spatial + // stream). + int target_bitrate_kbps[SBM_COUNT]; + // The minimum bitrate needed for encoding stream at |widthxheight|. + int min_bitrate_kbps[SBM_COUNT]; +}; + +// These tables describe from which resolution we can use how many +// simulcast layers at what bitrates (maximum, target, and minimum). +// Important!! Keep this table from high resolution to low resolution. +const SimulcastFormat kSimulcastFormats[] = { + {1280, 720, 3, {1200, 1200, 2500}, {1200, 1200, 2500}, {500, 600, 600}}, + {960, 540, 3, {900, 900, 900}, {900, 900, 900}, {350, 450, 450}}, + {640, 360, 2, {500, 700, 700}, {500, 500, 500}, {100, 150, 150}}, + {480, 270, 2, {350, 450, 450}, {350, 350, 350}, {100, 150, 150}}, + {320, 180, 1, {100, 200, 200}, {100, 150, 150}, {30, 30, 30}}, + {0, 0, 1, {100, 200, 200}, {100, 150, 150}, {30, 30, 30}} +}; + +// Multiway: Number of temporal layers for each simulcast stream, for maximum +// possible number of simulcast streams |kMaxSimulcastStreams|. The array +// goes from lowest resolution at position 0 to highest resolution. +// For example, first three elements correspond to say: QVGA, VGA, WHD. +static const int + kDefaultConferenceNumberOfTemporalLayers[webrtc::kMaxSimulcastStreams] = + {3, 3, 3, 3}; + +static const int kScreencastWithTemporalLayerTargetVideoBitrate = 100; +static const int kScreencastWithTemporalLayerMaxVideoBitrate = 1000; + +void GetSimulcastSsrcs(const StreamParams& sp, std::vector* ssrcs) { + const SsrcGroup* sim_group = sp.get_ssrc_group(kSimSsrcGroupSemantics); + if (sim_group) { + ssrcs->insert( + ssrcs->end(), sim_group->ssrcs.begin(), sim_group->ssrcs.end()); + } +} + +SimulcastBitrateMode GetSimulcastBitrateMode( + const VideoOptions& options) { + VideoOptions::HighestBitrate bitrate_mode; + if (options.video_highest_bitrate.Get(&bitrate_mode)) { + switch (bitrate_mode) { + case VideoOptions::HIGH: + return SBM_HIGH; + case VideoOptions::VERY_HIGH: + return SBM_VERY_HIGH; + default: + break; + } + } + return SBM_NORMAL; +} + +void MaybeExchangeWidthHeight(int* width, int* height) { + // |kSimulcastFormats| assumes |width| >= |height|. If not, exchange them + // before comparing. + if (*width < *height) { + int temp = *width; + *width = *height; + *height = temp; + } +} + +int FindSimulcastFormatIndex(int width, int height) { + MaybeExchangeWidthHeight(&width, &height); + + for (int i = 0; i < ARRAY_SIZE(kSimulcastFormats); ++i) { + if (width >= kSimulcastFormats[i].width && + height >= kSimulcastFormats[i].height) { + return i; + } + } + return -1; +} + +int FindSimulcastFormatIndex(int width, int height, size_t max_layers) { + MaybeExchangeWidthHeight(&width, &height); + + for (int i = 0; i < ARRAY_SIZE(kSimulcastFormats); ++i) { + if (width >= kSimulcastFormats[i].width && + height >= kSimulcastFormats[i].height && + max_layers == kSimulcastFormats[i].max_layers) { + return i; + } + } + return -1; +} + +SimulcastBitrateMode FindSimulcastBitrateMode( + size_t max_layers, + int stream_idx, + SimulcastBitrateMode highest_enabled) { + + if (highest_enabled > SBM_NORMAL) { + // We want high or very high for all layers if enabled. + return highest_enabled; + } + if (kSimulcastFormats[stream_idx].max_layers == max_layers) { + // We want high for the top layer. + return SBM_HIGH; + } + // And normal for everything else. + return SBM_NORMAL; +} + +// Simulcast stream width and height must both be dividable by +// |2 ^ simulcast_layers - 1|. +int NormalizeSimulcastSize(int size, size_t simulcast_layers) { + const int base2_exponent = static_cast(simulcast_layers) - 1; + return ((size >> base2_exponent) << base2_exponent); +} + +size_t FindSimulcastMaxLayers(int width, int height) { + int index = FindSimulcastFormatIndex(width, height); + if (index == -1) { + return -1; + } + return kSimulcastFormats[index].max_layers; +} + +// TODO(marpan): Investigate if we should return 0 instead of -1 in +// FindSimulcast[Max/Target/Min]Bitrate functions below, since the +// codec struct max/min/targeBitrates are unsigned. +int FindSimulcastMaxBitrateBps(int width, + int height, + size_t max_layers, + SimulcastBitrateMode highest_enabled) { + const int format_index = FindSimulcastFormatIndex(width, height); + if (format_index == -1) { + return -1; + } + const SimulcastBitrateMode bitrate_mode = FindSimulcastBitrateMode( + max_layers, format_index, highest_enabled); + return kSimulcastFormats[format_index].max_bitrate_kbps[bitrate_mode] * 1000; +} + +int FindSimulcastTargetBitrateBps(int width, + int height, + size_t max_layers, + SimulcastBitrateMode highest_enabled) { + const int format_index = FindSimulcastFormatIndex(width, height); + if (format_index == -1) { + return -1; + } + const SimulcastBitrateMode bitrate_mode = FindSimulcastBitrateMode( + max_layers, format_index, highest_enabled); + return kSimulcastFormats[format_index].target_bitrate_kbps[bitrate_mode] * + 1000; +} + +int FindSimulcastMinBitrateBps(int width, + int height, + size_t max_layers, + SimulcastBitrateMode highest_enabled) { + const int format_index = FindSimulcastFormatIndex(width, height); + if (format_index == -1) { + return -1; + } + const SimulcastBitrateMode bitrate_mode = FindSimulcastBitrateMode( + max_layers, format_index, highest_enabled); + return kSimulcastFormats[format_index].min_bitrate_kbps[bitrate_mode] * 1000; +} + +bool SlotSimulcastMaxResolution(size_t max_layers, int* width, int* height) { + int index = FindSimulcastFormatIndex(*width, *height, max_layers); + if (index == -1) { + LOG(LS_ERROR) << "SlotSimulcastMaxResolution"; + return false; + } + + *width = kSimulcastFormats[index].width; + *height = kSimulcastFormats[index].height; + LOG(LS_INFO) << "SlotSimulcastMaxResolution to width:" << *width + << " height:" << *height; + return true; +} + +int GetTotalMaxBitrateBps(const std::vector& streams) { + int total_max_bitrate_bps = 0; + for (size_t s = 0; s < streams.size() - 1; ++s) { + total_max_bitrate_bps += streams[s].target_bitrate_bps; + } + total_max_bitrate_bps += streams.back().max_bitrate_bps; + return total_max_bitrate_bps; +} + +std::vector GetSimulcastConfig( + size_t max_streams, + SimulcastBitrateMode bitrate_mode, + int width, + int height, + int min_bitrate_bps, + int max_bitrate_bps, + int max_qp, + int max_framerate) { + size_t simulcast_layers = FindSimulcastMaxLayers(width, height); + if (simulcast_layers > max_streams) { + // If the number of SSRCs in the group differs from our target + // number of simulcast streams for current resolution, switch down + // to a resolution that matches our number of SSRCs. + if (!SlotSimulcastMaxResolution(max_streams, &width, &height)) { + return std::vector(); + } + simulcast_layers = max_streams; + } + std::vector streams; + streams.resize(simulcast_layers); + + // Format width and height has to be divisible by |2 ^ number_streams - 1|. + width = NormalizeSimulcastSize(width, simulcast_layers); + height = NormalizeSimulcastSize(height, simulcast_layers); + + // Add simulcast sub-streams from lower resolution to higher resolutions. + // Add simulcast streams, from highest resolution (|s| = number_streams -1) + // to lowest resolution at |s| = 0. + for (size_t s = simulcast_layers - 1;; --s) { + streams[s].width = width; + streams[s].height = height; + // TODO(pbos): Fill actual temporal-layer bitrate thresholds. + streams[s].temporal_layer_thresholds_bps.resize( + kDefaultConferenceNumberOfTemporalLayers[s] - 1); + streams[s].max_bitrate_bps = FindSimulcastMaxBitrateBps( + width, height, simulcast_layers, bitrate_mode); + streams[s].target_bitrate_bps = FindSimulcastTargetBitrateBps( + width, height, simulcast_layers, bitrate_mode); + streams[s].min_bitrate_bps = FindSimulcastMinBitrateBps( + width, height, simulcast_layers, bitrate_mode); + streams[s].max_qp = max_qp; + streams[s].max_framerate = max_framerate; + width /= 2; + height /= 2; + if (s == 0) { + break; + } + } + + // Spend additional bits to boost the max stream. + int bitrate_left_bps = max_bitrate_bps - GetTotalMaxBitrateBps(streams); + if (bitrate_left_bps > 0) { + streams.back().max_bitrate_bps += bitrate_left_bps; + } + + // Make sure the first stream respects the bitrate minimum. + if (streams[0].min_bitrate_bps < min_bitrate_bps) { + streams[0].min_bitrate_bps = min_bitrate_bps; + } + + return streams; +} + +bool ConfigureSimulcastCodec( + int number_ssrcs, + SimulcastBitrateMode bitrate_mode, + webrtc::VideoCodec* codec) { + std::vector streams = + GetSimulcastConfig(static_cast(number_ssrcs), + bitrate_mode, + static_cast(codec->width), + static_cast(codec->height), + codec->minBitrate * 1000, + codec->maxBitrate * 1000, + codec->qpMax, + codec->maxFramerate); + // Add simulcast sub-streams from lower resolution to higher resolutions. + codec->numberOfSimulcastStreams = static_cast(streams.size()); + codec->width = static_cast(streams.back().width); + codec->height = static_cast(streams.back().height); + // When using simulcast, |codec->maxBitrate| is set to the sum of the max + // bitrates over all streams. For a given stream |s|, the max bitrate for that + // stream is set by |simulcastStream[s].targetBitrate|, if it is not the + // highest resolution stream, otherwise it is set by + // |simulcastStream[s].maxBitrate|. + + for (size_t s = 0; s < streams.size(); ++s) { + codec->simulcastStream[s].width = + static_cast(streams[s].width); + codec->simulcastStream[s].height = + static_cast(streams[s].height); + codec->simulcastStream[s].numberOfTemporalLayers = + static_cast( + streams[s].temporal_layer_thresholds_bps.size() + 1); + codec->simulcastStream[s].minBitrate = streams[s].min_bitrate_bps / 1000; + codec->simulcastStream[s].targetBitrate = + streams[s].target_bitrate_bps / 1000; + codec->simulcastStream[s].maxBitrate = streams[s].max_bitrate_bps / 1000; + codec->simulcastStream[s].qpMax = streams[s].max_qp; + } + + codec->maxBitrate = + static_cast(GetTotalMaxBitrateBps(streams) / 1000); + + codec->codecSpecific.VP8.numberOfTemporalLayers = + kDefaultConferenceNumberOfTemporalLayers[0]; + + return true; +} + +bool ConfigureSimulcastCodec( + const StreamParams& sp, + const VideoOptions& options, + webrtc::VideoCodec* codec) { + std::vector ssrcs; + GetSimulcastSsrcs(sp, &ssrcs); + SimulcastBitrateMode bitrate_mode = GetSimulcastBitrateMode(options); + return ConfigureSimulcastCodec(static_cast(ssrcs.size()), bitrate_mode, + codec); +} + +void ConfigureSimulcastTemporalLayers( + int num_temporal_layers, webrtc::VideoCodec* codec) { + for (size_t i = 0; i < codec->numberOfSimulcastStreams; ++i) { + codec->simulcastStream[i].numberOfTemporalLayers = num_temporal_layers; + } +} + +void DisableSimulcastCodec(webrtc::VideoCodec* codec) { + // TODO(hellner): the proper solution is to uncomment the next code line + // and remove the lines following it in this condition. This is pending + // b/7012070 being fixed. + // codec->numberOfSimulcastStreams = 0; + // It is possible to set non simulcast without the above line. However, + // the max bitrate for every simulcast layer must be set to 0. Further, + // there is a sanity check making sure that the aspect ratio is the same + // for all simulcast layers. The for-loop makes sure that the sanity check + // does not fail. + if (codec->numberOfSimulcastStreams > 0) { + const int ratio = codec->width / codec->height; + for (int i = 0; i < codec->numberOfSimulcastStreams - 1; ++i) { + // Min/target bitrate has to be zero not to influence padding + // calculations in VideoEngine. + codec->simulcastStream[i].minBitrate = 0; + codec->simulcastStream[i].targetBitrate = 0; + codec->simulcastStream[i].maxBitrate = 0; + codec->simulcastStream[i].width = + codec->simulcastStream[i].height * ratio; + codec->simulcastStream[i].numberOfTemporalLayers = 1; + } + // The for loop above did not set the bitrate of the highest layer. + codec->simulcastStream[codec->numberOfSimulcastStreams - 1] + .minBitrate = 0; + codec->simulcastStream[codec->numberOfSimulcastStreams - 1] + .targetBitrate = 0; + codec->simulcastStream[codec->numberOfSimulcastStreams - 1]. + maxBitrate = 0; + // The highest layer has to correspond to the non-simulcast resolution. + codec->simulcastStream[codec->numberOfSimulcastStreams - 1]. + width = codec->width; + codec->simulcastStream[codec->numberOfSimulcastStreams - 1]. + height = codec->height; + codec->simulcastStream[codec->numberOfSimulcastStreams - 1]. + numberOfTemporalLayers = 1; + // TODO(hellner): the maxFramerate should also be set here according to + // the screencasts framerate. Doing so will break some + // unittests. + } +} + +void LogSimulcastSubstreams(const webrtc::VideoCodec& codec) { + for (size_t i = 0; i < codec.numberOfSimulcastStreams; ++i) { + LOG(LS_INFO) << "Simulcast substream " << i << ": " + << codec.simulcastStream[i].width << "x" + << codec.simulcastStream[i].height << "@" + << codec.simulcastStream[i].minBitrate << "-" + << codec.simulcastStream[i].maxBitrate << "kbps" + << " with " << codec.simulcastStream[i].numberOfTemporalLayers + << " temporal layers"; + } +} + +void ConfigureConferenceModeScreencastCodec(webrtc::VideoCodec* codec) { + codec->codecSpecific.VP8.numberOfTemporalLayers = 2; + codec->maxBitrate = kScreencastWithTemporalLayerMaxVideoBitrate; + codec->targetBitrate = kScreencastWithTemporalLayerTargetVideoBitrate; +} + +} // namespace cricket diff --git a/talk/media/webrtc/simulcast.h b/talk/media/webrtc/simulcast.h new file mode 100755 index 0000000000..be8097369f --- /dev/null +++ b/talk/media/webrtc/simulcast.h @@ -0,0 +1,108 @@ +/* + * libjingle + * Copyright 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef TALK_MEDIA_WEBRTC_SIMULCAST_H_ +#define TALK_MEDIA_WEBRTC_SIMULCAST_H_ + +#include + +#include "webrtc/base/basictypes.h" +#include "webrtc/config.h" + +namespace webrtc { +struct VideoCodec; +} + +namespace cricket { +struct VideoOptions; +struct StreamParams; + +enum SimulcastBitrateMode { + SBM_NORMAL = 0, + SBM_HIGH, + SBM_VERY_HIGH, + SBM_COUNT +}; + +// TODO(pthatcher): Write unit tests just for these functions, +// independent of WebrtcVideoEngine. + +// Get the simulcast bitrate mode to use based on +// options.video_highest_bitrate. +SimulcastBitrateMode GetSimulcastBitrateMode( + const VideoOptions& options); + +// Get the ssrcs of the SIM group from the stream params. +void GetSimulcastSsrcs(const StreamParams& sp, std::vector* ssrcs); + +// Get simulcast settings. +std::vector GetSimulcastConfig( + size_t max_streams, + SimulcastBitrateMode bitrate_mode, + int width, + int height, + int min_bitrate_bps, + int max_bitrate_bps, + int max_qp, + int max_framerate); + +// Set the codec->simulcastStreams, codec->width, and codec->height +// based on the number of ssrcs to use and the bitrate mode to use. +bool ConfigureSimulcastCodec(int number_ssrcs, + SimulcastBitrateMode bitrate_mode, + webrtc::VideoCodec* codec); + +// Set the codec->simulcastStreams, codec->width, and codec->height +// based on the video options (to get the simulcast bitrate mode) and +// the stream params (to get the number of ssrcs). This is really a +// convenience function. +bool ConfigureSimulcastCodec(const StreamParams& sp, + const VideoOptions& options, + webrtc::VideoCodec* codec); + +// Set the numberOfTemporalLayers in each codec->simulcastStreams[i]. +// Apparently it is useful to do this at a different time than +// ConfigureSimulcastCodec. +// TODO(pthatcher): Figure out why and put this code into +// ConfigureSimulcastCodec. +void ConfigureSimulcastTemporalLayers( + int num_temporal_layers, webrtc::VideoCodec* codec); + +// Turn off all simulcasting for the given codec. +void DisableSimulcastCodec(webrtc::VideoCodec* codec); + +// Log useful info about each of the simulcast substreams of the +// codec. +void LogSimulcastSubstreams(const webrtc::VideoCodec& codec); + +// Configure the codec's bitrate and temporal layers so that it's good +// for a screencast in conference mode. Technically, this shouldn't +// go in simulcast.cc. But it's closely related. +void ConfigureConferenceModeScreencastCodec(webrtc::VideoCodec* codec); + +} // namespace cricket + +#endif // TALK_MEDIA_WEBRTC_SIMULCAST_H_ diff --git a/talk/media/webrtc/webrtcvideoengine.cc b/talk/media/webrtc/webrtcvideoengine.cc index 0d55c319a0..a072eb7c5b 100644 --- a/talk/media/webrtc/webrtcvideoengine.cc +++ b/talk/media/webrtc/webrtcvideoengine.cc @@ -43,6 +43,7 @@ #include "talk/media/base/videorenderer.h" #include "talk/media/devices/filevideocapturer.h" #include "talk/media/webrtc/constants.h" +#include "talk/media/webrtc/simulcast.h" #include "talk/media/webrtc/webrtcpassthroughrender.h" #include "talk/media/webrtc/webrtctexturevideoframe.h" #include "talk/media/webrtc/webrtcvideocapturer.h" @@ -64,6 +65,8 @@ #include "webrtc/base/timeutils.h" #include "webrtc/experiments.h" #include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" +#include "webrtc/modules/video_coding/codecs/vp8/simulcast_encoder_adapter.h" +#include "webrtc/modules/video_coding/codecs/vp8/vp8_factory.h" #include "webrtc/system_wrappers/interface/field_trial.h" namespace { @@ -97,6 +100,58 @@ bool Changed(cricket::Settable proposed, return proposed.Get(value) && proposed != original; } +// Wrap cricket::WebRtcVideoEncoderFactory as a webrtc::VideoEncoderFactory. +class EncoderFactoryAdapter : public webrtc::VideoEncoderFactory { + public: + // EncoderFactoryAdapter doesn't take ownership of |factory|, which is owned + // by e.g. PeerConnectionFactory. + explicit EncoderFactoryAdapter(cricket::WebRtcVideoEncoderFactory* factory) + : factory_(factory) {} + virtual ~EncoderFactoryAdapter() {} + + // Implement webrtc::VideoEncoderFactory. + virtual webrtc::VideoEncoder* Create() OVERRIDE { + return factory_->CreateVideoEncoder(webrtc::kVideoCodecVP8); + } + + virtual void Destroy(webrtc::VideoEncoder* encoder) OVERRIDE { + return factory_->DestroyVideoEncoder(encoder); + } + + private: + cricket::WebRtcVideoEncoderFactory* factory_; +}; + +// Wrap encoder factory to a simulcast encoder factory. +class SimulcastEncoderFactory : public cricket::WebRtcVideoEncoderFactory { + public: + // SimulcastEncoderFactory doesn't take ownership of |factory|, which is owned + // by e.g. PeerConnectionFactory. + explicit SimulcastEncoderFactory(cricket::WebRtcVideoEncoderFactory* factory) + : factory_(factory) {} + virtual ~SimulcastEncoderFactory() {} + + virtual webrtc::VideoEncoder* CreateVideoEncoder( + webrtc::VideoCodecType type) OVERRIDE { + ASSERT(type == webrtc::kVideoCodecVP8); + ASSERT(factory_ != NULL); + return new webrtc::SimulcastEncoderAdapter( + webrtc::scoped_ptr( + new EncoderFactoryAdapter(factory_)).Pass()); + } + + virtual const std::vector& codecs() const OVERRIDE { + return factory_->codecs(); + } + + virtual void DestroyVideoEncoder(webrtc::VideoEncoder* encoder) OVERRIDE { + delete encoder; + } + + private: + cricket::WebRtcVideoEncoderFactory* factory_; +}; + } // namespace namespace cricket { @@ -1097,6 +1152,11 @@ WebRtcVideoEngine::~WebRtcVideoEngine() { if (initialized_) { Terminate(); } + + if (simulcast_encoder_factory_) { + SetExternalEncoderFactory(NULL); + } + tracing_->SetTraceCallback(NULL); // Test to see if the media processor was deregistered properly. ASSERT(SignalMediaFrame.is_empty()); @@ -1615,6 +1675,20 @@ void WebRtcVideoEngine::SetExternalDecoderFactory( void WebRtcVideoEngine::SetExternalEncoderFactory( WebRtcVideoEncoderFactory* encoder_factory) { + // Deleted after WebRtcVideoEngine::SetExternalEncoderFactory is + // completed, which will remove the references to it. + rtc::scoped_ptr old_factory( + simulcast_encoder_factory_.release()); + if (encoder_factory) { + const std::vector& codecs = + encoder_factory->codecs(); + if (codecs.size() == 1 && codecs[0].type == webrtc::kVideoCodecVP8) { + simulcast_encoder_factory_.reset( + new SimulcastEncoderFactory(encoder_factory)); + encoder_factory = simulcast_encoder_factory_.get(); + } + } + if (encoder_factory_ == encoder_factory) return; @@ -3010,6 +3084,13 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { VideoOptions original = options_; options_.SetAll(options); + bool use_simulcast_adapter; + if (options.use_simulcast_adapter.Get(&use_simulcast_adapter) && + options.use_simulcast_adapter != original.use_simulcast_adapter) { + webrtc::VP8EncoderFactoryConfig::set_use_simulcast_adapter( + use_simulcast_adapter); + } + // Set CPU options and codec options for all send channels. for (SendChannelMap::iterator iter = send_channels_.begin(); iter != send_channels_.end(); ++iter) { @@ -3775,6 +3856,8 @@ void WebRtcVideoMediaChannel::LogSendCodecChange(const std::string& reason) { if (send_rtx_type_ != -1) { LOG(LS_INFO) << "RTX payload type: " << send_rtx_type_; } + + LogSimulcastSubstreams(vie_codec); } bool WebRtcVideoMediaChannel::SetReceiveCodecs( @@ -4004,6 +4087,29 @@ bool WebRtcVideoMediaChannel::ConfigureVieCodecFromSendParams( } } + if (webrtc::kVideoCodecVP8 == codec.codecType) { + ConfigureSimulcastTemporalLayers( + kDefaultNumberOfTemporalLayers, &codec); + if (IsSimulcastStream(send_params.stream)) { + codec.codecSpecific.VP8.automaticResizeOn = false; + // TODO(pthatcher): Pass in options in VideoSendParams. + VideoOptions options; + GetOptions(&options); + if (ConferenceModeIsEnabled()) { + ConfigureSimulcastCodec(send_params.stream, options, &codec); + } + } + + if (last_captured_frame_info.screencast) { + // Use existing bitrate if not in conference mode. + if (ConferenceModeIsEnabled()) { + ConfigureConferenceModeScreencastCodec(&codec); + } + + DisableSimulcastCodec(&codec); + } + } + *codec_out = codec; return true; } @@ -4045,6 +4151,12 @@ void WebRtcVideoMediaChannel::SanitizeBitrates( codec->startBitrate = current_target_bitrate; } } + + // Make sure the start bitrate is larger than lowest layer's min bitrate. + if (codec->numberOfSimulcastStreams > 1 && + codec->startBitrate < codec->simulcastStream[0].minBitrate) { + codec->startBitrate = codec->simulcastStream[0].minBitrate; + } } void WebRtcVideoMediaChannel::OnMessage(rtc::Message* msg) { @@ -4193,11 +4305,11 @@ bool WebRtcVideoMediaChannel::SetLimitedNumberOfSendSsrcs( return true; } -bool WebRtcVideoMediaChannel::SetSendSsrcs( - int channel_id, const StreamParams& sp, - const webrtc::VideoCodec& codec) { - // TODO(pthatcher): Support more than one primary SSRC per stream. - return SetLimitedNumberOfSendSsrcs(channel_id, sp, 1); +bool WebRtcVideoMediaChannel::SetSendSsrcs(int channel_id, + const StreamParams& sp, + const webrtc::VideoCodec& codec) { + size_t limit = codec.numberOfSimulcastStreams; + return SetLimitedNumberOfSendSsrcs(channel_id, sp, limit); } void WebRtcVideoMediaChannel::MaybeConnectCapturer(VideoCapturer* capturer) { diff --git a/talk/media/webrtc/webrtcvideoengine.h b/talk/media/webrtc/webrtcvideoengine.h index d1ace7dbf7..78a10e0bf0 100644 --- a/talk/media/webrtc/webrtcvideoengine.h +++ b/talk/media/webrtc/webrtcvideoengine.h @@ -219,6 +219,7 @@ class WebRtcVideoEngine : public sigslot::has_slots<>, rtc::scoped_ptr tracing_; WebRtcVoiceEngine* voice_engine_; rtc::scoped_ptr render_module_; + rtc::scoped_ptr simulcast_encoder_factory_; WebRtcVideoEncoderFactory* encoder_factory_; WebRtcVideoDecoderFactory* decoder_factory_; std::vector video_codecs_; diff --git a/talk/media/webrtc/webrtcvideoengine2.cc b/talk/media/webrtc/webrtcvideoengine2.cc index 5b563dcbaf..419f251149 100644 --- a/talk/media/webrtc/webrtcvideoengine2.cc +++ b/talk/media/webrtc/webrtcvideoengine2.cc @@ -35,6 +35,7 @@ #include "talk/media/base/videocapturer.h" #include "talk/media/base/videorenderer.h" #include "talk/media/webrtc/constants.h" +#include "talk/media/webrtc/simulcast.h" #include "talk/media/webrtc/webrtcvideocapturer.h" #include "talk/media/webrtc/webrtcvideoengine.h" #include "talk/media/webrtc/webrtcvideoframe.h" @@ -184,15 +185,43 @@ static std::vector FilterRtpExtensions( WebRtcVideoEncoderFactory2::~WebRtcVideoEncoderFactory2() { } +std::vector +WebRtcVideoEncoderFactory2::CreateSimulcastVideoStreams( + const VideoCodec& codec, + const VideoOptions& options, + size_t num_streams) { + // Use default factory for non-simulcast. + int max_qp = kDefaultQpMax; + codec.GetParam(kCodecParamMaxQuantization, &max_qp); + + int min_bitrate_kbps; + if (!codec.GetParam(kCodecParamMinBitrate, &min_bitrate_kbps) || + min_bitrate_kbps < kMinVideoBitrate) { + min_bitrate_kbps = kMinVideoBitrate; + } + + int max_bitrate_kbps; + if (!codec.GetParam(kCodecParamMaxBitrate, &max_bitrate_kbps)) { + max_bitrate_kbps = 0; + } + + return GetSimulcastConfig( + num_streams, + GetSimulcastBitrateMode(options), + codec.width, + codec.height, + min_bitrate_kbps * 1000, + max_bitrate_kbps * 1000, + max_qp, + codec.framerate != 0 ? codec.framerate : kDefaultVideoMaxFramerate); +} + std::vector WebRtcVideoEncoderFactory2::CreateVideoStreams( const VideoCodec& codec, const VideoOptions& options, size_t num_streams) { - if (num_streams != 1) { - LOG(LS_WARNING) << "Unsupported number of streams (" << num_streams - << "), falling back to one."; - num_streams = 1; - } + if (num_streams != 1) + return CreateSimulcastVideoStreams(codec, options, num_streams); webrtc::VideoStream stream; stream.width = codec.width; diff --git a/talk/media/webrtc/webrtcvideoengine2.h b/talk/media/webrtc/webrtcvideoengine2.h index 8ce6f3603e..a7532f54da 100644 --- a/talk/media/webrtc/webrtcvideoengine2.h +++ b/talk/media/webrtc/webrtcvideoengine2.h @@ -106,6 +106,7 @@ class DefaultUnsignalledSsrcHandler : public UnsignalledSsrcHandler { VideoRenderer* default_renderer_; }; +// TODO(pbos): Remove this class and just inline configuring code. class WebRtcVideoEncoderFactory2 { public: virtual ~WebRtcVideoEncoderFactory2(); @@ -114,6 +115,11 @@ class WebRtcVideoEncoderFactory2 { const VideoOptions& options, size_t num_streams); + std::vector CreateSimulcastVideoStreams( + const VideoCodec& codec, + const VideoOptions& options, + size_t num_streams); + virtual void* CreateVideoEncoderSettings(const VideoCodec& codec, const VideoOptions& options); diff --git a/talk/media/webrtc/webrtcvideoengine2_unittest.cc b/talk/media/webrtc/webrtcvideoengine2_unittest.cc index db0b044a5f..2f72f1cddc 100644 --- a/talk/media/webrtc/webrtcvideoengine2_unittest.cc +++ b/talk/media/webrtc/webrtcvideoengine2_unittest.cc @@ -31,6 +31,7 @@ #include "talk/media/base/testutils.h" #include "talk/media/base/videoengine_unittest.h" #include "talk/media/webrtc/fakewebrtcvideoengine.h" +#include "talk/media/webrtc/simulcast.h" #include "talk/media/webrtc/webrtcvideochannelfactory.h" #include "talk/media/webrtc/webrtcvideoengine2.h" #include "talk/media/webrtc/webrtcvideoengine2_unittest.h" @@ -40,8 +41,13 @@ #include "webrtc/video_encoder.h" namespace { +static const int kDefaultQpMax = 56; +static const int kDefaultFramerate = 30; +static const int kMinBitrateBps = 30000; + static const cricket::VideoCodec kVp8Codec720p(100, "VP8", 1280, 720, 30, 0); static const cricket::VideoCodec kVp8Codec360p(100, "VP8", 640, 360, 30, 0); +static const cricket::VideoCodec kVp8Codec270p(100, "VP8", 480, 270, 30, 0); static const cricket::VideoCodec kVp8Codec(100, "VP8", 640, 400, 30, 0); static const cricket::VideoCodec kVp9Codec(101, "VP9", 640, 400, 30, 0); @@ -51,6 +57,7 @@ static const cricket::VideoCodec kRedCodec(116, "red", 0, 0, 0, 0); static const cricket::VideoCodec kUlpfecCodec(117, "ulpfec", 0, 0, 0, 0); static const uint32 kSsrcs1[] = {1}; +static const uint32 kSsrcs3[] = {1, 2, 3}; static const uint32 kRtxSsrcs1[] = {4}; static const char kUnsupportedExtensionName[] = "urn:ietf:params:rtp-hdrext:unsupported"; @@ -1897,4 +1904,451 @@ TEST_F(WebRtcVideoChannel2Test, GetStatsReportsUpperResolution) { EXPECT_EQ(90, info.senders[0].send_frame_height); } +class WebRtcVideoEngine2SimulcastTest : public testing::Test { + public: + WebRtcVideoEngine2SimulcastTest() + : engine_codecs_(engine_.codecs()) { + assert(!engine_codecs_.empty()); + + bool codec_set = false; + for (size_t i = 0; i < engine_codecs_.size(); ++i) { + if (engine_codecs_[i].name == "red") { + default_red_codec_ = engine_codecs_[i]; + } else if (engine_codecs_[i].name == "ulpfec") { + default_ulpfec_codec_ = engine_codecs_[i]; + } else if (engine_codecs_[i].name == "rtx") { + default_rtx_codec_ = engine_codecs_[i]; + } else if (!codec_set) { + default_codec_ = engine_codecs_[i]; + codec_set = true; + } + } + + assert(codec_set); + } + + protected: + WebRtcVideoEngine2 engine_; + VideoCodec default_codec_; + VideoCodec default_red_codec_; + VideoCodec default_ulpfec_codec_; + VideoCodec default_rtx_codec_; + // TODO(pbos): Remove engine_codecs_ unless used a lot. + std::vector engine_codecs_; +}; + +class WebRtcVideoChannel2SimulcastTest : public WebRtcVideoEngine2SimulcastTest, + public WebRtcCallFactory { + public: + WebRtcVideoChannel2SimulcastTest() : fake_call_(NULL) {} + + virtual void SetUp() OVERRIDE { + engine_.SetCallFactory(this); + engine_.Init(rtc::Thread::Current()); + channel_.reset(engine_.CreateChannel(VideoOptions(), NULL)); + ASSERT_TRUE(fake_call_ != NULL) << "Call not created through factory."; + last_ssrc_ = 123; + } + + protected: + virtual webrtc::Call* CreateCall( + const webrtc::Call::Config& config) OVERRIDE { + assert(fake_call_ == NULL); + fake_call_ = new FakeCall(config); + return fake_call_; + } + + void VerifySimulcastSettings(const VideoCodec& codec, + VideoOptions::HighestBitrate bitrate_mode, + size_t num_configured_streams, + size_t expected_num_streams, + SimulcastBitrateMode simulcast_bitrate_mode) { + cricket::VideoOptions options; + options.video_highest_bitrate.Set(bitrate_mode); + EXPECT_TRUE(channel_->SetOptions(options)); + + std::vector codecs; + codecs.push_back(codec); + ASSERT_TRUE(channel_->SetSendCodecs(codecs)); + + std::vector ssrcs = MAKE_VECTOR(kSsrcs3); + assert(num_configured_streams <= ssrcs.size()); + ssrcs.resize(num_configured_streams); + + FakeVideoSendStream* stream = + AddSendStream(CreateSimStreamParams("cname", ssrcs)); + + std::vector video_streams = stream->GetVideoStreams(); + ASSERT_EQ(expected_num_streams, video_streams.size()); + + std::vector expected_streams = GetSimulcastConfig( + num_configured_streams, + simulcast_bitrate_mode, + codec.width, + codec.height, + kMinBitrateBps, + 0, + kDefaultQpMax, + codec.framerate != 0 ? codec.framerate : kDefaultFramerate); + + ASSERT_EQ(expected_streams.size(), video_streams.size()); + + size_t num_streams = video_streams.size(); + for (size_t i = 0; i < num_streams; ++i) { + EXPECT_EQ(expected_streams[i].width, video_streams[i].width); + EXPECT_EQ(expected_streams[i].height, video_streams[i].height); + + EXPECT_GT(video_streams[i].max_framerate, 0); + EXPECT_EQ(expected_streams[i].max_framerate, + video_streams[i].max_framerate); + + EXPECT_GT(video_streams[i].min_bitrate_bps, 0); + EXPECT_EQ(expected_streams[i].min_bitrate_bps, + video_streams[i].min_bitrate_bps); + + EXPECT_GT(video_streams[i].target_bitrate_bps, 0); + EXPECT_EQ(expected_streams[i].target_bitrate_bps, + video_streams[i].target_bitrate_bps); + + EXPECT_GT(video_streams[i].max_bitrate_bps, 0); + EXPECT_EQ(expected_streams[i].max_bitrate_bps, + video_streams[i].max_bitrate_bps); + + EXPECT_GT(video_streams[i].max_qp, 0); + EXPECT_EQ(expected_streams[i].max_qp, video_streams[i].max_qp); + + EXPECT_FALSE(expected_streams[i].temporal_layer_thresholds_bps.empty()); + EXPECT_EQ(expected_streams[i].temporal_layer_thresholds_bps, + video_streams[i].temporal_layer_thresholds_bps); + } + + EXPECT_EQ(kMinBitrateBps, video_streams[0].min_bitrate_bps); + } + + FakeVideoSendStream* AddSendStream() { + return AddSendStream(StreamParams::CreateLegacy(last_ssrc_++)); + } + + FakeVideoSendStream* AddSendStream(const StreamParams& sp) { + size_t num_streams = + fake_call_->GetVideoSendStreams().size(); + EXPECT_TRUE(channel_->AddSendStream(sp)); + std::vector streams = + fake_call_->GetVideoSendStreams(); + EXPECT_EQ(num_streams + 1, streams.size()); + return streams[streams.size() - 1]; + } + + std::vector GetFakeSendStreams() { + return fake_call_->GetVideoSendStreams(); + } + + FakeVideoReceiveStream* AddRecvStream() { + return AddRecvStream(StreamParams::CreateLegacy(last_ssrc_++)); + } + + FakeVideoReceiveStream* AddRecvStream(const StreamParams& sp) { + size_t num_streams = + fake_call_->GetVideoReceiveStreams().size(); + EXPECT_TRUE(channel_->AddRecvStream(sp)); + std::vector streams = + fake_call_->GetVideoReceiveStreams(); + EXPECT_EQ(num_streams + 1, streams.size()); + return streams[streams.size() - 1]; + } + + FakeCall* fake_call_; + rtc::scoped_ptr channel_; + uint32 last_ssrc_; +}; + +TEST_F(WebRtcVideoChannel2SimulcastTest, SetSendCodecsWith2SimulcastStreams) { + VerifySimulcastSettings(kVp8Codec, VideoOptions::NORMAL, 2, 2, SBM_NORMAL); +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, SetSendCodecsWith3SimulcastStreams) { + VerifySimulcastSettings( + kVp8Codec720p, VideoOptions::NORMAL, 3, 3, SBM_NORMAL); +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, + SetSendCodecsWith2SimulcastStreamsHighBitrateMode) { + VerifySimulcastSettings(kVp8Codec, VideoOptions::HIGH, 2, 2, SBM_HIGH); +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, + SetSendCodecsWith3SimulcastStreamsHighBitrateMode) { + VerifySimulcastSettings(kVp8Codec720p, VideoOptions::HIGH, 3, 3, SBM_HIGH); +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, + SetSendCodecsWith2SimulcastStreamsVeryHighBitrateMode) { + VerifySimulcastSettings( + kVp8Codec, VideoOptions::VERY_HIGH, 2, 2, SBM_VERY_HIGH); +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, + SetSendCodecsWith3SimulcastStreamsVeryHighBitrateMode) { + VerifySimulcastSettings( + kVp8Codec720p, VideoOptions::VERY_HIGH, 3, 3, SBM_VERY_HIGH); +} + +// Test that we normalize send codec format size in simulcast. +TEST_F(WebRtcVideoChannel2SimulcastTest, SetSendCodecsWithOddSizeInSimulcast) { + cricket::VideoCodec codec(kVp8Codec270p); + codec.width += 1; + codec.height += 1; + VerifySimulcastSettings(codec, VideoOptions::NORMAL, 2, 2, SBM_NORMAL); +} + +// Test that if we add a stream with RTX SSRC's, SSRC's get set correctly. +TEST_F(WebRtcVideoEngine2SimulcastTest, DISABLED_TestStreamWithRtx) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test that if we get too few ssrcs are given in AddSendStream(), +// only supported sub-streams will be added. +TEST_F(WebRtcVideoEngine2SimulcastTest, DISABLED_TooFewSimulcastSsrcs) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test that even more than enough ssrcs are given in AddSendStream(), +// only supported sub-streams will be added. +TEST_F(WebRtcVideoEngine2SimulcastTest, DISABLED_MoreThanEnoughSimulcastSscrs) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test that SetSendStreamFormat works well with simulcast. +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_SetSendStreamFormatWithSimulcast) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test that simulcast send codec is reset on new video frame size. +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_ResetSimulcastSendCodecOnNewFrameSize) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test that simulcast send codec is reset on new portait mode video frame. +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_ResetSimulcastSendCodecOnNewPortaitFrame) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_SetBandwidthInConferenceWithSimulcast) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test that sending screencast frames in conference mode changes +// bitrate. +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_SetBandwidthScreencastInConference) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test AddSendStream with simulcast rejects bad StreamParams. +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_AddSendStreamWithBadStreamParams) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test AddSendStream with simulcast sets ssrc and cname correctly. +TEST_F(WebRtcVideoEngine2SimulcastTest, DISABLED_AddSendStreamWithSimulcast) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test RemoveSendStream with simulcast. +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_RemoveSendStreamWithSimulcast) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test AddSendStream after send codec has already been set will reset +// send codec with simulcast settings. +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_AddSimulcastStreamAfterSetSendCodec) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoEngine2SimulcastTest, DISABLED_GetStatsWithMultipleSsrcs) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test receiving channel(s) local ssrc is set to the same as the first +// simulcast sending ssrc. +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_AddSimulcastStreamAfterCreatingRecvChannels) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test 1:1 call never turn on simulcast. +TEST_F(WebRtcVideoEngine2SimulcastTest, DISABLED_NoSimulcastWith1on1) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test SetOptions with OPT_CONFERENCE flag. +TEST_F(WebRtcVideoEngine2SimulcastTest, DISABLED_SetOptionsWithConferenceMode) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test that two different streams can have different formats. +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_MultipleSendStreamsDifferentFormats) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoEngine2SimulcastTest, DISABLED_TestAdaptToOutputFormat) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoEngine2SimulcastTest, DISABLED_TestAdaptToCpuLoad) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoEngine2SimulcastTest, DISABLED_TestAdaptToCpuLoadDisabled) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_TestAdaptWithCpuOveruseObserver) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test that codec is not reset for every frame sent in non-conference and +// non-screencast mode. +TEST_F(WebRtcVideoEngine2SimulcastTest, DISABLED_DontResetCodecOnSendFrame) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_UseSimulcastAdapterOnVp8OnlyFactory) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_DontUseSimulcastAdapterOnNoneVp8Factory) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoEngine2SimulcastTest, + DISABLED_DontUseSimulcastAdapterOnMultipleCodecsFactory) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, DISABLED_SimulcastSend_1280x800) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, DISABLED_SimulcastSend_1280x720) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, DISABLED_SimulcastSend_960x540) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, DISABLED_SimulcastSend_960x600) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, DISABLED_SimulcastSend_640x400) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, DISABLED_SimulcastSend_640x360) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, DISABLED_SimulcastSend_480x300) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, + DISABLED_DISABLED_SimulcastSend_480x270) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, DISABLED_SimulcastSend_320x200) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoChannel2SimulcastTest, DISABLED_SimulcastSend_320x180) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test reset send codec with simulcast. +// Disabled per b/6773425 +TEST_F(WebRtcVideoChannel2SimulcastTest, + DISABLED_DISABLED_SimulcastResetSendCodec) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Test simulcast streams are decodeable with expected sizes. +TEST_F(WebRtcVideoChannel2SimulcastTest, DISABLED_SimulcastStreams) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Simulcast and resolution resizing should be turned off when screencasting +// but not otherwise. +TEST_F(WebRtcVideoChannel2SimulcastTest, DISABLED_ScreencastRendering) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Ensures that the correct settings are applied to the codec when single +// temporal layer screencasting is enabled, and that the correct simulcast +// settings are reapplied when disabling screencasting. +TEST_F(WebRtcVideoChannel2SimulcastTest, + DISABLED_OneTemporalLayerScreencastSettings) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} + +// Ensures that the correct settings are applied to the codec when two temporal +// layer screencasting is enabled, and that the correct simulcast settings are +// reapplied when disabling screencasting. +TEST_F(WebRtcVideoChannel2SimulcastTest, + DISABLED_TwoTemporalLayerScreencastSettings) { + // TODO(pbos): Implement. + FAIL() << "Not implemented."; +} } // namespace cricket diff --git a/talk/media/webrtc/webrtcvideoengine_unittest.cc b/talk/media/webrtc/webrtcvideoengine_unittest.cc index aaf959501d..9ea24ee0a5 100644 --- a/talk/media/webrtc/webrtcvideoengine_unittest.cc +++ b/talk/media/webrtc/webrtcvideoengine_unittest.cc @@ -27,26 +27,32 @@ #include "talk/media/base/constants.h" #include "talk/media/base/fakenetworkinterface.h" +#include "talk/media/base/fakevideorenderer.h" #include "talk/media/base/mediachannel.h" #include "talk/media/base/testutils.h" +#include "talk/media/base/videoadapter.h" #include "talk/media/base/videoengine_unittest.h" #include "talk/media/webrtc/fakewebrtcvideoengine.h" +#include "talk/media/webrtc/simulcast.h" +#include "talk/media/webrtc/webrtcvideoengine.h" +#include "talk/media/webrtc/webrtcvideoframe.h" +#include "talk/media/webrtc/webrtcvoiceengine.h" +#include "talk/session/media/mediasession.h" #include "webrtc/base/fakecpumonitor.h" #include "webrtc/base/gunit.h" #include "webrtc/base/logging.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/stream.h" -#include "talk/media/webrtc/webrtcvideoengine.h" -#include "talk/media/webrtc/webrtcvideoframe.h" -#include "talk/media/webrtc/webrtcvoiceengine.h" -#include "talk/session/media/mediasession.h" #include "webrtc/system_wrappers/interface/trace.h" + // Tests for the WebRtcVideoEngine/VideoChannel code. using cricket::kRtpTimestampOffsetHeaderExtension; using cricket::kRtpAbsoluteSenderTimeHeaderExtension; static const cricket::VideoCodec kVP8Codec720p(100, "VP8", 1280, 720, 30, 0); +static const cricket::VideoCodec kVP8Codec360p(100, "VP8", 640, 360, 30, 0); +static const cricket::VideoCodec kVP8Codec270p(100, "VP8", 480, 270, 30, 0); static const cricket::VideoCodec kVP8Codec(100, "VP8", 640, 400, 30, 0); static const cricket::VideoCodec kH264Codec(127, "H264", 640, 400, 30, 0); @@ -2612,3 +2618,1893 @@ TEST_F(WebRtcVideoMediaChannelTest, DontResetSequenceNumbers) { EXPECT_EQ(seq_before, seq_after); } + +static const unsigned int kNumberOfTemporalLayers = 1; +static const unsigned int kSimulcastNumberOfTemporalLayers = 3; +static const unsigned int kSimStream0Bitrate = 100; +static const unsigned int kSimStream0MaxBitrateBoosted = 200; +static const unsigned int kSimStream0MaxBitrateXd = 200; +static const unsigned int kSimStream0TargetBitrateBoosted = 150; +static const unsigned int kSimStream0TargetBitrateXd = 150; +static const unsigned int kSimStream1Bitrate = 350; +static const unsigned int kSimStream1MaxBitrateBoosted = 450; +static const unsigned int kSimStream1MaxBitrateXd = 450; +static const unsigned int kSimStream1TargetBitrateBoosted = 350; +static const unsigned int kSimStream1TargetBitrateXd = 350; +static const unsigned int kSimStream2Bitrate = 500; +static const unsigned int kSimStream2MaxBitrateBoosted = 700; +static const unsigned int kSimStream2MaxBitrateXd = 700; +static const unsigned int kSimStream2TargetBitrateBoosted = 500; +static const unsigned int kSimStream2TargetBitrateXd = 500; +static const unsigned int kSimStream3Bitrate = 900; +static const unsigned int kSimStream3MaxBitrateBoosted = 900; +static const unsigned int kSimStream3MaxBitrateXd = 900; +static const unsigned int kSimStream3TargetBitrateBoosted = 900; +static const unsigned int kSimStream3TargetBitrateXd = 900; +static const unsigned int kSimStream4Bitrate = 1200; +static const unsigned int kSimStream4MaxBitrateBoosted = 1200; +static const unsigned int kSimStream4MaxBitrateXd = 2500; +static const unsigned int kSimStream4TargetBitrateBoosted = 1200; +static const unsigned int kSimStream4TargetBitrateXd = 2500; + +struct SimulcastFormat { + unsigned int width; + unsigned int height; + unsigned int max_layers; + unsigned int max_bitrate[cricket::SBM_COUNT]; + unsigned int target_bitrate[cricket::SBM_COUNT]; +}; + +static const SimulcastFormat kSimulcastFormats[] = { + {1280, 720, 3, + {kSimStream4Bitrate, kSimStream4MaxBitrateBoosted, + kSimStream4MaxBitrateXd}, + {kSimStream4Bitrate, kSimStream4TargetBitrateBoosted, + kSimStream4TargetBitrateXd}}, + {960, 540, 3, + {kSimStream3Bitrate, kSimStream3MaxBitrateBoosted, + kSimStream3MaxBitrateXd}, + {kSimStream3Bitrate, kSimStream3TargetBitrateBoosted, + kSimStream3TargetBitrateXd}}, + {640, 360, 2, + {kSimStream2Bitrate, kSimStream2MaxBitrateBoosted, + kSimStream2MaxBitrateXd}, + {kSimStream2Bitrate, kSimStream2TargetBitrateBoosted, + kSimStream2TargetBitrateXd}}, + {480, 270, 2, + {kSimStream1Bitrate, kSimStream1MaxBitrateBoosted, + kSimStream1MaxBitrateXd}, + {kSimStream1Bitrate, kSimStream1TargetBitrateBoosted, + kSimStream1TargetBitrateXd}}, + {320, 180, 1, + {kSimStream0Bitrate, kSimStream0MaxBitrateBoosted, + kSimStream0MaxBitrateXd}, + {kSimStream0Bitrate, kSimStream0TargetBitrateBoosted, + kSimStream0TargetBitrateXd}}, + {0, 0, 1, + {kSimStream0Bitrate, kSimStream0MaxBitrateBoosted, + kSimStream0MaxBitrateXd}, + {kSimStream0Bitrate, kSimStream0TargetBitrateBoosted, + kSimStream0TargetBitrateXd}} +}; + +// Test fixture to test WebRtcVideoEngine with a fake webrtc::VideoEngine. +// Useful for testing failure paths. +class WebRtcVideoEngineSimulcastTestFake : public testing::Test, + public sigslot::has_slots<> { + public: + WebRtcVideoEngineSimulcastTestFake() + : vie_(kVideoCodecs, ARRAY_SIZE(kVideoCodecs)), + cpu_monitor_(new rtc::FakeCpuMonitor( + rtc::Thread::Current())), + engine_(NULL, // cricket::WebRtcVoiceEngineExtended + new FakeViEWrapper(&vie_), cpu_monitor_), + channel_(NULL), + voice_channel_(NULL), + last_error_(cricket::VideoMediaChannel::ERROR_NONE) { + } + bool SetupEngine() { + bool result = engine_.Init(rtc::Thread::Current()); + if (result) { + channel_ = engine_.CreateChannel(cricket::VideoOptions(), voice_channel_); + channel_->SignalMediaError.connect(this, + &WebRtcVideoEngineSimulcastTestFake::OnMediaError); + result = (channel_ != NULL); + } + return result; + } + void OnMediaError(uint32 ssrc, cricket::VideoMediaChannel::Error error) { + last_error_ = error; + } + bool SendI420Frame(int width, int height) { + cricket::FakeVideoCapturer capturer; + return SendI420Frame(&capturer, width, height); + } + + bool SendI420Frame( + cricket::FakeVideoCapturer* capturer, int width, int height) { + if (NULL == channel_) { + return false; + } + cricket::WebRtcVideoFrame frame; + if (!frame.InitToBlack(width, height, 1, 1, 0, 0)) { + return false; + } + channel_->SendFrame(capturer, &frame); + return true; + } + bool SendI420ScreencastFrame(int width, int height) { + return SendI420ScreencastFrameWithTimestamp(width, height, 0); + } + bool SendI420ScreencastFrameWithTimestamp( + int width, int height, int64 timestamp) { + if (NULL == channel_) { + return false; + } + cricket::WebRtcVideoFrame frame; + if (!frame.InitToBlack(width, height, 1, 1, 0, 0)) { + return false; + } + cricket::FakeVideoCapturer capturer; + capturer.SetScreencast(true); + channel_->SendFrame(&capturer, &frame); + return true; + } + void VerifyVP8SendCodec( + int channel_num, + unsigned int width, + unsigned int height, + unsigned int layers = 0, + unsigned int max_bitrate = kMaxBandwidthKbps, + unsigned int min_bitrate = kMinBandwidthKbps, + unsigned int start_bitrate = kStartBandwidthKbps, + unsigned int fps = 30, + unsigned int max_quantization = 0, + cricket::SimulcastBitrateMode bitrate_mode = cricket::SBM_NORMAL) { + webrtc::VideoCodec gcodec; + EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec)); + + // Video codec properties. + EXPECT_EQ(webrtc::kVideoCodecVP8, gcodec.codecType); + EXPECT_STREQ("VP8", gcodec.plName); + EXPECT_EQ(100, gcodec.plType); + EXPECT_EQ(width, gcodec.width); + EXPECT_EQ(height, gcodec.height); + EXPECT_EQ(rtc::_min(start_bitrate, max_bitrate), gcodec.startBitrate); + EXPECT_EQ(max_bitrate, gcodec.maxBitrate); + EXPECT_EQ(min_bitrate, gcodec.minBitrate); + EXPECT_EQ(fps, gcodec.maxFramerate); + // VP8 specific. + EXPECT_FALSE(gcodec.codecSpecific.VP8.pictureLossIndicationOn); + EXPECT_FALSE(gcodec.codecSpecific.VP8.feedbackModeOn); + EXPECT_EQ(webrtc::kComplexityNormal, gcodec.codecSpecific.VP8.complexity); + EXPECT_EQ(webrtc::kResilienceOff, gcodec.codecSpecific.VP8.resilience); + EXPECT_EQ(max_quantization, gcodec.qpMax); + // Simulcast. + EXPECT_EQ(layers, gcodec.numberOfSimulcastStreams); + if (layers > 0) { + EXPECT_EQ(kSimulcastNumberOfTemporalLayers, + gcodec.codecSpecific.VP8.numberOfTemporalLayers); + } else { + EXPECT_EQ(kNumberOfTemporalLayers, + gcodec.codecSpecific.VP8.numberOfTemporalLayers); + } + for (int i = 0; i < static_cast(layers) - 1; ++i) { + EXPECT_EQ(gcodec.width / (0x01 << (layers - 1 - i)), + gcodec.simulcastStream[i].width); + EXPECT_EQ(gcodec.height / (0x01 << (layers - 1 - i)), + gcodec.simulcastStream[i].height); + EXPECT_EQ(kSimulcastNumberOfTemporalLayers, + gcodec.simulcastStream[i].numberOfTemporalLayers); + EXPECT_EQ(FindSimulcastMaxBitrate(gcodec.simulcastStream[i].width, + gcodec.simulcastStream[i].height, + bitrate_mode), + gcodec.simulcastStream[i].maxBitrate); + EXPECT_EQ(gcodec.qpMax, + gcodec.simulcastStream[i].qpMax); + } + if (layers > 0) + EXPECT_EQ(kMinBandwidthKbps, gcodec.simulcastStream[0].minBitrate); + } + unsigned int FindSimulcastMaxBitrate( + unsigned int width, + unsigned int height, + cricket::SimulcastBitrateMode bitrate_mode) { + for (size_t i = 0; i < ARRAY_SIZE(kSimulcastFormats); ++i) { + if (width >= kSimulcastFormats[i].width && + height >= kSimulcastFormats[i].height) { + return kSimulcastFormats[i].max_bitrate[bitrate_mode]; + } + } + return 0; + } + void SetUp2SimulcastStreams( + cricket::VideoOptions::HighestBitrate high_bitrate_mode, + const cricket::VideoCodec& codec) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + options.video_highest_bitrate.Set(high_bitrate_mode); + EXPECT_TRUE(channel_->SetOptions(options)); + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + std::vector codec_list; + codec_list.push_back(codec); + SendI420Frame(kVP8Codec.width, kVP8Codec.height); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + } + void SetUp3SimulcastStreams( + cricket::VideoOptions::HighestBitrate high_bitrate_mode, + const cricket::VideoCodec& codec) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + options.video_highest_bitrate.Set(high_bitrate_mode); + EXPECT_TRUE(channel_->SetOptions(options)); + + // Set max settings of 1280x720x30 to make sure we can have maximumly 3 + // simulcast streams. + EXPECT_TRUE(engine_.SetDefaultEncoderConfig( + cricket::VideoEncoderConfig(kVP8Codec720p))); + + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs3)))); + std::vector codec_list; + codec_list.push_back(codec); + SendI420Frame(kVP8Codec720p.width, kVP8Codec720p.height); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + } + void TestSimulcastAdapter(const cricket::VideoCodec& send_codec, + bool expect_use_adapter) { + // Setup + engine_.SetExternalEncoderFactory(&encoder_factory_); + EXPECT_TRUE(SetupEngine()); + std::vector codecs; + codecs.push_back(send_codec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(channel_->AddSendStream( + cricket::StreamParams::CreateLegacy(kSsrc))); + + // Verify + int channel_num = vie_.GetLastChannel(); + EXPECT_EQ(1, vie_.GetNumExternalEncoderRegistered(channel_num)); + // If simulcast adapter is used, no external encoder instance will be + // created at this point. Otherwise 1 instance should have been created. + int num_enc_instance = expect_use_adapter ? 0 : 1; + EXPECT_EQ(num_enc_instance, encoder_factory_.GetNumCreatedEncoders()); + + // Clean up + EXPECT_TRUE(channel_->RemoveSendStream(kSsrc)); + } + virtual void TearDown() { + delete channel_; + engine_.Terminate(); + } + + protected: + cricket::FakeWebRtcVideoEngine vie_; + cricket::FakeWebRtcVideoDecoderFactory decoder_factory_; + cricket::FakeWebRtcVideoEncoderFactory encoder_factory_; + rtc::FakeCpuMonitor* cpu_monitor_; + cricket::WebRtcVideoEngine engine_; + cricket::WebRtcVideoMediaChannel* channel_; + cricket::WebRtcVoiceMediaChannel* voice_channel_; + cricket::VideoMediaChannel::Error last_error_; +}; + +// Test fixtures to test WebRtcVideoEngine with a real +// webrtc::VideoEngine. +class WebRtcVideoEngineSimulcastTest + : public VideoEngineTest { + protected: + typedef VideoEngineTest Base; +}; +class WebRtcVideoMediaChannelSimulcastTest + : public VideoMediaChannelTest< + cricket::WebRtcVideoEngine, cricket::WebRtcVideoMediaChannel> { + protected: + typedef VideoMediaChannelTest Base; + virtual cricket::VideoCodec DefaultCodec() { return kVP8Codec; } + virtual void SetUp() { + Base::SetUp(); + } + virtual void TearDown() { + Base::TearDown(); + } + // Tests that simulcast sends rtp packets containing expected number of + // layers and payload type. + void SimulcastSend(const cricket::VideoCodec& codec, + const std::vector& ssrcs) { + // Remove stream added in Setup. + EXPECT_TRUE(channel_->RemoveSendStream(kSsrc)); + + // Setup channel for sending and receiving simulcast packets. + cricket::VideoOptions vmo; + vmo.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(vmo)); + EXPECT_TRUE(SetOneCodec(codec)); + if (ssrcs.size() > 1) { + cricket::StreamParams sp; + cricket::SsrcGroup sg(cricket::kSimSsrcGroupSemantics, ssrcs); + sp.ssrcs = ssrcs; + sp.ssrc_groups.push_back(sg); + sp.cname = "cname"; + EXPECT_TRUE(channel_->AddSendStream(sp)); + } else { + EXPECT_TRUE(channel_->AddSendStream( + cricket::StreamParams::CreateLegacy(ssrcs[0]))); + } + + // Re-configure capturer. + channel_->SetCapturer(kSsrc, NULL); + cricket::VideoFormat capture_format(codec.width, codec.height, + cricket::VideoFormat::FpsToInterval(codec.framerate), + cricket::FOURCC_I420); + EXPECT_TRUE(channel_->SetCapturer(ssrcs[0], video_capturer_.get())); + EXPECT_EQ(cricket::CS_RUNNING, video_capturer_->Start(capture_format)); + + EXPECT_TRUE(SetSend(true)); + + // Send one frame. + EXPECT_EQ(0, NumRtpPackets()); + EXPECT_EQ(0, NumRtpBytes()); + EXPECT_EQ(0, NumSentSsrcs()); + EXPECT_TRUE(SendFrame()); + + // Verify received expected simulcast layers. + EXPECT_TRUE_WAIT(NumSentSsrcs() == static_cast(ssrcs.size()), + 10 * kTimeout); + + // Stop sending before counting the packets, as padding will keep being sent + // which can cause a difference between total number of packets and the + // sum of packets on each ssrc if padding is sent in the summation loop. + EXPECT_TRUE(SetSend(false)); + + int total_num_packets = 0; + int total_num_bytes = 0; + for (size_t i = 0; i < ssrcs.size(); ++i) { + EXPECT_GE(NumRtpPackets(ssrcs[i]), 1); + EXPECT_GT(NumRtpBytes(ssrcs[i]), NumRtpPackets(ssrcs[i])); + total_num_packets += NumRtpPackets(ssrcs[i]); + total_num_bytes += NumRtpBytes(ssrcs[i]); + } + EXPECT_EQ(total_num_packets, NumRtpPackets()); + EXPECT_EQ(total_num_bytes, NumRtpBytes()); + + // All packets have the given payload type. + for (int i = 0; i < total_num_packets; ++i) { + rtc::scoped_ptr p(GetRtpPacket(i)); + EXPECT_EQ(codec.id, GetPayloadType(p.get())); + } + } + void TestScreencastSettings() { + // Remove stream added in SetUp. + EXPECT_TRUE(channel_->RemoveSendStream(kSsrc)); + + cricket::VideoOptions options; + options.conference_mode.Set(true); + // Enable noise reduction to ensure that it's being disabled for + // screencasting. + options.video_noise_reduction.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + + // Setup a simulcast stream and verify that we have the correct + // settings. + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + cricket::VideoCodec codec(DefaultCodec()); + EXPECT_TRUE(SetOneCodec(codec)); + + channel_->SetCapturer(kSsrc, NULL); + cricket::VideoFormat capture_format(codec.width, codec.height, + cricket::VideoFormat::FpsToInterval(codec.framerate), + cricket::FOURCC_I420); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs2[0], video_capturer_.get())); + EXPECT_EQ(cricket::CS_RUNNING, video_capturer_->Start(capture_format)); + + EXPECT_TRUE(SetSend(true)); + EXPECT_TRUE(SendFrame()); + webrtc::VideoCodec video_codec; + int default_channel_id = channel_->GetDefaultChannelId(); + EXPECT_EQ(0, channel_->engine()->vie()->codec()->GetSendCodec( + default_channel_id, video_codec)); + EXPECT_FALSE(video_codec.codecSpecific.VP8.automaticResizeOn); + EXPECT_TRUE(video_codec.codecSpecific.VP8.denoisingOn); + EXPECT_EQ(webrtc::kRealtimeVideo, video_codec.mode); + EXPECT_EQ(3, video_codec.codecSpecific.VP8.numberOfTemporalLayers); + EXPECT_EQ(2, video_codec.numberOfSimulcastStreams); + EXPECT_NE(0u, video_codec.simulcastStream[0].minBitrate); + EXPECT_NE(0u, video_codec.simulcastStream[0].targetBitrate); + EXPECT_NE(0u, video_codec.simulcastStream[0].maxBitrate); + EXPECT_NE(0u, video_codec.simulcastStream[1].minBitrate); + EXPECT_NE(0u, video_codec.simulcastStream[1].targetBitrate); + EXPECT_NE(0u, video_codec.simulcastStream[1].maxBitrate); + + // Register a fake screencast capturer and verify that the video settings + // change as expected. + rtc::scoped_ptr capturer( + new cricket::FakeVideoCapturer); + capturer->SetScreencast(true); + const std::vector* formats = + capturer->GetSupportedFormats(); + capture_format = (*formats)[0]; + EXPECT_EQ(cricket::CS_RUNNING, capturer->Start(capture_format)); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs2[0], capturer.get())); + EXPECT_TRUE(rtc::Thread::Current()->ProcessMessages(30)); + EXPECT_TRUE(capturer->CaptureFrame()); + default_channel_id = channel_->GetDefaultChannelId(); + EXPECT_EQ(0, channel_->engine()->vie()->codec()->GetSendCodec( + default_channel_id, video_codec)); + EXPECT_FALSE(video_codec.codecSpecific.VP8.automaticResizeOn); + EXPECT_FALSE(video_codec.codecSpecific.VP8.denoisingOn); + EXPECT_EQ(webrtc::kScreensharing, video_codec.mode); + const int expected_num_layers = 2; + EXPECT_EQ(expected_num_layers, + video_codec.codecSpecific.VP8.numberOfTemporalLayers); + EXPECT_EQ(2, video_codec.numberOfSimulcastStreams); + EXPECT_EQ(0u, video_codec.simulcastStream[0].minBitrate); + EXPECT_EQ(0u, video_codec.simulcastStream[0].targetBitrate); + EXPECT_EQ(0u, video_codec.simulcastStream[0].maxBitrate); + + // Make sure that removing screencast restores the simulcast settings. + EXPECT_TRUE(channel_->SetCapturer(kSsrcs2[0], video_capturer_.get())); + EXPECT_TRUE(WaitAndSendFrame(30)); + EXPECT_EQ(0, channel_->engine()->vie()->codec()->GetSendCodec( + default_channel_id, video_codec)); + EXPECT_FALSE(video_codec.codecSpecific.VP8.automaticResizeOn); + EXPECT_TRUE(video_codec.codecSpecific.VP8.denoisingOn); + EXPECT_EQ(webrtc::kRealtimeVideo, video_codec.mode); + EXPECT_EQ(3, video_codec.codecSpecific.VP8.numberOfTemporalLayers); + EXPECT_EQ(2, video_codec.numberOfSimulcastStreams); + EXPECT_NE(0u, video_codec.simulcastStream[0].minBitrate); + EXPECT_NE(0u, video_codec.simulcastStream[0].targetBitrate); + EXPECT_NE(0u, video_codec.simulcastStream[0].maxBitrate); + EXPECT_NE(0u, video_codec.simulcastStream[1].minBitrate); + EXPECT_NE(0u, video_codec.simulcastStream[1].targetBitrate); + EXPECT_NE(0u, video_codec.simulcastStream[1].maxBitrate); + } +}; + +// Test that we apply send codec with simulcast properly. +TEST_F(WebRtcVideoEngineSimulcastTestFake, SetSendCodecsWith1SimulcastStreams) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + + // One ssrc simulcast StreamParams should be rejected. + EXPECT_FALSE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs1)))); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, SetSendCodecsWith2SimulcastStreams) { + cricket::VideoCodec codec(kVP8Codec); + SetUp2SimulcastStreams(cricket::VideoOptions::NORMAL, codec); + int channel_num = vie_.GetLastChannel(); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 2, + kSimStream0Bitrate + kSimStream2MaxBitrateBoosted); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, SetSendCodecsWith3SimulcastStreams) { + cricket::VideoCodec codec(kVP8Codec720p); + SetUp3SimulcastStreams(cricket::VideoOptions::NORMAL, codec); + int channel_num = vie_.GetLastChannel(); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 3, + kSimStream0Bitrate + kSimStream2Bitrate + + kSimStream4Bitrate); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, + SetSendCodecsWith2SimulcastStreamsHighBitrateMode) { + cricket::VideoCodec codec(kVP8Codec); + SetUp2SimulcastStreams(cricket::VideoOptions::HIGH, codec); + int channel_num = vie_.GetLastChannel(); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 2, + kSimStream0TargetBitrateBoosted + + kSimStream2MaxBitrateBoosted, kMinBandwidthKbps, + kStartBandwidthKbps, 30, 0, cricket::SBM_HIGH); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, + SetSendCodecsWith3SimulcastStreamsHighBitrateMode) { + cricket::VideoCodec codec(kVP8Codec720p); + SetUp3SimulcastStreams(cricket::VideoOptions::HIGH, codec); + int channel_num = vie_.GetLastChannel(); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 3, + kSimStream0TargetBitrateBoosted + + kSimStream2TargetBitrateBoosted + + kSimStream4MaxBitrateBoosted, kMinBandwidthKbps, + kStartBandwidthKbps, 30, 0, cricket::SBM_HIGH); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, + SetSendCodecsWith2SimulcastStreamsVeryHighBitrateMode) { + cricket::VideoCodec codec(kVP8Codec); + SetUp2SimulcastStreams(cricket::VideoOptions::VERY_HIGH, + codec); + int channel_num = vie_.GetLastChannel(); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 2, + kSimStream0TargetBitrateXd + + kSimStream2MaxBitrateXd, kMinBandwidthKbps, + kStartBandwidthKbps, 30, 0, cricket::SBM_VERY_HIGH); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, + SetSendCodecsWith3SimulcastStreamsVeryHighBitrateMode) { + cricket::VideoCodec codec(kVP8Codec720p); + SetUp3SimulcastStreams(cricket::VideoOptions::VERY_HIGH, + codec); + int channel_num = vie_.GetLastChannel(); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 3, + kSimStream0TargetBitrateXd + + kSimStream2TargetBitrateXd + + kSimStream4MaxBitrateXd, kMinBandwidthKbps, + kStartBandwidthKbps, 30, 0, cricket::SBM_VERY_HIGH); +} + +// Test that we normalize send codec format size in simulcast. +TEST_F(WebRtcVideoEngineSimulcastTestFake, + SetSendCodecsWithOddSizeInSimulcast) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + int channel_num = vie_.GetLastChannel(); + + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + + // Set send codec and verify we get two simulcast sub-streams with + // normalized size. + cricket::VideoCodec codec(kVP8Codec270p); + codec.width += 0x01; + codec.height += 0x01; + std::vector codec_list; + codec_list.push_back(codec); + SendI420Frame(kVP8Codec.width, kVP8Codec.height); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + + codec.width &= ~0x01; + codec.height &= ~0x01; + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 2, + kSimStream0Bitrate + kSimStream1MaxBitrateBoosted); +} + +// Test that if we add a stream with RTX SSRC's, SSRC's get set correctly. +TEST_F(WebRtcVideoEngineSimulcastTestFake, TestStreamWithRtx) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + int channel_num = vie_.GetLastChannel(); + + // Set max settings of 1280x720x30 to make sure we can have maximumly 3 + // simulcast streams. + EXPECT_TRUE(engine_.SetDefaultEncoderConfig( + cricket::VideoEncoderConfig(kVP8Codec720p))); + + // Verify only the first SSRC is set here. + // The rest are set during SetSendCodecs. + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimWithRtxStreamParams("cname", + MAKE_VECTOR(kSsrcs3), + MAKE_VECTOR(kRtxSsrcs3)))); + EXPECT_EQ(1, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(1, vie_.GetNumRtxSsrcs(channel_num)); + + std::vector codec_list; + codec_list.push_back(kVP8Codec720p); + cricket::VideoCodec rtx_codec(96, "rtx", 0, 0, 0, 0); + rtx_codec.SetParam("apt", kVP8Codec.id); + codec_list.push_back(rtx_codec); + SendI420Frame(kVP8Codec720p.width, kVP8Codec720p.height); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + + // RTX payload type should now be set. + EXPECT_EQ(96, vie_.GetRtxSendPayloadType(channel_num)); + + // Verify all SSRCs are set after SetSendCodecs. + EXPECT_EQ(3, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(3, vie_.GetNumRtxSsrcs(channel_num)); + EXPECT_EQ(1, vie_.GetSsrc(channel_num, 0)); + EXPECT_EQ(2, vie_.GetSsrc(channel_num, 1)); + EXPECT_EQ(3, vie_.GetSsrc(channel_num, 2)); + EXPECT_EQ(4, vie_.GetRtxSsrc(channel_num, 0)); + EXPECT_EQ(5, vie_.GetRtxSsrc(channel_num, 1)); + EXPECT_EQ(6, vie_.GetRtxSsrc(channel_num, 2)); + uint32 ssrc; + EXPECT_EQ(0, vie_.GetLocalSSRC(channel_num, ssrc)); + EXPECT_EQ(1U, ssrc); +} + +// Test that if we get too few ssrcs are given in AddSendStream(), +// only supported sub-streams will be added. +TEST_F(WebRtcVideoEngineSimulcastTestFake, TooFewSimulcastSsrcs) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + int channel_num = vie_.GetLastChannel(); + + // Set max settings of 1280x720x30 to make sure we can have maximumly 3 + // simulcast streams. + EXPECT_TRUE(engine_.SetDefaultEncoderConfig( + cricket::VideoEncoderConfig(kVP8Codec720p))); + + // Verify only the first SSRC is set here. + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + EXPECT_EQ(1, vie_.GetNumSsrcs(channel_num)); + + cricket::VideoCodec codec(kVP8Codec720p); + std::vector codec_list; + codec_list.push_back(codec); + SendI420Frame(kVP8Codec720p.width, kVP8Codec720p.height); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + + // Verify all SSRCs are set after SetSendCodecs. + EXPECT_EQ(2, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(1, vie_.GetSsrc(channel_num, 0)); + EXPECT_EQ(2, vie_.GetSsrc(channel_num, 1)); + uint32 ssrc; + EXPECT_EQ(0, vie_.GetLocalSSRC(channel_num, ssrc)); + EXPECT_EQ(1U, ssrc); + VerifyVP8SendCodec(channel_num, 640, 360, 2, + kSimStream0Bitrate + kSimStream2MaxBitrateBoosted); +} + +// Test that even more than enough ssrcs are given in AddSendStream(), +// only supported sub-streams will be added. +TEST_F(WebRtcVideoEngineSimulcastTestFake, MoreThanEnoughSimulcastSscrs) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + int channel_num = vie_.GetLastChannel(); + + // Set max settings of 1280x720x30 to make sure we can have maximumly 3 + // simulcast streams. + EXPECT_TRUE(engine_.SetDefaultEncoderConfig( + cricket::VideoEncoderConfig(kVP8Codec720p))); + + // Verify only the first SSRC is set here. + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs4)))); + EXPECT_EQ(1, vie_.GetNumSsrcs(channel_num)); + + cricket::VideoCodec codec(kVP8Codec720p); + std::vector codec_list; + codec_list.push_back(codec); + SendI420Frame(kVP8Codec720p.width, kVP8Codec720p.height); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + + // Verify all SSRCs are set after SetSendCodecs. + EXPECT_EQ(3, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(1, vie_.GetSsrc(channel_num, 0)); + EXPECT_EQ(2, vie_.GetSsrc(channel_num, 1)); + EXPECT_EQ(3, vie_.GetSsrc(channel_num, 2)); + uint32 ssrc; + EXPECT_EQ(0, vie_.GetLocalSSRC(channel_num, ssrc)); + EXPECT_EQ(1U, ssrc); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 3, + kSimStream0Bitrate + kSimStream2Bitrate + kSimStream4Bitrate); +} + +// Test that SetSendStreamFormat works well with simulcast. +TEST_F(WebRtcVideoEngineSimulcastTestFake, SetSendStreamFormatWithSimulcast) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + int channel_num = vie_.GetLastChannel(); + + // Verify SetSendStreamFormat fail before there is a send stream. + cricket::VideoFormat format = engine_.default_codec_format(); + EXPECT_FALSE(channel_->SetSendStreamFormat(1, format)); + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + + // Capture format HD + cricket::FakeVideoCapturer video_capturer; + const std::vector* formats = + video_capturer.GetSupportedFormats(); + cricket::VideoFormat capture_format_hd = (*formats)[0]; + EXPECT_EQ(cricket::CS_RUNNING, video_capturer.Start(capture_format_hd)); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs2[0], &video_capturer)); + + cricket::VideoCodec codec(kVP8Codec360p); + std::vector codec_list; + codec_list.push_back(codec); + SendI420Frame(&video_capturer, kVP8Codec.width, kVP8Codec.height); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + + EXPECT_EQ(2, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(1, vie_.GetSsrc(channel_num, 0)); + EXPECT_EQ(2, vie_.GetSsrc(channel_num, 1)); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 2, + kSimStream0Bitrate + kSimStream2MaxBitrateBoosted); + + // Verify SetSendStreamFormat with the same resolution doesn't + // change the number of simulcast sub-streams. + format.width = codec.width; + format.height = codec.height; + EXPECT_TRUE(channel_->SetSendStreamFormat(1, format)); + EXPECT_EQ(2, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(1, vie_.GetSsrc(channel_num, 0)); + EXPECT_EQ(2, vie_.GetSsrc(channel_num, 1)); + + // Capture format HD -> adapt (OnOutputFormatRequest VGA) -> VGA. + EXPECT_TRUE(video_capturer.CaptureFrame()); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 2, + kSimStream0Bitrate + kSimStream2MaxBitrateBoosted); + + // Now, ask for a small resolution and verify that we only get one stream. + format.width = codec.width / 2; + format.height = codec.height / 2; + EXPECT_TRUE(channel_->SetSendStreamFormat(1, format)); + // Capture format HD -> adapt (OnOutputFormatRequest QVGA) -> QVGA. + EXPECT_TRUE(video_capturer.CaptureFrame()); + VerifyVP8SendCodec(channel_num, format.width, format.height, 1, + kSimStream0MaxBitrateBoosted); + + // Bump resolution back and verify there are 2 sub-streams again. + format.width = codec.width; + format.height = codec.height; + EXPECT_TRUE(channel_->SetSendStreamFormat(1, format)); + EXPECT_EQ(2, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(1, vie_.GetSsrc(channel_num, 0)); + EXPECT_EQ(2, vie_.GetSsrc(channel_num, 1)); + // Capture format HD -> adapt (OnOutputFormatRequest VGA) -> VGA. + EXPECT_TRUE(video_capturer.CaptureFrame()); + VerifyVP8SendCodec(channel_num, format.width, format.height, 2, + kSimStream0Bitrate + kSimStream2MaxBitrateBoosted); + + // Verify SetSendStreamFormat can use either ssrc in the simulcast + // ssrc group. + format.width = codec.width / 2; + format.height = codec.height / 2; + EXPECT_TRUE(channel_->SetSendStreamFormat(2, format)); + EXPECT_EQ(2, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(1, vie_.GetSsrc(channel_num, 0)); + EXPECT_EQ(2, vie_.GetSsrc(channel_num, 1)); + // Capture format HD -> adapt (OnOutputFormatRequest QVGA) -> QVGA. + EXPECT_TRUE(video_capturer.CaptureFrame()); + VerifyVP8SendCodec(channel_num, format.width, format.height, 1, + kSimStream0MaxBitrateBoosted); + + // Verify SetSendStreamFormat with bad ssrc fail. + format.width = codec.width; + format.height = codec.height; + EXPECT_FALSE(channel_->SetSendStreamFormat(3, format)); + EXPECT_EQ(2, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(1, vie_.GetSsrc(channel_num, 0)); + EXPECT_EQ(2, vie_.GetSsrc(channel_num, 1)); + VerifyVP8SendCodec(channel_num, format.width / 2, format.height / 2, 1, + kSimStream0MaxBitrateBoosted); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs2[0], NULL)); +} + +// Test that simulcast send codec is reset on new video frame size. +TEST_F(WebRtcVideoEngineSimulcastTestFake, + ResetSimulcastSendCodecOnNewFrameSize) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + int channel_num = vie_.GetLastChannel(); + + // Set send codec. + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + cricket::VideoCodec codec(kVP8Codec); + std::vector codec_list; + codec_list.push_back(codec); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + EXPECT_TRUE(channel_->SetSend(true)); + EXPECT_EQ(1, vie_.GetNumSetSendCodecs()); + + // Feed in frames with different sizes and verify simulcast send codec + // changes accordingly. + + // Send an odd frame. Verify format size get normalized. + SendI420Frame(kVP8Codec.width | 0x01, kVP8Codec.height | 0x01); + VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height, 2, + kSimStream0Bitrate + kSimStream2MaxBitrateBoosted); + EXPECT_EQ(2, vie_.GetNumSetSendCodecs()); + + // Send a big frame. Verify format size never exceed original send codec + // format size. + SendI420Frame(kVP8Codec.width * 2, kVP8Codec.height * 2); + VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height, 2, + kSimStream0Bitrate + kSimStream2MaxBitrateBoosted); + EXPECT_EQ(2, vie_.GetNumSetSendCodecs()); + + // Send a small frame. Verify format size is bumped in order to meet + // the minimum simulcast reqirement while keeping the aspect ratio. + SendI420Frame(kVP8Codec.width / 2, kVP8Codec.height / 2); + // Need at least 480x270 to have simulcast. + VerifyVP8SendCodec(channel_num, kVP8Codec.width / 2, kVP8Codec.height / 2, 1, + kSimStream0MaxBitrateBoosted); + EXPECT_EQ(3, vie_.GetNumSetSendCodecs()); + + // Send a normal frame. Verify format size is back to normal. + SendI420Frame(kVP8Codec.width, kVP8Codec.height); + VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height, 2, + kSimStream0Bitrate + kSimStream2MaxBitrateBoosted); + EXPECT_EQ(4, vie_.GetNumSetSendCodecs()); + + // Send an odd frame again. + SendI420Frame(kVP8Codec.width | 0x01, kVP8Codec.height | 0x01); + VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height, 2, + kSimStream0Bitrate + kSimStream2MaxBitrateBoosted); + EXPECT_EQ(4, vie_.GetNumSetSendCodecs()); +} + +// Test that simulcast send codec is reset on new portait mode video frame. +TEST_F(WebRtcVideoEngineSimulcastTestFake, + ResetSimulcastSendCodecOnNewPortaitFrame) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + int channel_num = vie_.GetLastChannel(); + + // Set send codec. + EXPECT_TRUE(engine_.SetDefaultEncoderConfig( + cricket::VideoEncoderConfig(kVP8Codec720p))); + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + cricket::VideoCodec codec(kVP8Codec720p); + std::vector codec_list; + codec_list.push_back(codec); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + EXPECT_TRUE(channel_->SetSend(true)); + EXPECT_EQ(1, vie_.GetNumSetSendCodecs()); + + // Feed in frames with different sizes and verify simulcast send codec + // changes accordingly. + + // Send a portait mode frame. + SendI420Frame(kVP8Codec.height, kVP8Codec.width); + VerifyVP8SendCodec(channel_num, kVP8Codec.height, kVP8Codec.width, 2, + kSimStream0Bitrate + kSimStream2MaxBitrateBoosted); + EXPECT_EQ(2, vie_.GetNumSetSendCodecs()); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, + SetBandwidthInConferenceWithSimulcast) { + cricket::VideoCodec ccodec(kVP8Codec720p); + SetUp3SimulcastStreams(cricket::VideoOptions::NORMAL, ccodec); + + // A higher value than the sum of max of all layers should add the extra + // bandwidth to the higher layer. + uint32 max_bandwidth = 5000; + EXPECT_TRUE(channel_->SetMaxSendBandwidth(max_bandwidth * 1000)); + + int channel_num = vie_.GetLastChannel(); + webrtc::VideoCodec codec; + codec.width = 0; codec.height = 0; + EXPECT_EQ(0, vie_.GetSendCodec(channel_num, codec)); + EXPECT_EQ(max_bandwidth, codec.maxBitrate); + + int nr_layers = 3; + uint32 sum_bandwidth = 0; + for (int i = 0; i < nr_layers; ++i) { + if (i == nr_layers - 1) { + EXPECT_EQ(max_bandwidth - sum_bandwidth, + codec.simulcastStream[i].maxBitrate); + } else { + EXPECT_EQ(FindSimulcastMaxBitrate(codec.simulcastStream[i].width, + codec.simulcastStream[i].height, + cricket::SBM_NORMAL), + codec.simulcastStream[i].maxBitrate); + sum_bandwidth += codec.simulcastStream[i].maxBitrate; + } + } + + // Test setting max bandwidth below the sum of layer specific max bandwidth. + // Basically, the simulcast max overrides the SetMaxSendBandwidth value. + EXPECT_TRUE(channel_->SetMaxSendBandwidth(1)); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 3, + kSimStream0Bitrate + kSimStream2Bitrate + + kSimStream4Bitrate); +} + +// Test that sending screencast frames in conference mode changes +// bitrate. +TEST_F(WebRtcVideoEngineSimulcastTestFake, SetBandwidthScreencastInConference) { + EXPECT_TRUE(SetupEngine()); + int channel_num = vie_.GetLastChannel(); + + // Set send codec. + cricket::VideoCodec codec(kVP8Codec); + std::vector codec_list; + codec_list.push_back(codec); + EXPECT_TRUE(channel_->AddSendStream( + cricket::StreamParams::CreateLegacy(123))); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + EXPECT_TRUE(channel_->SetMaxSendBandwidth(1200000)); + EXPECT_EQ(2, vie_.GetNumSetSendCodecs()); + + // Set conference mode and verify that this caps maxBitrate. + webrtc::VideoCodec gcodec; + EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec)); + EXPECT_GT(gcodec.maxBitrate, 1000u); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + EXPECT_TRUE(channel_->SetSend(true)); + SendI420ScreencastFrame(kVP8Codec.width, kVP8Codec.height); + EXPECT_EQ(3, vie_.GetNumSetSendCodecs()); + EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec)); + EXPECT_EQ(1000u, gcodec.maxBitrate); +} + +// Test AddSendStream with simulcast rejects bad StreamParams. +TEST_F(WebRtcVideoEngineSimulcastTestFake, AddSendStreamWithBadStreamParams) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + + // Verify non-sim StreamParams is rejected. + cricket::StreamParams sp_bad; + std::vector ssrcs; + ssrcs.push_back(1234); + ssrcs.push_back(5678); + cricket::SsrcGroup sg("bad", ssrcs); + sp_bad.ssrcs = ssrcs; + sp_bad.ssrc_groups.push_back(sg); + sp_bad.cname = "cname"; + EXPECT_FALSE(channel_->AddSendStream(sp_bad)); + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); +} + +// Test AddSendStream with simulcast sets ssrc and cname correctly. +TEST_F(WebRtcVideoEngineSimulcastTestFake, AddSendStreamWithSimulcast) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + int channel_num = vie_.GetLastChannel(); + + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + + unsigned int ssrc = 0; + EXPECT_EQ(1, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(0, vie_.GetLocalSSRC(channel_num, ssrc)); + EXPECT_EQ(1U, ssrc); + + char rtcp_cname[256]; + EXPECT_EQ(0, vie_.GetRTCPCName(channel_num, rtcp_cname)); + EXPECT_STREQ("cname", rtcp_cname); + + EXPECT_TRUE(channel_->RemoveSendStream(1)); +} + +// Test RemoveSendStream with simulcast. +TEST_F(WebRtcVideoEngineSimulcastTestFake, RemoveSendStreamWithSimulcast) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + + // RemoveSendStream should fail when there is no send stream. + EXPECT_FALSE(channel_->RemoveSendStream(1)); + + cricket::StreamParams stream = + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)); + EXPECT_TRUE(channel_->AddSendStream(stream)); + + // RemoveSendStream should fail with bad ssrc. + EXPECT_FALSE(channel_->RemoveSendStream(3)); + EXPECT_TRUE(channel_->RemoveSendStream(1)); + + // RemoveSendStream should work with either ssrc. + EXPECT_TRUE(channel_->AddSendStream(stream)); + EXPECT_TRUE(channel_->RemoveSendStream(1)); + EXPECT_TRUE(channel_->AddSendStream(stream)); + EXPECT_TRUE(channel_->RemoveSendStream(2)); + + // RemoveSendStream should fail when there is no send stream. + EXPECT_FALSE(channel_->RemoveSendStream(1)); +} + +// Test AddSendStream after send codec has already been set will reset +// send codec with simulcast settings. +TEST_F(WebRtcVideoEngineSimulcastTestFake, + AddSimulcastStreamAfterSetSendCodec) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + int channel_num = vie_.GetLastChannel(); + + // Set send codec. + cricket::VideoCodec codec(kVP8Codec); + std::vector codec_list; + codec_list.push_back(codec); + SendI420Frame(kVP8Codec.width, kVP8Codec.height); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + VerifyVP8SendCodec(channel_num, codec.width, codec.height); + + // Add simulcast send stream and verify send codec has been reset. + // Provide 3 ssrcs here and only 2 layers should be added. + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs3)))); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 2, + kSimStream0Bitrate + kSimStream2MaxBitrateBoosted); + + // Verify local SSRCs are now populated correctly. + EXPECT_EQ(2, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(1, vie_.GetSsrc(channel_num, 0)); + EXPECT_EQ(2, vie_.GetSsrc(channel_num, 1)); + uint32 ssrc; + EXPECT_EQ(0, vie_.GetLocalSSRC(channel_num, ssrc)); + EXPECT_EQ(1U, ssrc); + + // Remove the stream and reset send codec with a smaller size. + EXPECT_TRUE(channel_->RemoveSendStream(1)); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 2, + kSimStream0Bitrate + kSimStream2MaxBitrateBoosted); + codec_list.clear(); + codec.width /= 2; + codec.height /= 2; + codec_list.push_back(codec); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + VerifyVP8SendCodec(channel_num, codec.width, codec.height); + + // Add simulcast send stream again and verify simulcast have one stream. + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 1, + kSimStream0MaxBitrateBoosted); + + // Verify we still have 2 SSRCs. + EXPECT_EQ(2, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(1, vie_.GetSsrc(channel_num, 0)); + EXPECT_EQ(2, vie_.GetSsrc(channel_num, 1)); + EXPECT_EQ(0, vie_.GetLocalSSRC(channel_num, ssrc)); + EXPECT_EQ(1U, ssrc); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, GetStatsWithMultipleSsrcs) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + + // Add simulcast stream. + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + + // Set send codec and verify we get two simulcast sub-streams. + cricket::VideoCodec codec(kVP8Codec); + std::vector codec_list; + codec_list.push_back(codec); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + EXPECT_TRUE(channel_->SetSend(true)); + + cricket::FakeVideoCapturer video_capturer; + const std::vector* formats = + video_capturer.GetSupportedFormats(); + cricket::VideoFormat capture_format_vga = (*formats)[1]; + EXPECT_EQ(cricket::CS_RUNNING, video_capturer.Start(capture_format_vga)); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs2[0], &video_capturer)); + + // Only require one cpu sample for testing whether the adapter is hooked up. + cricket::CoordinatedVideoAdapter* video_adapter = NULL; + ASSERT_TRUE(channel_->GetVideoAdapter(0u, &video_adapter)); + video_adapter->set_cpu_load_min_samples(1); + cpu_monitor_->SignalUpdate(1, 1, 0.1f, 0.85f); + + // Capture format VGA -> adapt (OnCpuLoadUpdate downgrade) -> VGA/2. + EXPECT_TRUE(video_capturer.CaptureFrame()); + + // Get stats and verify there are 2 ssrcs. + cricket::VideoMediaInfo info; + EXPECT_TRUE(channel_->GetStats(cricket::StatsOptions(), &info)); + ASSERT_EQ(1U, info.senders.size()); + ASSERT_EQ(2U, info.senders[0].ssrcs().size()); + EXPECT_EQ(1U, info.senders[0].ssrcs()[0]); + EXPECT_EQ(2U, info.senders[0].ssrcs()[1]); + // Verify the input/send width/height. + EXPECT_EQ(capture_format_vga.width, info.senders[0].input_frame_width); + EXPECT_EQ(capture_format_vga.height, info.senders[0].input_frame_height); + EXPECT_EQ(capture_format_vga.width * 3 / 4, + info.senders[0].send_frame_width); + EXPECT_EQ(capture_format_vga.height * 3 / 4, + info.senders[0].send_frame_height); + + EXPECT_TRUE(channel_->SetCapturer(kSsrcs2[0], NULL)); +} + +// Test receiving channel(s) local ssrc is set to the same as the first +// simulcast sending ssrc. +TEST_F(WebRtcVideoEngineSimulcastTestFake, + AddSimulcastStreamAfterCreatingRecvChannels) { + EXPECT_TRUE(SetupEngine()); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + int channel_num = vie_.GetLastChannel(); + + EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(10))); + int receive_channel_1 = vie_.GetLastChannel(); + EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(11))); + int receive_channel_2 = vie_.GetLastChannel(); + + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + + unsigned int ssrc = 0; + EXPECT_EQ(1, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(0, vie_.GetLocalSSRC(channel_num, ssrc)); + EXPECT_EQ(1U, ssrc); + + EXPECT_EQ(0, vie_.GetLocalSSRC(receive_channel_1, ssrc)); + EXPECT_EQ(1U, ssrc); + EXPECT_EQ(1, vie_.GetNumSsrcs(receive_channel_1)); + + EXPECT_EQ(0, vie_.GetLocalSSRC(receive_channel_2, ssrc)); + EXPECT_EQ(1U, ssrc); + EXPECT_EQ(1, vie_.GetNumSsrcs(receive_channel_2)); +} + +// Test 1:1 call never turn on simulcast. +TEST_F(WebRtcVideoEngineSimulcastTestFake, NoSimulcastWith1on1) { + EXPECT_TRUE(SetupEngine()); + int channel_num = vie_.GetLastChannel(); + + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + + // Set send codec and verify there is no simulcast sub-streams. + cricket::VideoCodec codec(kVP8Codec); + std::vector codec_list; + codec_list.push_back(codec); + SendI420Frame(kVP8Codec.width, kVP8Codec.height); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + VerifyVP8SendCodec(channel_num, codec.width, codec.height); + EXPECT_EQ(1, vie_.GetNumSsrcs(channel_num)); + + // Set options with OPT_CONFERENCE flag and set send codec again. + // Verify simulcast is used now. + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + VerifyVP8SendCodec(channel_num, codec.width, codec.height, 2, + kSimStream0Bitrate + kSimStream2MaxBitrateBoosted); + EXPECT_EQ(2, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(1, vie_.GetSsrc(channel_num, 0)); + EXPECT_EQ(2, vie_.GetSsrc(channel_num, 1)); + + // Remove OPT_CONFERENCE flag and set send codec again. + // Verify simulcast is off again. + options.conference_mode.Set(false); + EXPECT_TRUE(channel_->SetOptions(options)); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + VerifyVP8SendCodec(channel_num, codec.width, codec.height); + + // Verify we still have 2 SSRCs. + EXPECT_EQ(2, vie_.GetNumSsrcs(channel_num)); + EXPECT_EQ(1, vie_.GetSsrc(channel_num, 0)); + EXPECT_EQ(2, vie_.GetSsrc(channel_num, 1)); +} + +// Test SetOptions with OPT_CONFERENCE flag. +TEST_F(WebRtcVideoEngineSimulcastTestFake, SetOptionsWithConferenceMode) { + EXPECT_TRUE(SetupEngine()); + int channel_num = vie_.GetLastChannel(); + + // Set send codec. + cricket::VideoCodec codec(kVP8Codec); + std::vector codec_list; + codec_list.push_back(codec); + SendI420Frame(kVP8Codec.width, kVP8Codec.height); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + + // Verify default send codec and bitrate. + VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height); + + // Set options with OPT_CONFERENCE flag. + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + + // Change the max bitrate and verify it has changed. + channel_->SetMaxSendBandwidth(kSimStream2MaxBitrateBoosted * 1000); + VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height, 0, + kSimStream2MaxBitrateBoosted); + + // Turn off conference mode and verify the max bandwidth changed + // back. + options.conference_mode.Set(false); + EXPECT_TRUE(channel_->SetOptions(options)); + VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height); + + // Set send codec again with a smaller size. + codec_list.clear(); + codec.width /= 2; + codec.height /= 2; + codec_list.push_back(codec); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + + // Set options with OPT_CONFERENCE flag and set the max bitrate again. + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + channel_->SetMaxSendBandwidth(kSimStream0MaxBitrateBoosted * 1000); + + // Verify channel now has a new max bitrate. + VerifyVP8SendCodec(channel_num, kVP8Codec.width / 2, kVP8Codec.height / 2, 0, + kSimStream0MaxBitrateBoosted); +} + +// Test that two different streams can have different formats. +TEST_F(WebRtcVideoEngineSimulcastTestFake, + MultipleSendStreamsDifferentFormats) { + EXPECT_TRUE(SetupEngine()); + for (unsigned int i = 0; i < sizeof(kSsrcs2)/sizeof(kSsrcs2[0]); ++i) { + EXPECT_TRUE(channel_->AddSendStream( + cricket::StreamParams::CreateLegacy(kSsrcs2[i]))); + } + const int channel0 = vie_.GetChannelFromLocalSsrc(kSsrcs2[0]); + ASSERT_NE(-1, channel0); + const int channel1 = vie_.GetChannelFromLocalSsrc(kSsrcs2[1]); + ASSERT_NE(-1, channel1); + ASSERT_NE(channel0, channel1); + std::vector codecs; + codecs.push_back(kVP8Codec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + cricket::VideoFormat format(kVP8Codec.width / 2, kVP8Codec.height / 2, + cricket::VideoFormat::FpsToInterval(kVP8Codec.framerate / 2), + cricket::FOURCC_I420); + EXPECT_TRUE(channel_->SetSendStreamFormat(kSsrcs2[1], format)); + + SendI420Frame(kVP8Codec.width, kVP8Codec.height); + VerifyVP8SendCodec(channel0, kVP8Codec.width, kVP8Codec.height, 0, + kMaxBandwidthKbps, kMinBandwidthKbps, kStartBandwidthKbps, + kVP8Codec.framerate); + VerifyVP8SendCodec(channel1, kVP8Codec.width / 2, kVP8Codec.height / 2, 0, + kMaxBandwidthKbps, kMinBandwidthKbps, kStartBandwidthKbps, + kVP8Codec.framerate / 2); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, TestAdaptToOutputFormat) { + EXPECT_TRUE(SetupEngine()); + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs3)))); + + // Capture format HD + cricket::FakeVideoCapturer video_capturer; + const std::vector* formats = + video_capturer.GetSupportedFormats(); + cricket::VideoFormat capture_format_hd = (*formats)[0]; + EXPECT_EQ(cricket::CS_RUNNING, video_capturer.Start(capture_format_hd)); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], &video_capturer)); + + cricket::VideoCodec send_codec(100, "VP8", 800, 600, 30, 0); + cricket::VideoFormat vga_format(640, 360, + cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420); + std::vector codecs; + codecs.push_back(send_codec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(channel_->SetSendStreamFormat(kSsrcs3[0], vga_format)); + EXPECT_TRUE(channel_->SetSend(true)); + + // Capture format HD -> adapt (OnOutputFormatRequest VGA) -> VGA. + EXPECT_TRUE(video_capturer.CaptureFrame()); + const int channel0 = vie_.GetChannelFromLocalSsrc(kSsrcs3[0]); + ASSERT_NE(-1, channel0); + VerifyVP8SendCodec(channel0, vga_format.width, vga_format.height, 0, + kMaxBandwidthKbps, kMinBandwidthKbps, kStartBandwidthKbps, + 30); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], NULL)); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, TestAdaptToCpuLoad) { + EXPECT_TRUE(SetupEngine()); + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs3)))); + + // Capture format VGA + cricket::FakeVideoCapturer video_capturer; + const std::vector* formats = + video_capturer.GetSupportedFormats(); + cricket::VideoFormat capture_format_vga = (*formats)[1]; + + // Make OnCpuLoadUpdate trigger downgrade. + cricket::VideoOptions options; + options.adapt_input_to_cpu_usage.Set(true); + options.process_adaptation_threshhold.Set(0.1f); + options.system_low_adaptation_threshhold.Set(0.65f); + options.system_high_adaptation_threshhold.Set(0.85f); + options.cpu_overuse_detection.Set(false); + EXPECT_TRUE(channel_->SetOptions(options)); + + // Verify toggling cpu overuse detection works. + options.cpu_overuse_detection.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + options.cpu_overuse_detection.Set(false); + EXPECT_TRUE(channel_->SetOptions(options)); + + EXPECT_EQ(cricket::CS_RUNNING, video_capturer.Start(capture_format_vga)); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], &video_capturer)); + + // Only require one cpu sample for testing whether the adapter is hooked up. + cricket::CoordinatedVideoAdapter* video_adapter = NULL; + ASSERT_TRUE(channel_->GetVideoAdapter(0u, &video_adapter)); + video_adapter->set_cpu_load_min_samples(1); + + cpu_monitor_->SignalUpdate(1, 1, 0.1f, 0.85f); + + cricket::VideoCodec send_codec(100, "VP8", 640, 480, 30, 0); + std::vector codecs; + codecs.push_back(send_codec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(channel_->SetSend(true)); + + // Capture format VGA -> adapt (OnCpuLoadUpdate downgrade) -> VGA/2. + EXPECT_TRUE(video_capturer.CaptureFrame()); + const int channel0 = vie_.GetChannelFromLocalSsrc(kSsrcs3[0]); + ASSERT_NE(-1, channel0); + VerifyVP8SendCodec( + channel0, 3 * send_codec.width / 4, 3 * send_codec.height / 4, 0, + kMaxBandwidthKbps, kMinBandwidthKbps, kStartBandwidthKbps, + send_codec.framerate); + + // Trigger more downgrades and check for multiple unable to adapt signal. + EXPECT_EQ(cricket::VideoMediaChannel::ERROR_NONE, last_error_); + cpu_monitor_->SignalUpdate(1, 1, 0.1f, 0.85f); + cpu_monitor_->SignalUpdate(1, 1, 0.1f, 0.85f); + EXPECT_EQ(cricket::VideoMediaChannel::ERROR_REC_CPU_MAX_CANT_DOWNGRADE, + last_error_); + + last_error_ = cricket::VideoMediaChannel::ERROR_NONE; + cpu_monitor_->SignalUpdate(1, 1, 0.1f, 0.85f); + EXPECT_EQ(cricket::VideoMediaChannel::ERROR_REC_CPU_MAX_CANT_DOWNGRADE, + last_error_); + + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], NULL)); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, TestAdaptToCpuLoadDisabled) { + EXPECT_TRUE(SetupEngine()); + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs3)))); + + // Capture format VGA + cricket::FakeVideoCapturer video_capturer; + const std::vector* formats = + video_capturer.GetSupportedFormats(); + cricket::VideoFormat capture_format_vga = (*formats)[1]; + EXPECT_EQ(cricket::CS_RUNNING, video_capturer.Start(capture_format_vga)); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], &video_capturer)); + + // Make OnCpuLoadUpdate trigger downgrade. + cricket::VideoOptions options; + options.adapt_input_to_cpu_usage.Set(false); + options.process_adaptation_threshhold.Set(0.1f); + options.system_low_adaptation_threshhold.Set(0.65f); + options.system_high_adaptation_threshhold.Set(0.85f); + EXPECT_TRUE(channel_->SetOptions(options)); + + // Only require one cpu sample for testing whether the adapter is hooked up. + cricket::CoordinatedVideoAdapter* video_adapter = NULL; + ASSERT_TRUE(channel_->GetVideoAdapter(0u, &video_adapter)); + video_adapter->set_cpu_load_min_samples(1); + cpu_monitor_->SignalUpdate(1, 1, 0.1f, 0.85f); + + cricket::VideoCodec send_codec(100, "VP8", 640, 480, 30, 0); + std::vector codecs; + codecs.push_back(send_codec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(channel_->SetSend(true)); + + // Capture format VGA -> no adapt -> VGA. + EXPECT_TRUE(video_capturer.CaptureFrame()); + const int channel0 = vie_.GetChannelFromLocalSsrc(kSsrcs3[0]); + ASSERT_NE(-1, channel0); + VerifyVP8SendCodec(channel0, capture_format_vga.width, + capture_format_vga.height, 0, + kMaxBandwidthKbps, kMinBandwidthKbps, kStartBandwidthKbps, + send_codec.framerate); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], NULL)); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, TestAdaptWithCpuOveruseObserver) { + EXPECT_TRUE(SetupEngine()); + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs3)))); + + // Verify that the CpuOveruseObserver is not set by default. + const int channel0 = vie_.GetChannelFromLocalSsrc(kSsrcs3[0]); + ASSERT_NE(-1, channel0); + webrtc::CpuOveruseObserver* observer = vie_.GetCpuOveruseObserver(channel0); + EXPECT_TRUE(observer == NULL); + + // Capture format VGA. + cricket::FakeVideoCapturer video_capturer; + const std::vector* formats = + video_capturer.GetSupportedFormats(); + cricket::VideoFormat capture_format_vga = (*formats)[1]; + EXPECT_EQ(cricket::CS_RUNNING, video_capturer.Start(capture_format_vga)); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], &video_capturer)); + + // Verify that the CpuOveruseObserver is registered and trigger downgrade. + cricket::VideoOptions options; + options.cpu_overuse_detection.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + observer = vie_.GetCpuOveruseObserver(channel0); + ASSERT_TRUE(observer != NULL); + observer->OveruseDetected(); + + cricket::VideoCodec send_codec(100, "VP8", 640, 480, 30, 0); + std::vector codecs; + codecs.push_back(send_codec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(channel_->SetSend(true)); + + // Capture format VGA -> adapt (OnCpuResolutionRequest downgrade) -> VGA/2. + EXPECT_TRUE(video_capturer.CaptureFrame()); + VerifyVP8SendCodec( + channel0, 3 * send_codec.width / 4, 3 * send_codec.height / 4, 0, + kMaxBandwidthKbps, kMinBandwidthKbps, kStartBandwidthKbps, + send_codec.framerate); + + // Trigger upgrade and verify that we adapt back up to VGA. + observer->NormalUsage(); + EXPECT_TRUE(video_capturer.CaptureFrame()); + VerifyVP8SendCodec( + channel0, send_codec.width, send_codec.height, 0, + kMaxBandwidthKbps, kMinBandwidthKbps, kStartBandwidthKbps, + send_codec.framerate); + + // Verify that the CpuOveruseObserver is deregistered. + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], NULL)); + EXPECT_TRUE(vie_.GetCpuOveruseObserver(channel0) == NULL); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, + TestAdaptWithCpuOveruseObserverDisabled) { + EXPECT_TRUE(SetupEngine()); + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs3)))); + + // Verify that the CpuOveruseObserver is not set by default. + const int channel0 = vie_.GetChannelFromLocalSsrc(kSsrcs3[0]); + ASSERT_NE(-1, channel0); + webrtc::CpuOveruseObserver* observer = vie_.GetCpuOveruseObserver(channel0); + EXPECT_TRUE(observer == NULL); + + // Capture format VGA. + cricket::FakeVideoCapturer video_capturer; + const std::vector* formats = + video_capturer.GetSupportedFormats(); + cricket::VideoFormat capture_format_vga = (*formats)[1]; + EXPECT_EQ(cricket::CS_RUNNING, video_capturer.Start(capture_format_vga)); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], &video_capturer)); + + // Disable cpu overuse detection. + cricket::VideoOptions options; + options.cpu_overuse_detection.Set(false); + EXPECT_TRUE(channel_->SetOptions(options)); + cricket::CoordinatedVideoAdapter* video_adapter = NULL; + ASSERT_TRUE(channel_->GetVideoAdapter(0u, &video_adapter)); + EXPECT_FALSE(video_adapter->cpu_adaptation()); + + // Verify that the CpuOveruseObserver is registered and trigger downgrade. + observer = vie_.GetCpuOveruseObserver(channel0); + ASSERT_TRUE(observer != NULL); + observer->OveruseDetected(); + + cricket::VideoCodec send_codec(100, "VP8", 640, 480, 30, 0); + std::vector codecs; + codecs.push_back(send_codec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(channel_->SetSend(true)); + + // Capture format VGA -> no adapt -> VGA. + EXPECT_TRUE(video_capturer.CaptureFrame()); + VerifyVP8SendCodec( + channel0, capture_format_vga.width, capture_format_vga.height, 0, + kMaxBandwidthKbps, kMinBandwidthKbps, kStartBandwidthKbps, + send_codec.framerate); + + // Verify that the CpuOveruseObserver is deregistered. + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], NULL)); + EXPECT_TRUE(vie_.GetCpuOveruseObserver(channel0) == NULL); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, GetAdaptStats) { + EXPECT_TRUE(SetupEngine()); + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs3)))); + + // Capture format VGA. + cricket::FakeVideoCapturer video_capturer_vga; + const std::vector* formats = + video_capturer_vga.GetSupportedFormats(); + cricket::VideoFormat capture_format_vga = (*formats)[1]; + EXPECT_EQ(cricket::CS_RUNNING, video_capturer_vga.Start(capture_format_vga)); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], &video_capturer_vga)); + EXPECT_TRUE(video_capturer_vga.CaptureFrame()); + + cricket::VideoCodec send_codec(100, "VP8", 640, 480, 30, 0); + std::vector codecs; + codecs.push_back(send_codec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(channel_->SetSend(true)); + + // Verify that the CpuOveruseObserver is registered and trigger downgrade. + cricket::VideoOptions options; + options.cpu_overuse_detection.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + const int channel0 = vie_.GetChannelFromLocalSsrc(kSsrcs3[0]); + ASSERT_NE(-1, channel0); + webrtc::CpuOveruseObserver* observer = vie_.GetCpuOveruseObserver(channel0); + ASSERT_TRUE(observer != NULL); + observer->OveruseDetected(); + + // Capture format VGA -> adapt (OnCpuResolutionRequest downgrade) -> VGA/2. + EXPECT_TRUE(video_capturer_vga.CaptureFrame()); + cricket::VideoMediaInfo info; + EXPECT_TRUE(channel_->GetStats(cricket::StatsOptions(), &info)); + ASSERT_EQ(1U, info.senders.size()); + EXPECT_EQ(1, info.senders[0].adapt_changes); + EXPECT_EQ(cricket::CoordinatedVideoAdapter::ADAPTREASON_CPU, + info.senders[0].adapt_reason); + + // Trigger upgrade and verify that we adapt back up to VGA. + observer->NormalUsage(); + EXPECT_TRUE(video_capturer_vga.CaptureFrame()); + info.Clear(); + EXPECT_TRUE(channel_->GetStats(cricket::StatsOptions(), &info)); + ASSERT_EQ(1U, info.senders.size()); + EXPECT_EQ(2, info.senders[0].adapt_changes); + EXPECT_EQ(cricket::CoordinatedVideoAdapter::ADAPTREASON_NONE, + info.senders[0].adapt_reason); + + // No capturer (no adapter). Adapt changes from old adapter should be kept. + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], NULL)); + EXPECT_TRUE(vie_.GetCpuOveruseObserver(channel0) == NULL); + info.Clear(); + EXPECT_TRUE(channel_->GetStats(cricket::StatsOptions(), &info)); + ASSERT_EQ(1U, info.senders.size()); + EXPECT_EQ(2, info.senders[0].adapt_changes); + EXPECT_EQ(cricket::CoordinatedVideoAdapter::ADAPTREASON_NONE, + info.senders[0].adapt_reason); + + // Set new capturer, capture format HD. + cricket::FakeVideoCapturer video_capturer_hd; + cricket::VideoFormat capture_format_hd = (*formats)[0]; + EXPECT_EQ(cricket::CS_RUNNING, video_capturer_hd.Start(capture_format_hd)); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], &video_capturer_hd)); + EXPECT_TRUE(video_capturer_hd.CaptureFrame()); + observer = vie_.GetCpuOveruseObserver(channel0); + ASSERT_TRUE(observer != NULL); + + // Trigger overuse, HD -> adapt (OnCpuResolutionRequest downgrade) -> HD/2. + observer->OveruseDetected(); + EXPECT_TRUE(video_capturer_hd.CaptureFrame()); + info.Clear(); + EXPECT_TRUE(channel_->GetStats(cricket::StatsOptions(), &info)); + ASSERT_EQ(1U, info.senders.size()); + EXPECT_EQ(3, info.senders[0].adapt_changes); + EXPECT_EQ(cricket::CoordinatedVideoAdapter::ADAPTREASON_CPU, + info.senders[0].adapt_reason); + + EXPECT_TRUE(channel_->SetCapturer(kSsrcs3[0], NULL)); +} + +// Test that the codec is not reset for every frame sent in +// non-conference and non-screencast mode. +TEST_F(WebRtcVideoEngineSimulcastTestFake, DontResetCodecOnSendFrame) { + EXPECT_TRUE(SetupEngine()); + + // Set send codec. + cricket::VideoCodec codec(kVP8Codec); + std::vector codec_list; + codec_list.push_back(codec); + EXPECT_TRUE(channel_->AddSendStream( + cricket::StreamParams::CreateLegacy(123))); + SendI420Frame(kVP8Codec.width, kVP8Codec.height); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + EXPECT_TRUE(channel_->SetSend(true)); + EXPECT_EQ(1, vie_.GetNumSetSendCodecs()); + + SendI420Frame(kVP8Codec.width, kVP8Codec.height); + EXPECT_EQ(1, vie_.GetNumSetSendCodecs()); + SendI420Frame(kVP8Codec.width, kVP8Codec.height); + EXPECT_EQ(1, vie_.GetNumSetSendCodecs()); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, + UseSimulcastAdapterOnVp8OnlyFactory) { + encoder_factory_.AddSupportedVideoCodecType(webrtc::kVideoCodecVP8, "VP8"); + TestSimulcastAdapter(kVP8Codec, true); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, + DontUseSimulcastAdapterOnNoneVp8Factory) { + encoder_factory_.AddSupportedVideoCodecType(webrtc::kVideoCodecGeneric, + "H264"); + static const cricket::VideoCodec kH264Codec(100, "H264", 640, 400, 30, 0); + TestSimulcastAdapter(kH264Codec, false); +} + +TEST_F(WebRtcVideoEngineSimulcastTestFake, + DontUseSimulcastAdapterOnMultipleCodecsFactory) { + encoder_factory_.AddSupportedVideoCodecType(webrtc::kVideoCodecVP8, "VP8"); + encoder_factory_.AddSupportedVideoCodecType(webrtc::kVideoCodecGeneric, + "H264"); + TestSimulcastAdapter(kVP8Codec, false); +} + +TEST_F(WebRtcVideoMediaChannelSimulcastTest, SimulcastSend_1280x800) { + cricket::VideoCodec codec = kVP8Codec; + codec.width = 1280; + codec.height = 800; + // TODO(zhurunz): Support 3 layers of simulcast. + SimulcastSend(codec, MAKE_VECTOR(kSsrcs2)); +} + +TEST_F(WebRtcVideoMediaChannelSimulcastTest, SimulcastSend_1280x720) { + cricket::VideoCodec codec = kVP8Codec; + codec.width = 1280; + codec.height = 720; + // TODO(zhurunz): Support 3 layers of simulcast. + SimulcastSend(codec, MAKE_VECTOR(kSsrcs2)); +} + +TEST_F(WebRtcVideoMediaChannelSimulcastTest, SimulcastSend_960x540) { + cricket::VideoCodec codec = kVP8Codec; + codec.width = 960; + codec.height = 540; + // TODO(zhurunz): Support 3 layers of simulcast. + SimulcastSend(codec, MAKE_VECTOR(kSsrcs2)); +} + +TEST_F(WebRtcVideoMediaChannelSimulcastTest, SimulcastSend_960x600) { + cricket::VideoCodec codec = kVP8Codec; + codec.width = 960; + codec.height = 600; + // TODO(zhurunz): Support 3 layers of simulcast. + SimulcastSend(codec, MAKE_VECTOR(kSsrcs2)); +} + +TEST_F(WebRtcVideoMediaChannelSimulcastTest, SimulcastSend_640x400) { + cricket::VideoCodec codec = kVP8Codec; + codec.width = 640; + codec.height = 400; + SimulcastSend(codec, MAKE_VECTOR(kSsrcs2)); +} + +TEST_F(WebRtcVideoMediaChannelSimulcastTest, SimulcastSend_640x360) { + cricket::VideoCodec codec = kVP8Codec; + codec.width = 640; + codec.height = 360; + SimulcastSend(codec, MAKE_VECTOR(kSsrcs2)); +} + +TEST_F(WebRtcVideoMediaChannelSimulcastTest, SimulcastSend_480x300) { + cricket::VideoCodec codec = kVP8Codec; + codec.width = 480; + codec.height = 300; + SimulcastSend(codec, MAKE_VECTOR(kSsrcs2)); +} + +TEST_F(WebRtcVideoMediaChannelSimulcastTest, DISABLED_SimulcastSend_480x270) { + cricket::VideoCodec codec = kVP8Codec; + codec.width = 480; + codec.height = 270; + SimulcastSend(codec, MAKE_VECTOR(kSsrcs2)); +} + +TEST_F(WebRtcVideoMediaChannelSimulcastTest, SimulcastSend_320x200) { + cricket::VideoCodec codec = kVP8Codec; + codec.width = 320; + codec.height = 200; + SimulcastSend(codec, MAKE_VECTOR(kSsrcs1)); +} + +TEST_F(WebRtcVideoMediaChannelSimulcastTest, SimulcastSend_320x180) { + cricket::VideoCodec codec = kVP8Codec; + codec.width = 320; + codec.height = 180; + SimulcastSend(codec, MAKE_VECTOR(kSsrcs1)); +} + +// Test reset send codec with simulcast. +// Disabled per b/6773425 +TEST_F(WebRtcVideoMediaChannelSimulcastTest, DISABLED_SimulcastResetSendCodec) { + // Remove stream added in Setup. + EXPECT_TRUE(channel_->RemoveSendStream(kSsrc)); + + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + cricket::VideoCodec codec(DefaultCodec()); + EXPECT_TRUE(SetOneCodec(codec)); + EXPECT_TRUE(SetSend(true)); + int packets = 0; + + // Send the first frame with default format size. + EXPECT_TRUE(SendFrame()); + EXPECT_TRUE_WAIT(NumRtpPackets() > packets, kTimeout); + DrainOutgoingPackets(); + packets = NumRtpPackets(); + + // Simulate capture frame size changes. + // Verify there is new rtp packet coming out of the encoder. + + // Send an odd frame. + EXPECT_TRUE(SendCustomVideoFrame(DefaultCodec().width | 0x01, + DefaultCodec().height | 0x01)); + EXPECT_TRUE_WAIT(NumRtpPackets() > packets, kTimeout); + packets = DrainOutgoingPackets(); + + // Send a small frame in width. + EXPECT_TRUE(SendCustomVideoFrame(DefaultCodec().width / 2, + DefaultCodec().height)); + EXPECT_TRUE_WAIT(NumRtpPackets() > packets, kTimeout); + packets = DrainOutgoingPackets(); + + // Send a small frame in height. + EXPECT_TRUE(SendCustomVideoFrame(DefaultCodec().width, + DefaultCodec().height / 2)); + EXPECT_TRUE_WAIT(NumRtpPackets() > packets, kTimeout); + packets = DrainOutgoingPackets(); + + // Send a big frame in width. + EXPECT_TRUE(SendCustomVideoFrame(DefaultCodec().width * 2, + DefaultCodec().height)); + EXPECT_TRUE_WAIT(NumRtpPackets() > packets, kTimeout); + packets = DrainOutgoingPackets(); + + // Send a big frame in height. + EXPECT_TRUE(SendCustomVideoFrame(DefaultCodec().width, + DefaultCodec().height * 2)); + EXPECT_TRUE_WAIT(NumRtpPackets() > packets, kTimeout); + packets = DrainOutgoingPackets(); + + // Send a normal frame. + EXPECT_TRUE(SendCustomVideoFrame(DefaultCodec().width, + DefaultCodec().height)); + EXPECT_TRUE_WAIT(NumRtpPackets() > packets, kTimeout); + packets = DrainOutgoingPackets(); +} + +// Test simulcast streams are decodeable with expected sizes. +TEST_F(WebRtcVideoMediaChannelSimulcastTest, SimulcastStreams) { + // Remove stream added in Setup. + EXPECT_TRUE(channel_->RemoveSendStream(kSsrc)); + + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + cricket::VideoCodec codec(DefaultCodec()); + EXPECT_TRUE(SetOneCodec(codec)); + + // Re-configure capturer. + channel_->SetCapturer(kSsrc, NULL); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs2[0], video_capturer_.get())); + cricket::VideoFormat capture_format(codec.width, codec.height, + cricket::VideoFormat::FpsToInterval(codec.framerate), + cricket::FOURCC_I420); + EXPECT_EQ(cricket::CS_RUNNING, video_capturer_->Start(capture_format)); + + EXPECT_TRUE(SetSend(true)); + + cricket::FakeVideoRenderer renderer1, renderer2; + EXPECT_TRUE(channel_->AddRecvStream( + cricket::StreamParams::CreateLegacy(kSsrcs2[0]))); + EXPECT_TRUE(channel_->AddRecvStream( + cricket::StreamParams::CreateLegacy(kSsrcs2[1]))); + EXPECT_TRUE(channel_->SetRenderer(kSsrcs2[0], &renderer1)); + EXPECT_TRUE(channel_->SetRenderer(kSsrcs2[1], &renderer2)); + EXPECT_TRUE(channel_->SetRender(true)); + EXPECT_EQ(0, renderer1.num_rendered_frames()); + EXPECT_EQ(0, renderer2.num_rendered_frames()); + + EXPECT_TRUE(SendFrame()); + EXPECT_FRAME_ON_RENDERER_WAIT(renderer1, 1, + DefaultCodec().width / 2, DefaultCodec().height / 2, kTimeout); + EXPECT_FRAME_ON_RENDERER_WAIT(renderer2, 1, + DefaultCodec().width, DefaultCodec().height, kTimeout); + EXPECT_EQ(1, renderer1.num_set_sizes()); + EXPECT_EQ(1, renderer2.num_set_sizes()); +} + +// Simulcast and resolution resizing should be turned off when screencasting +// but not otherwise. +// TODO(dmunene): Fix flaky test with bug 15773660. +TEST_F(WebRtcVideoMediaChannelSimulcastTest, DISABLED_ScreencastRendering) { + // Remove stream added in SetUp. + EXPECT_TRUE(channel_->RemoveSendStream(kSsrc)); + + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + + // Setup a simulcast stream and verify that frames are rendered on + // both renderers. + EXPECT_TRUE(channel_->AddSendStream( + cricket::CreateSimStreamParams("cname", MAKE_VECTOR(kSsrcs2)))); + cricket::VideoCodec codec(DefaultCodec()); + EXPECT_TRUE(SetOneCodec(codec)); + EXPECT_TRUE(SetSend(true)); + + cricket::FakeVideoRenderer renderer1, renderer2; + EXPECT_TRUE(channel_->AddRecvStream( + cricket::StreamParams::CreateLegacy(kSsrcs2[0]))); + EXPECT_TRUE(channel_->AddRecvStream( + cricket::StreamParams::CreateLegacy(kSsrcs2[1]))); + EXPECT_TRUE(channel_->SetRenderer(kSsrcs2[0], &renderer1)); + EXPECT_TRUE(channel_->SetRenderer(kSsrcs2[1], &renderer2)); + EXPECT_TRUE(channel_->SetRender(true)); + EXPECT_EQ(0, renderer1.num_rendered_frames()); + EXPECT_EQ(0, renderer2.num_rendered_frames()); + + // Re-configure capturer. + channel_->SetCapturer(kSsrc, NULL); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs2[0], video_capturer_.get())); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs2[1], video_capturer_.get())); + cricket::VideoFormat capture_format(codec.width, codec.height, + cricket::VideoFormat::FpsToInterval(codec.framerate), + cricket::FOURCC_I420); + EXPECT_EQ(cricket::CS_RUNNING, video_capturer_->Start(capture_format)); + + EXPECT_TRUE(SendFrame()); + EXPECT_FRAME_ON_RENDERER_WAIT(renderer1, 1, codec.width / 2, codec.height / 2, + kTimeout); + EXPECT_FRAME_ON_RENDERER_WAIT(renderer2, 1, codec.width, codec.height, + kTimeout); + + // Register a fake screencast capturer and verify that frames are only + // rendered to a single renderer. + rtc::scoped_ptr capturer( + new cricket::FakeVideoCapturer); + capturer->SetScreencast(true); + const std::vector* formats = + capturer->GetSupportedFormats(); + capture_format = (*formats)[0]; + EXPECT_EQ(cricket::CS_RUNNING, capturer->Start(capture_format)); + // Capture a frame to increment the frame timestamp since the default video + // capturer starts at the same timestamp. + EXPECT_TRUE(capturer->CaptureFrame()); + EXPECT_TRUE(channel_->SetCapturer(kSsrcs2[0], capturer.get())); + EXPECT_TRUE(rtc::Thread::Current()->ProcessMessages(30)); + EXPECT_TRUE(capturer->CaptureFrame()); + EXPECT_FRAME_ON_RENDERER_WAIT(renderer1, 2, capture_format.width, + capture_format.height, kTimeout); + // When screencasting we only encode a single stream (no simulcast), which is + // why we only expect frames to be rendered in one of the renderers. + EXPECT_EQ(1, renderer2.num_rendered_frames()); + + // Disable screencast and make sure frames are now rendered in both renderers. + EXPECT_TRUE(channel_->SetCapturer(kSsrcs2[0], NULL)); + EXPECT_FRAME_ON_RENDERER_WAIT(renderer1, 3, codec.width / 2, codec.height / 2, + kTimeout); + EXPECT_FRAME_ON_RENDERER_WAIT(renderer2, 2, codec.width, codec.height, + kTimeout); +} + +// Disable for TSan v2, see +// https://code.google.com/p/webrtc/issues/detail?id=3525 for details. +#if !defined(THREAD_SANITIZER) +// Ensures that the correct settings are applied to the codec when two temporal +// layer screencasting is enabled, and that the correct simulcast settings are +// reapplied when disabling screencasting. +TEST_F(WebRtcVideoMediaChannelSimulcastTest, ConferenceModeScreencastSettings) { + TestScreencastSettings(); +} +#endif // if !defined(THREAD_SANITIZER)