From d266a2020f9e86a787eada77d458ee75426d68af Mon Sep 17 00:00:00 2001 From: "pbos@webrtc.org" Date: Mon, 12 May 2014 14:32:01 +0000 Subject: [PATCH] Initial wiring of new webrtc API in libjingle. BUG=1788 R=pthatcher@google.com, pthatcher@webrtc.org TBR=juberti@webrtc.org, mflodman@webrtc.org Review URL: https://webrtc-codereview.appspot.com/8549005 git-svn-id: http://webrtc.googlecode.com/svn/trunk@6104 4adac7df-926f-26a2-2b94-8c16560cd09d --- talk/app/webrtc/peerconnectionfactory.cc | 26 +- talk/libjingle.gyp | 4 +- talk/libjingle_tests.gyp | 1 + talk/media/base/codec.cc | 50 + talk/media/base/codec.h | 16 + talk/media/base/codec_unittest.cc | 81 + talk/media/base/constants.cc | 2 + talk/media/base/constants.h | 3 + talk/media/webrtc/webrtcvideochannelfactory.h | 44 + talk/media/webrtc/webrtcvideoengine2.cc | 1658 +++++++++++++++++ talk/media/webrtc/webrtcvideoengine2.h | 332 ++++ .../webrtc/webrtcvideoengine2_unittest.cc | 1187 ++++++++++++ .../libjingle_media_unittest.gtest-tsan.txt | 5 + 13 files changed, 3396 insertions(+), 13 deletions(-) create mode 100644 talk/media/webrtc/webrtcvideochannelfactory.h create mode 100644 talk/media/webrtc/webrtcvideoengine2.cc create mode 100644 talk/media/webrtc/webrtcvideoengine2.h create mode 100644 talk/media/webrtc/webrtcvideoengine2_unittest.cc diff --git a/talk/app/webrtc/peerconnectionfactory.cc b/talk/app/webrtc/peerconnectionfactory.cc index a328bd0e9..ee89e09fc 100644 --- a/talk/app/webrtc/peerconnectionfactory.cc +++ b/talk/app/webrtc/peerconnectionfactory.cc @@ -111,9 +111,9 @@ enum { namespace webrtc { -scoped_refptr +talk_base::scoped_refptr CreatePeerConnectionFactory() { - scoped_refptr pc_factory( + talk_base::scoped_refptr pc_factory( new talk_base::RefCountedObject()); if (!pc_factory->Initialize()) { @@ -122,17 +122,19 @@ CreatePeerConnectionFactory() { return pc_factory; } -scoped_refptr +talk_base::scoped_refptr CreatePeerConnectionFactory( talk_base::Thread* worker_thread, talk_base::Thread* signaling_thread, AudioDeviceModule* default_adm, cricket::WebRtcVideoEncoderFactory* encoder_factory, cricket::WebRtcVideoDecoderFactory* decoder_factory) { - scoped_refptr pc_factory( - new talk_base::RefCountedObject( - worker_thread, signaling_thread, default_adm, - encoder_factory, decoder_factory)); + talk_base::scoped_refptr pc_factory( + new talk_base::RefCountedObject(worker_thread, + signaling_thread, + default_adm, + encoder_factory, + decoder_factory)); if (!pc_factory->Initialize()) { return NULL; } @@ -278,7 +280,7 @@ bool PeerConnectionFactory::StartAecDump_s(talk_base::PlatformFile file) { return channel_manager_->StartAecDump(file); } -scoped_refptr +talk_base::scoped_refptr PeerConnectionFactory::CreatePeerConnection( const PeerConnectionInterface::RTCConfiguration& configuration, const MediaConstraintsInterface* constraints, @@ -314,7 +316,7 @@ PeerConnectionFactory::CreatePeerConnection_s( return PeerConnectionProxy::Create(signaling_thread(), pc); } -scoped_refptr +talk_base::scoped_refptr PeerConnectionFactory::CreateLocalMediaStream(const std::string& label) { return MediaStreamProxy::Create(signaling_thread_, MediaStream::Create(label)); @@ -348,9 +350,9 @@ PeerConnectionFactory::CreateVideoTrack( return VideoTrackProxy::Create(signaling_thread_, track); } -scoped_refptr PeerConnectionFactory::CreateAudioTrack( - const std::string& id, - AudioSourceInterface* source) { +talk_base::scoped_refptr +PeerConnectionFactory::CreateAudioTrack(const std::string& id, + AudioSourceInterface* source) { talk_base::scoped_refptr track( AudioTrack::Create(id, source)); return AudioTrackProxy::Create(signaling_thread_, track); diff --git a/talk/libjingle.gyp b/talk/libjingle.gyp index 143e96b6e..770bcd42d 100755 --- a/talk/libjingle.gyp +++ b/talk/libjingle.gyp @@ -806,7 +806,7 @@ '<(DEPTH)/third_party/usrsctp/usrsctp.gyp:usrsctplib', '<(webrtc_root)/modules/modules.gyp:video_capture_module', '<(webrtc_root)/modules/modules.gyp:video_render_module', - '<(webrtc_root)/video_engine/video_engine.gyp:video_engine_core', + '<(webrtc_root)/webrtc.gyp:webrtc', '<(webrtc_root)/voice_engine/voice_engine.gyp:voice_engine', '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', 'libjingle', @@ -889,6 +889,8 @@ 'media/webrtc/webrtcvideoencoderfactory.h', 'media/webrtc/webrtcvideoengine.cc', 'media/webrtc/webrtcvideoengine.h', + 'media/webrtc/webrtcvideoengine2.cc', + 'media/webrtc/webrtcvideoengine2.h', 'media/webrtc/webrtcvideoframe.cc', 'media/webrtc/webrtcvideoframe.h', 'media/webrtc/webrtcvie.h', diff --git a/talk/libjingle_tests.gyp b/talk/libjingle_tests.gyp index f700261bc..d21c77549 100755 --- a/talk/libjingle_tests.gyp +++ b/talk/libjingle_tests.gyp @@ -300,6 +300,7 @@ # TODO(ronghuawu): Reenable these tests. # 'media/devices/devicemanager_unittest.cc', 'media/webrtc/webrtcvideoengine_unittest.cc', + 'media/webrtc/webrtcvideoengine2_unittest.cc', 'media/webrtc/webrtcvoiceengine_unittest.cc', ], 'conditions': [ diff --git a/talk/media/base/codec.cc b/talk/media/base/codec.cc index 2d54c9907..6d3a3f7f6 100644 --- a/talk/media/base/codec.cc +++ b/talk/media/base/codec.cc @@ -31,6 +31,7 @@ #include #include "talk/base/common.h" +#include "talk/base/logging.h" #include "talk/base/stringencode.h" #include "talk/base/stringutils.h" @@ -160,6 +161,55 @@ std::string VideoCodec::ToString() const { return os.str(); } +VideoCodec VideoCodec::CreateRtxCodec(int rtx_payload_type, + int associated_payload_type) { + VideoCodec rtx_codec(rtx_payload_type, kRtxCodecName, 0, 0, 0, 0); + rtx_codec.SetParam(kCodecParamAssociatedPayloadType, associated_payload_type); + return rtx_codec; +} + +VideoCodec::CodecType VideoCodec::GetCodecType() const { + const char* payload_name = name.c_str(); + if (_stricmp(payload_name, kRedCodecName) == 0) { + return CODEC_RED; + } + if (_stricmp(payload_name, kUlpfecCodecName) == 0) { + return CODEC_ULPFEC; + } + if (_stricmp(payload_name, kRtxCodecName) == 0) { + return CODEC_RTX; + } + + return CODEC_VIDEO; +} + +bool VideoCodec::ValidateCodecFormat() const { + if (id < 0 || id > 127) { + LOG(LS_ERROR) << "Codec with invalid payload type: " << ToString(); + return false; + } + if (GetCodecType() != CODEC_VIDEO) { + return true; + } + + // Video validation from here on. + + if (width <= 0 || height <= 0) { + LOG(LS_ERROR) << "Codec with invalid dimensions: " << ToString(); + return false; + } + int min_bitrate; + int max_bitrate; + if (GetParam(kCodecParamMinBitrate, &min_bitrate) && + GetParam(kCodecParamMaxBitrate, &max_bitrate)) { + if (max_bitrate < min_bitrate) { + LOG(LS_ERROR) << "Codec with max < min bitrate: " << ToString(); + return false; + } + } + return true; +} + std::string DataCodec::ToString() const { std::ostringstream os; os << "DataCodec[" << id << ":" << name << "]"; diff --git a/talk/media/base/codec.h b/talk/media/base/codec.h index 120c17b0f..0e9bf3ca0 100644 --- a/talk/media/base/codec.h +++ b/talk/media/base/codec.h @@ -246,6 +246,22 @@ struct VideoCodec : public Codec { bool operator!=(const VideoCodec& c) const { return !(*this == c); } + + static VideoCodec CreateRtxCodec(int rtx_payload_type, + int associated_payload_type); + + enum CodecType { + CODEC_VIDEO, + CODEC_RED, + CODEC_ULPFEC, + CODEC_RTX, + }; + + CodecType GetCodecType() const; + // Validates a VideoCodec's payload type, dimensions and bitrates etc. If they + // don't make sense (such as max < min bitrate), and error is logged and + // ValidateCodecFormat returns false. + bool ValidateCodecFormat() const; }; struct DataCodec : public Codec { diff --git a/talk/media/base/codec_unittest.cc b/talk/media/base/codec_unittest.cc index f2bf4c70d..35d1ab761 100644 --- a/talk/media/base/codec_unittest.cc +++ b/talk/media/base/codec_unittest.cc @@ -34,6 +34,9 @@ using cricket::DataCodec; using cricket::FeedbackParam; using cricket::VideoCodec; using cricket::VideoEncoderConfig; +using cricket::kCodecParamAssociatedPayloadType; +using cricket::kCodecParamMaxBitrate; +using cricket::kCodecParamMinBitrate; class CodecTest : public testing::Test { public: @@ -312,3 +315,81 @@ TEST_F(CodecTest, TestIntersectFeedbackParams) { EXPECT_FALSE(c1.HasFeedbackParam(b2)); EXPECT_FALSE(c1.HasFeedbackParam(c3)); } + +TEST_F(CodecTest, TestGetCodecType) { + // Codec type comparison should be case insenstive on names. + const VideoCodec codec(96, "V", 320, 200, 30, 3); + const VideoCodec rtx_codec(96, "rTx", 320, 200, 30, 3); + const VideoCodec ulpfec_codec(96, "ulpFeC", 320, 200, 30, 3); + const VideoCodec red_codec(96, "ReD", 320, 200, 30, 3); + EXPECT_EQ(VideoCodec::CODEC_VIDEO, codec.GetCodecType()); + EXPECT_EQ(VideoCodec::CODEC_RTX, rtx_codec.GetCodecType()); + EXPECT_EQ(VideoCodec::CODEC_ULPFEC, ulpfec_codec.GetCodecType()); + EXPECT_EQ(VideoCodec::CODEC_RED, red_codec.GetCodecType()); +} + +TEST_F(CodecTest, TestCreateRtxCodec) { + VideoCodec rtx_codec = VideoCodec::CreateRtxCodec(96, 120); + EXPECT_EQ(96, rtx_codec.id); + EXPECT_EQ(VideoCodec::CODEC_RTX, rtx_codec.GetCodecType()); + int associated_payload_type; + ASSERT_TRUE(rtx_codec.GetParam(kCodecParamAssociatedPayloadType, + &associated_payload_type)); + EXPECT_EQ(120, associated_payload_type); +} + +TEST_F(CodecTest, TestValidateCodecFormat) { + const VideoCodec codec(96, "V", 320, 200, 30, 3); + ASSERT_TRUE(codec.ValidateCodecFormat()); + + // Accept 0-127 as payload types. + VideoCodec low_payload_type = codec; + low_payload_type.id = 0; + VideoCodec high_payload_type = codec; + high_payload_type.id = 127; + ASSERT_TRUE(low_payload_type.ValidateCodecFormat()); + EXPECT_TRUE(high_payload_type.ValidateCodecFormat()); + + // Reject negative payloads. + VideoCodec negative_payload_type = codec; + negative_payload_type.id = -1; + EXPECT_FALSE(negative_payload_type.ValidateCodecFormat()); + + // Reject too-high payloads. + VideoCodec too_high_payload_type = codec; + too_high_payload_type.id = 128; + EXPECT_FALSE(too_high_payload_type.ValidateCodecFormat()); + + // Reject zero-width codecs. + VideoCodec zero_width = codec; + zero_width.width = 0; + EXPECT_FALSE(zero_width.ValidateCodecFormat()); + + // Reject zero-height codecs. + VideoCodec zero_height = codec; + zero_height.height = 0; + EXPECT_FALSE(zero_height.ValidateCodecFormat()); + + // Accept non-video codecs with zero dimensions. + VideoCodec zero_width_rtx_codec = VideoCodec::CreateRtxCodec(96, 120); + zero_width_rtx_codec.width = 0; + EXPECT_TRUE(zero_width_rtx_codec.ValidateCodecFormat()); + + // Reject codecs with min bitrate > max bitrate. + VideoCodec incorrect_bitrates = codec; + incorrect_bitrates.params[kCodecParamMinBitrate] = "100"; + incorrect_bitrates.params[kCodecParamMaxBitrate] = "80"; + EXPECT_FALSE(incorrect_bitrates.ValidateCodecFormat()); + + // Accept min bitrate == max bitrate. + VideoCodec equal_bitrates = codec; + equal_bitrates.params[kCodecParamMinBitrate] = "100"; + equal_bitrates.params[kCodecParamMaxBitrate] = "100"; + EXPECT_TRUE(equal_bitrates.ValidateCodecFormat()); + + // Accept min bitrate < max bitrate. + VideoCodec different_bitrates = codec; + different_bitrates.params[kCodecParamMinBitrate] = "99"; + different_bitrates.params[kCodecParamMaxBitrate] = "100"; + EXPECT_TRUE(different_bitrates.ValidateCodecFormat()); +} diff --git a/talk/media/base/constants.cc b/talk/media/base/constants.cc index e81a537f0..cd10ef75f 100644 --- a/talk/media/base/constants.cc +++ b/talk/media/base/constants.cc @@ -40,6 +40,8 @@ const float kLowSystemCpuThreshold = 0.65f; const float kProcessCpuThreshold = 0.10f; const char kRtxCodecName[] = "rtx"; +const char kRedCodecName[] = "red"; +const char kUlpfecCodecName[] = "ulpfec"; // RTP payload type is in the 0-127 range. Use 128 to indicate "all" payload // types. diff --git a/talk/media/base/constants.h b/talk/media/base/constants.h index 6a2302e3d..dc5405d17 100644 --- a/talk/media/base/constants.h +++ b/talk/media/base/constants.h @@ -44,6 +44,9 @@ extern const float kLowSystemCpuThreshold; extern const float kProcessCpuThreshold; extern const char kRtxCodecName[]; +extern const char kRedCodecName[]; +extern const char kUlpfecCodecName[]; + // Codec parameters extern const int kWildcardPayloadType; diff --git a/talk/media/webrtc/webrtcvideochannelfactory.h b/talk/media/webrtc/webrtcvideochannelfactory.h new file mode 100644 index 000000000..646348cd5 --- /dev/null +++ b/talk/media/webrtc/webrtcvideochannelfactory.h @@ -0,0 +1,44 @@ +/* + * libjingle + * Copyright 2004 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_WEBRTCVIDEOCHANNEL_H_ +#define TALK_MEDIA_WEBRTC_WEBRTCVIDEOCHANNEL_H_ + +namespace cricket { +class VoiceMediaChannel; +class WebRtcVideoEngine2; +class WebRtcVideoChannel2; + +class WebRtcVideoChannelFactory { + public: + virtual ~WebRtcVideoChannelFactory() {} + virtual WebRtcVideoChannel2* Create(WebRtcVideoEngine2* engine, + VoiceMediaChannel* voice_channel) = 0; +}; +} // namespace cricket + +#endif // TALK_MEDIA_WEBRTC_WEBRTCVIDEOCHANNEL_H_ diff --git a/talk/media/webrtc/webrtcvideoengine2.cc b/talk/media/webrtc/webrtcvideoengine2.cc new file mode 100644 index 000000000..68492ce19 --- /dev/null +++ b/talk/media/webrtc/webrtcvideoengine2.cc @@ -0,0 +1,1658 @@ +/* + * 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. + */ + +#ifdef HAVE_WEBRTC_VIDEO +#include "talk/media/webrtc/webrtcvideoengine2.h" + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "libyuv/convert_from.h" +#include "talk/base/buffer.h" +#include "talk/base/logging.h" +#include "talk/base/stringutils.h" +#include "talk/media/base/videocapturer.h" +#include "talk/media/base/videorenderer.h" +#include "talk/media/webrtc/webrtcvideocapturer.h" +#include "talk/media/webrtc/webrtcvideoframe.h" +#include "talk/media/webrtc/webrtcvoiceengine.h" +#include "webrtc/call.h" +// TODO(pbos): Move codecs out of modules (webrtc:3070). +#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h" + +#define UNIMPLEMENTED \ + LOG(LS_ERROR) << "Call to unimplemented function " << __FUNCTION__; \ + ASSERT(false) + +namespace cricket { + +static const int kCpuMonitorPeriodMs = 2000; // 2 seconds. + +// This constant is really an on/off, lower-level configurable NACK history +// duration hasn't been implemented. +static const int kNackHistoryMs = 1000; + +static const int kDefaultFramerate = 30; +static const int kMinVideoBitrate = 50; +static const int kMaxVideoBitrate = 2000; + +static const int kVideoMtu = 1200; +static const int kVideoRtpBufferSize = 65536; + +static const char kVp8PayloadName[] = "VP8"; + +static const int kDefaultRtcpReceiverReportSsrc = 1; + +struct VideoCodecPref { + int payload_type; + const char* name; + int rtx_payload_type; +} kDefaultVideoCodecPref = {100, kVp8PayloadName, 96}; + +VideoCodecPref kRedPref = {116, kRedCodecName, -1}; +VideoCodecPref kUlpfecPref = {117, kUlpfecCodecName, -1}; + +// The formats are sorted by the descending order of width. We use the order to +// find the next format for CPU and bandwidth adaptation. +const VideoFormatPod kDefaultVideoFormat = { + 640, 400, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}; +const VideoFormatPod kVideoFormats[] = { + {1280, 800, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {1280, 720, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {960, 600, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {960, 540, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + kDefaultVideoFormat, + {640, 360, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {640, 480, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {480, 300, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {480, 270, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {480, 360, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {320, 200, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {320, 180, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {320, 240, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {240, 150, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {240, 135, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {240, 180, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {160, 100, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {160, 90, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, + {160, 120, FPS_TO_INTERVAL(kDefaultFramerate), FOURCC_ANY}, }; + +static bool FindFirstMatchingCodec(const std::vector& codecs, + const VideoCodec& requested_codec, + VideoCodec* matching_codec) { + for (size_t i = 0; i < codecs.size(); ++i) { + if (requested_codec.Matches(codecs[i])) { + *matching_codec = codecs[i]; + return true; + } + } + return false; +} +static bool FindBestVideoFormat(int max_width, + int max_height, + int aspect_width, + int aspect_height, + VideoFormat* video_format) { + assert(max_width > 0); + assert(max_height > 0); + assert(aspect_width > 0); + assert(aspect_height > 0); + VideoFormat best_format; + for (int i = 0; i < ARRAY_SIZE(kVideoFormats); ++i) { + const VideoFormat format(kVideoFormats[i]); + + // Skip any format that is larger than the local or remote maximums, or + // smaller than the current best match + if (format.width > max_width || format.height > max_height || + (format.width < best_format.width && + format.height < best_format.height)) { + continue; + } + + // If we don't have any matches yet, this is the best so far. + if (best_format.width == 0) { + best_format = format; + continue; + } + + // Prefer closer aspect ratios i.e: + // |format| aspect - requested aspect < + // |best_format| aspect - requested aspect + if (abs(format.width * aspect_height * best_format.height - + aspect_width * format.height * best_format.height) < + abs(best_format.width * aspect_height * format.height - + aspect_width * format.height * best_format.height)) { + best_format = format; + } + } + if (best_format.width != 0) { + *video_format = best_format; + return true; + } + return false; +} + +static VideoCodec DefaultVideoCodec() { + VideoCodec default_codec(kDefaultVideoCodecPref.payload_type, + kDefaultVideoCodecPref.name, + kDefaultVideoFormat.width, + kDefaultVideoFormat.height, + kDefaultFramerate, + 0); + return default_codec; +} + +static VideoCodec DefaultRedCodec() { + return VideoCodec(kRedPref.payload_type, kRedPref.name, 0, 0, 0, 0); +} + +static VideoCodec DefaultUlpfecCodec() { + return VideoCodec(kUlpfecPref.payload_type, kUlpfecPref.name, 0, 0, 0, 0); +} + +static std::vector DefaultVideoCodecs() { + std::vector codecs; + codecs.push_back(DefaultVideoCodec()); + codecs.push_back(DefaultRedCodec()); + codecs.push_back(DefaultUlpfecCodec()); + if (kDefaultVideoCodecPref.rtx_payload_type != -1) { + codecs.push_back( + VideoCodec::CreateRtxCodec(kDefaultVideoCodecPref.rtx_payload_type, + kDefaultVideoCodecPref.payload_type)); + } + return codecs; +} + +class DefaultVideoEncoderFactory : public WebRtcVideoEncoderFactory2 { + public: + virtual bool CreateEncoderSettings( + webrtc::VideoSendStream::Config::EncoderSettings* encoder_settings, + const VideoOptions& options, + const VideoCodec& codec, + size_t num_streams) OVERRIDE { + if (num_streams != 1) { + LOG(LS_ERROR) << "Unsupported number of streams: " << num_streams; + return false; + } + if (!SupportsCodec(codec)) { + LOG(LS_ERROR) << "Can't create encoder settings for unsupported codec: '" + << codec.name << "'"; + return false; + } + + *encoder_settings = webrtc::VideoSendStream::Config::EncoderSettings(); + + webrtc::VideoStream stream; + stream.width = codec.width; + stream.height = codec.height; + stream.max_framerate = + codec.framerate != 0 ? codec.framerate : kDefaultFramerate; + + int min_bitrate = kMinVideoBitrate; + codec.GetParam(kCodecParamMinBitrate, &min_bitrate); + int max_bitrate = kMaxVideoBitrate; + codec.GetParam(kCodecParamMaxBitrate, &max_bitrate); + stream.min_bitrate_bps = min_bitrate * 1000; + stream.target_bitrate_bps = stream.max_bitrate_bps = max_bitrate * 1000; + + int max_qp = 56; + codec.GetParam(kCodecParamMaxQuantization, &max_qp); + stream.max_qp = max_qp; + encoder_settings->streams.push_back(stream); + + encoder_settings->encoder = webrtc::VP8Encoder::Create(); + encoder_settings->payload_type = kDefaultVideoCodecPref.payload_type; + encoder_settings->payload_name = kDefaultVideoCodecPref.name; + + return true; + } + + virtual bool SupportsCodec(const VideoCodec& codec) OVERRIDE { + return _stricmp(codec.name.c_str(), kVp8PayloadName) == 0; + } +} default_encoder_factory; + +WebRtcVideoEngine2::WebRtcVideoEngine2() { + // Construct without a factory or voice engine. + Construct(NULL, NULL, new talk_base::CpuMonitor(NULL)); +} + +WebRtcVideoEngine2::WebRtcVideoEngine2( + WebRtcVideoChannelFactory* channel_factory) { + // Construct without a voice engine. + Construct(channel_factory, NULL, new talk_base::CpuMonitor(NULL)); +} + +void WebRtcVideoEngine2::Construct(WebRtcVideoChannelFactory* channel_factory, + WebRtcVoiceEngine* voice_engine, + talk_base::CpuMonitor* cpu_monitor) { + LOG(LS_INFO) << "WebRtcVideoEngine2::WebRtcVideoEngine2"; + worker_thread_ = NULL; + voice_engine_ = voice_engine; + initialized_ = false; + capture_started_ = false; + cpu_monitor_.reset(cpu_monitor); + channel_factory_ = channel_factory; + + video_codecs_ = DefaultVideoCodecs(); + default_codec_format_ = VideoFormat(kDefaultVideoFormat); +} + +WebRtcVideoEngine2::~WebRtcVideoEngine2() { + LOG(LS_INFO) << "WebRtcVideoEngine2::~WebRtcVideoEngine2"; + + if (initialized_) { + Terminate(); + } +} + +bool WebRtcVideoEngine2::Init(talk_base::Thread* worker_thread) { + LOG(LS_INFO) << "WebRtcVideoEngine2::Init"; + worker_thread_ = worker_thread; + ASSERT(worker_thread_ != NULL); + + cpu_monitor_->set_thread(worker_thread_); + if (!cpu_monitor_->Start(kCpuMonitorPeriodMs)) { + LOG(LS_ERROR) << "Failed to start CPU monitor."; + cpu_monitor_.reset(); + } + + initialized_ = true; + return true; +} + +void WebRtcVideoEngine2::Terminate() { + LOG(LS_INFO) << "WebRtcVideoEngine2::Terminate"; + + cpu_monitor_->Stop(); + + initialized_ = false; +} + +int WebRtcVideoEngine2::GetCapabilities() { return VIDEO_RECV | VIDEO_SEND; } + +bool WebRtcVideoEngine2::SetOptions(const VideoOptions& options) { + // TODO(pbos): Do we need this? This is a no-op in the existing + // WebRtcVideoEngine implementation. + LOG(LS_VERBOSE) << "SetOptions: " << options.ToString(); + // options_ = options; + return true; +} + +bool WebRtcVideoEngine2::SetDefaultEncoderConfig( + const VideoEncoderConfig& config) { + // TODO(pbos): Implement. Should be covered by corresponding unit tests. + LOG(LS_VERBOSE) << "SetDefaultEncoderConfig()"; + return true; +} + +VideoEncoderConfig WebRtcVideoEngine2::GetDefaultEncoderConfig() const { + return VideoEncoderConfig(DefaultVideoCodec()); +} + +WebRtcVideoChannel2* WebRtcVideoEngine2::CreateChannel( + VoiceMediaChannel* voice_channel) { + LOG(LS_INFO) << "CreateChannel: " + << (voice_channel != NULL ? "With" : "Without") + << " voice channel."; + WebRtcVideoChannel2* channel = + channel_factory_ != NULL + ? channel_factory_->Create(this, voice_channel) + : new WebRtcVideoChannel2( + this, voice_channel, GetDefaultVideoEncoderFactory()); + if (!channel->Init()) { + delete channel; + return NULL; + } + return channel; +} + +const std::vector& WebRtcVideoEngine2::codecs() const { + return video_codecs_; +} + +const std::vector& +WebRtcVideoEngine2::rtp_header_extensions() const { + return rtp_header_extensions_; +} + +void WebRtcVideoEngine2::SetLogging(int min_sev, const char* filter) { + // TODO(pbos): Set up logging. + LOG(LS_VERBOSE) << "SetLogging: " << min_sev << '"' << filter << '"'; + // if min_sev == -1, we keep the current log level. + if (min_sev < 0) { + assert(min_sev == -1); + return; + } +} + +bool WebRtcVideoEngine2::EnableTimedRender() { + // TODO(pbos): Figure out whether this can be removed. + return true; +} + +bool WebRtcVideoEngine2::SetLocalRenderer(VideoRenderer* renderer) { + // TODO(pbos): Implement or remove. Unclear which stream should be rendered + // locally even. + return true; +} + +// Checks to see whether we comprehend and could receive a particular codec +bool WebRtcVideoEngine2::FindCodec(const VideoCodec& in) { + // TODO(pbos): Probe encoder factory to figure out that the codec is supported + // if supported by the encoder factory. Add a corresponding test that fails + // with this code (that doesn't ask the factory). + for (int i = 0; i < ARRAY_SIZE(kVideoFormats); ++i) { + const VideoFormat fmt(kVideoFormats[i]); + if ((in.width != 0 || in.height != 0) && + (fmt.width != in.width || fmt.height != in.height)) { + continue; + } + for (size_t j = 0; j < video_codecs_.size(); ++j) { + VideoCodec codec(video_codecs_[j].id, video_codecs_[j].name, 0, 0, 0, 0); + if (codec.Matches(in)) { + return true; + } + } + } + return false; +} + +// Tells whether the |requested| codec can be transmitted or not. If it can be +// transmitted |out| is set with the best settings supported. Aspect ratio will +// be set as close to |current|'s as possible. If not set |requested|'s +// dimensions will be used for aspect ratio matching. +bool WebRtcVideoEngine2::CanSendCodec(const VideoCodec& requested, + const VideoCodec& current, + VideoCodec* out) { + assert(out != NULL); + // TODO(pbos): Implement. + + if (requested.width != requested.height && + (requested.height == 0 || requested.width == 0)) { + // 0xn and nx0 are invalid resolutions. + return false; + } + + VideoCodec matching_codec; + if (!FindFirstMatchingCodec(video_codecs_, requested, &matching_codec)) { + // Codec not supported. + return false; + } + + // Pick the best quality that is within their and our bounds and has the + // correct aspect ratio. + VideoFormat format; + if (requested.width == 0 && requested.height == 0) { + // Special case with resolution 0. The channel should not send frames. + } else { + int max_width = talk_base::_min(requested.width, matching_codec.width); + int max_height = talk_base::_min(requested.height, matching_codec.height); + int aspect_width = max_width; + int aspect_height = max_height; + if (current.width > 0 && current.height > 0) { + aspect_width = current.width; + aspect_height = current.height; + } + if (!FindBestVideoFormat( + max_width, max_height, aspect_width, aspect_height, &format)) { + return false; + } + } + + out->id = requested.id; + out->name = requested.name; + out->preference = requested.preference; + out->params = requested.params; + out->framerate = + talk_base::_min(requested.framerate, matching_codec.framerate); + out->width = format.width; + out->height = format.height; + out->params = requested.params; + out->feedback_params = requested.feedback_params; + return true; +} + +bool WebRtcVideoEngine2::SetVoiceEngine(WebRtcVoiceEngine* voice_engine) { + if (initialized_) { + LOG(LS_WARNING) << "SetVoiceEngine can not be called after Init"; + return false; + } + voice_engine_ = voice_engine; + return true; +} + +// Ignore spammy trace messages, mostly from the stats API when we haven't +// gotten RTCP info yet from the remote side. +bool WebRtcVideoEngine2::ShouldIgnoreTrace(const std::string& trace) { + static const char* const kTracesToIgnore[] = {NULL}; + for (const char* const* p = kTracesToIgnore; *p; ++p) { + if (trace.find(*p) == 0) { + return true; + } + } + return false; +} + +WebRtcVideoEncoderFactory2* WebRtcVideoEngine2::GetDefaultVideoEncoderFactory() + const { + return &default_encoder_factory; +} + +// Thin map between cricket::VideoFrame and an existing webrtc::I420VideoFrame +// to avoid having to copy the rendered VideoFrame prematurely. +// This implementation is only safe to use in a const context and should never +// be written to. +class WebRtcVideoRenderFrame : public cricket::VideoFrame { + public: + explicit WebRtcVideoRenderFrame(const webrtc::I420VideoFrame* frame) + : frame_(frame) {} + + virtual bool InitToBlack(int w, + int h, + size_t pixel_width, + size_t pixel_height, + int64 elapsed_time, + int64 time_stamp) OVERRIDE { + UNIMPLEMENTED; + return false; + } + + virtual bool Reset(uint32 fourcc, + int w, + int h, + int dw, + int dh, + uint8* sample, + size_t sample_size, + size_t pixel_width, + size_t pixel_height, + int64 elapsed_time, + int64 time_stamp, + int rotation) OVERRIDE { + UNIMPLEMENTED; + return false; + } + + virtual size_t GetWidth() const OVERRIDE { + return static_cast(frame_->width()); + } + virtual size_t GetHeight() const OVERRIDE { + return static_cast(frame_->height()); + } + + virtual const uint8* GetYPlane() const OVERRIDE { + return frame_->buffer(webrtc::kYPlane); + } + virtual const uint8* GetUPlane() const OVERRIDE { + return frame_->buffer(webrtc::kUPlane); + } + virtual const uint8* GetVPlane() const OVERRIDE { + return frame_->buffer(webrtc::kVPlane); + } + + virtual uint8* GetYPlane() OVERRIDE { + UNIMPLEMENTED; + return NULL; + } + virtual uint8* GetUPlane() OVERRIDE { + UNIMPLEMENTED; + return NULL; + } + virtual uint8* GetVPlane() OVERRIDE { + UNIMPLEMENTED; + return NULL; + } + + virtual int32 GetYPitch() const OVERRIDE { + return frame_->stride(webrtc::kYPlane); + } + virtual int32 GetUPitch() const OVERRIDE { + return frame_->stride(webrtc::kUPlane); + } + virtual int32 GetVPitch() const OVERRIDE { + return frame_->stride(webrtc::kVPlane); + } + + virtual void* GetNativeHandle() const OVERRIDE { return NULL; } + + virtual size_t GetPixelWidth() const OVERRIDE { return 1; } + virtual size_t GetPixelHeight() const OVERRIDE { return 1; } + + virtual int64 GetElapsedTime() const OVERRIDE { + // Convert millisecond render time to ns timestamp. + return frame_->render_time_ms() * talk_base::kNumNanosecsPerMillisec; + } + virtual int64 GetTimeStamp() const OVERRIDE { + // Convert 90K rtp timestamp to ns timestamp. + return (frame_->timestamp() / 90) * talk_base::kNumNanosecsPerMillisec; + } + virtual void SetElapsedTime(int64 elapsed_time) OVERRIDE { UNIMPLEMENTED; } + virtual void SetTimeStamp(int64 time_stamp) OVERRIDE { UNIMPLEMENTED; } + + virtual int GetRotation() const OVERRIDE { + UNIMPLEMENTED; + return ROTATION_0; + } + + virtual VideoFrame* Copy() const OVERRIDE { + UNIMPLEMENTED; + return NULL; + } + + virtual bool MakeExclusive() OVERRIDE { + UNIMPLEMENTED; + return false; + } + + virtual size_t CopyToBuffer(uint8* buffer, size_t size) const { + UNIMPLEMENTED; + return 0; + } + + // TODO(fbarchard): Refactor into base class and share with LMI + virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, + uint8* buffer, + size_t size, + int stride_rgb) const OVERRIDE { + size_t width = GetWidth(); + size_t height = GetHeight(); + size_t needed = (stride_rgb >= 0 ? stride_rgb : -stride_rgb) * height; + if (size < needed) { + LOG(LS_WARNING) << "RGB buffer is not large enough"; + return needed; + } + + if (libyuv::ConvertFromI420(GetYPlane(), + GetYPitch(), + GetUPlane(), + GetUPitch(), + GetVPlane(), + GetVPitch(), + buffer, + stride_rgb, + static_cast(width), + static_cast(height), + to_fourcc)) { + LOG(LS_ERROR) << "RGB type not supported: " << to_fourcc; + return 0; // 0 indicates error + } + return needed; + } + + protected: + virtual VideoFrame* CreateEmptyFrame(int w, + int h, + size_t pixel_width, + size_t pixel_height, + int64 elapsed_time, + int64 time_stamp) const OVERRIDE { + // TODO(pbos): Remove WebRtcVideoFrame dependency, and have a non-const + // version of I420VideoFrame wrapped. + WebRtcVideoFrame* frame = new WebRtcVideoFrame(); + frame->InitToBlack( + w, h, pixel_width, pixel_height, elapsed_time, time_stamp); + return frame; + } + + private: + const webrtc::I420VideoFrame* const frame_; +}; + +WebRtcVideoRenderer::WebRtcVideoRenderer() + : last_width_(-1), last_height_(-1), renderer_(NULL) {} + +void WebRtcVideoRenderer::RenderFrame(const webrtc::I420VideoFrame& frame, + int time_to_render_ms) { + talk_base::CritScope crit(&lock_); + if (renderer_ == NULL) { + LOG(LS_WARNING) << "VideoReceiveStream not connected to a VideoRenderer."; + return; + } + + if (frame.width() != last_width_ || frame.height() != last_height_) { + SetSize(frame.width(), frame.height()); + } + + LOG(LS_VERBOSE) << "RenderFrame: (" << frame.width() << "x" << frame.height() + << ")"; + + const WebRtcVideoRenderFrame render_frame(&frame); + renderer_->RenderFrame(&render_frame); +} + +void WebRtcVideoRenderer::SetRenderer(cricket::VideoRenderer* renderer) { + talk_base::CritScope crit(&lock_); + renderer_ = renderer; + if (renderer_ != NULL && last_width_ != -1) { + SetSize(last_width_, last_height_); + } +} + +VideoRenderer* WebRtcVideoRenderer::GetRenderer() { + talk_base::CritScope crit(&lock_); + return renderer_; +} + +void WebRtcVideoRenderer::SetSize(int width, int height) { + talk_base::CritScope crit(&lock_); + if (!renderer_->SetSize(width, height, 0)) { + LOG(LS_ERROR) << "Could not set renderer size."; + } + last_width_ = width; + last_height_ = height; +} + +// WebRtcVideoChannel2 + +WebRtcVideoChannel2::WebRtcVideoChannel2( + WebRtcVideoEngine2* engine, + VoiceMediaChannel* voice_channel, + WebRtcVideoEncoderFactory2* encoder_factory) + : encoder_factory_(encoder_factory) { + // TODO(pbos): Connect the video and audio with |voice_channel|. + webrtc::Call::Config config(this); + Construct(webrtc::Call::Create(config), engine); +} + +WebRtcVideoChannel2::WebRtcVideoChannel2( + webrtc::Call* call, + WebRtcVideoEngine2* engine, + WebRtcVideoEncoderFactory2* encoder_factory) + : encoder_factory_(encoder_factory) { + Construct(call, engine); +} + +void WebRtcVideoChannel2::Construct(webrtc::Call* call, + WebRtcVideoEngine2* engine) { + rtcp_receiver_report_ssrc_ = kDefaultRtcpReceiverReportSsrc; + sending_ = false; + call_.reset(call); + default_renderer_ = NULL; + default_send_ssrc_ = 0; + default_recv_ssrc_ = 0; +} + +WebRtcVideoChannel2::~WebRtcVideoChannel2() { + for (std::map::iterator it = + send_streams_.begin(); + it != send_streams_.end(); + ++it) { + delete it->second; + } + + for (std::map::iterator it = + receive_streams_.begin(); + it != receive_streams_.end(); + ++it) { + assert(it->second != NULL); + call_->DestroyVideoReceiveStream(it->second); + } + + for (std::map::iterator it = renderers_.begin(); + it != renderers_.end(); + ++it) { + assert(it->second != NULL); + delete it->second; + } +} + +bool WebRtcVideoChannel2::Init() { return true; } + +namespace { + +static bool ValidateCodecFormats(const std::vector& codecs) { + for (size_t i = 0; i < codecs.size(); ++i) { + if (!codecs[i].ValidateCodecFormat()) { + return false; + } + } + return true; +} + +static std::string CodecVectorToString(const std::vector& codecs) { + std::stringstream out; + out << '{'; + for (size_t i = 0; i < codecs.size(); ++i) { + out << codecs[i].ToString(); + if (i != codecs.size() - 1) { + out << ", "; + } + } + out << '}'; + return out.str(); +} + +} // namespace + +bool WebRtcVideoChannel2::SetRecvCodecs(const std::vector& codecs) { + // TODO(pbos): Must these receive codecs propagate to existing receive + // streams? + LOG(LS_INFO) << "SetRecvCodecs: " << CodecVectorToString(codecs); + if (!ValidateCodecFormats(codecs)) { + return false; + } + + const std::vector mapped_codecs = MapCodecs(codecs); + if (mapped_codecs.empty()) { + LOG(LS_ERROR) << "SetRecvCodecs called without video codec payloads."; + return false; + } + + // TODO(pbos): Add a decoder factory which controls supported codecs. + // Blocked on webrtc:2854. + for (size_t i = 0; i < mapped_codecs.size(); ++i) { + if (_stricmp(mapped_codecs[i].codec.name.c_str(), kVp8PayloadName) != 0) { + LOG(LS_ERROR) << "SetRecvCodecs called with unsupported codec: '" + << mapped_codecs[i].codec.name << "'"; + return false; + } + } + + recv_codecs_ = mapped_codecs; + return true; +} + +bool WebRtcVideoChannel2::SetSendCodecs(const std::vector& codecs) { + LOG(LS_INFO) << "SetSendCodecs: " << CodecVectorToString(codecs); + if (!ValidateCodecFormats(codecs)) { + return false; + } + + const std::vector supported_codecs = + FilterSupportedCodecs(MapCodecs(codecs)); + + if (supported_codecs.empty()) { + LOG(LS_ERROR) << "No video codecs supported by encoder factory."; + return false; + } + + send_codec_.Set(supported_codecs.front()); + LOG(LS_INFO) << "Using codec: " << supported_codecs.front().codec.ToString(); + + SetCodecForAllSendStreams(supported_codecs.front()); + + return true; +} + +bool WebRtcVideoChannel2::GetSendCodec(VideoCodec* codec) { + VideoCodecSettings codec_settings; + if (!send_codec_.Get(&codec_settings)) { + LOG(LS_VERBOSE) << "GetSendCodec: No send codec set."; + return false; + } + *codec = codec_settings.codec; + return true; +} + +bool WebRtcVideoChannel2::SetSendStreamFormat(uint32 ssrc, + const VideoFormat& format) { + LOG(LS_VERBOSE) << "SetSendStreamFormat:" << ssrc << " -> " + << format.ToString(); + if (send_streams_.find(ssrc) == send_streams_.end()) { + return false; + } + return send_streams_[ssrc]->SetVideoFormat(format); +} + +bool WebRtcVideoChannel2::SetRender(bool render) { + // TODO(pbos): Implement. Or refactor away as it shouldn't be needed. + LOG(LS_VERBOSE) << "SetRender: " << (render ? "true" : "false"); + return true; +} + +bool WebRtcVideoChannel2::SetSend(bool send) { + LOG(LS_VERBOSE) << "SetSend: " << (send ? "true" : "false"); + if (send && !send_codec_.IsSet()) { + LOG(LS_ERROR) << "SetSend(true) called before setting codec."; + return false; + } + if (send) { + StartAllSendStreams(); + } else { + StopAllSendStreams(); + } + sending_ = send; + return true; +} + +static bool ConfigureSendSsrcs(webrtc::VideoSendStream::Config* config, + const StreamParams& sp) { + if (!sp.has_ssrc_groups()) { + config->rtp.ssrcs = sp.ssrcs; + return true; + } + + if (sp.get_ssrc_group(kFecSsrcGroupSemantics) != NULL) { + LOG(LS_ERROR) << "Standalone FEC SSRCs not supported."; + return false; + } + + const SsrcGroup* sim_group = sp.get_ssrc_group(kSimSsrcGroupSemantics); + if (sim_group == NULL) { + LOG(LS_ERROR) << "Grouped StreamParams without regular SSRC group: " + << sp.ToString(); + return false; + } + + // Map RTX SSRCs. + std::vector rtx_ssrcs; + for (size_t i = 0; i < sim_group->ssrcs.size(); ++i) { + uint32_t rtx_ssrc; + if (!sp.GetFidSsrc(sim_group->ssrcs[i], &rtx_ssrc)) { + continue; + } + rtx_ssrcs.push_back(rtx_ssrc); + } + if (!rtx_ssrcs.empty() && sim_group->ssrcs.size() != rtx_ssrcs.size()) { + LOG(LS_ERROR) + << "RTX SSRCs exist, but don't cover all SSRCs (unsupported): " + << sp.ToString(); + return false; + } + config->rtp.rtx.ssrcs = rtx_ssrcs; + config->rtp.ssrcs = sim_group->ssrcs; + return true; +} + +bool WebRtcVideoChannel2::AddSendStream(const StreamParams& sp) { + LOG(LS_INFO) << "AddSendStream: " << sp.ToString(); + if (sp.ssrcs.empty()) { + LOG(LS_ERROR) << "No SSRCs in stream parameters."; + return false; + } + + uint32 ssrc = sp.first_ssrc(); + assert(ssrc != 0); + // TODO(pbos): Make sure none of sp.ssrcs are used, not just the identifying + // ssrc. + if (send_streams_.find(ssrc) != send_streams_.end()) { + LOG(LS_ERROR) << "Send stream with ssrc '" << ssrc << "' already exists."; + return false; + } + + webrtc::VideoSendStream::Config config = call_->GetDefaultSendConfig(); + + if (!ConfigureSendSsrcs(&config, sp)) { + return false; + } + + VideoCodecSettings codec_settings; + if (!send_codec_.Get(&codec_settings)) { + // TODO(pbos): Set up a temporary fake encoder for VideoSendStream instead + // of setting default codecs not to break CreateEncoderSettings. + SetSendCodecs(DefaultVideoCodecs()); + assert(send_codec_.IsSet()); + send_codec_.Get(&codec_settings); + // This is only to bring up defaults to make VideoSendStream setup easier + // and avoid complexity. We still don't want to allow sending with the + // default codec. + send_codec_.Clear(); + } + + // CreateEncoderSettings will allocate a suitable VideoEncoder instance + // matching current settings. + if (!encoder_factory_->CreateEncoderSettings(&config.encoder_settings, + options_, + codec_settings.codec, + config.rtp.ssrcs.size())) { + LOG(LS_ERROR) << "Failed to create suitable encoder settings."; + return false; + } + + config.rtp.c_name = sp.cname; + config.rtp.fec = codec_settings.fec; + if (!config.rtp.rtx.ssrcs.empty()) { + config.rtp.rtx.payload_type = codec_settings.rtx_payload_type; + } + + config.rtp.nack.rtp_history_ms = kNackHistoryMs; + config.rtp.max_packet_size = kVideoMtu; + + WebRtcVideoSendStream* stream = + new WebRtcVideoSendStream(call_.get(), config, encoder_factory_); + send_streams_[ssrc] = stream; + + if (rtcp_receiver_report_ssrc_ == kDefaultRtcpReceiverReportSsrc) { + rtcp_receiver_report_ssrc_ = ssrc; + } + if (default_send_ssrc_ == 0) { + default_send_ssrc_ = ssrc; + } + if (sending_) { + stream->Start(); + } + + return true; +} + +bool WebRtcVideoChannel2::RemoveSendStream(uint32 ssrc) { + LOG(LS_INFO) << "RemoveSendStream: " << ssrc; + + if (ssrc == 0) { + if (default_send_ssrc_ == 0) { + LOG(LS_ERROR) << "No default send stream active."; + return false; + } + + LOG(LS_VERBOSE) << "Removing default stream: " << default_send_ssrc_; + ssrc = default_send_ssrc_; + } + + std::map::iterator it = + send_streams_.find(ssrc); + if (it == send_streams_.end()) { + return false; + } + + delete it->second; + send_streams_.erase(it); + + if (ssrc == default_send_ssrc_) { + default_send_ssrc_ = 0; + } + + return true; +} + +bool WebRtcVideoChannel2::AddRecvStream(const StreamParams& sp) { + LOG(LS_INFO) << "AddRecvStream: " << sp.ToString(); + assert(sp.ssrcs.size() > 0); + + uint32 ssrc = sp.first_ssrc(); + assert(ssrc != 0); // TODO(pbos): Is this ever valid? + if (default_recv_ssrc_ == 0) { + default_recv_ssrc_ = ssrc; + } + + // TODO(pbos): Check if any of the SSRCs overlap. + if (receive_streams_.find(ssrc) != receive_streams_.end()) { + LOG(LS_ERROR) << "Receive stream for SSRC " << ssrc << "already exists."; + return false; + } + + webrtc::VideoReceiveStream::Config config = call_->GetDefaultReceiveConfig(); + config.rtp.remote_ssrc = ssrc; + config.rtp.local_ssrc = rtcp_receiver_report_ssrc_; + uint32 rtx_ssrc = 0; + if (sp.GetFidSsrc(ssrc, &rtx_ssrc)) { + // TODO(pbos): Right now, VideoReceiveStream accepts any rtx payload, this + // should use the actual codec payloads that may be received. + // (for each receive payload, set rtx[payload].ssrc = rtx_ssrc. + config.rtp.rtx[0].ssrc = rtx_ssrc; + } + + config.rtp.remb = true; + // TODO(pbos): This protection is against setting the same local ssrc as + // remote which is not permitted by the lower-level API. RTCP requires a + // corresponding sender SSRC. Figure out what to do when we don't have + // (receive-only) or know a good local SSRC. + if (config.rtp.remote_ssrc == config.rtp.local_ssrc) { + if (config.rtp.local_ssrc != kDefaultRtcpReceiverReportSsrc) { + config.rtp.local_ssrc = kDefaultRtcpReceiverReportSsrc; + } else { + config.rtp.local_ssrc = kDefaultRtcpReceiverReportSsrc + 1; + } + } + bool default_renderer_used = false; + for (std::map::iterator it = renderers_.begin(); + it != renderers_.end(); + ++it) { + if (it->second->GetRenderer() == default_renderer_) { + default_renderer_used = true; + break; + } + } + + assert(renderers_[ssrc] == NULL); + renderers_[ssrc] = new WebRtcVideoRenderer(); + if (!default_renderer_used) { + renderers_[ssrc]->SetRenderer(default_renderer_); + } + config.renderer = renderers_[ssrc]; + + { + // TODO(pbos): Base receive codecs off recv_codecs_ and set up using a + // DecoderFactory similar to send side. Pending webrtc:2854. + // Also set up default codecs if there's nothing in recv_codecs_. + webrtc::VideoCodec codec; + memset(&codec, 0, sizeof(codec)); + + codec.plType = kDefaultVideoCodecPref.payload_type; + strcpy(codec.plName, kDefaultVideoCodecPref.name); + codec.codecType = webrtc::kVideoCodecVP8; + codec.codecSpecific.VP8.resilience = webrtc::kResilientStream; + codec.codecSpecific.VP8.numberOfTemporalLayers = 1; + codec.codecSpecific.VP8.denoisingOn = true; + codec.codecSpecific.VP8.errorConcealmentOn = false; + codec.codecSpecific.VP8.automaticResizeOn = false; + codec.codecSpecific.VP8.frameDroppingOn = true; + codec.codecSpecific.VP8.keyFrameInterval = 3000; + // Bitrates don't matter and are ignored for the receiver. This is put in to + // have the current underlying implementation accept the VideoCodec. + codec.minBitrate = codec.startBitrate = codec.maxBitrate = 300; + config.codecs.push_back(codec); + for (size_t i = 0; i < recv_codecs_.size(); ++i) { + if (recv_codecs_[i].codec.id == codec.plType) { + config.rtp.fec = recv_codecs_[i].fec; + if (recv_codecs_[i].rtx_payload_type != -1 && rtx_ssrc != 0) { + config.rtp.rtx[codec.plType].ssrc = rtx_ssrc; + config.rtp.rtx[codec.plType].payload_type = + recv_codecs_[i].rtx_payload_type; + } + break; + } + } + } + + webrtc::VideoReceiveStream* receive_stream = + call_->CreateVideoReceiveStream(config); + assert(receive_stream != NULL); + + receive_streams_[ssrc] = receive_stream; + receive_stream->Start(); + + return true; +} + +bool WebRtcVideoChannel2::RemoveRecvStream(uint32 ssrc) { + LOG(LS_INFO) << "RemoveRecvStream: " << ssrc; + if (ssrc == 0) { + ssrc = default_recv_ssrc_; + } + + std::map::iterator stream = + receive_streams_.find(ssrc); + if (stream == receive_streams_.end()) { + LOG(LS_ERROR) << "Stream not found for ssrc: " << ssrc; + return false; + } + call_->DestroyVideoReceiveStream(stream->second); + receive_streams_.erase(stream); + + std::map::iterator renderer = + renderers_.find(ssrc); + assert(renderer != renderers_.end()); + delete renderer->second; + renderers_.erase(renderer); + + if (ssrc == default_recv_ssrc_) { + default_recv_ssrc_ = 0; + } + + return true; +} + +bool WebRtcVideoChannel2::SetRenderer(uint32 ssrc, VideoRenderer* renderer) { + LOG(LS_INFO) << "SetRenderer: ssrc:" << ssrc << " " + << (renderer ? "(ptr)" : "NULL"); + bool is_default_ssrc = false; + if (ssrc == 0) { + is_default_ssrc = true; + ssrc = default_recv_ssrc_; + default_renderer_ = renderer; + } + + std::map::iterator it = renderers_.find(ssrc); + if (it == renderers_.end()) { + return is_default_ssrc; + } + + it->second->SetRenderer(renderer); + return true; +} + +bool WebRtcVideoChannel2::GetRenderer(uint32 ssrc, VideoRenderer** renderer) { + if (ssrc == 0) { + if (default_renderer_ == NULL) { + return false; + } + *renderer = default_renderer_; + return true; + } + + std::map::iterator it = renderers_.find(ssrc); + if (it == renderers_.end()) { + return false; + } + *renderer = it->second->GetRenderer(); + return true; +} + +bool WebRtcVideoChannel2::GetStats(const StatsOptions& options, + VideoMediaInfo* info) { + // TODO(pbos): Implement. + return true; +} + +bool WebRtcVideoChannel2::SetCapturer(uint32 ssrc, VideoCapturer* capturer) { + LOG(LS_INFO) << "SetCapturer: " << ssrc << " -> " + << (capturer != NULL ? "(capturer)" : "NULL"); + assert(ssrc != 0); + if (send_streams_.find(ssrc) == send_streams_.end()) { + LOG(LS_ERROR) << "No sending stream on ssrc " << ssrc; + return false; + } + return send_streams_[ssrc]->SetCapturer(capturer); +} + +bool WebRtcVideoChannel2::SendIntraFrame() { + // TODO(pbos): Implement. + LOG(LS_VERBOSE) << "SendIntraFrame()."; + return true; +} + +bool WebRtcVideoChannel2::RequestIntraFrame() { + // TODO(pbos): Implement. + LOG(LS_VERBOSE) << "SendIntraFrame()."; + return true; +} + +void WebRtcVideoChannel2::OnPacketReceived( + talk_base::Buffer* packet, + const talk_base::PacketTime& packet_time) { + if (call_->Receiver()->DeliverPacket( + reinterpret_cast(packet->data()), packet->length())) { + return; + } + // Packet ignored most likely because there's no receiver for it, try to + // create one unless it already exists. + + uint32 ssrc = 0; + if (default_recv_ssrc_ != 0) { // Already one default stream. + LOG(LS_WARNING) << "Default receive stream already set."; + return; + } + + if (!GetRtpSsrc(packet->data(), packet->length(), &ssrc)) { + return; + } + + StreamParams sp; + sp.ssrcs.push_back(ssrc); + AddRecvStream(sp); + + if (!call_->Receiver()->DeliverPacket( + reinterpret_cast(packet->data()), packet->length())) { + LOG(LS_WARNING) << "Failed to deliver RTP packet."; + return; + } +} + +void WebRtcVideoChannel2::OnRtcpReceived( + talk_base::Buffer* packet, + const talk_base::PacketTime& packet_time) { + if (!call_->Receiver()->DeliverPacket( + reinterpret_cast(packet->data()), packet->length())) { + LOG(LS_WARNING) << "Failed to deliver RTCP packet."; + } +} + +void WebRtcVideoChannel2::OnReadyToSend(bool ready) { + LOG(LS_VERBOSE) << "OnReadySend: " << (ready ? "Ready." : "Not ready."); +} + +bool WebRtcVideoChannel2::MuteStream(uint32 ssrc, bool mute) { + LOG(LS_VERBOSE) << "MuteStream: " << ssrc << " -> " + << (mute ? "mute" : "unmute"); + assert(ssrc != 0); + if (send_streams_.find(ssrc) == send_streams_.end()) { + LOG(LS_ERROR) << "No sending stream on ssrc " << ssrc; + return false; + } + return send_streams_[ssrc]->MuteStream(mute); +} + +bool WebRtcVideoChannel2::SetRecvRtpHeaderExtensions( + const std::vector& extensions) { + // TODO(pbos): Implement. + LOG(LS_VERBOSE) << "SetRecvRtpHeaderExtensions()"; + return true; +} + +bool WebRtcVideoChannel2::SetSendRtpHeaderExtensions( + const std::vector& extensions) { + // TODO(pbos): Implement. + LOG(LS_VERBOSE) << "SetSendRtpHeaderExtensions()"; + return true; +} + +bool WebRtcVideoChannel2::SetStartSendBandwidth(int bps) { + // TODO(pbos): Implement. + LOG(LS_VERBOSE) << "SetStartSendBandwidth: " << bps; + return true; +} + +bool WebRtcVideoChannel2::SetMaxSendBandwidth(int bps) { + // TODO(pbos): Implement. + LOG(LS_VERBOSE) << "SetMaxSendBandwidth: " << bps; + return true; +} + +bool WebRtcVideoChannel2::SetOptions(const VideoOptions& options) { + LOG(LS_VERBOSE) << "SetOptions: " << options.ToString(); + options_.SetAll(options); + return true; +} + +void WebRtcVideoChannel2::SetInterface(NetworkInterface* iface) { + MediaChannel::SetInterface(iface); + // Set the RTP recv/send buffer to a bigger size + MediaChannel::SetOption(NetworkInterface::ST_RTP, + talk_base::Socket::OPT_RCVBUF, + kVideoRtpBufferSize); + + // TODO(sriniv): Remove or re-enable this. + // As part of b/8030474, send-buffer is size now controlled through + // portallocator flags. + // network_interface_->SetOption(NetworkInterface::ST_RTP, + // talk_base::Socket::OPT_SNDBUF, + // kVideoRtpBufferSize); +} + +void WebRtcVideoChannel2::UpdateAspectRatio(int ratio_w, int ratio_h) { + // TODO(pbos): Implement. +} + +void WebRtcVideoChannel2::OnMessage(talk_base::Message* msg) { + // Ignored. +} + +bool WebRtcVideoChannel2::SendRtp(const uint8_t* data, size_t len) { + talk_base::Buffer packet(data, len, kMaxRtpPacketLen); + return MediaChannel::SendPacket(&packet); +} + +bool WebRtcVideoChannel2::SendRtcp(const uint8_t* data, size_t len) { + talk_base::Buffer packet(data, len, kMaxRtpPacketLen); + return MediaChannel::SendRtcp(&packet); +} + +void WebRtcVideoChannel2::StartAllSendStreams() { + for (std::map::iterator it = + send_streams_.begin(); + it != send_streams_.end(); + ++it) { + it->second->Start(); + } +} + +void WebRtcVideoChannel2::StopAllSendStreams() { + for (std::map::iterator it = + send_streams_.begin(); + it != send_streams_.end(); + ++it) { + it->second->Stop(); + } +} + +void WebRtcVideoChannel2::SetCodecForAllSendStreams( + const WebRtcVideoChannel2::VideoCodecSettings& codec) { + for (std::map::iterator it = + send_streams_.begin(); + it != send_streams_.end(); + ++it) { + assert(it->second != NULL); + it->second->SetCodec(options_, codec); + } +} + +WebRtcVideoChannel2::WebRtcVideoSendStream::WebRtcVideoSendStream( + webrtc::Call* call, + const webrtc::VideoSendStream::Config& config, + WebRtcVideoEncoderFactory2* encoder_factory) + : call_(call), + config_(config), + encoder_factory_(encoder_factory), + capturer_(NULL), + stream_(NULL), + sending_(false), + muted_(false), + format_(static_cast(config.encoder_settings.streams.back().height), + static_cast(config.encoder_settings.streams.back().width), + VideoFormat::FpsToInterval( + config.encoder_settings.streams.back().max_framerate), + FOURCC_I420) { + RecreateWebRtcStream(); +} + +WebRtcVideoChannel2::WebRtcVideoSendStream::~WebRtcVideoSendStream() { + DisconnectCapturer(); + call_->DestroyVideoSendStream(stream_); + delete config_.encoder_settings.encoder; +} + +static void SetWebRtcFrameToBlack(webrtc::I420VideoFrame* video_frame) { + assert(video_frame != NULL); + memset(video_frame->buffer(webrtc::kYPlane), + 16, + video_frame->allocated_size(webrtc::kYPlane)); + memset(video_frame->buffer(webrtc::kUPlane), + 128, + video_frame->allocated_size(webrtc::kUPlane)); + memset(video_frame->buffer(webrtc::kVPlane), + 128, + video_frame->allocated_size(webrtc::kVPlane)); +} + +static void CreateBlackFrame(webrtc::I420VideoFrame* video_frame, + int width, + int height) { + video_frame->CreateEmptyFrame( + width, height, width, (width + 1) / 2, (width + 1) / 2); + SetWebRtcFrameToBlack(video_frame); +} + +static void ConvertToI420VideoFrame(const VideoFrame& frame, + webrtc::I420VideoFrame* i420_frame) { + i420_frame->CreateFrame( + static_cast(frame.GetYPitch() * frame.GetHeight()), + frame.GetYPlane(), + static_cast(frame.GetUPitch() * ((frame.GetHeight() + 1) / 2)), + frame.GetUPlane(), + static_cast(frame.GetVPitch() * ((frame.GetHeight() + 1) / 2)), + frame.GetVPlane(), + static_cast(frame.GetWidth()), + static_cast(frame.GetHeight()), + static_cast(frame.GetYPitch()), + static_cast(frame.GetUPitch()), + static_cast(frame.GetVPitch())); +} + +void WebRtcVideoChannel2::WebRtcVideoSendStream::InputFrame( + VideoCapturer* capturer, + const VideoFrame* frame) { + LOG(LS_VERBOSE) << "InputFrame: " << frame->GetWidth() << "x" + << frame->GetHeight(); + bool is_screencast = capturer->IsScreencast(); + // Lock before copying, can be called concurrently when swapping input source. + talk_base::CritScope frame_cs(&frame_lock_); + if (!muted_) { + ConvertToI420VideoFrame(*frame, &video_frame_); + } else { + // Create a tiny black frame to transmit instead. + CreateBlackFrame(&video_frame_, 1, 1); + is_screencast = false; + } + talk_base::CritScope cs(&lock_); + if (format_.width == 0) { // Dropping frames. + assert(format_.height == 0); + LOG(LS_VERBOSE) << "VideoFormat 0x0 set, Dropping frame."; + return; + } + // Reconfigure codec if necessary. + if (is_screencast) { + SetDimensions(video_frame_.width(), video_frame_.height()); + } + LOG(LS_VERBOSE) << "SwapFrame: " << video_frame_.width() << "x" + << video_frame_.height() << " -> (codec) " + << config_.encoder_settings.streams.back().width << "x" + << config_.encoder_settings.streams.back().height; + stream_->Input()->SwapFrame(&video_frame_); +} + +bool WebRtcVideoChannel2::WebRtcVideoSendStream::SetCapturer( + VideoCapturer* capturer) { + if (!DisconnectCapturer() && capturer == NULL) { + return false; + } + + { + talk_base::CritScope cs(&lock_); + + if (capturer == NULL) { + LOG(LS_VERBOSE) << "Disabling capturer, sending black frame."; + webrtc::I420VideoFrame black_frame; + + int width = format_.width; + int height = format_.height; + int half_width = (width + 1) / 2; + black_frame.CreateEmptyFrame( + width, height, width, half_width, half_width); + SetWebRtcFrameToBlack(&black_frame); + SetDimensions(width, height); + stream_->Input()->SwapFrame(&black_frame); + + capturer_ = NULL; + return true; + } + + capturer_ = capturer; + } + // Lock cannot be held while connecting the capturer to prevent lock-order + // violations. + capturer->SignalVideoFrame.connect(this, &WebRtcVideoSendStream::InputFrame); + return true; +} + +bool WebRtcVideoChannel2::WebRtcVideoSendStream::SetVideoFormat( + const VideoFormat& format) { + if ((format.width == 0 || format.height == 0) && + format.width != format.height) { + LOG(LS_ERROR) << "Can't set VideoFormat, width or height is zero (but not " + "both, 0x0 drops frames)."; + return false; + } + + talk_base::CritScope cs(&lock_); + if (format.width == 0 && format.height == 0) { + LOG(LS_INFO) + << "0x0 resolution selected. Captured frames will be dropped for ssrc: " + << config_.rtp.ssrcs[0] << "."; + } else { + // TODO(pbos): Fix me, this only affects the last stream! + config_.encoder_settings.streams.back().max_framerate = + VideoFormat::IntervalToFps(format.interval); + SetDimensions(format.width, format.height); + } + + format_ = format; + return true; +} + +bool WebRtcVideoChannel2::WebRtcVideoSendStream::MuteStream(bool mute) { + talk_base::CritScope cs(&lock_); + bool was_muted = muted_; + muted_ = mute; + return was_muted != mute; +} + +bool WebRtcVideoChannel2::WebRtcVideoSendStream::DisconnectCapturer() { + talk_base::CritScope cs(&lock_); + if (capturer_ == NULL) { + return false; + } + capturer_->SignalVideoFrame.disconnect(this); + capturer_ = NULL; + return true; +} + +void WebRtcVideoChannel2::WebRtcVideoSendStream::SetCodec( + const VideoOptions& options, + const VideoCodecSettings& codec) { + talk_base::CritScope cs(&lock_); + webrtc::VideoEncoder* old_encoder = config_.encoder_settings.encoder; + if (!encoder_factory_->CreateEncoderSettings( + &config_.encoder_settings, + options, + codec.codec, + config_.encoder_settings.streams.size())) { + LOG(LS_ERROR) << "Could not create encoder settings for: '" + << codec.codec.name + << "'. This is most definitely a bug as SetCodec should only " + "receive codecs which the encoder factory claims to " + "support."; + return; + } + format_ = VideoFormat(codec.codec.width, + codec.codec.height, + VideoFormat::FpsToInterval(30), + FOURCC_I420); + config_.rtp.fec = codec.fec; + // TODO(pbos): Should changing RTX payload type be allowed? + RecreateWebRtcStream(); + delete old_encoder; +} + +void WebRtcVideoChannel2::WebRtcVideoSendStream::SetDimensions(int width, + int height) { + assert(!config_.encoder_settings.streams.empty()); + LOG(LS_VERBOSE) << "SetDimensions: " << width << "x" << height; + if (config_.encoder_settings.streams.back().width == width && + config_.encoder_settings.streams.back().height == height) { + return; + } + + // TODO(pbos): Fix me, this only affects the last stream! + config_.encoder_settings.streams.back().width = width; + config_.encoder_settings.streams.back().height = height; + // TODO(pbos): Last parameter shouldn't always be NULL? + if (!stream_->ReconfigureVideoEncoder(config_.encoder_settings.streams, + NULL)) { + LOG(LS_WARNING) << "Failed to reconfigure video encoder for dimensions: " + << width << "x" << height; + return; + } +} + +void WebRtcVideoChannel2::WebRtcVideoSendStream::Start() { + talk_base::CritScope cs(&lock_); + stream_->Start(); + sending_ = true; +} + +void WebRtcVideoChannel2::WebRtcVideoSendStream::Stop() { + talk_base::CritScope cs(&lock_); + stream_->Stop(); + sending_ = false; +} + +void WebRtcVideoChannel2::WebRtcVideoSendStream::RecreateWebRtcStream() { + if (stream_ != NULL) { + call_->DestroyVideoSendStream(stream_); + } + stream_ = call_->CreateVideoSendStream(config_); + if (sending_) { + stream_->Start(); + } +} + +WebRtcVideoChannel2::VideoCodecSettings::VideoCodecSettings() + : rtx_payload_type(-1) {} + +std::vector +WebRtcVideoChannel2::MapCodecs(const std::vector& codecs) { + assert(!codecs.empty()); + + std::vector video_codecs; + std::map payload_used; + std::map rtx_mapping; // video payload type -> rtx payload type. + + webrtc::FecConfig fec_settings; + + for (size_t i = 0; i < codecs.size(); ++i) { + const VideoCodec& in_codec = codecs[i]; + int payload_type = in_codec.id; + + if (payload_used[payload_type]) { + LOG(LS_ERROR) << "Payload type already registered: " + << in_codec.ToString(); + return std::vector(); + } + payload_used[payload_type] = true; + + switch (in_codec.GetCodecType()) { + case VideoCodec::CODEC_RED: { + // RED payload type, should not have duplicates. + assert(fec_settings.red_payload_type == -1); + fec_settings.red_payload_type = in_codec.id; + continue; + } + + case VideoCodec::CODEC_ULPFEC: { + // ULPFEC payload type, should not have duplicates. + assert(fec_settings.ulpfec_payload_type == -1); + fec_settings.ulpfec_payload_type = in_codec.id; + continue; + } + + case VideoCodec::CODEC_RTX: { + int associated_payload_type; + if (!in_codec.GetParam(kCodecParamAssociatedPayloadType, + &associated_payload_type)) { + LOG(LS_ERROR) << "RTX codec without associated payload type: " + << in_codec.ToString(); + return std::vector(); + } + rtx_mapping[associated_payload_type] = in_codec.id; + continue; + } + + case VideoCodec::CODEC_VIDEO: + break; + } + + video_codecs.push_back(VideoCodecSettings()); + video_codecs.back().codec = in_codec; + } + + // One of these codecs should have been a video codec. Only having FEC + // parameters into this code is a logic error. + assert(!video_codecs.empty()); + + // TODO(pbos): Write tests that figure out that I have not verified that RTX + // codecs aren't mapped to bogus payloads. + for (size_t i = 0; i < video_codecs.size(); ++i) { + video_codecs[i].fec = fec_settings; + if (rtx_mapping[video_codecs[i].codec.id] != 0) { + video_codecs[i].rtx_payload_type = rtx_mapping[video_codecs[i].codec.id]; + } + } + + return video_codecs; +} + +std::vector +WebRtcVideoChannel2::FilterSupportedCodecs( + const std::vector& mapped_codecs) { + std::vector supported_codecs; + for (size_t i = 0; i < mapped_codecs.size(); ++i) { + if (encoder_factory_->SupportsCodec(mapped_codecs[i].codec)) { + supported_codecs.push_back(mapped_codecs[i]); + } + } + return supported_codecs; +} + +} // namespace cricket + +#endif // HAVE_WEBRTC_VIDEO diff --git a/talk/media/webrtc/webrtcvideoengine2.h b/talk/media/webrtc/webrtcvideoengine2.h new file mode 100644 index 000000000..e6b9598b1 --- /dev/null +++ b/talk/media/webrtc/webrtcvideoengine2.h @@ -0,0 +1,332 @@ +/* + * 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_WEBRTCVIDEOENGINE2_H_ +#define TALK_MEDIA_WEBRTC_WEBRTCVIDEOENGINE2_H_ + +#include +#include +#include + +#include "talk/base/cpumonitor.h" +#include "talk/base/scoped_ptr.h" +#include "talk/media/base/mediaengine.h" +#include "talk/media/webrtc/webrtcvideochannelfactory.h" +#include "webrtc/common_video/interface/i420_video_frame.h" +#include "webrtc/system_wrappers/interface/thread_annotations.h" +#include "webrtc/transport.h" +#include "webrtc/video_renderer.h" +#include "webrtc/video_send_stream.h" + +namespace webrtc { +class Call; +class VideoCaptureModule; +class VideoDecoder; +class VideoEncoder; +class VideoRender; +class VideoSendStreamInput; +class VideoReceiveStream; +} + +namespace talk_base { +class CpuMonitor; +class Thread; +} // namespace talk_base + +namespace cricket { + +class VideoCapturer; +class VideoFrame; +class VideoProcessor; +class VideoRenderer; +class VoiceMediaChannel; +class WebRtcVideoChannel2; +class WebRtcDecoderObserver; +class WebRtcEncoderObserver; +class WebRtcLocalStreamInfo; +class WebRtcRenderAdapter; +class WebRtcVideoChannelRecvInfo; +class WebRtcVideoChannelSendInfo; +class WebRtcVideoDecoderFactory; +class WebRtcVoiceEngine; + +struct CapturedFrame; +struct Device; + +class WebRtcVideoEngine2; +class WebRtcVideoChannel2; + +class WebRtcVideoEncoderFactory2 { + public: + virtual bool CreateEncoderSettings( + webrtc::VideoSendStream::Config::EncoderSettings* encoder_settings, + const VideoOptions& options, + const cricket::VideoCodec& codec, + size_t num_streams) = 0; + virtual bool SupportsCodec(const cricket::VideoCodec& codec) = 0; +}; + +// WebRtcVideoEngine2 is used for the new native WebRTC Video API (webrtc:1667). +class WebRtcVideoEngine2 : public sigslot::has_slots<> { + public: + // Creates the WebRtcVideoEngine2 with internal VideoCaptureModule. + WebRtcVideoEngine2(); + // Custom WebRtcVideoChannelFactory for testing purposes. + explicit WebRtcVideoEngine2(WebRtcVideoChannelFactory* channel_factory); + ~WebRtcVideoEngine2(); + + // Basic video engine implementation. + bool Init(talk_base::Thread* worker_thread); + void Terminate(); + + int GetCapabilities(); + bool SetOptions(const VideoOptions& options); + bool SetDefaultEncoderConfig(const VideoEncoderConfig& config); + VideoEncoderConfig GetDefaultEncoderConfig() const; + + WebRtcVideoChannel2* CreateChannel(VoiceMediaChannel* voice_channel); + + const std::vector& codecs() const; + const std::vector& rtp_header_extensions() const; + void SetLogging(int min_sev, const char* filter); + + bool EnableTimedRender(); + // No-op, never used. + bool SetLocalRenderer(VideoRenderer* renderer); + // This is currently ignored. + sigslot::repeater2 SignalCaptureStateChange; + + // Set the VoiceEngine for A/V sync. This can only be called before Init. + bool SetVoiceEngine(WebRtcVoiceEngine* voice_engine); + + // Functions called by WebRtcVideoChannel2. + const VideoFormat& default_codec_format() const { + return default_codec_format_; + } + + bool FindCodec(const VideoCodec& in); + bool CanSendCodec(const VideoCodec& in, + const VideoCodec& current, + VideoCodec* out); + // Check whether the supplied trace should be ignored. + bool ShouldIgnoreTrace(const std::string& trace); + + VideoFormat GetStartCaptureFormat() const { return default_codec_format_; } + + talk_base::CpuMonitor* cpu_monitor() { return cpu_monitor_.get(); } + + virtual WebRtcVideoEncoderFactory2* GetDefaultVideoEncoderFactory() const; + + private: + void Construct(WebRtcVideoChannelFactory* channel_factory, + WebRtcVoiceEngine* voice_engine, + talk_base::CpuMonitor* cpu_monitor); + + talk_base::Thread* worker_thread_; + WebRtcVoiceEngine* voice_engine_; + std::vector video_codecs_; + std::vector rtp_header_extensions_; + VideoFormat default_codec_format_; + + bool initialized_; + + bool capture_started_; + + // Critical section to protect the media processor register/unregister + // while processing a frame + talk_base::CriticalSection signal_media_critical_; + + talk_base::scoped_ptr cpu_monitor_; + WebRtcVideoChannelFactory* channel_factory_; +}; + +// Adapter between webrtc::VideoRenderer and cricket::VideoRenderer. +// The webrtc::VideoRenderer is set once, whereas the cricket::VideoRenderer can +// be set after initialization. This adapter will also convert the incoming +// webrtc::I420VideoFrame to a frame type that cricket::VideoRenderer can +// render. +class WebRtcVideoRenderer : public webrtc::VideoRenderer { + public: + WebRtcVideoRenderer(); + + virtual void RenderFrame(const webrtc::I420VideoFrame& frame, + int time_to_render_ms) OVERRIDE; + + void SetRenderer(cricket::VideoRenderer* renderer); + cricket::VideoRenderer* GetRenderer(); + + private: + void SetSize(int width, int height); + int last_width_; + int last_height_; + talk_base::CriticalSection lock_; + cricket::VideoRenderer* renderer_ GUARDED_BY(lock_); +}; + +class WebRtcVideoChannel2 : public talk_base::MessageHandler, + public VideoMediaChannel, + public webrtc::newapi::Transport { + public: + WebRtcVideoChannel2(WebRtcVideoEngine2* engine, + VoiceMediaChannel* voice_channel, + WebRtcVideoEncoderFactory2* encoder_factory); + // For testing purposes insert a pre-constructed call to verify that + // WebRtcVideoChannel2 calls the correct corresponding methods. + WebRtcVideoChannel2(webrtc::Call* call, + WebRtcVideoEngine2* engine, + WebRtcVideoEncoderFactory2* encoder_factory); + ~WebRtcVideoChannel2(); + bool Init(); + + // VideoMediaChannel implementation + virtual bool SetRecvCodecs(const std::vector& codecs) OVERRIDE; + virtual bool SetSendCodecs(const std::vector& codecs) OVERRIDE; + virtual bool GetSendCodec(VideoCodec* send_codec) OVERRIDE; + virtual bool SetSendStreamFormat(uint32 ssrc, + const VideoFormat& format) OVERRIDE; + virtual bool SetRender(bool render) OVERRIDE; + virtual bool SetSend(bool send) OVERRIDE; + + virtual bool AddSendStream(const StreamParams& sp) OVERRIDE; + virtual bool RemoveSendStream(uint32 ssrc) OVERRIDE; + virtual bool AddRecvStream(const StreamParams& sp) OVERRIDE; + virtual bool RemoveRecvStream(uint32 ssrc) OVERRIDE; + virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer) OVERRIDE; + virtual bool GetStats(const StatsOptions& options, + VideoMediaInfo* info) OVERRIDE; + virtual bool SetCapturer(uint32 ssrc, VideoCapturer* capturer) OVERRIDE; + virtual bool SendIntraFrame() OVERRIDE; + virtual bool RequestIntraFrame() OVERRIDE; + + virtual void OnPacketReceived(talk_base::Buffer* packet, + const talk_base::PacketTime& packet_time) + OVERRIDE; + virtual void OnRtcpReceived(talk_base::Buffer* packet, + const talk_base::PacketTime& packet_time) + OVERRIDE; + virtual void OnReadyToSend(bool ready) OVERRIDE; + virtual bool MuteStream(uint32 ssrc, bool mute) OVERRIDE; + virtual bool SetRecvRtpHeaderExtensions( + const std::vector& extensions) OVERRIDE; + virtual bool SetSendRtpHeaderExtensions( + const std::vector& extensions) OVERRIDE; + virtual bool SetStartSendBandwidth(int bps) OVERRIDE; + virtual bool SetMaxSendBandwidth(int bps) OVERRIDE; + virtual bool SetOptions(const VideoOptions& options) OVERRIDE; + virtual bool GetOptions(VideoOptions* options) const OVERRIDE { + *options = options_; + return true; + } + virtual void SetInterface(NetworkInterface* iface) OVERRIDE; + virtual void UpdateAspectRatio(int ratio_w, int ratio_h) OVERRIDE; + + virtual void OnMessage(talk_base::Message* msg) OVERRIDE; + + // Implemented for VideoMediaChannelTest. + bool sending() const { return sending_; } + uint32 GetDefaultChannelSsrc() { return default_send_ssrc_; } + bool GetRenderer(uint32 ssrc, VideoRenderer** renderer); + + private: + struct VideoCodecSettings { + VideoCodecSettings(); + + cricket::VideoCodec codec; + webrtc::FecConfig fec; + int rtx_payload_type; + }; + + class WebRtcVideoSendStream : public sigslot::has_slots<> { + public: + WebRtcVideoSendStream(webrtc::Call* call, + const webrtc::VideoSendStream::Config& config, + WebRtcVideoEncoderFactory2* encoder_factory); + ~WebRtcVideoSendStream(); + void SetCodec(const VideoOptions& options, const VideoCodecSettings& codec); + + void InputFrame(VideoCapturer* capturer, const VideoFrame* frame); + bool SetCapturer(VideoCapturer* capturer); + bool SetVideoFormat(const VideoFormat& format); + bool MuteStream(bool mute); + bool DisconnectCapturer(); + + void Start(); + void Stop(); + + private: + void RecreateWebRtcStream(); + void SetDimensions(int width, int height); + + webrtc::Call* const call_; + WebRtcVideoEncoderFactory2* const encoder_factory_; + + talk_base::CriticalSection lock_; + webrtc::VideoSendStream* stream_ GUARDED_BY(lock_); + webrtc::VideoSendStream::Config config_ GUARDED_BY(lock_); + VideoCapturer* capturer_ GUARDED_BY(lock_); + bool sending_ GUARDED_BY(lock_); + bool muted_ GUARDED_BY(lock_); + VideoFormat format_ GUARDED_BY(lock_); + + talk_base::CriticalSection frame_lock_; + webrtc::I420VideoFrame video_frame_ GUARDED_BY(frame_lock_); + }; + + void Construct(webrtc::Call* call, WebRtcVideoEngine2* engine); + + virtual bool SendRtp(const uint8_t* data, size_t len) OVERRIDE; + virtual bool SendRtcp(const uint8_t* data, size_t len) OVERRIDE; + + void StartAllSendStreams(); + void StopAllSendStreams(); + void SetCodecForAllSendStreams(const VideoCodecSettings& codec); + static std::vector MapCodecs( + const std::vector& codecs); + std::vector FilterSupportedCodecs( + const std::vector& mapped_codecs); + + uint32_t rtcp_receiver_report_ssrc_; + bool sending_; + talk_base::scoped_ptr call_; + std::map renderers_; + VideoRenderer* default_renderer_; + uint32_t default_send_ssrc_; + uint32_t default_recv_ssrc_; + + // Using primary-ssrc (first ssrc) as key. + std::map send_streams_; + std::map receive_streams_; + + Settable send_codec_; + WebRtcVideoEncoderFactory2* const encoder_factory_; + std::vector recv_codecs_; + VideoOptions options_; +}; + +} // namespace cricket + +#endif // TALK_MEDIA_WEBRTC_WEBRTCVIDEOENGINE2_H_ diff --git a/talk/media/webrtc/webrtcvideoengine2_unittest.cc b/talk/media/webrtc/webrtcvideoengine2_unittest.cc new file mode 100644 index 000000000..4ef15a16c --- /dev/null +++ b/talk/media/webrtc/webrtcvideoengine2_unittest.cc @@ -0,0 +1,1187 @@ +/* + * libjingle + * Copyright 2004 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 + +#include "talk/base/gunit.h" +#include "talk/media/base/testutils.h" +#include "talk/media/base/videoengine_unittest.h" +#include "talk/media/webrtc/webrtcvideoengine2.h" +#include "talk/media/webrtc/webrtcvideochannelfactory.h" +#include "webrtc/call.h" + +namespace { +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 kVp8Codec180p(100, "VP8", 320, 180, 30, 0); + +static const cricket::VideoCodec kVp8Codec(100, "VP8", 640, 400, 30, 0); +static const cricket::VideoCodec kVp9Codec(101, "VP9", 640, 400, 30, 0); +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 kRtxSsrcs1[] = {4}; +} // namespace + +namespace cricket { +class FakeVideoSendStream : public webrtc::VideoSendStream { + public: + explicit FakeVideoSendStream(const webrtc::VideoSendStream::Config& config) + : sending_(false) { + config_ = config; + } + + webrtc::VideoSendStream::Config GetConfig() { return config_; } + + bool IsSending() { return sending_; } + + private: + virtual webrtc::VideoSendStream::Stats GetStats() const OVERRIDE { + return webrtc::VideoSendStream::Stats(); + } + + virtual bool ReconfigureVideoEncoder( + const std::vector& streams, + void* encoder_specific) OVERRIDE { + // TODO(pbos): Store encoder_specific ptr? + config_.encoder_settings.streams = streams; + return true; + } + + virtual webrtc::VideoSendStreamInput* Input() OVERRIDE { + // TODO(pbos): Fix. + return NULL; + } + + virtual void Start() OVERRIDE { sending_ = true; } + + virtual void Stop() OVERRIDE { sending_ = false; } + + bool sending_; + webrtc::VideoSendStream::Config config_; +}; + +class FakeVideoReceiveStream : public webrtc::VideoReceiveStream { + public: + explicit FakeVideoReceiveStream( + const webrtc::VideoReceiveStream::Config& config) + : config_(config), receiving_(false) {} + + webrtc::VideoReceiveStream::Config GetConfig() { return config_; } + + private: + virtual webrtc::VideoReceiveStream::Stats GetStats() const OVERRIDE { + return webrtc::VideoReceiveStream::Stats(); + } + + virtual void Start() OVERRIDE { receiving_ = true; } + virtual void Stop() OVERRIDE { receiving_ = false; } + virtual void GetCurrentReceiveCodec(webrtc::VideoCodec* codec) OVERRIDE {} + + webrtc::VideoReceiveStream::Config config_; + bool receiving_; +}; + +class FakeCall : public webrtc::Call { + public: + FakeCall() { SetVideoCodecs(GetDefaultVideoCodecs()); } + + ~FakeCall() { + EXPECT_EQ(0u, video_send_streams_.size()); + EXPECT_EQ(0u, video_receive_streams_.size()); + } + + void SetVideoCodecs(const std::vector codecs) { + codecs_ = codecs; + } + + std::vector GetVideoSendStreams() { + return video_send_streams_; + } + + std::vector GetVideoReceiveStreams() { + return video_receive_streams_; + } + + webrtc::VideoCodec GetEmptyVideoCodec() { + webrtc::VideoCodec codec; + codec.minBitrate = 300; + codec.startBitrate = 800; + codec.maxBitrate = 1500; + codec.maxFramerate = 10; + codec.width = 640; + codec.height = 480; + codec.qpMax = 56; + + return codec; + } + + webrtc::VideoCodec GetVideoCodecVp8() { + webrtc::VideoCodec vp8_codec = GetEmptyVideoCodec(); + vp8_codec.codecType = webrtc::kVideoCodecVP8; + strcpy(vp8_codec.plName, kVp8Codec.name.c_str()); + vp8_codec.plType = kVp8Codec.id; + + return vp8_codec; + } + + webrtc::VideoCodec GetVideoCodecVp9() { + webrtc::VideoCodec vp9_codec = GetEmptyVideoCodec(); + // TODO(pbos): Add a correct codecType when webrtc has one. + vp9_codec.codecType = webrtc::kVideoCodecVP8; + strcpy(vp9_codec.plName, kVp9Codec.name.c_str()); + vp9_codec.plType = kVp9Codec.id; + + return vp9_codec; + } + + std::vector GetDefaultVideoCodecs() { + std::vector codecs; + codecs.push_back(GetVideoCodecVp8()); + // codecs.push_back(GetVideoCodecVp9()); + + return codecs; + } + + private: + virtual webrtc::VideoSendStream::Config GetDefaultSendConfig() OVERRIDE { + webrtc::VideoSendStream::Config config; + // TODO(pbos): Encoder settings. + // config.codec = GetVideoCodecVp8(); + return config; + } + + virtual webrtc::VideoSendStream* CreateVideoSendStream( + const webrtc::VideoSendStream::Config& config) OVERRIDE { + FakeVideoSendStream* fake_stream = new FakeVideoSendStream(config); + video_send_streams_.push_back(fake_stream); + return fake_stream; + } + + virtual void DestroyVideoSendStream(webrtc::VideoSendStream* send_stream) + OVERRIDE { + FakeVideoSendStream* fake_stream = + static_cast(send_stream); + for (size_t i = 0; i < video_send_streams_.size(); ++i) { + if (video_send_streams_[i] == fake_stream) { + delete video_send_streams_[i]; + video_send_streams_.erase(video_send_streams_.begin() + i); + return; + } + } + ADD_FAILURE() << "DestroyVideoSendStream called with unknown paramter."; + } + + virtual webrtc::VideoReceiveStream::Config GetDefaultReceiveConfig() + OVERRIDE { + return webrtc::VideoReceiveStream::Config(); + } + + virtual webrtc::VideoReceiveStream* CreateVideoReceiveStream( + const webrtc::VideoReceiveStream::Config& config) OVERRIDE { + video_receive_streams_.push_back(new FakeVideoReceiveStream(config)); + return video_receive_streams_[video_receive_streams_.size() - 1]; + } + + virtual void DestroyVideoReceiveStream( + webrtc::VideoReceiveStream* receive_stream) OVERRIDE { + FakeVideoReceiveStream* fake_stream = + static_cast(receive_stream); + for (size_t i = 0; i < video_receive_streams_.size(); ++i) { + if (video_receive_streams_[i] == fake_stream) { + delete video_receive_streams_[i]; + video_receive_streams_.erase(video_receive_streams_.begin() + i); + return; + } + } + ADD_FAILURE() << "DestroyVideoReceiveStream called with unknown paramter."; + } + + virtual webrtc::PacketReceiver* Receiver() OVERRIDE { + // TODO(pbos): Fix this. + return NULL; + } + + virtual uint32_t SendBitrateEstimate() OVERRIDE { return 0; } + + virtual uint32_t ReceiveBitrateEstimate() OVERRIDE { return 0; } + + private: + std::vector codecs_; + std::vector video_send_streams_; + std::vector video_receive_streams_; +}; + +class FakeWebRtcVideoChannel2 : public WebRtcVideoChannel2 { + public: + FakeWebRtcVideoChannel2(FakeCall* call, + WebRtcVideoEngine2* engine, + VoiceMediaChannel* voice_channel) + : WebRtcVideoChannel2(call, + engine, + engine->GetDefaultVideoEncoderFactory()), + fake_call_(call), + voice_channel_(voice_channel) {} + + virtual ~FakeWebRtcVideoChannel2() {} + + VoiceMediaChannel* GetVoiceChannel() { return voice_channel_; } + FakeCall* GetFakeCall() { return fake_call_; } + + private: + FakeCall* fake_call_; + VoiceMediaChannel* voice_channel_; +}; + +class FakeWebRtcVideoMediaChannelFactory : public WebRtcVideoChannelFactory { + public: + FakeWebRtcVideoChannel2* GetFakeChannel(VideoMediaChannel* channel) { + return channel_map_[channel]; + } + + private: + virtual WebRtcVideoChannel2* Create(WebRtcVideoEngine2* engine, + VoiceMediaChannel* voice_channel) + OVERRIDE { + FakeWebRtcVideoChannel2* channel = + new FakeWebRtcVideoChannel2(new FakeCall(), engine, voice_channel); + channel_map_[channel] = channel; + return channel; + } + + std::map channel_map_; +}; + +class WebRtcVideoEngine2Test : public testing::Test { + public: + WebRtcVideoEngine2Test() + : engine_(&factory_), 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: + FakeWebRtcVideoMediaChannelFactory factory_; + 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_; +}; + +TEST_F(WebRtcVideoEngine2Test, CreateChannel) { + talk_base::scoped_ptr channel(engine_.CreateChannel(NULL)); + ASSERT_TRUE(channel.get() != NULL) << "Could not create channel."; + EXPECT_TRUE(factory_.GetFakeChannel(channel.get()) != NULL) + << "Channel not created through factory."; +} + +TEST_F(WebRtcVideoEngine2Test, CreateChannelWithVoiceEngine) { + VoiceMediaChannel* voice_channel = reinterpret_cast(0x42); + talk_base::scoped_ptr channel( + engine_.CreateChannel(voice_channel)); + ASSERT_TRUE(channel.get() != NULL) << "Could not create channel."; + + FakeWebRtcVideoChannel2* fake_channel = + factory_.GetFakeChannel(channel.get()); + ASSERT_TRUE(fake_channel != NULL) << "Channel not created through factory."; + + EXPECT_TRUE(fake_channel->GetVoiceChannel() != NULL) + << "VoiceChannel not set."; + EXPECT_EQ(voice_channel, fake_channel->GetVoiceChannel()) + << "Different VoiceChannel set than the provided one."; +} + +class WebRtcVideoChannel2BaseTest + : public VideoMediaChannelTest { + protected: + virtual cricket::VideoCodec DefaultCodec() OVERRIDE { return kVp8Codec; } + typedef VideoMediaChannelTest Base; +}; + +// TODO(pbos): Fix WebRtcVideoEngine2BaseTest, where we want CheckCoInitialize. +#if 0 +// TODO(juberti): Figure out why ViE is munging the COM refcount. +#ifdef WIN32 +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_CheckCoInitialize) { + Base::CheckCoInitialize(); +} +#endif +#endif + +TEST_F(WebRtcVideoChannel2BaseTest, SetSend) { Base::SetSend(); } + +TEST_F(WebRtcVideoChannel2BaseTest, SetSendWithoutCodecs) { + Base::SetSendWithoutCodecs(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, SetSendSetsTransportBufferSizes) { + Base::SetSendSetsTransportBufferSizes(); +} + +// TODO(juberti): Fix this test to tolerate missing stats. +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_GetStats) { Base::GetStats(); } + +// TODO(juberti): Fix this test to tolerate missing stats. +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_GetStatsMultipleRecvStreams) { + Base::GetStatsMultipleRecvStreams(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_GetStatsMultipleSendStreams) { + Base::GetStatsMultipleSendStreams(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, SetSendBandwidth) { + Base::SetSendBandwidth(); +} +TEST_F(WebRtcVideoChannel2BaseTest, SetSendSsrc) { Base::SetSendSsrc(); } +TEST_F(WebRtcVideoChannel2BaseTest, SetSendSsrcAfterSetCodecs) { + Base::SetSendSsrcAfterSetCodecs(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, SetRenderer) { Base::SetRenderer(); } + +TEST_F(WebRtcVideoChannel2BaseTest, AddRemoveRecvStreams) { + Base::AddRemoveRecvStreams(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_AddRemoveRecvStreamAndRender) { + Base::AddRemoveRecvStreamAndRender(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, AddRemoveRecvStreamsNoConference) { + Base::AddRemoveRecvStreamsNoConference(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, AddRemoveSendStreams) { + Base::AddRemoveSendStreams(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, SimulateConference) { + Base::SimulateConference(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, AddRemoveCapturer) { + Base::AddRemoveCapturer(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, RemoveCapturerWithoutAdd) { + Base::RemoveCapturerWithoutAdd(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, AddRemoveCapturerMultipleSources) { + Base::AddRemoveCapturerMultipleSources(); +} + +// TODO(pbos): Figure out why this fails so often. +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_HighAspectHighHeightCapturer) { + Base::HighAspectHighHeightCapturer(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, RejectEmptyStreamParams) { + Base::RejectEmptyStreamParams(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, AdaptResolution16x10) { + Base::AdaptResolution16x10(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, AdaptResolution4x3) { + Base::AdaptResolution4x3(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, MuteStream) { Base::MuteStream(); } + +TEST_F(WebRtcVideoChannel2BaseTest, MultipleSendStreams) { + Base::MultipleSendStreams(); +} + +// TODO(juberti): Restore this test once we support sending 0 fps. +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_AdaptDropAllFrames) { + Base::AdaptDropAllFrames(); +} +// TODO(juberti): Understand why we get decode errors on this test. +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_AdaptFramerate) { + Base::AdaptFramerate(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, SetSendStreamFormat0x0) { + Base::SetSendStreamFormat0x0(); +} + +// TODO(zhurunz): Fix the flakey test. +TEST_F(WebRtcVideoChannel2BaseTest, DISABLED_SetSendStreamFormat) { + Base::SetSendStreamFormat(); +} + +TEST_F(WebRtcVideoChannel2BaseTest, TwoStreamsSendAndReceive) { + Base::TwoStreamsSendAndReceive(kVp8Codec); +} + +TEST_F(WebRtcVideoChannel2BaseTest, TwoStreamsReUseFirstStream) { + Base::TwoStreamsReUseFirstStream(kVp8Codec); +} + +class WebRtcVideoChannel2Test : public WebRtcVideoEngine2Test { + public: + virtual void SetUp() OVERRIDE { + channel_.reset(engine_.CreateChannel(NULL)); + fake_channel_ = factory_.GetFakeChannel(channel_.get()); + last_ssrc_ = 123; + ASSERT_TRUE(fake_channel_ != NULL) + << "Channel not created through factory."; + } + + protected: + FakeVideoSendStream* AddSendStream() { + return AddSendStream(StreamParams::CreateLegacy(last_ssrc_++)); + } + + FakeVideoSendStream* AddSendStream(const StreamParams& sp) { + size_t num_streams = + fake_channel_->GetFakeCall()->GetVideoSendStreams().size(); + EXPECT_TRUE(channel_->AddSendStream(sp)); + std::vector streams = + fake_channel_->GetFakeCall()->GetVideoSendStreams(); + EXPECT_EQ(num_streams + 1, streams.size()); + return streams[streams.size() - 1]; + } + + std::vector GetFakeSendStreams() { + return fake_channel_->GetFakeCall()->GetVideoSendStreams(); + } + + FakeVideoReceiveStream* AddRecvStream() { + return AddRecvStream(StreamParams::CreateLegacy(last_ssrc_++)); + } + + FakeVideoReceiveStream* AddRecvStream(const StreamParams& sp) { + size_t num_streams = + fake_channel_->GetFakeCall()->GetVideoReceiveStreams().size(); + EXPECT_TRUE(channel_->AddRecvStream(sp)); + std::vector streams = + fake_channel_->GetFakeCall()->GetVideoReceiveStreams(); + EXPECT_EQ(num_streams + 1, streams.size()); + return streams[streams.size() - 1]; + } + + void SetSendCodecsShouldWorkForBitrates(const char* min_bitrate, + const char* max_bitrate) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs[0].params[kCodecParamMinBitrate] = min_bitrate; + codecs[0].params[kCodecParamMaxBitrate] = max_bitrate; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + + FakeVideoSendStream* stream = AddSendStream(); + + webrtc::VideoSendStream::Config::EncoderSettings encoder_settings = + stream->GetConfig().encoder_settings; + ASSERT_EQ(1u, encoder_settings.streams.size()); + EXPECT_EQ(atoi(min_bitrate), + encoder_settings.streams.back().min_bitrate_bps / 1000); + EXPECT_EQ(atoi(max_bitrate), + encoder_settings.streams.back().max_bitrate_bps / 1000); + + VideoCodec codec; + EXPECT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_EQ(min_bitrate, codec.params[kCodecParamMinBitrate]); + EXPECT_EQ(max_bitrate, codec.params[kCodecParamMaxBitrate]); + } + + void ExpectEqualCodecs(const VideoCodec video_codec, + const webrtc::VideoCodec& webrtc_codec) { + EXPECT_STREQ(video_codec.name.c_str(), webrtc_codec.plName); + EXPECT_EQ(video_codec.id, webrtc_codec.plType); + EXPECT_EQ(video_codec.width, webrtc_codec.width); + EXPECT_EQ(video_codec.height, webrtc_codec.height); + EXPECT_EQ(video_codec.framerate, webrtc_codec.maxFramerate); + } + talk_base::scoped_ptr channel_; + FakeWebRtcVideoChannel2* fake_channel_; + uint32 last_ssrc_; +}; + +TEST_F(WebRtcVideoChannel2Test, DISABLED_MaxBitrateResetsWithConferenceMode) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_StartSendBitrate) { + // TODO(pbos): Is this test testing vie_ ? this is confusing. No API to set + // start send bitrate from outside? Add defaults here that should be kept? + std::vector codec_list; + codec_list.push_back(kVp8Codec); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + const unsigned int kVideoMinSendBitrateKbps = 50; + const unsigned int kVideoTargetSendBitrateKbps = 300; + const unsigned int kVideoMaxSendBitrateKbps = 2000; + FakeVideoSendStream* stream = AddSendStream(); + webrtc::VideoSendStream::Config::EncoderSettings encoder_settings = + stream->GetConfig().encoder_settings; + ASSERT_EQ(1u, encoder_settings.streams.size()); + EXPECT_EQ(kVideoMinSendBitrateKbps, + encoder_settings.streams.back().min_bitrate_bps / 1000); + EXPECT_EQ(kVideoTargetSendBitrateKbps, + encoder_settings.streams.back().target_bitrate_bps / 1000); + EXPECT_EQ(kVideoMaxSendBitrateKbps, + encoder_settings.streams.back().max_bitrate_bps / 1000); +#if 0 + // TODO(pbos): un-#if + VerifyVP8SendCodec(send_channel, kVP8Codec.width, kVP8Codec.height, 0, + kVideoMaxSendBitrateKbps, kVideoMinSendBitrateKbps, + kVideoDefaultStartSendBitrateKbps); + EXPECT_EQ(0, vie_.StartSend(send_channel)); + + // Increase the send bitrate and verify it is used as start bitrate. + const unsigned int kVideoSendBitrateBps = 768000; + vie_.SetSendBitrates(send_channel, kVideoSendBitrateBps, 0, 0); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + VerifyVP8SendCodec(send_channel, kVP8Codec.width, kVP8Codec.height, 0, + kVideoMaxSendBitrateKbps, kVideoMinSendBitrateKbps, + kVideoSendBitrateBps / 1000); + + // Never set a start bitrate higher than the max bitrate. + vie_.SetSendBitrates(send_channel, kVideoMaxSendBitrateKbps + 500, 0, 0); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + VerifyVP8SendCodec(send_channel, kVP8Codec.width, kVP8Codec.height, 0, + kVideoMaxSendBitrateKbps, kVideoMinSendBitrateKbps, + kVideoDefaultStartSendBitrateKbps); + + // Use the default start bitrate if the send bitrate is lower. + vie_.SetSendBitrates(send_channel, kVideoDefaultStartSendBitrateKbps - 50, 0, + 0); + EXPECT_TRUE(channel_->SetSendCodecs(codec_list)); + VerifyVP8SendCodec(send_channel, kVP8Codec.width, kVP8Codec.height, 0, + kVideoMaxSendBitrateKbps, kVideoMinSendBitrateKbps, + kVideoDefaultStartSendBitrateKbps); +#endif +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_RtcpEnabled) { + // Note(pbos): This is a receiver-side setting, dumbo. + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_KeyFrameRequestEnabled) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, RembIsEnabledByDefault) { + FakeVideoReceiveStream* stream = AddRecvStream(); + EXPECT_TRUE(stream->GetConfig().rtp.remb); +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_RembEnabledOnReceiveChannels) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, RecvStreamWithRtx) { + EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs())); + EXPECT_TRUE(channel_->SetSend(true)); + cricket::VideoOptions options; + options.conference_mode.Set(true); + EXPECT_TRUE(channel_->SetOptions(options)); + + // Send side. + const std::vector ssrcs = MAKE_VECTOR(kSsrcs1); + const std::vector rtx_ssrcs = MAKE_VECTOR(kRtxSsrcs1); + FakeVideoSendStream* send_stream = AddSendStream( + cricket::CreateSimWithRtxStreamParams("cname", ssrcs, rtx_ssrcs)); + + ASSERT_EQ(rtx_ssrcs.size(), send_stream->GetConfig().rtp.rtx.ssrcs.size()); + for (size_t i = 0; i < rtx_ssrcs.size(); ++i) + EXPECT_EQ(rtx_ssrcs[i], send_stream->GetConfig().rtp.rtx.ssrcs[i]); + + // Receiver side. + FakeVideoReceiveStream* recv_stream = AddRecvStream( + cricket::CreateSimWithRtxStreamParams("cname", ssrcs, rtx_ssrcs)); + ASSERT_GT(recv_stream->GetConfig().rtp.rtx.size(), 0u) + << "No SSRCs for RTX configured by AddRecvStream."; + ASSERT_EQ(1u, recv_stream->GetConfig().rtp.rtx.size()) + << "This test only works with one receive codec. Please update the test."; + EXPECT_EQ(rtx_ssrcs[0], + recv_stream->GetConfig().rtp.rtx.begin()->second.ssrc); + // TODO(pbos): Make sure we set the RTX for correct payloads etc. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_RecvStreamWithRtxOnMultiplePayloads) { + FAIL() << "Not implemented."; +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_RecvStreamNoRtx) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_RtpTimestampOffsetHeaderExtensions) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_AbsoluteSendTimeHeaderExtensions) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_LeakyBucketTest) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_BufferedModeLatency) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_AdditiveVideoOptions) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, AddRecvStreamOnlyUsesOneReceiveStream) { + EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1))); + EXPECT_EQ(1u, fake_channel_->GetFakeCall()->GetVideoReceiveStreams().size()); +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_NoRembChangeAfterAddRecvStream) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_RembOnOff) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_NackEnabled) { + // Verify NACK on both sender and receiver. + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_VideoProtectionInterop) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_VideoProtectionInteropReversed) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_HybridNackFecConference) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_AddRemoveRecvStreamConference) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetRender) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetBandwidthAuto) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetBandwidthAutoCapped) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetBandwidthFixed) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetBandwidthInConference) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetBandwidthScreencast) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetSendSsrcAndCname) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_SetSendSsrcAfterCreatingReceiveChannel) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetOptionsWithDenoising) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_MultipleSendStreamsWithOneCapturer) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_DISABLED_SendReceiveBitratesStats) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_TestSetAdaptInputToCpuUsage) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_TestSetCpuThreshold) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_TestSetInvalidCpuThreshold) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_WebRtcShouldLog) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_WebRtcShouldNotLog) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoEngine2Test, FindCodec) { + const std::vector& c = engine_.codecs(); + EXPECT_EQ(4U, c.size()); + + cricket::VideoCodec vp8(104, "VP8", 320, 200, 30, 0); + EXPECT_TRUE(engine_.FindCodec(vp8)); + + cricket::VideoCodec vp8_ci(104, "vp8", 320, 200, 30, 0); + EXPECT_TRUE(engine_.FindCodec(vp8)); + + cricket::VideoCodec vp8_diff_fr_diff_pref(104, "VP8", 320, 200, 50, 50); + EXPECT_TRUE(engine_.FindCodec(vp8_diff_fr_diff_pref)); + + cricket::VideoCodec vp8_diff_id(95, "VP8", 320, 200, 30, 0); + EXPECT_FALSE(engine_.FindCodec(vp8_diff_id)); + vp8_diff_id.id = 97; + EXPECT_TRUE(engine_.FindCodec(vp8_diff_id)); + + cricket::VideoCodec vp8_diff_res(104, "VP8", 320, 111, 30, 0); + EXPECT_FALSE(engine_.FindCodec(vp8_diff_res)); + + // PeerConnection doesn't negotiate the resolution at this point. + // Test that FindCodec can handle the case when width/height is 0. + cricket::VideoCodec vp8_zero_res(104, "VP8", 0, 0, 30, 0); + EXPECT_TRUE(engine_.FindCodec(vp8_zero_res)); + + cricket::VideoCodec red(101, "RED", 0, 0, 30, 0); + EXPECT_TRUE(engine_.FindCodec(red)); + + cricket::VideoCodec red_ci(101, "red", 0, 0, 30, 0); + EXPECT_TRUE(engine_.FindCodec(red)); + + cricket::VideoCodec fec(102, "ULPFEC", 0, 0, 30, 0); + EXPECT_TRUE(engine_.FindCodec(fec)); + + cricket::VideoCodec fec_ci(102, "ulpfec", 0, 0, 30, 0); + EXPECT_TRUE(engine_.FindCodec(fec)); + + cricket::VideoCodec rtx(96, "rtx", 0, 0, 30, 0); + EXPECT_TRUE(engine_.FindCodec(rtx)); +} + +TEST_F(WebRtcVideoEngine2Test, DefaultRtxCodecHasAssociatedPayloadTypeSet) { + for (size_t i = 0; i < engine_codecs_.size(); ++i) { + if (engine_codecs_[i].name != kRtxCodecName) + continue; + int associated_payload_type; + EXPECT_TRUE(engine_codecs_[i].GetParam(kCodecParamAssociatedPayloadType, + &associated_payload_type)); + EXPECT_EQ(default_codec_.id, associated_payload_type); + return; + } + FAIL() << "No RTX codec found among default codecs."; +} + +TEST_F(WebRtcVideoChannel2Test, SetDefaultSendCodecs) { + ASSERT_TRUE(channel_->SetSendCodecs(engine_codecs_)); + + VideoCodec codec; + EXPECT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_TRUE(codec.Matches(engine_codecs_[0])); + + // Using a RTX setup to verify that the default RTX payload type is good. + const std::vector ssrcs = MAKE_VECTOR(kSsrcs1); + const std::vector rtx_ssrcs = MAKE_VECTOR(kRtxSsrcs1); + FakeVideoSendStream* stream = AddSendStream( + cricket::CreateSimWithRtxStreamParams("cname", ssrcs, rtx_ssrcs)); + webrtc::VideoSendStream::Config config = stream->GetConfig(); + // TODO(pbos): Replace ExpectEqualCodecs. + // ExpectEqualCodecs(engine_codecs_[0], config.codec); + + // Make sure NACK and FEC are enabled on the correct payload types. + EXPECT_EQ(1000, config.rtp.nack.rtp_history_ms); + EXPECT_EQ(default_ulpfec_codec_.id, config.rtp.fec.ulpfec_payload_type); + EXPECT_EQ(default_red_codec_.id, config.rtp.fec.red_payload_type); + // TODO(pbos): Verify that the rtx ssrc is set, correct, not taken by anything + // else. + // ASSERT_EQ(1u, config.rtp.rtx.ssrcs.size()); + EXPECT_EQ(static_cast(default_rtx_codec_.id), + config.rtp.rtx.payload_type); + // TODO(juberti): Check RTCP, PLI, TMMBR. +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsWithoutFec) { + std::vector codecs; + codecs.push_back(kVp8Codec); + ASSERT_TRUE(channel_->SetSendCodecs(codecs)); + + FakeVideoSendStream* stream = AddSendStream(); + webrtc::VideoSendStream::Config config = stream->GetConfig(); + + EXPECT_EQ(-1, config.rtp.fec.ulpfec_payload_type); + EXPECT_EQ(-1, config.rtp.fec.red_payload_type); +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_SetSendCodecRejectsRtxWithoutAssociatedPayloadType) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +}; + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_SetSendCodecRejectsRtxWithoutMatchingVideoCodec) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +}; + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_SetCodecsWithoutFecDisablesCurrentFec) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +}; + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetSendCodecsChangesExistingStreams) { + FAIL(); // TODO(pbos): Implement, make sure that it's changing running + // streams. Should it? +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_ConstrainsSetCodecsAccordingToEncoderConfig) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsWithMinMaxBitrate) { + SetSendCodecsShouldWorkForBitrates("10", "20"); +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsRejectsMaxLessThanMinBitrate) { + engine_codecs_[0].params[kCodecParamMinBitrate] = "30"; + engine_codecs_[0].params[kCodecParamMaxBitrate] = "20"; + EXPECT_FALSE(channel_->SetSendCodecs(engine_codecs_)); +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsAcceptLargeMinMaxBitrate) { + SetSendCodecsShouldWorkForBitrates("1000", "2000"); +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsWithMaxQuantization) { + static const char* kMaxQuantization = "21"; + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs[0].params[kCodecParamMaxQuantization] = kMaxQuantization; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ( + static_cast(atoi(kMaxQuantization)), + AddSendStream()->GetConfig().encoder_settings.streams.back().max_qp); + + VideoCodec codec; + EXPECT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_EQ(kMaxQuantization, codec.params[kCodecParamMaxQuantization]); +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsRejectBadDimensions) { + std::vector codecs; + codecs.push_back(kVp8Codec); + + codecs[0].width = 0; + EXPECT_FALSE(channel_->SetSendCodecs(codecs)) + << "Codec set though codec width is zero."; + + codecs[0].width = kVp8Codec.width; + codecs[0].height = 0; + EXPECT_FALSE(channel_->SetSendCodecs(codecs)) + << "Codec set though codec height is zero."; +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsRejectBadPayloadTypes) { + // TODO(pbos): Should we only allow the dynamic range? + static const size_t kNumIncorrectPayloads = 4; + static const int kIncorrectPayloads[kNumIncorrectPayloads] = {-2, -1, 128, + 129}; + std::vector codecs; + codecs.push_back(kVp8Codec); + for (size_t i = 0; i < kNumIncorrectPayloads; ++i) { + int payload_type = kIncorrectPayloads[i]; + codecs[0].id = payload_type; + EXPECT_FALSE(channel_->SetSendCodecs(codecs)) + << "Bad payload type '" << payload_type << "' accepted."; + } +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsAcceptAllValidPayloadTypes) { + std::vector codecs; + codecs.push_back(kVp8Codec); + for (int payload_type = 0; payload_type <= 127; ++payload_type) { + codecs[0].id = payload_type; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)) + << "Payload type '" << payload_type << "' rejected."; + } +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_ResetVieSendCodecOnNewFrameSize) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, SetRecvCodecsWithOnlyVp8) { + std::vector codecs; + codecs.push_back(kVp8Codec); + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); +} + +TEST_F(WebRtcVideoChannel2Test, SetRecvCodecsDifferentPayloadType) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs[0].id = 99; + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetRecvCodecsAcceptDefaultCodecs) { + EXPECT_TRUE(channel_->SetRecvCodecs(engine_.codecs())); + // (I've added this one.) Make sure they propagate down to VideoReceiveStream! + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, SetRecvCodecsRejectUnsupportedCodec) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs.push_back(VideoCodec(101, "WTF3", 640, 400, 30, 0)); + EXPECT_FALSE(channel_->SetRecvCodecs(codecs)); +} + +// TODO(pbos): Enable VP9 through external codec support +TEST_F(WebRtcVideoChannel2Test, + DISABLED_SetRecvCodecsAcceptsMultipleVideoCodecs) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs.push_back(kVp9Codec); + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_SetRecvCodecsSetsFecForAllVideoCodecs) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs.push_back(kVp9Codec); + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); + FAIL(); // TODO(pbos): Verify that the FEC parameters are set for all codecs. +} + +TEST_F(WebRtcVideoChannel2Test, SetSendCodecsRejectDuplicateFecPayloads) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs.push_back(kRedCodec); + codecs[1].id = codecs[0].id; + EXPECT_FALSE(channel_->SetRecvCodecs(codecs)); +} + +TEST_F(WebRtcVideoChannel2Test, SetRecvCodecsRejectDuplicateCodecPayloads) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs.push_back(kVp9Codec); + codecs[1].id = codecs[0].id; + EXPECT_FALSE(channel_->SetRecvCodecs(codecs)); +} + +TEST_F(WebRtcVideoChannel2Test, + SetRecvCodecsAcceptSameCodecOnMultiplePayloadTypes) { + std::vector codecs; + codecs.push_back(kVp8Codec); + codecs.push_back(kVp8Codec); + codecs[1].id += 1; + EXPECT_TRUE(channel_->SetRecvCodecs(codecs)); +} + +TEST_F(WebRtcVideoChannel2Test, SendStreamNotSendingByDefault) { + EXPECT_FALSE(AddSendStream()->IsSending()); +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_ReceiveStreamReceivingByDefault) { + // Is this test correct though? Auto-receive? Enable receive on first packet? + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, SetSend) { + AddSendStream(); + EXPECT_FALSE(channel_->SetSend(true)) + << "Channel should not start without codecs."; + EXPECT_TRUE(channel_->SetSend(false)) + << "Channel should be stoppable even without set codecs."; + + std::vector codecs; + codecs.push_back(kVp8Codec); + channel_->SetSendCodecs(codecs); + std::vector streams = GetFakeSendStreams(); + ASSERT_EQ(1u, streams.size()); + FakeVideoSendStream* stream = streams.back(); + + EXPECT_FALSE(stream->IsSending()); + + // false->true + EXPECT_TRUE(channel_->SetSend(true)); + EXPECT_TRUE(stream->IsSending()); + // true->true + EXPECT_TRUE(channel_->SetSend(true)); + EXPECT_TRUE(stream->IsSending()); + // true->false + EXPECT_TRUE(channel_->SetSend(false)); + EXPECT_FALSE(stream->IsSending()); + // false->false + EXPECT_TRUE(channel_->SetSend(false)); + EXPECT_FALSE(stream->IsSending()); + + EXPECT_TRUE(channel_->SetSend(true)); + FakeVideoSendStream* new_stream = AddSendStream(); + EXPECT_TRUE(new_stream->IsSending()) + << "Send stream created after SetSend(true) not sending initially."; +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SendAndReceiveVp8Vga) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SendAndReceiveVp8Qvga) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SendAndReceiveH264SvcQqvga) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SendManyResizeOnce) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SendVp8HdAndReceiveAdaptedVp8Vga) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_TestSetDscpOptions) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetOptionsWithMaxBitrate) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetOptionsWithLoweredBitrate) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_SetOptionsSucceedsWhenSending) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_ResetCodecOnScreencast) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_DontResetCodecOnSendFrame) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_DontRegisterDecoderIfFactoryIsNotGiven) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_RegisterDecoderIfFactoryIsGiven) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_DontRegisterDecoderMultipleTimes) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_DontRegisterDecoderForNonVP8) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_DontRegisterEncoderIfFactoryIsNotGiven) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_RegisterEncoderIfFactoryIsGiven) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_DontRegisterEncoderMultipleTimes) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, + DISABLED_RegisterEncoderWithMultipleSendStreams) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_DontRegisterEncoderForNonVP8) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_FeedbackParamsForNonVP8) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_ExternalCodecAddedToTheEnd) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_ExternalCodecIgnored) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_UpdateEncoderCodecsAfterSetFactory) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_OnReadyToSend) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} + +TEST_F(WebRtcVideoChannel2Test, DISABLED_CaptureFrameTimestampToNtpTimestamp) { + FAIL() << "Not implemented."; // TODO(pbos): Implement. +} +} // namespace cricket diff --git a/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-tsan.txt b/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-tsan.txt index 863d5859e..958893388 100644 --- a/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-tsan.txt +++ b/tools/valgrind-webrtc/gtest_exclude/libjingle_media_unittest.gtest-tsan.txt @@ -5,3 +5,8 @@ WebRtcVideoMediaChannelTest.TwoStreamsSendAndFailUnsignalledRecv WebRtcVideoMediaChannelTest.TwoStreamsSendAndFailUnsignalledRecvInOneToOne WebRtcVideoMediaChannelTest.TwoStreamsSendAndReceive WebRtcVideoMediaChannelTest.TwoStreamsSendAndUnsignalledRecv + +TODO(pbos): This suppression is overly broad, but offline talks with kjellander@ +indicate that we can move over to tsanv2 and deprecate tsanv1, which will remove +the need for tsanv1 suppressions. +WebRtcVideoChannel2BaseTest.*