diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc index e7a1a11157..749f77c4e7 100644 --- a/pc/peer_connection.cc +++ b/pc/peer_connection.cc @@ -698,6 +698,15 @@ void ReportSimulcastApiVersion(const char* name, } } +const ContentInfo* FindTransceiverMSection( + RtpTransceiverProxyWithInternal* transceiver, + const SessionDescriptionInterface* session_description) { + return transceiver->mid() + ? session_description->description()->GetContentByName( + *transceiver->mid()) + : nullptr; +} + } // namespace // Upon completion, posts a task to execute the callback of the @@ -1254,7 +1263,7 @@ bool PeerConnection::AddStream(MediaStreamInterface* local_stream) { } stats_->AddStream(local_stream); - Observer()->OnRenegotiationNeeded(); + UpdateNegotiationNeeded(); return true; } @@ -1284,7 +1293,7 @@ void PeerConnection::RemoveStream(MediaStreamInterface* local_stream) { if (IsClosed()) { return; } - Observer()->OnRenegotiationNeeded(); + UpdateNegotiationNeeded(); } RTCErrorOr> PeerConnection::AddTrack( @@ -1313,7 +1322,7 @@ RTCErrorOr> PeerConnection::AddTrack( (IsUnifiedPlan() ? AddTrackUnifiedPlan(track, stream_ids) : AddTrackPlanB(track, stream_ids)); if (sender_or_error.ok()) { - Observer()->OnRenegotiationNeeded(); + UpdateNegotiationNeeded(); stats_->AddTrack(track); } return sender_or_error; @@ -1460,7 +1469,7 @@ RTCError PeerConnection::RemoveTrackNew( "Couldn't find sender " + sender->id() + " to remove."); } } - Observer()->OnRenegotiationNeeded(); + UpdateNegotiationNeeded(); return RTCError::OK(); } @@ -1527,7 +1536,7 @@ PeerConnection::AddTransceiver( cricket::MediaType media_type, rtc::scoped_refptr track, const RtpTransceiverInit& init, - bool fire_callback) { + bool update_negotiation_needed) { RTC_DCHECK((media_type == cricket::MEDIA_TYPE_AUDIO || media_type == cricket::MEDIA_TYPE_VIDEO)); if (track) { @@ -1616,8 +1625,8 @@ PeerConnection::AddTransceiver( auto transceiver = CreateAndAddTransceiver(sender, receiver); transceiver->internal()->set_direction(init.direction); - if (fire_callback) { - Observer()->OnRenegotiationNeeded(); + if (update_negotiation_needed) { + UpdateNegotiationNeeded(); } return rtc::scoped_refptr(transceiver); @@ -1695,7 +1704,7 @@ PeerConnection::CreateAndAddTransceiver( void PeerConnection::OnNegotiationNeeded() { RTC_DCHECK_RUN_ON(signaling_thread()); RTC_DCHECK(!IsClosed()); - Observer()->OnRenegotiationNeeded(); + UpdateNegotiationNeeded(); } rtc::scoped_refptr PeerConnection::CreateSender( @@ -1943,7 +1952,7 @@ rtc::scoped_refptr PeerConnection::CreateDataChannel( // Trigger the onRenegotiationNeeded event for every new RTP DataChannel, or // the first SCTP DataChannel. if (data_channel_type() == cricket::DCT_RTP || first_datachannel) { - Observer()->OnRenegotiationNeeded(); + UpdateNegotiationNeeded(); } NoteUsageEvent(UsageEvent::DATA_ADDED); return DataChannelProxy::Create(signaling_thread(), channel.get()); @@ -2045,7 +2054,8 @@ void PeerConnection::AddUpToOneReceivingTransceiverOfType( << " transceiver since CreateOffer specified offer_to_receive=1"; RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kRecvOnly; - AddTransceiver(media_type, nullptr, init, /*fire_callback=*/false); + AddTransceiver(media_type, nullptr, init, + /*update_negotiation_needed=*/false); } } @@ -2189,6 +2199,14 @@ void PeerConnection::SetLocalDescription( // Make UMA notes about what was agreed to. ReportNegotiatedSdpSemantics(*local_description()); } + + bool was_negotiation_needed = is_negotiation_needed_; + UpdateNegotiationNeeded(); + if (signaling_state() == kStable && was_negotiation_needed && + is_negotiation_needed_) { + Observer()->OnRenegotiationNeeded(); + } + NoteUsageEvent(UsageEvent::SET_LOCAL_DESCRIPTION_CALLED); } @@ -2546,6 +2564,13 @@ void PeerConnection::SetRemoteDescription( ReportNegotiatedSdpSemantics(*remote_description()); } + bool was_negotiation_needed = is_negotiation_needed_; + UpdateNegotiationNeeded(); + if (signaling_state() == kStable && was_negotiation_needed && + is_negotiation_needed_) { + Observer()->OnRenegotiationNeeded(); + } + observer->OnSetRemoteDescriptionComplete(RTCError::OK()); NoteUsageEvent(UsageEvent::SET_REMOTE_DESCRIPTION_CALLED); } @@ -4158,7 +4183,7 @@ void PeerConnection::OnAudioTrackAdded(AudioTrackInterface* track, return; } AddAudioTrack(track, stream); - Observer()->OnRenegotiationNeeded(); + UpdateNegotiationNeeded(); } void PeerConnection::OnAudioTrackRemoved(AudioTrackInterface* track, @@ -4167,7 +4192,7 @@ void PeerConnection::OnAudioTrackRemoved(AudioTrackInterface* track, return; } RemoveAudioTrack(track, stream); - Observer()->OnRenegotiationNeeded(); + UpdateNegotiationNeeded(); } void PeerConnection::OnVideoTrackAdded(VideoTrackInterface* track, @@ -4176,7 +4201,7 @@ void PeerConnection::OnVideoTrackAdded(VideoTrackInterface* track, return; } AddVideoTrack(track, stream); - Observer()->OnRenegotiationNeeded(); + UpdateNegotiationNeeded(); } void PeerConnection::OnVideoTrackRemoved(VideoTrackInterface* track, @@ -4185,7 +4210,7 @@ void PeerConnection::OnVideoTrackRemoved(VideoTrackInterface* track, return; } RemoveVideoTrack(track, stream); - Observer()->OnRenegotiationNeeded(); + UpdateNegotiationNeeded(); } void PeerConnection::PostSetSessionDescriptionSuccess( @@ -7125,4 +7150,169 @@ void PeerConnection::RequestUsagePatternReportForTesting() { nullptr); } +void PeerConnection::UpdateNegotiationNeeded() { + RTC_DCHECK_RUN_ON(signaling_thread()); + // If connection's [[IsClosed]] slot is true, abort these steps. + if (IsClosed()) + return; + + // If connection's signaling state is not "stable", abort these steps. + if (signaling_state() != kStable) + return; + + // NOTE + // The negotiation-needed flag will be updated once the state transitions to + // "stable", as part of the steps for setting an RTCSessionDescription. + + // If the result of checking if negotiation is needed is false, clear the + // negotiation-needed flag by setting connection's [[NegotiationNeeded]] slot + // to false, and abort these steps. + bool is_negotiation_needed = CheckIfNegotiationIsNeeded(); + if (!is_negotiation_needed) { + is_negotiation_needed_ = false; + return; + } + + // If connection's [[NegotiationNeeded]] slot is already true, abort these + // steps. + if (is_negotiation_needed_) + return; + + // Set connection's [[NegotiationNeeded]] slot to true. + is_negotiation_needed_ = true; + + // Queue a task that runs the following steps: + // If connection's [[IsClosed]] slot is true, abort these steps. + // If connection's [[NegotiationNeeded]] slot is false, abort these steps. + // Fire an event named negotiationneeded at connection. + Observer()->OnRenegotiationNeeded(); +} + +bool PeerConnection::CheckIfNegotiationIsNeeded() { + RTC_DCHECK_RUN_ON(signaling_thread()); + // 1. If any implementation-specific negotiation is required, as described at + // the start of this section, return true. + + // 2. Let description be connection.[[CurrentLocalDescription]]. + const SessionDescriptionInterface* description = current_local_description(); + if (!description) + return true; + + // 3. If connection has created any RTCDataChannels, and no m= section in + // description has been negotiated yet for data, return true. + if (!sctp_data_channels_.empty()) { + if (!cricket::GetFirstDataContent(description->description()->contents())) + return true; + } + + // 4. For each transceiver in connection's set of transceivers, perform the + // following checks: + for (const auto& transceiver : transceivers_) { + const ContentInfo* current_local_msection = + FindTransceiverMSection(transceiver.get(), description); + + const ContentInfo* current_remote_msection = FindTransceiverMSection( + transceiver.get(), current_remote_description()); + + // 4.3 If transceiver is stopped and is associated with an m= section, + // but the associated m= section is not yet rejected in + // connection.[[CurrentLocalDescription]] or + // connection.[[CurrentRemoteDescription]], return true. + if (transceiver->stopped()) { + if (current_local_msection && !current_local_msection->rejected && + ((current_remote_msection && !current_remote_msection->rejected) || + !current_remote_msection)) { + return true; + } + continue; + } + + // 4.1 If transceiver isn't stopped and isn't yet associated with an m= + // section in description, return true. + if (!current_local_msection) + return true; + + const MediaContentDescription* current_local_media_description = + current_local_msection->media_description(); + // 4.2 If transceiver isn't stopped and is associated with an m= section + // in description then perform the following checks: + + // 4.2.1 If transceiver.[[Direction]] is "sendrecv" or "sendonly", and the + // associated m= section in description either doesn't contain a single + // "a=msid" line, or the number of MSIDs from the "a=msid" lines in this + // m= section, or the MSID values themselves, differ from what is in + // transceiver.sender.[[AssociatedMediaStreamIds]], return true. + if (RtpTransceiverDirectionHasSend(transceiver->direction())) { + if (current_local_media_description->streams().size() == 0) + return true; + + std::vector msection_msids; + for (const auto& stream : current_local_media_description->streams()) { + for (const std::string& msid : stream.stream_ids()) + msection_msids.push_back(msid); + } + + std::vector transceiver_msids = + transceiver->sender()->stream_ids(); + if (msection_msids.size() != transceiver_msids.size()) + return true; + + absl::c_sort(transceiver_msids); + absl::c_sort(msection_msids); + if (transceiver_msids != msection_msids) + return true; + } + + // 4.2.2 If description is of type "offer", and the direction of the + // associated m= section in neither connection.[[CurrentLocalDescription]] + // nor connection.[[CurrentRemoteDescription]] matches + // transceiver.[[Direction]], return true. + if (description->GetType() == SdpType::kOffer) { + if (!current_remote_description()) + return true; + + if (!current_remote_msection) + return true; + + RtpTransceiverDirection current_local_direction = + current_local_media_description->direction(); + RtpTransceiverDirection current_remote_direction = + current_remote_msection->media_description()->direction(); + if (transceiver->direction() != current_local_direction && + transceiver->direction() != + RtpTransceiverDirectionReversed(current_remote_direction)) { + return true; + } + } + + // 4.2.3 If description is of type "answer", and the direction of the + // associated m= section in the description does not match + // transceiver.[[Direction]] intersected with the offered direction (as + // described in [JSEP] (section 5.3.1.)), return true. + if (description->GetType() == SdpType::kAnswer) { + if (!remote_description()) + return true; + + const ContentInfo* offered_remote_msection = + FindTransceiverMSection(transceiver.get(), remote_description()); + + RtpTransceiverDirection offered_direction = + offered_remote_msection + ? offered_remote_msection->media_description()->direction() + : RtpTransceiverDirection::kInactive; + + if (current_local_media_description->direction() != + (RtpTransceiverDirectionIntersection( + transceiver->direction(), + RtpTransceiverDirectionReversed(offered_direction)))) { + return true; + } + } + } + + // If all the preceding checks were performed and true was not returned, + // nothing remains to be negotiated; return false. + return false; +} + } // namespace webrtc diff --git a/pc/peer_connection.h b/pc/peer_connection.h index c639a58829..2cf3662b65 100644 --- a/pc/peer_connection.h +++ b/pc/peer_connection.h @@ -1086,6 +1086,9 @@ class PeerConnection : public PeerConnectionInternal, return media_transport; } + void UpdateNegotiationNeeded(); + bool CheckIfNegotiationIsNeeded(); + sigslot::signal1 SignalDataChannelCreated_ RTC_GUARDED_BY(signaling_thread()); @@ -1329,6 +1332,7 @@ class PeerConnection : public PeerConnectionInternal, // channel manager and the session description factory. rtc::UniqueRandomIdGenerator ssrc_generator_ RTC_GUARDED_BY(signaling_thread()); + bool is_negotiation_needed_ RTC_GUARDED_BY(signaling_thread()) = false; }; } // namespace webrtc diff --git a/pc/peer_connection_interface_unittest.cc b/pc/peer_connection_interface_unittest.cc index e69fc96fe5..d73e45a53d 100644 --- a/pc/peer_connection_interface_unittest.cc +++ b/pc/peer_connection_interface_unittest.cc @@ -814,8 +814,6 @@ class PeerConnectionInterfaceBaseTest : public ::testing::Test { auto sender_or_error = pc_->AddTrack(CreateVideoTrack(track_label), stream_ids); ASSERT_EQ(RTCErrorType::NONE, sender_or_error.error().type()); - EXPECT_TRUE_WAIT(observer_.renegotiation_needed_, kTimeout); - observer_.renegotiation_needed_ = false; } void AddVideoStream(const std::string& label) { @@ -823,8 +821,6 @@ class PeerConnectionInterfaceBaseTest : public ::testing::Test { pc_factory_->CreateLocalMediaStream(label)); stream->AddTrack(CreateVideoTrack(label + "v0")); ASSERT_TRUE(pc_->AddStream(stream)); - EXPECT_TRUE_WAIT(observer_.renegotiation_needed_, kTimeout); - observer_.renegotiation_needed_ = false; } rtc::scoped_refptr CreateAudioTrack( @@ -837,8 +833,6 @@ class PeerConnectionInterfaceBaseTest : public ::testing::Test { auto sender_or_error = pc_->AddTrack(CreateAudioTrack(track_label), stream_ids); ASSERT_EQ(RTCErrorType::NONE, sender_or_error.error().type()); - EXPECT_TRUE_WAIT(observer_.renegotiation_needed_, kTimeout); - observer_.renegotiation_needed_ = false; } void AddAudioStream(const std::string& label) { @@ -846,8 +840,6 @@ class PeerConnectionInterfaceBaseTest : public ::testing::Test { pc_factory_->CreateLocalMediaStream(label)); stream->AddTrack(CreateAudioTrack(label + "a0")); ASSERT_TRUE(pc_->AddStream(stream)); - EXPECT_TRUE_WAIT(observer_.renegotiation_needed_, kTimeout); - observer_.renegotiation_needed_ = false; } void AddAudioVideoStream(const std::string& stream_id, @@ -859,8 +851,6 @@ class PeerConnectionInterfaceBaseTest : public ::testing::Test { stream->AddTrack(CreateAudioTrack(audio_track_label)); stream->AddTrack(CreateVideoTrack(video_track_label)); ASSERT_TRUE(pc_->AddStream(stream)); - EXPECT_TRUE_WAIT(observer_.renegotiation_needed_, kTimeout); - observer_.renegotiation_needed_ = false; } rtc::scoped_refptr GetFirstReceiverOfType( @@ -2204,9 +2194,12 @@ TEST_P(PeerConnectionInterfaceTest, RenegotiationNeededForNewRtpDataChannel) { EXPECT_TRUE(observer_.renegotiation_needed_); observer_.renegotiation_needed_ = false; + CreateOfferReceiveAnswer(); + rtc::scoped_refptr dc2 = pc_->CreateDataChannel("test2", NULL); - EXPECT_TRUE(observer_.renegotiation_needed_); + EXPECT_EQ(observer_.renegotiation_needed_, + GetParam() == SdpSemantics::kPlanB); } // This test that a data channel closes when a PeerConnection is deleted/closed. @@ -3894,14 +3887,17 @@ TEST_F(PeerConnectionInterfaceTestPlanB, EXPECT_TRUE_WAIT(observer_.renegotiation_needed_, kTimeout); observer_.renegotiation_needed_ = false; + CreateOfferReceiveAnswer(); stream->AddTrack(video_track); EXPECT_TRUE_WAIT(observer_.renegotiation_needed_, kTimeout); observer_.renegotiation_needed_ = false; + CreateOfferReceiveAnswer(); stream->RemoveTrack(audio_track); EXPECT_TRUE_WAIT(observer_.renegotiation_needed_, kTimeout); observer_.renegotiation_needed_ = false; + CreateOfferReceiveAnswer(); stream->RemoveTrack(video_track); EXPECT_TRUE_WAIT(observer_.renegotiation_needed_, kTimeout); observer_.renegotiation_needed_ = false; diff --git a/pc/peer_connection_rtp_unittest.cc b/pc/peer_connection_rtp_unittest.cc index c03bab4317..6925300a71 100644 --- a/pc/peer_connection_rtp_unittest.cc +++ b/pc/peer_connection_rtp_unittest.cc @@ -1135,11 +1135,14 @@ TEST_F(PeerConnectionRtpTestUnifiedPlan, TEST_F(PeerConnectionRtpTestUnifiedPlan, AddTrackChangesDirectionFromInactiveToSendOnly) { auto caller = CreatePeerConnection(); + auto callee = CreatePeerConnection(); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kInactive; auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init); + EXPECT_TRUE(caller->observer()->negotiation_needed()); + ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); caller->observer()->clear_negotiation_needed(); ASSERT_TRUE(caller->AddAudioTrack("a")); EXPECT_TRUE(caller->observer()->negotiation_needed()); @@ -1152,11 +1155,14 @@ TEST_F(PeerConnectionRtpTestUnifiedPlan, TEST_F(PeerConnectionRtpTestUnifiedPlan, AddTrackChangesDirectionFromRecvOnlyToSendRecv) { auto caller = CreatePeerConnection(); + auto callee = CreatePeerConnection(); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kRecvOnly; auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init); + EXPECT_TRUE(caller->observer()->negotiation_needed()); + ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); caller->observer()->clear_negotiation_needed(); ASSERT_TRUE(caller->AddAudioTrack("a")); EXPECT_TRUE(caller->observer()->negotiation_needed()); @@ -1219,18 +1225,21 @@ TEST_F(PeerConnectionRtpTestUnifiedPlan, RemoveTrackClearsSenderTrack) { TEST_F(PeerConnectionRtpTestUnifiedPlan, RemoveTrackChangesDirectionFromSendRecvToRecvOnly) { auto caller = CreatePeerConnection(); + auto callee = CreatePeerConnection(); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendRecv; auto transceiver = caller->AddTransceiver(caller->CreateAudioTrack("a"), init); + EXPECT_TRUE(caller->observer()->negotiation_needed()); + ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); caller->observer()->clear_negotiation_needed(); + ASSERT_TRUE(caller->pc()->RemoveTrack(transceiver->sender())); EXPECT_TRUE(caller->observer()->negotiation_needed()); EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, transceiver->direction()); - EXPECT_TRUE(caller->observer()->renegotiation_needed_); } // Test that calling RemoveTrack on a sender where the transceiver is configured @@ -1238,13 +1247,17 @@ TEST_F(PeerConnectionRtpTestUnifiedPlan, TEST_F(PeerConnectionRtpTestUnifiedPlan, RemoveTrackChangesDirectionFromSendOnlyToInactive) { auto caller = CreatePeerConnection(); + auto callee = CreatePeerConnection(); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendOnly; auto transceiver = caller->AddTransceiver(caller->CreateAudioTrack("a"), init); + EXPECT_TRUE(caller->observer()->negotiation_needed()); + ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); caller->observer()->clear_negotiation_needed(); + ASSERT_TRUE(caller->pc()->RemoveTrack(transceiver->sender())); EXPECT_TRUE(caller->observer()->negotiation_needed()); @@ -1394,10 +1407,15 @@ TEST_F(PeerConnectionRtpTestUnifiedPlan, TEST_F(PeerConnectionRtpTestUnifiedPlan, RenegotiationNeededAfterTransceiverSetDirection) { auto caller = CreatePeerConnection(); + auto callee = CreatePeerConnection(); + EXPECT_FALSE(caller->observer()->negotiation_needed()); auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); + EXPECT_TRUE(caller->observer()->negotiation_needed()); + ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); caller->observer()->clear_negotiation_needed(); + transceiver->SetDirection(RtpTransceiverDirection::kInactive); EXPECT_TRUE(caller->observer()->negotiation_needed()); } @@ -1777,6 +1795,42 @@ TEST_P(PeerConnectionRtpTest, CreateTwoSendersWithSameTrack) { } } +// This test exercises the code path that fires a NegotiationNeeded +// notification when the stream IDs of the local description differ from +// the ones in the transceiver. Since SetStreams() is not yet available +// on RtpSenderInterface, adding a track is used to trigger the check for +// the NegotiationNeeded notification. +// TODO(https://crbug.com/webrtc/10129): Replace this test with a test that +// checks that calling SetStreams() on a sender fires the notification once +// the method becomes available in RtpSenderInterface. +TEST_F(PeerConnectionRtpTestUnifiedPlan, + ChangeAssociatedStreamsTriggersRenegotiation) { + auto caller = CreatePeerConnection(); + auto callee = CreatePeerConnection(); + + RtpTransceiverInit init; + init.direction = RtpTransceiverDirection::kSendRecv; + auto transceiver = + caller->AddTransceiver(caller->CreateAudioTrack("a"), init); + EXPECT_TRUE(caller->observer()->negotiation_needed()); + + ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); + caller->observer()->clear_negotiation_needed(); + + SessionDescriptionInterface* cld = const_cast( + caller->pc()->current_local_description()); + ASSERT_EQ(cld->description()->contents().size(), 1u); + + cricket::SessionDescription* description = cld->description(); + cricket::ContentInfo& content_info = description->contents()[0]; + ASSERT_EQ(content_info.media_description()->mutable_streams().size(), 1u); + content_info.media_description()->mutable_streams()[0].set_stream_ids( + {"stream3", "stream4", "stream5"}); + + ASSERT_TRUE(caller->AddTrack(caller->CreateAudioTrack("a2"))); + EXPECT_TRUE(caller->observer()->negotiation_needed()); +} + INSTANTIATE_TEST_SUITE_P(PeerConnectionRtpTest, PeerConnectionRtpTest, Values(SdpSemantics::kPlanB, diff --git a/pc/rtp_media_utils.cc b/pc/rtp_media_utils.cc index f4a61207a6..6e8be589c7 100644 --- a/pc/rtp_media_utils.cc +++ b/pc/rtp_media_utils.cc @@ -81,4 +81,14 @@ const char* RtpTransceiverDirectionToString(RtpTransceiverDirection direction) { return ""; } +RtpTransceiverDirection RtpTransceiverDirectionIntersection( + RtpTransceiverDirection lhs, + RtpTransceiverDirection rhs) { + return RtpTransceiverDirectionFromSendRecv( + RtpTransceiverDirectionHasSend(lhs) && + RtpTransceiverDirectionHasSend(rhs), + RtpTransceiverDirectionHasRecv(lhs) && + RtpTransceiverDirectionHasRecv(rhs)); +} + } // namespace webrtc diff --git a/pc/rtp_media_utils.h b/pc/rtp_media_utils.h index 3ff40c9f0f..f556fe3977 100644 --- a/pc/rtp_media_utils.h +++ b/pc/rtp_media_utils.h @@ -44,6 +44,11 @@ RtpTransceiverDirection RtpTransceiverDirectionWithRecvSet( // Returns an unspecified string representation of the given direction. const char* RtpTransceiverDirectionToString(RtpTransceiverDirection direction); +// Returns the intersection of the directions of two transceivers. +RtpTransceiverDirection RtpTransceiverDirectionIntersection( + RtpTransceiverDirection lhs, + RtpTransceiverDirection rhs); + #ifdef UNIT_TEST inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) std::ostream& os, // no-presubmit-check TODO(webrtc:8982) diff --git a/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionTest.java b/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionTest.java index bf23d19256..cedb356bc1 100644 --- a/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionTest.java +++ b/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionTest.java @@ -1132,8 +1132,10 @@ public class PeerConnectionTest { System.gc(); } + // TODO(https://bugs.webrtc.org/10526): Fix and re-enable this test. @Test @MediumTest + @DisabledTest public void testDataChannelOnlySession() throws Exception { // Allow loopback interfaces too since our Android devices often don't // have those. @@ -1291,8 +1293,10 @@ public class PeerConnectionTest { System.gc(); } + // TODO(https://bugs.webrtc.org/10526): Fix and re-enable this test. @Test @MediumTest + @DisabledTest public void testTrackRemovalAndAddition() throws Exception { // Allow loopback interfaces too since our Android devices often don't // have those.