diff --git a/dom/media/webrtc/jsep/JsepTrack.cpp b/dom/media/webrtc/jsep/JsepTrack.cpp index 1157c8772147..f77ceaedba7f 100644 --- a/dom/media/webrtc/jsep/JsepTrack.cpp +++ b/dom/media/webrtc/jsep/JsepTrack.cpp @@ -733,11 +733,13 @@ nsresult JsepTrack::Negotiate(const SdpMediaSection& answer, /* static */ void JsepTrack::SetUniqueReceivePayloadTypes(std::vector& tracks, bool localOffer) { - // Maps to track details if no other track contains the payload type, - // otherwise maps to nullptr. - std::map> payloadTypeToDetailsMap; + // Maps payload types to all tracks that have negotiated them. + std::multimap payloadTypeToTracks; for (JsepTrack* track : tracks) { + track->mUniqueReceivePayloadTypes.clear(); + track->mDuplicateReceivePayloadTypes.clear(); + if (track->GetMediaType() == SdpMediaSection::kApplication) { continue; } @@ -755,24 +757,23 @@ void JsepTrack::SetUniqueReceivePayloadTypes(std::vector& tracks, } for (uint16_t pt : payloadTypesForTrack) { - payloadTypeToDetailsMap[pt] = - std::make_tuple(track, !payloadTypeToDetailsMap.count(pt)); + payloadTypeToTracks.insert({pt, track}); } } - for (auto ptAndDetails : payloadTypeToDetailsMap) { - uint16_t uniquePt = ptAndDetails.first; - MOZ_ASSERT(uniquePt <= UINT8_MAX); - auto* trackDetails = std::get(ptAndDetails.second); - - if (trackDetails) { - if (std::get(ptAndDetails.second)) { - trackDetails->mUniqueReceivePayloadTypes.push_back( - static_cast(uniquePt)); - } else { - trackDetails->mDuplicateReceivePayloadTypes.push_back( - static_cast(uniquePt)); - } + for (auto it = payloadTypeToTracks.begin(), end = payloadTypeToTracks.end(); + it != end;) { + const auto& [key, firstTrackForPt] = *it; + const auto pt = AssertedCast(key); + const size_t count = payloadTypeToTracks.count(key); + if (count == 1) { + firstTrackForPt->mUniqueReceivePayloadTypes.push_back(pt); + ++it; + continue; + } + for (auto next = payloadTypeToTracks.upper_bound(key); it != next; ++it) { + const auto& [_pt, track] = *it; + track->mDuplicateReceivePayloadTypes.push_back(pt); } } } diff --git a/media/webrtc/signaling/gtest/jsep_session_unittest.cpp b/media/webrtc/signaling/gtest/jsep_session_unittest.cpp index 65f582be8fb8..ae457a413bff 100644 --- a/media/webrtc/signaling/gtest/jsep_session_unittest.cpp +++ b/media/webrtc/signaling/gtest/jsep_session_unittest.cpp @@ -14,6 +14,7 @@ #include "mozilla/RefPtr.h" #define GTEST_HAS_RTTI 0 +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "CodecConfig.h" @@ -24,6 +25,8 @@ #include "jsep/JsepSession.h" #include "jsep/JsepSessionImpl.h" +using testing::UnorderedElementsAre; + namespace mozilla { MOZ_RUNINIT static std::string kAEqualsCandidate("a=candidate:"); const static size_t kNumCandidatesPerComponent = 3; @@ -4972,55 +4975,65 @@ TEST_F(JsepSessionTest, TestUniqueReceivePayloadTypes) { ASSERT_FALSE(IsNull(offerTransceivers[0].mRecvTrack)); ASSERT_TRUE(offerTransceivers[0].mRecvTrack.GetNegotiatedDetails()); - ASSERT_EQ( - 0U, - offerTransceivers[0].mRecvTrack.GetUniqueReceivePayloadTypes().size()); + ASSERT_THAT(offerTransceivers[0].mRecvTrack.GetUniqueReceivePayloadTypes(), + UnorderedElementsAre()); + ASSERT_THAT(offerTransceivers[0].mRecvTrack.GetDuplicateReceivePayloadTypes(), + UnorderedElementsAre(0, 8, 9, 101, 109)); ASSERT_FALSE(IsNull(offerTransceivers[1].mRecvTrack)); ASSERT_TRUE(offerTransceivers[1].mRecvTrack.GetNegotiatedDetails()); - ASSERT_EQ( - 0U, - offerTransceivers[1].mRecvTrack.GetUniqueReceivePayloadTypes().size()); + ASSERT_THAT(offerTransceivers[1].mRecvTrack.GetUniqueReceivePayloadTypes(), + UnorderedElementsAre()); + ASSERT_THAT(offerTransceivers[1].mRecvTrack.GetDuplicateReceivePayloadTypes(), + UnorderedElementsAre(0, 8, 9, 101, 109)); - // First video transceiver is the only one receiving, so gets unique pts. ASSERT_FALSE(IsNull(offerTransceivers[2].mRecvTrack)); ASSERT_TRUE(offerTransceivers[2].mRecvTrack.GetNegotiatedDetails()); - ASSERT_NE( - 0U, - offerTransceivers[2].mRecvTrack.GetUniqueReceivePayloadTypes().size()); + ASSERT_THAT(offerTransceivers[2].mRecvTrack.GetUniqueReceivePayloadTypes(), + UnorderedElementsAre(97, 99, 103, 105, 120, 121, 122, 123, 126)); + ASSERT_THAT(offerTransceivers[2].mRecvTrack.GetDuplicateReceivePayloadTypes(), + UnorderedElementsAre()); - // First video transceiver is not receiving, so does not get unique pts. ASSERT_TRUE(IsNull(offerTransceivers[3].mRecvTrack)); ASSERT_TRUE(offerTransceivers[3].mRecvTrack.GetNegotiatedDetails()); - ASSERT_EQ( - 0U, - offerTransceivers[3].mRecvTrack.GetUniqueReceivePayloadTypes().size()); + ASSERT_THAT(offerTransceivers[3].mRecvTrack.GetUniqueReceivePayloadTypes(), + UnorderedElementsAre()); + ASSERT_THAT(offerTransceivers[3].mRecvTrack.GetDuplicateReceivePayloadTypes(), + UnorderedElementsAre(97, 99, 103, 105, 120, 121, 122, 123, 126)); ASSERT_FALSE(IsNull(answerTransceivers[0].mRecvTrack)); ASSERT_TRUE(answerTransceivers[0].mRecvTrack.GetNegotiatedDetails()); - ASSERT_EQ( - 0U, - answerTransceivers[0].mRecvTrack.GetUniqueReceivePayloadTypes().size()); + ASSERT_THAT(answerTransceivers[0].mRecvTrack.GetUniqueReceivePayloadTypes(), + UnorderedElementsAre()); + ASSERT_THAT( + answerTransceivers[0].mRecvTrack.GetDuplicateReceivePayloadTypes(), + UnorderedElementsAre(0, 8, 9, 101, 109)); ASSERT_FALSE(IsNull(answerTransceivers[1].mRecvTrack)); ASSERT_TRUE(answerTransceivers[1].mRecvTrack.GetNegotiatedDetails()); - ASSERT_EQ( - 0U, - answerTransceivers[1].mRecvTrack.GetUniqueReceivePayloadTypes().size()); + ASSERT_THAT(answerTransceivers[1].mRecvTrack.GetUniqueReceivePayloadTypes(), + UnorderedElementsAre()); + ASSERT_THAT( + answerTransceivers[1].mRecvTrack.GetDuplicateReceivePayloadTypes(), + UnorderedElementsAre(0, 8, 9, 101, 109)); // Answerer is receiving two video streams with the same payload types. // Neither recv track should have unique pts. ASSERT_FALSE(IsNull(answerTransceivers[2].mRecvTrack)); ASSERT_TRUE(answerTransceivers[2].mRecvTrack.GetNegotiatedDetails()); - ASSERT_EQ( - 0U, - answerTransceivers[2].mRecvTrack.GetUniqueReceivePayloadTypes().size()); + ASSERT_THAT(answerTransceivers[2].mRecvTrack.GetUniqueReceivePayloadTypes(), + UnorderedElementsAre()); + ASSERT_THAT( + answerTransceivers[2].mRecvTrack.GetDuplicateReceivePayloadTypes(), + UnorderedElementsAre(97, 99, 103, 105, 120, 121, 122, 123, 126)); ASSERT_FALSE(IsNull(answerTransceivers[3].mRecvTrack)); ASSERT_TRUE(answerTransceivers[3].mRecvTrack.GetNegotiatedDetails()); - ASSERT_EQ( - 0U, - answerTransceivers[3].mRecvTrack.GetUniqueReceivePayloadTypes().size()); + ASSERT_THAT(answerTransceivers[3].mRecvTrack.GetUniqueReceivePayloadTypes(), + UnorderedElementsAre()); + ASSERT_THAT( + answerTransceivers[3].mRecvTrack.GetDuplicateReceivePayloadTypes(), + UnorderedElementsAre(97, 99, 103, 105, 120, 121, 122, 123, 126)); } TEST_F(JsepSessionTest, UnknownFingerprintAlgorithm) { diff --git a/media/webrtc/signaling/gtest/jsep_track_unittest.cpp b/media/webrtc/signaling/gtest/jsep_track_unittest.cpp index 6bdf985195d8..817c45144e8f 100644 --- a/media/webrtc/signaling/gtest/jsep_track_unittest.cpp +++ b/media/webrtc/signaling/gtest/jsep_track_unittest.cpp @@ -8,6 +8,7 @@ #include "ssl.h" #define GTEST_HAS_RTTI 0 +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "MockJsepCodecPreferences.h" @@ -17,6 +18,8 @@ #include "sdp/SipccSdpParser.h" #include "sdp/SdpHelper.h" +using testing::UnorderedElementsAre; + namespace mozilla { class JsepTrackTestBase : public ::testing::Test { @@ -1790,4 +1793,380 @@ TEST_F(JsepTrackTest, NonDefaultVideoSdpFmtpLine) { EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing")); } +TEST(JsepTrackRecvPayloadTypesTest, SingleTrackPTsAreUnique) +{ + constexpr auto audio = SdpMediaSection::MediaType::kAudio; + + std::vector> codecs; + codecs.emplace_back( + MakeUnique("1", "codec1", 48000, 1, true)); + + SipccSdp offer1(SdpOrigin("", 0, 0, sdp::kIPv4, "")); + SdpMediaSection& offer1Msection1 = offer1.AddMediaSection( + audio, SdpDirectionAttribute::kRecvonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + + SipccSdp answer1(SdpOrigin("", 0, 0, sdp::kIPv4, "")); + SdpMediaSection& answer1Msection1 = answer1.AddMediaSection( + audio, SdpDirectionAttribute::kSendonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + + for (const auto& codec : codecs) { + codec->mDirection = sdp::kSend; + offer1Msection1.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock, + codec->mChannels); + auto clone = WrapUnique(codec->Clone()); + clone->mDirection = sdp::kRecv; + clone->AddToMediaSection(answer1Msection1); + } + + JsepTrack t1{audio, sdp::Direction::kRecv}; + t1.PopulateCodecs(codecs, false); + t1.RecvTrackSetLocal(offer1Msection1); + t1.RecvTrackSetRemote(answer1, answer1Msection1); + ASSERT_EQ(t1.Negotiate(answer1Msection1, answer1Msection1, offer1Msection1), + NS_OK); + + std::vector tracks{&t1}; + JsepTrack::SetUniqueReceivePayloadTypes(tracks); + EXPECT_THAT(t1.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(1)); + EXPECT_THAT(t1.GetDuplicateReceivePayloadTypes(), UnorderedElementsAre()); +} + +TEST(JsepTrackRecvPayloadTypesTest, DoubleTrackPTsAreUnique) +{ + constexpr auto audio = SdpMediaSection::MediaType::kAudio; + + std::vector> codecs1; + codecs1.emplace_back( + MakeUnique("1", "codec1", 48000, 1, true)); + + std::vector> codecs2; + codecs2.emplace_back( + MakeUnique("2", "codec1", 48000, 1, true)); + + SipccSdp offer1(SdpOrigin("", 0, 0, sdp::kIPv4, "")); + SdpMediaSection& offer1Msection1 = offer1.AddMediaSection( + audio, SdpDirectionAttribute::kRecvonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + SdpMediaSection& offer1Msection2 = offer1.AddMediaSection( + audio, SdpDirectionAttribute::kRecvonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + + SipccSdp answer1(SdpOrigin("", 0, 0, sdp::kIPv4, "")); + SdpMediaSection& answer1Msection1 = answer1.AddMediaSection( + audio, SdpDirectionAttribute::kSendonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + SdpMediaSection& answer1Msection2 = answer1.AddMediaSection( + audio, SdpDirectionAttribute::kSendonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + + for (const auto& codec : codecs1) { + codec->mDirection = sdp::kSend; + offer1Msection1.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock, + codec->mChannels); + auto clone = WrapUnique(codec->Clone()); + clone->mDirection = sdp::kRecv; + clone->AddToMediaSection(answer1Msection1); + } + + for (const auto& codec : codecs2) { + codec->mDirection = sdp::kSend; + offer1Msection2.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock, + codec->mChannels); + auto clone = WrapUnique(codec->Clone()); + clone->mDirection = sdp::kRecv; + clone->AddToMediaSection(answer1Msection2); + } + + JsepTrack t1{audio, sdp::Direction::kRecv}; + t1.PopulateCodecs(codecs1, false); + t1.RecvTrackSetLocal(offer1Msection1); + t1.RecvTrackSetRemote(answer1, answer1Msection1); + ASSERT_EQ(t1.Negotiate(answer1Msection1, answer1Msection1, offer1Msection1), + NS_OK); + + JsepTrack t2{audio, sdp::Direction::kRecv}; + t2.PopulateCodecs(codecs2, false); + t2.RecvTrackSetLocal(offer1Msection2); + t2.RecvTrackSetRemote(answer1, answer1Msection2); + ASSERT_EQ(t2.Negotiate(answer1Msection2, answer1Msection2, offer1Msection2), + NS_OK); + + std::vector tracks{&t1, &t2}; + JsepTrack::SetUniqueReceivePayloadTypes(tracks); + EXPECT_THAT(t1.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(1)); + EXPECT_THAT(t1.GetDuplicateReceivePayloadTypes(), UnorderedElementsAre()); + EXPECT_THAT(t2.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(2)); + EXPECT_THAT(t2.GetDuplicateReceivePayloadTypes(), UnorderedElementsAre()); +} + +TEST(JsepTrackRecvPayloadTypesTest, DoubleTrackPTsAreDuplicates) +{ + constexpr auto audio = SdpMediaSection::MediaType::kAudio; + + std::vector> codecs1; + codecs1.emplace_back( + MakeUnique("1", "codec1", 48000, 1, true)); + + std::vector> codecs2; + codecs2.emplace_back( + MakeUnique("1", "codec1", 48000, 1, true)); + + SipccSdp offer1(SdpOrigin("", 0, 0, sdp::kIPv4, "")); + SdpMediaSection& offer1Msection1 = offer1.AddMediaSection( + audio, SdpDirectionAttribute::kRecvonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + SdpMediaSection& offer1Msection2 = offer1.AddMediaSection( + audio, SdpDirectionAttribute::kRecvonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + + SipccSdp answer1(SdpOrigin("", 0, 0, sdp::kIPv4, "")); + SdpMediaSection& answer1Msection1 = answer1.AddMediaSection( + audio, SdpDirectionAttribute::kSendonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + SdpMediaSection& answer1Msection2 = answer1.AddMediaSection( + audio, SdpDirectionAttribute::kSendonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + + for (const auto& codec : codecs1) { + codec->mDirection = sdp::kSend; + offer1Msection1.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock, + codec->mChannels); + auto clone = WrapUnique(codec->Clone()); + clone->mDirection = sdp::kRecv; + clone->AddToMediaSection(answer1Msection1); + } + for (const auto& codec : codecs2) { + codec->mDirection = sdp::kSend; + offer1Msection2.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock, + codec->mChannels); + auto clone = WrapUnique(codec->Clone()); + clone->mDirection = sdp::kRecv; + clone->AddToMediaSection(answer1Msection2); + } + + JsepTrack t1{audio, sdp::Direction::kRecv}; + t1.PopulateCodecs(codecs1, false); + t1.RecvTrackSetLocal(offer1Msection1); + t1.RecvTrackSetRemote(answer1, answer1Msection1); + ASSERT_EQ(t1.Negotiate(answer1Msection1, answer1Msection1, offer1Msection1), + NS_OK); + + JsepTrack t2{audio, sdp::Direction::kRecv}; + t2.PopulateCodecs(codecs2, false); + t2.RecvTrackSetLocal(offer1Msection2); + t2.RecvTrackSetRemote(answer1, answer1Msection2); + ASSERT_EQ(t2.Negotiate(answer1Msection2, answer1Msection2, offer1Msection2), + NS_OK); + + std::vector tracks{&t1, &t2}; + JsepTrack::SetUniqueReceivePayloadTypes(tracks); + EXPECT_THAT(t1.GetUniqueReceivePayloadTypes(), UnorderedElementsAre()); + EXPECT_THAT(t1.GetDuplicateReceivePayloadTypes(), UnorderedElementsAre(1)); + EXPECT_THAT(t2.GetUniqueReceivePayloadTypes(), UnorderedElementsAre()); + EXPECT_THAT(t2.GetDuplicateReceivePayloadTypes(), UnorderedElementsAre(1)); +} + +TEST(JsepTrackRecvPayloadTypesTest, DoubleTrackPTsOverlap) +{ + constexpr auto audio = SdpMediaSection::MediaType::kAudio; + + std::vector> codecs1; + codecs1.emplace_back( + MakeUnique("1", "codec1", 48000, 1, true)); + codecs1.emplace_back( + MakeUnique("2", "codec2", 48000, 1, true)); + + std::vector> codecs2; + codecs2.emplace_back( + MakeUnique("1", "codec1", 48000, 1, true)); + codecs2.emplace_back( + MakeUnique("3", "codec2", 48000, 1, true)); + + SipccSdp offer1(SdpOrigin("", 0, 0, sdp::kIPv4, "")); + SdpMediaSection& offer1Msection1 = offer1.AddMediaSection( + audio, SdpDirectionAttribute::kRecvonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + SdpMediaSection& offer1Msection2 = offer1.AddMediaSection( + audio, SdpDirectionAttribute::kRecvonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + + SipccSdp answer1(SdpOrigin("", 0, 0, sdp::kIPv4, "")); + SdpMediaSection& answer1Msection1 = answer1.AddMediaSection( + audio, SdpDirectionAttribute::kSendonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + SdpMediaSection& answer1Msection2 = answer1.AddMediaSection( + audio, SdpDirectionAttribute::kSendonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + + for (const auto& codec : codecs1) { + codec->mDirection = sdp::kSend; + offer1Msection1.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock, + codec->mChannels); + auto clone = WrapUnique(codec->Clone()); + clone->mDirection = sdp::kRecv; + clone->AddToMediaSection(answer1Msection1); + } + + for (const auto& codec : codecs2) { + codec->mDirection = sdp::kSend; + offer1Msection2.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock, + codec->mChannels); + auto clone = WrapUnique(codec->Clone()); + clone->mDirection = sdp::kRecv; + clone->AddToMediaSection(answer1Msection2); + } + + JsepTrack t1{audio, sdp::Direction::kRecv}; + t1.PopulateCodecs(codecs1, false); + t1.RecvTrackSetLocal(offer1Msection1); + t1.RecvTrackSetRemote(answer1, answer1Msection1); + ASSERT_EQ(t1.Negotiate(answer1Msection1, answer1Msection1, offer1Msection1), + NS_OK); + + JsepTrack t2{audio, sdp::Direction::kRecv}; + t2.PopulateCodecs(codecs2, false); + t2.RecvTrackSetLocal(offer1Msection2); + t2.RecvTrackSetRemote(answer1, answer1Msection2); + ASSERT_EQ(t2.Negotiate(answer1Msection2, answer1Msection2, offer1Msection2), + NS_OK); + + std::vector tracks{&t1, &t2}; + JsepTrack::SetUniqueReceivePayloadTypes(tracks); + EXPECT_THAT(t1.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(2)); + EXPECT_THAT(t1.GetDuplicateReceivePayloadTypes(), UnorderedElementsAre(1)); + EXPECT_THAT(t2.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(3)); + EXPECT_THAT(t2.GetDuplicateReceivePayloadTypes(), UnorderedElementsAre(1)); +} + +TEST(JsepTrackRecvPayloadTypesTest, DoubleTrackPTsDuplicateAfterRenegotiation) +{ + constexpr auto audio = SdpMediaSection::MediaType::kAudio; + + std::vector> codecs1; + codecs1.emplace_back( + MakeUnique("1", "codec1", 48000, 1, true)); + codecs1.emplace_back( + MakeUnique("2", "codec2", 48000, 1, true)); + + std::vector> codecs2; + codecs2.emplace_back( + MakeUnique("3", "codec1", 48000, 1, true)); + codecs2.emplace_back( + MakeUnique("4", "codec2", 48000, 1, true)); + + // First negotiation. + SipccSdp offer1(SdpOrigin("", 0, 0, sdp::kIPv4, "")); + SdpMediaSection& offer1Msection1 = offer1.AddMediaSection( + audio, SdpDirectionAttribute::kRecvonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + SdpMediaSection& offer1Msection2 = offer1.AddMediaSection( + audio, SdpDirectionAttribute::kRecvonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + + SipccSdp answer1(SdpOrigin("", 0, 0, sdp::kIPv4, "")); + SdpMediaSection& answer1Msection1 = answer1.AddMediaSection( + audio, SdpDirectionAttribute::kSendonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + SdpMediaSection& answer1Msection2 = answer1.AddMediaSection( + audio, SdpDirectionAttribute::kSendonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + + for (const auto& codec : codecs1) { + codec->mDirection = sdp::kSend; + offer1Msection1.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock, + codec->mChannels); + auto clone = WrapUnique(codec->Clone()); + clone->mDirection = sdp::kRecv; + clone->AddToMediaSection(answer1Msection1); + } + + for (const auto& codec : codecs2) { + codec->mDirection = sdp::kSend; + offer1Msection2.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock, + codec->mChannels); + auto clone = WrapUnique(codec->Clone()); + clone->mDirection = sdp::kRecv; + clone->AddToMediaSection(answer1Msection2); + } + + // t1 and t2 use distinct payload types in the first negotiation. + JsepTrack t1{audio, sdp::Direction::kRecv}; + t1.PopulateCodecs(codecs1, false); + t1.RecvTrackSetLocal(offer1Msection1); + t1.RecvTrackSetRemote(answer1, answer1Msection1); + ASSERT_EQ(t1.Negotiate(answer1Msection1, answer1Msection1, offer1Msection1), + NS_OK); + + JsepTrack t2{audio, sdp::Direction::kRecv}; + t2.PopulateCodecs(codecs2, false); + t2.RecvTrackSetLocal(offer1Msection2); + t2.RecvTrackSetRemote(answer1, answer1Msection2); + ASSERT_EQ(t2.Negotiate(answer1Msection2, answer1Msection2, offer1Msection2), + NS_OK); + + std::vector tracks{&t1, &t2}; + JsepTrack::SetUniqueReceivePayloadTypes(tracks); + EXPECT_THAT(t1.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(1, 2)); + EXPECT_THAT(t1.GetDuplicateReceivePayloadTypes(), UnorderedElementsAre()); + EXPECT_THAT(t2.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(3, 4)); + EXPECT_THAT(t2.GetDuplicateReceivePayloadTypes(), UnorderedElementsAre()); + + // Second negotiation. + SipccSdp offer2(SdpOrigin("", 0, 0, sdp::kIPv4, "")); + SdpMediaSection& offer2Msection1 = offer2.AddMediaSection( + audio, SdpDirectionAttribute::kRecvonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + SdpMediaSection& offer2Msection2 = offer2.AddMediaSection( + audio, SdpDirectionAttribute::kRecvonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + + SipccSdp answer2(SdpOrigin("", 0, 0, sdp::kIPv4, "")); + SdpMediaSection& answer2Msection1 = answer2.AddMediaSection( + audio, SdpDirectionAttribute::kSendonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + SdpMediaSection& answer2Msection2 = answer2.AddMediaSection( + audio, SdpDirectionAttribute::kSendonly, 0, + SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0"); + + for (const auto& codec : codecs1) { + codec->mDirection = sdp::kSend; + offer2Msection1.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock, + codec->mChannels); + auto clone = WrapUnique(codec->Clone()); + clone->mDirection = sdp::kRecv; + clone->AddToMediaSection(answer2Msection1); + } + + for (const auto& codec : codecs2) { + codec->mDirection = sdp::kSend; + offer2Msection2.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock, + codec->mChannels); + auto clone = WrapUnique(codec->Clone()); + clone->mDirection = sdp::kRecv; + clone->AddToMediaSection(answer2Msection2); + } + + t1.PopulateCodecs(codecs1, false); + t1.RecvTrackSetLocal(offer2Msection1); + t1.RecvTrackSetRemote(answer2, answer2Msection1); + ASSERT_EQ(t1.Negotiate(answer2Msection1, answer2Msection1, offer2Msection1), + NS_OK); + + // Change t2 to use the same payload types as t1. Both tracks should now mark + // all their payload types as duplicates. + t2.PopulateCodecs(codecs1, false); + t2.RecvTrackSetLocal(offer2Msection2); + t2.RecvTrackSetRemote(answer2, answer2Msection2); + ASSERT_EQ(t2.Negotiate(answer2Msection2, answer2Msection2, offer2Msection2), + NS_OK); + + std::vector newTracks{&t1, &t2}; + JsepTrack::SetUniqueReceivePayloadTypes(newTracks); + EXPECT_THAT(t1.GetUniqueReceivePayloadTypes(), UnorderedElementsAre()); + EXPECT_THAT(t1.GetDuplicateReceivePayloadTypes(), UnorderedElementsAre(1, 2)); + EXPECT_THAT(t2.GetUniqueReceivePayloadTypes(), UnorderedElementsAre()); + EXPECT_THAT(t2.GetDuplicateReceivePayloadTypes(), UnorderedElementsAre(1, 2)); +} } // namespace mozilla