diff --git a/talk/media/webrtc/webrtcvideoengine2.cc b/talk/media/webrtc/webrtcvideoengine2.cc index d6d1354bfb..716c5a8855 100644 --- a/talk/media/webrtc/webrtcvideoengine2.cc +++ b/talk/media/webrtc/webrtcvideoengine2.cc @@ -278,6 +278,13 @@ void WebRtcVideoEngine2::Construct(WebRtcVideoChannelFactory* channel_factory, video_codecs_ = DefaultVideoCodecs(); default_codec_format_ = VideoFormat(kDefaultVideoFormat); + + rtp_header_extensions_.push_back( + RtpHeaderExtension(kRtpTimestampOffsetHeaderExtension, + kRtpTimestampOffsetHeaderExtensionDefaultId)); + rtp_header_extensions_.push_back( + RtpHeaderExtension(kRtpAbsoluteSenderTimeHeaderExtension, + kRtpAbsoluteSenderTimeHeaderExtensionDefaultId)); } WebRtcVideoEngine2::~WebRtcVideoEngine2() { @@ -774,6 +781,20 @@ static bool ValidateCodecFormats(const std::vector& codecs) { return true; } +static std::string RtpExtensionsToString( + const std::vector& extensions) { + std::stringstream out; + out << '{'; + for (size_t i = 0; i < extensions.size(); ++i) { + out << "{" << extensions[i].uri << ": " << extensions[i].id << "}"; + if (i != extensions.size() - 1) { + out << ", "; + } + } + out << '}'; + return out.str(); +} + } // namespace bool WebRtcVideoChannel2::SetRecvCodecs(const std::vector& codecs) { @@ -967,6 +988,8 @@ bool WebRtcVideoChannel2::AddSendStream(const StreamParams& sp) { config.rtp.rtx.payload_type = codec_settings.rtx_payload_type; } + config.rtp.extensions = send_rtp_extensions_; + if (IsNackEnabled(codec_settings.codec)) { config.rtp.nack.rtp_history_ms = kNackHistoryMs; } @@ -1047,6 +1070,7 @@ bool WebRtcVideoChannel2::AddRecvStream(const StreamParams& sp) { config.rtp.nack.rtp_history_ms = kNackHistoryMs; } config.rtp.remb = true; + config.rtp.extensions = recv_rtp_extensions_; // 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 @@ -1280,15 +1304,31 @@ bool WebRtcVideoChannel2::MuteStream(uint32 ssrc, bool mute) { bool WebRtcVideoChannel2::SetRecvRtpHeaderExtensions( const std::vector& extensions) { - // TODO(pbos): Implement. - LOG(LS_VERBOSE) << "SetRecvRtpHeaderExtensions()"; + LOG(LS_INFO) << "SetRecvRtpHeaderExtensions: " + << RtpExtensionsToString(extensions); + std::vector webrtc_extensions; + for (size_t i = 0; i < extensions.size(); ++i) { + // TODO(pbos): Make sure we don't pass unsupported extensions! + webrtc::RtpExtension webrtc_extension(extensions[i].uri.c_str(), + extensions[i].id); + webrtc_extensions.push_back(webrtc_extension); + } + recv_rtp_extensions_ = webrtc_extensions; return true; } bool WebRtcVideoChannel2::SetSendRtpHeaderExtensions( const std::vector& extensions) { - // TODO(pbos): Implement. - LOG(LS_VERBOSE) << "SetSendRtpHeaderExtensions()"; + LOG(LS_INFO) << "SetSendRtpHeaderExtensions: " + << RtpExtensionsToString(extensions); + std::vector webrtc_extensions; + for (size_t i = 0; i < extensions.size(); ++i) { + // TODO(pbos): Make sure we don't pass unsupported extensions! + webrtc::RtpExtension webrtc_extension(extensions[i].uri.c_str(), + extensions[i].id); + webrtc_extensions.push_back(webrtc_extension); + } + send_rtp_extensions_ = webrtc_extensions; return true; } diff --git a/talk/media/webrtc/webrtcvideoengine2.h b/talk/media/webrtc/webrtcvideoengine2.h index d1a784dc4f..81466eb657 100644 --- a/talk/media/webrtc/webrtcvideoengine2.h +++ b/talk/media/webrtc/webrtcvideoengine2.h @@ -236,6 +236,9 @@ class WebRtcVideoChannel2 : public talk_base::MessageHandler, OVERRIDE; virtual void OnReadyToSend(bool ready) OVERRIDE; virtual bool MuteStream(uint32 ssrc, bool mute) OVERRIDE; + + // Set send/receive RTP header extensions. This must be done before creating + // streams as it only has effect on future streams. virtual bool SetRecvRtpHeaderExtensions( const std::vector& extensions) OVERRIDE; virtual bool SetSendRtpHeaderExtensions( @@ -351,8 +354,11 @@ class WebRtcVideoChannel2 : public talk_base::MessageHandler, std::map receive_streams_; Settable send_codec_; + std::vector send_rtp_extensions_; + WebRtcVideoEncoderFactory2* const encoder_factory_; std::vector recv_codecs_; + std::vector recv_rtp_extensions_; VideoOptions options_; }; diff --git a/talk/media/webrtc/webrtcvideoengine2_unittest.cc b/talk/media/webrtc/webrtcvideoengine2_unittest.cc index c9ff182903..6886300234 100644 --- a/talk/media/webrtc/webrtcvideoengine2_unittest.cc +++ b/talk/media/webrtc/webrtcvideoengine2_unittest.cc @@ -396,6 +396,31 @@ TEST_F(WebRtcVideoEngine2Test, DefaultRtxCodecHasAssociatedPayloadTypeSet) { FAIL() << "No RTX codec found among default codecs."; } +TEST_F(WebRtcVideoEngine2Test, SupportsTimestampOffsetHeaderExtension) { + std::vector extensions = engine_.rtp_header_extensions(); + ASSERT_FALSE(extensions.empty()); + for (size_t i = 0; i < extensions.size(); ++i) { + if (extensions[i].uri == kRtpTimestampOffsetHeaderExtension) { + EXPECT_EQ(kRtpTimestampOffsetHeaderExtensionDefaultId, extensions[i].id); + return; + } + } + FAIL() << "Timestamp offset extension not in header-extension list."; +} + +TEST_F(WebRtcVideoEngine2Test, SupportsAbsoluteSenderTimeHeaderExtension) { + std::vector extensions = engine_.rtp_header_extensions(); + ASSERT_FALSE(extensions.empty()); + for (size_t i = 0; i < extensions.size(); ++i) { + if (extensions[i].uri == kRtpAbsoluteSenderTimeHeaderExtension) { + EXPECT_EQ(kRtpAbsoluteSenderTimeHeaderExtensionDefaultId, + extensions[i].id); + return; + } + } + FAIL() << "Absolute Sender Time extension not in header-extension list."; +} + class WebRtcVideoChannel2BaseTest : public VideoMediaChannelTest { protected: @@ -598,6 +623,67 @@ class WebRtcVideoChannel2Test : public WebRtcVideoEngine2Test { EXPECT_EQ(video_codec.height, webrtc_codec.height); EXPECT_EQ(video_codec.framerate, webrtc_codec.maxFramerate); } + + void TestSetSendRtpHeaderExtensions(const std::string& cricket_ext, + const std::string& webrtc_ext) { + // Enable extension. + const int id = 1; + std::vector extensions; + extensions.push_back(cricket::RtpHeaderExtension(cricket_ext, id)); + EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); + + FakeVideoSendStream* send_stream = + AddSendStream(cricket::StreamParams::CreateLegacy(123)); + + // Verify the send extension id. + ASSERT_EQ(1u, send_stream->GetConfig().rtp.extensions.size()); + EXPECT_EQ(id, send_stream->GetConfig().rtp.extensions[0].id); + EXPECT_EQ(webrtc_ext, send_stream->GetConfig().rtp.extensions[0].name); + // Verify call with same set of extensions returns true. + EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); + // Verify that SetSendRtpHeaderExtensions doesn't implicitly add them for + // receivers. + EXPECT_TRUE(AddRecvStream(cricket::StreamParams::CreateLegacy(123)) + ->GetConfig() + .rtp.extensions.empty()); + + // Remove the extension id, verify that this doesn't reset extensions as + // they should be set before creating channels. + std::vector empty_extensions; + EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(empty_extensions)); + EXPECT_FALSE(send_stream->GetConfig().rtp.extensions.empty()); + } + + void TestSetRecvRtpHeaderExtensions(const std::string& cricket_ext, + const std::string& webrtc_ext) { + // Enable extension. + const int id = 1; + std::vector extensions; + extensions.push_back(cricket::RtpHeaderExtension(cricket_ext, id)); + EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions)); + + FakeVideoReceiveStream* recv_stream = + AddRecvStream(cricket::StreamParams::CreateLegacy(123)); + + // Verify the recv extension id. + ASSERT_EQ(1u, recv_stream->GetConfig().rtp.extensions.size()); + EXPECT_EQ(id, recv_stream->GetConfig().rtp.extensions[0].id); + EXPECT_EQ(webrtc_ext, recv_stream->GetConfig().rtp.extensions[0].name); + // Verify call with same set of extensions returns true. + EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions)); + // Verify that SetRecvRtpHeaderExtensions doesn't implicitly add them for + // senders. + EXPECT_TRUE(AddSendStream(cricket::StreamParams::CreateLegacy(123)) + ->GetConfig() + .rtp.extensions.empty()); + + // Remove the extension id, verify that this doesn't reset extensions as + // they should be set before creating channels. + std::vector empty_extensions; + EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(empty_extensions)); + EXPECT_FALSE(recv_stream->GetConfig().rtp.extensions.empty()); + } + talk_base::scoped_ptr channel_; FakeWebRtcVideoChannel2* fake_channel_; uint32 last_ssrc_; @@ -723,12 +809,34 @@ TEST_F(WebRtcVideoChannel2Test, RecvStreamNoRtx) { ASSERT_TRUE(recv_stream->GetConfig().rtp.rtx.empty()); } -TEST_F(WebRtcVideoChannel2Test, DISABLED_RtpTimestampOffsetHeaderExtensions) { - FAIL() << "Not implemented."; // TODO(pbos): Implement. +TEST_F(WebRtcVideoChannel2Test, NoHeaderExtesionsByDefault) { + FakeVideoSendStream* send_stream = + AddSendStream(cricket::StreamParams::CreateLegacy(kSsrcs1[0])); + ASSERT_TRUE(send_stream->GetConfig().rtp.extensions.empty()); + + FakeVideoReceiveStream* recv_stream = + AddRecvStream(cricket::StreamParams::CreateLegacy(kSsrcs1[0])); + ASSERT_TRUE(recv_stream->GetConfig().rtp.extensions.empty()); } -TEST_F(WebRtcVideoChannel2Test, DISABLED_AbsoluteSendTimeHeaderExtensions) { - FAIL() << "Not implemented."; // TODO(pbos): Implement. +// Test support for RTP timestamp offset header extension. +TEST_F(WebRtcVideoChannel2Test, SendRtpTimestampOffsetHeaderExtensions) { + TestSetSendRtpHeaderExtensions(kRtpTimestampOffsetHeaderExtension, + webrtc::RtpExtension::kTOffset); +} +TEST_F(WebRtcVideoChannel2Test, RecvRtpTimestampOffsetHeaderExtensions) { + TestSetRecvRtpHeaderExtensions(kRtpTimestampOffsetHeaderExtension, + webrtc::RtpExtension::kTOffset); +} + +// Test support for absolute send time header extension. +TEST_F(WebRtcVideoChannel2Test, SendAbsoluteSendTimeHeaderExtensions) { + TestSetSendRtpHeaderExtensions(kRtpAbsoluteSenderTimeHeaderExtension, + webrtc::RtpExtension::kAbsSendTime); +} +TEST_F(WebRtcVideoChannel2Test, RecvAbsoluteSendTimeHeaderExtensions) { + TestSetRecvRtpHeaderExtensions(kRtpAbsoluteSenderTimeHeaderExtension, + webrtc::RtpExtension::kAbsSendTime); } TEST_F(WebRtcVideoChannel2Test, DISABLED_LeakyBucketTest) {