diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /media/webrtc/signaling/gtest/jsep_track_unittest.cpp | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'media/webrtc/signaling/gtest/jsep_track_unittest.cpp')
-rw-r--r-- | media/webrtc/signaling/gtest/jsep_track_unittest.cpp | 1748 |
1 files changed, 1748 insertions, 0 deletions
diff --git a/media/webrtc/signaling/gtest/jsep_track_unittest.cpp b/media/webrtc/signaling/gtest/jsep_track_unittest.cpp new file mode 100644 index 0000000000..778b87102e --- /dev/null +++ b/media/webrtc/signaling/gtest/jsep_track_unittest.cpp @@ -0,0 +1,1748 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nss.h" +#include "ssl.h" + +#define GTEST_HAS_RTTI 0 +#include "gtest/gtest.h" + +#include "jsep/JsepTrack.h" +#include "sdp/SipccSdp.h" +#include "sdp/SipccSdpParser.h" +#include "sdp/SdpHelper.h" + +namespace mozilla { + +class JsepTrackTestBase : public ::testing::Test { + public: + static void SetUpTestCase() { + NSS_NoDB_Init(nullptr); + NSS_SetDomesticPolicy(); + } +}; + +class JsepTrackTest : public JsepTrackTestBase { + public: + JsepTrackTest() + : mSendOff(SdpMediaSection::kAudio, sdp::kSend), + mRecvOff(SdpMediaSection::kAudio, sdp::kRecv), + mSendAns(SdpMediaSection::kAudio, sdp::kSend), + mRecvAns(SdpMediaSection::kAudio, sdp::kRecv) {} + + void TearDown() override { + if (::testing::UnitTest::GetInstance() + ->current_test_info() + ->result() + ->Failed()) { + if (mOffer) { + std::cerr << "Offer SDP: " << std::endl; + mOffer->Serialize(std::cerr); + } + + if (mAnswer) { + std::cerr << "Answer SDP: " << std::endl; + mAnswer->Serialize(std::cerr); + } + } + } + + std::vector<UniquePtr<JsepCodecDescription>> MakeCodecs( + bool addFecCodecs = false, bool preferRed = false, + bool addDtmfCodec = false) const { + std::vector<UniquePtr<JsepCodecDescription>> results; + results.emplace_back(JsepAudioCodecDescription::CreateDefaultOpus()); + results.emplace_back(JsepAudioCodecDescription::CreateDefaultG722()); + if (addDtmfCodec) { + results.emplace_back( + JsepAudioCodecDescription::CreateDefaultTelephoneEvent()); + } + + if (addFecCodecs && preferRed) { + results.emplace_back(JsepVideoCodecDescription::CreateDefaultRed()); + } + + results.emplace_back(JsepVideoCodecDescription::CreateDefaultVP8(false)); + results.emplace_back(JsepVideoCodecDescription::CreateDefaultH264_1(false)); + + if (addFecCodecs) { + if (!preferRed) { + results.emplace_back(JsepVideoCodecDescription::CreateDefaultRed()); + } + results.emplace_back(JsepVideoCodecDescription::CreateDefaultUlpFec()); + } + + results.emplace_back(new JsepApplicationCodecDescription( + "webrtc-datachannel", 256, 5999, 499)); + + // if we're doing something with red, it needs + // to update the redundant encodings list + for (auto& codec : results) { + if (codec->mName == "red") { + JsepVideoCodecDescription& red = + static_cast<JsepVideoCodecDescription&>(*codec); + red.UpdateRedundantEncodings(results); + } + } + + return results; + } + + void Init(SdpMediaSection::MediaType type) { + InitCodecs(); + InitTracks(type); + InitSdp(type); + } + + void InitCodecs() { + mOffCodecs = MakeCodecs(); + mAnsCodecs = MakeCodecs(); + } + + void InitTracks(SdpMediaSection::MediaType type) { + mSendOff = JsepTrack(type, sdp::kSend); + if (type != SdpMediaSection::MediaType::kApplication) { + mSendOff.UpdateStreamIds(std::vector<std::string>(1, "stream_id")); + } + mRecvOff = JsepTrack(type, sdp::kRecv); + mSendOff.PopulateCodecs(mOffCodecs); + mRecvOff.PopulateCodecs(mOffCodecs); + + mSendAns = JsepTrack(type, sdp::kSend); + if (type != SdpMediaSection::MediaType::kApplication) { + mSendAns.UpdateStreamIds(std::vector<std::string>(1, "stream_id")); + } + mRecvAns = JsepTrack(type, sdp::kRecv); + mSendAns.PopulateCodecs(mAnsCodecs); + mRecvAns.PopulateCodecs(mAnsCodecs); + } + + void InitSdp(SdpMediaSection::MediaType type) { + std::vector<std::string> msids(1, "*"); + std::string error; + SdpHelper helper(&error); + + mOffer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, ""))); + mOffer->AddMediaSection(type, SdpDirectionAttribute::kSendrecv, 0, + SdpHelper::GetProtocolForMediaType(type), + sdp::kIPv4, "0.0.0.0"); + // JsepTrack doesn't set msid-semantic + helper.SetupMsidSemantic(msids, mOffer.get()); + + mAnswer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, ""))); + mAnswer->AddMediaSection(type, SdpDirectionAttribute::kSendrecv, 0, + SdpHelper::GetProtocolForMediaType(type), + sdp::kIPv4, "0.0.0.0"); + // JsepTrack doesn't set msid-semantic + helper.SetupMsidSemantic(msids, mAnswer.get()); + } + + SdpMediaSection& GetOffer() { return mOffer->GetMediaSection(0); } + + SdpMediaSection& GetAnswer() { return mAnswer->GetMediaSection(0); } + + void CreateOffer() { + mSendOff.AddToOffer(mSsrcGenerator, &GetOffer()); + mRecvOff.AddToOffer(mSsrcGenerator, &GetOffer()); + } + + void CreateAnswer() { + if (mRecvAns.GetMediaType() != SdpMediaSection::MediaType::kApplication) { + mRecvAns.RecvTrackSetRemote(*mOffer, GetOffer()); + mSendAns.SendTrackSetRemote(mSsrcGenerator, GetOffer()); + } + + mSendAns.AddToAnswer(GetOffer(), mSsrcGenerator, &GetAnswer()); + mRecvAns.AddToAnswer(GetOffer(), mSsrcGenerator, &GetAnswer()); + } + + void Negotiate() { + if (mRecvOff.GetMediaType() != SdpMediaSection::MediaType::kApplication) { + mRecvOff.RecvTrackSetRemote(*mAnswer, GetAnswer()); + mSendOff.SendTrackSetRemote(mSsrcGenerator, GetAnswer()); + } + + if (GetAnswer().IsSending()) { + mSendAns.Negotiate(GetAnswer(), GetOffer(), GetAnswer()); + mRecvOff.Negotiate(GetAnswer(), GetAnswer(), GetOffer()); + } + + if (GetAnswer().IsReceiving()) { + mRecvAns.Negotiate(GetAnswer(), GetOffer(), GetAnswer()); + mSendOff.Negotiate(GetAnswer(), GetAnswer(), GetOffer()); + } + } + + void OfferAnswer() { + CreateOffer(); + CreateAnswer(); + Negotiate(); + SanityCheck(); + } + + // TODO: Look into writing a macro that wraps an ASSERT_ and returns false + // if it fails (probably requires writing a bool-returning function that + // takes a void-returning lambda with a bool outparam, which will in turn + // invokes the ASSERT_) + static void CheckEncodingCount(size_t expected, const JsepTrack& send, + const JsepTrack& recv) { + if (expected) { + ASSERT_TRUE(send.GetNegotiatedDetails()); + ASSERT_TRUE(recv.GetNegotiatedDetails()); + } + + if (!send.GetStreamIds().empty() && send.GetNegotiatedDetails()) { + ASSERT_EQ(expected, send.GetNegotiatedDetails()->GetEncodingCount()); + } + + if (!recv.GetStreamIds().empty() && recv.GetNegotiatedDetails()) { + ASSERT_EQ(expected, recv.GetNegotiatedDetails()->GetEncodingCount()); + } + } + + void CheckOffEncodingCount(size_t expected) const { + CheckEncodingCount(expected, mSendOff, mRecvAns); + } + + void CheckAnsEncodingCount(size_t expected) const { + CheckEncodingCount(expected, mSendAns, mRecvOff); + } + + UniquePtr<JsepCodecDescription> GetCodec(const JsepTrack& track, + SdpMediaSection::MediaType type, + size_t expectedSize, + size_t codecIndex) const { + if (!track.GetNegotiatedDetails() || + track.GetNegotiatedDetails()->GetEncodingCount() != 1U || + track.GetMediaType() != type) { + return nullptr; + } + const auto& codecs = + track.GetNegotiatedDetails()->GetEncoding(0).GetCodecs(); + // it should not be possible for codecs to have a different type + // than the track, but we'll check the codec here just in case. + if (codecs.size() != expectedSize || codecIndex >= expectedSize || + codecs[codecIndex]->Type() != type) { + return nullptr; + } + return UniquePtr<JsepCodecDescription>(codecs[codecIndex]->Clone()); + } + + UniquePtr<JsepVideoCodecDescription> GetVideoCodec( + const JsepTrack& track, size_t expectedSize = 1, + size_t codecIndex = 0) const { + auto codec = + GetCodec(track, SdpMediaSection::kVideo, expectedSize, codecIndex); + return UniquePtr<JsepVideoCodecDescription>( + static_cast<JsepVideoCodecDescription*>(codec.release())); + } + + UniquePtr<JsepAudioCodecDescription> GetAudioCodec( + const JsepTrack& track, size_t expectedSize = 1, + size_t codecIndex = 0) const { + auto codec = + GetCodec(track, SdpMediaSection::kAudio, expectedSize, codecIndex); + return UniquePtr<JsepAudioCodecDescription>( + static_cast<JsepAudioCodecDescription*>(codec.release())); + } + + void CheckOtherFbExists(const JsepVideoCodecDescription& videoCodec, + SdpRtcpFbAttributeList::Type type) const { + for (const auto& fb : videoCodec.mOtherFbTypes) { + if (fb.type == type) { + return; // found the RtcpFb type, so stop looking + } + } + FAIL(); // RtcpFb type not found + } + + void SanityCheckRtcpFbs(const JsepVideoCodecDescription& a, + const JsepVideoCodecDescription& b) const { + ASSERT_EQ(a.mNackFbTypes.size(), b.mNackFbTypes.size()); + ASSERT_EQ(a.mAckFbTypes.size(), b.mAckFbTypes.size()); + ASSERT_EQ(a.mCcmFbTypes.size(), b.mCcmFbTypes.size()); + ASSERT_EQ(a.mOtherFbTypes.size(), b.mOtherFbTypes.size()); + } + + void SanityCheckCodecs(const JsepCodecDescription& a, + const JsepCodecDescription& b) const { +#define MSG \ + "For codecs " << a.mName << " (" << a.mDirection << ") and " << b.mName \ + << " (" << b.mDirection << ")" + ASSERT_EQ(a.Type(), b.Type()) << MSG; + if (a.Type() != SdpMediaSection::kApplication) { + ASSERT_EQ(a.mDefaultPt, b.mDefaultPt) << MSG; + } + ASSERT_EQ(a.mName, b.mName); + if (!mExpectDifferingFmtp) { + ASSERT_EQ(a.mSdpFmtpLine, b.mSdpFmtpLine) << MSG; + } + ASSERT_EQ(a.mClock, b.mClock) << MSG; + ASSERT_EQ(a.mChannels, b.mChannels) << MSG; + ASSERT_NE(a.mDirection, b.mDirection) << MSG; + // These constraints are for fmtp and rid, which _are_ signaled + ASSERT_EQ(a.mConstraints, b.mConstraints) << MSG; +#undef MSG + + if (a.Type() == SdpMediaSection::kVideo) { + SanityCheckRtcpFbs(static_cast<const JsepVideoCodecDescription&>(a), + static_cast<const JsepVideoCodecDescription&>(b)); + } + } + + void SanityCheckEncodings(const JsepTrackEncoding& a, + const JsepTrackEncoding& b) const { + ASSERT_EQ(a.GetCodecs().size(), b.GetCodecs().size()); + for (size_t i = 0; i < a.GetCodecs().size(); ++i) { + SanityCheckCodecs(*a.GetCodecs()[i], *b.GetCodecs()[i]); + } + + ASSERT_EQ(a.mRid, b.mRid); + // mConstraints will probably differ, since they are not signaled to the + // other side. + } + + void SanityCheckNegotiatedDetails(const JsepTrackNegotiatedDetails& a, + const JsepTrackNegotiatedDetails& b) const { + ASSERT_EQ(a.GetEncodingCount(), b.GetEncodingCount()); + for (size_t i = 0; i < a.GetEncodingCount(); ++i) { + SanityCheckEncodings(a.GetEncoding(i), b.GetEncoding(i)); + } + + ASSERT_EQ(a.GetUniquePayloadTypes().size(), + b.GetUniquePayloadTypes().size()); + for (size_t i = 0; i < a.GetUniquePayloadTypes().size(); ++i) { + ASSERT_EQ(a.GetUniquePayloadTypes()[i], b.GetUniquePayloadTypes()[i]); + } + } + + void SanityCheckTracks(const JsepTrack& a, const JsepTrack& b) const { + if (!a.GetNegotiatedDetails()) { + ASSERT_FALSE(!!b.GetNegotiatedDetails()); + return; + } + + ASSERT_TRUE(!!a.GetNegotiatedDetails()); + ASSERT_TRUE(!!b.GetNegotiatedDetails()); + ASSERT_EQ(a.GetMediaType(), b.GetMediaType()); + ASSERT_EQ(a.GetStreamIds(), b.GetStreamIds()); + ASSERT_EQ(a.GetCNAME(), b.GetCNAME()); + ASSERT_NE(a.GetDirection(), b.GetDirection()); + ASSERT_EQ(a.GetSsrcs().size(), b.GetSsrcs().size()); + for (size_t i = 0; i < a.GetSsrcs().size(); ++i) { + ASSERT_EQ(a.GetSsrcs()[i], b.GetSsrcs()[i]); + } + + SanityCheckNegotiatedDetails(*a.GetNegotiatedDetails(), + *b.GetNegotiatedDetails()); + } + + void SanityCheck() const { + SanityCheckTracks(mSendOff, mRecvAns); + SanityCheckTracks(mRecvOff, mSendAns); + } + + protected: + JsepTrack mSendOff; + JsepTrack mRecvOff; + JsepTrack mSendAns; + JsepTrack mRecvAns; + std::vector<UniquePtr<JsepCodecDescription>> mOffCodecs; + std::vector<UniquePtr<JsepCodecDescription>> mAnsCodecs; + UniquePtr<Sdp> mOffer; + UniquePtr<Sdp> mAnswer; + SsrcGenerator mSsrcGenerator; + bool mExpectDifferingFmtp = false; +}; + +TEST_F(JsepTrackTestBase, CreateDestroy) {} + +TEST_F(JsepTrackTest, CreateDestroy) { Init(SdpMediaSection::kAudio); } + +TEST_F(JsepTrackTest, AudioNegotiation) { + Init(SdpMediaSection::kAudio); + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); +} + +TEST_F(JsepTrackTest, VideoNegotiation) { + Init(SdpMediaSection::kVideo); + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); +} + +class CheckForCodecType { + public: + explicit CheckForCodecType(SdpMediaSection::MediaType type, bool* result) + : mResult(result), mType(type) {} + + void operator()(const UniquePtr<JsepCodecDescription>& codec) { + if (codec->Type() == mType) { + *mResult = true; + } + } + + private: + bool* mResult; + SdpMediaSection::MediaType mType; +}; + +TEST_F(JsepTrackTest, CheckForMismatchedAudioCodecAndVideoTrack) { + std::vector<UniquePtr<JsepCodecDescription>> offerCodecs; + + // make codecs including telephone-event (an audio codec) + offerCodecs = MakeCodecs(false, false, true); + JsepTrack videoTrack(SdpMediaSection::kVideo, sdp::kSend); + videoTrack.UpdateStreamIds(std::vector<std::string>(1, "stream_id")); + // populate codecs and then make sure we don't have any audio codecs + // in the video track + videoTrack.PopulateCodecs(offerCodecs); + + bool found = false; + videoTrack.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found)); + ASSERT_FALSE(found); + + found = false; + videoTrack.ForEachCodec(CheckForCodecType(SdpMediaSection::kVideo, &found)); + ASSERT_TRUE(found); // for sanity, make sure we did find video codecs +} + +TEST_F(JsepTrackTest, CheckVideoTrackWithHackedDtmfSdp) { + Init(SdpMediaSection::kVideo); + CreateOffer(); + // make sure we don't find sdp containing telephone-event in video track + ASSERT_EQ(mOffer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + // force audio codec telephone-event into video m= section of offer + GetOffer().AddCodec("101", "telephone-event", 8000, 1); + // make sure we _do_ find sdp containing telephone-event in video track + ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + + CreateAnswer(); + // make sure we don't find sdp containing telephone-event in video track + ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + // force audio codec telephone-event into video m= section of answer + GetAnswer().AddCodec("101", "telephone-event", 8000, 1); + // make sure we _do_ find sdp containing telephone-event in video track + ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + + Negotiate(); + SanityCheck(); + + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + // make sure we still don't find any audio codecs in the video track after + // hacking the sdp + bool found = false; + mSendOff.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found)); + ASSERT_FALSE(found); + mRecvOff.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found)); + ASSERT_FALSE(found); + mSendAns.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found)); + ASSERT_FALSE(found); + mRecvAns.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found)); + ASSERT_FALSE(found); +} + +TEST_F(JsepTrackTest, AudioNegotiationOffererDtmf) { + mOffCodecs = MakeCodecs(false, false, true); + mAnsCodecs = MakeCodecs(false, false, false); + + InitTracks(SdpMediaSection::kAudio); + InitSdp(SdpMediaSection::kAudio); + OfferAnswer(); + + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + + ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos); + + UniquePtr<JsepAudioCodecDescription> track; + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2, 1))); + ASSERT_EQ("9", track->mDefaultPt); +} + +TEST_F(JsepTrackTest, AudioNegotiationAnswererDtmf) { + mOffCodecs = MakeCodecs(false, false, false); + mAnsCodecs = MakeCodecs(false, false, true); + + InitTracks(SdpMediaSection::kAudio); + InitSdp(SdpMediaSection::kAudio); + OfferAnswer(); + + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_EQ(mOffer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + + ASSERT_EQ(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos); + + UniquePtr<JsepAudioCodecDescription> track; + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 2, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 2, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 2, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 2, 1))); + ASSERT_EQ("9", track->mDefaultPt); +} + +TEST_F(JsepTrackTest, AudioNegotiationOffererAnswererDtmf) { + mOffCodecs = MakeCodecs(false, false, true); + mAnsCodecs = MakeCodecs(false, false, true); + + InitTracks(SdpMediaSection::kAudio); + InitSdp(SdpMediaSection::kAudio); + OfferAnswer(); + + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + + ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=fmtp:101 0-15"), std::string::npos); + + UniquePtr<JsepAudioCodecDescription> track; + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); +} + +TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererNoFmtpAnswererFmtp) { + mOffCodecs = MakeCodecs(false, false, true); + mAnsCodecs = MakeCodecs(false, false, true); + + mExpectDifferingFmtp = true; + + InitTracks(SdpMediaSection::kAudio); + InitSdp(SdpMediaSection::kAudio); + + CreateOffer(); + GetOffer().RemoveFmtp("101"); + + CreateAnswer(); + + Negotiate(); + SanityCheck(); + + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + + ASSERT_EQ(mOffer->ToString().find("a=fmtp:101"), std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=fmtp:101 0-15"), std::string::npos); + + UniquePtr<JsepAudioCodecDescription> track; + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_EQ("0-15", track->mSdpFmtpLine.valueOr("nothing")); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing")); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing")); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_EQ("0-15", track->mSdpFmtpLine.valueOr("nothing")); +} + +TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererFmtpAnswererNoFmtp) { + mOffCodecs = MakeCodecs(false, false, true); + mAnsCodecs = MakeCodecs(false, false, true); + + mExpectDifferingFmtp = true; + + InitTracks(SdpMediaSection::kAudio); + InitSdp(SdpMediaSection::kAudio); + + CreateOffer(); + + CreateAnswer(); + GetAnswer().RemoveFmtp("101"); + + Negotiate(); + SanityCheck(); + + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + + ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos); + + UniquePtr<JsepAudioCodecDescription> track; + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing")); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_EQ("0-15", track->mSdpFmtpLine.valueOr("nothing")); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_EQ("0-15", track->mSdpFmtpLine.valueOr("nothing")); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing")); +} + +TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererNoFmtpAnswererNoFmtp) { + mOffCodecs = MakeCodecs(false, false, true); + mAnsCodecs = MakeCodecs(false, false, true); + + mExpectDifferingFmtp = true; + + InitTracks(SdpMediaSection::kAudio); + InitSdp(SdpMediaSection::kAudio); + + CreateOffer(); + GetOffer().RemoveFmtp("101"); + + CreateAnswer(); + GetAnswer().RemoveFmtp("101"); + + Negotiate(); + SanityCheck(); + + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"), + std::string::npos); + + ASSERT_EQ(mOffer->ToString().find("a=fmtp:101"), std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos); + + UniquePtr<JsepAudioCodecDescription> track; + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 0))); + ASSERT_EQ("109", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 1))); + ASSERT_EQ("9", track->mDefaultPt); + ASSERT_TRUE((track = GetAudioCodec(mSendOff, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing")); + ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing")); + ASSERT_TRUE((track = GetAudioCodec(mSendAns, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing")); + ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 3, 2))); + ASSERT_EQ("101", track->mDefaultPt); + ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing")); +} + +TEST_F(JsepTrackTest, VideoNegotationOffererFEC) { + mOffCodecs = MakeCodecs(true); + mAnsCodecs = MakeCodecs(false); + + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos); + ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos); + + ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), + std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=fmtp:122"), std::string::npos); + + UniquePtr<JsepVideoCodecDescription> track; + ASSERT_TRUE((track = GetVideoCodec(mSendOff, 2, 0))); + ASSERT_EQ("120", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 2, 0))); + ASSERT_EQ("120", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mSendAns, 2, 0))); + ASSERT_EQ("120", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 2, 0))); + ASSERT_EQ("120", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mSendOff, 2, 1))); + ASSERT_EQ("126", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 2, 1))); + ASSERT_EQ("126", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mSendAns, 2, 1))); + ASSERT_EQ("126", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 2, 1))); + ASSERT_EQ("126", track->mDefaultPt); +} + +TEST_F(JsepTrackTest, VideoNegotationAnswererFEC) { + mOffCodecs = MakeCodecs(false); + mAnsCodecs = MakeCodecs(true); + + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_EQ(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos); + ASSERT_EQ(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos); + + ASSERT_EQ(mOffer->ToString().find("a=fmtp:122"), std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=fmtp:122"), std::string::npos); + + UniquePtr<JsepVideoCodecDescription> track; + ASSERT_TRUE((track = GetVideoCodec(mSendOff, 2, 0))); + ASSERT_EQ("120", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 2, 0))); + ASSERT_EQ("120", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mSendAns, 2, 0))); + ASSERT_EQ("120", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 2, 0))); + ASSERT_EQ("120", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mSendOff, 2, 1))); + ASSERT_EQ("126", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 2, 1))); + ASSERT_EQ("126", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mSendAns, 2, 1))); + ASSERT_EQ("126", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 2, 1))); + ASSERT_EQ("126", track->mDefaultPt); +} + +TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFEC) { + mOffCodecs = MakeCodecs(true); + mAnsCodecs = MakeCodecs(true); + + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos); + ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos); + + ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), + std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123"), + std::string::npos); + + UniquePtr<JsepVideoCodecDescription> track; + ASSERT_TRUE((track = GetVideoCodec(mSendOff, 4))); + ASSERT_EQ("120", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 4))); + ASSERT_EQ("120", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mSendAns, 4))); + ASSERT_EQ("120", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 4))); + ASSERT_EQ("120", track->mDefaultPt); +} + +TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECPreferred) { + mOffCodecs = MakeCodecs(true, true); + mAnsCodecs = MakeCodecs(true); + + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos); + ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos); + + ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), + std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123"), + std::string::npos); + + UniquePtr<JsepVideoCodecDescription> track; + ASSERT_TRUE((track = GetVideoCodec(mSendOff, 4))); + ASSERT_EQ("122", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 4))); + ASSERT_EQ("122", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mSendAns, 4))); + ASSERT_EQ("122", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 4))); + ASSERT_EQ("122", track->mDefaultPt); +} + +// Make sure we only put the right things in the fmtp:122 120/.... line +TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECMismatch) { + mOffCodecs = MakeCodecs(true, true); + mAnsCodecs = MakeCodecs(true); + // remove h264 from answer codecs + ASSERT_EQ("H264", mAnsCodecs[3]->mName); + mAnsCodecs.erase(mAnsCodecs.begin() + 3); + + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos); + ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos); + + ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), + std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/123"), std::string::npos); + + UniquePtr<JsepVideoCodecDescription> track; + ASSERT_TRUE((track = GetVideoCodec(mSendOff, 3))); + ASSERT_EQ("122", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 3))); + ASSERT_EQ("122", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mSendAns, 3))); + ASSERT_EQ("122", track->mDefaultPt); + ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 3))); + ASSERT_EQ("122", track->mDefaultPt); +} + +TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECZeroVP9Codec) { + mOffCodecs = MakeCodecs(true); + auto vp9 = JsepVideoCodecDescription::CreateDefaultVP9(false); + vp9->mDefaultPt = "0"; + mOffCodecs.push_back(std::move(vp9)); + + ASSERT_EQ(8U, mOffCodecs.size()); + JsepVideoCodecDescription& red = + static_cast<JsepVideoCodecDescription&>(*mOffCodecs[4]); + ASSERT_EQ("red", red.mName); + // rebuild the redundant encodings with our newly added "wacky" VP9 + red.mRedundantEncodings.clear(); + red.UpdateRedundantEncodings(mOffCodecs); + + mAnsCodecs = MakeCodecs(true); + + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos); + ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos); + + ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123/0"), + std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123\r\n"), + std::string::npos); +} + +TEST_F(JsepTrackTest, VideoNegotiationOfferRemb) { + InitCodecs(); + // enable remb on the offer codecs + ((JsepVideoCodecDescription&)*mOffCodecs[2]).EnableRemb(); + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + // make sure REMB is on offer and not on answer + ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"), + std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"), + std::string::npos); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + UniquePtr<JsepVideoCodecDescription> codec; + ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); + ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); + ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); + ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); +} + +TEST_F(JsepTrackTest, VideoNegotiationAnswerRemb) { + InitCodecs(); + // enable remb on the answer codecs + ((JsepVideoCodecDescription&)*mAnsCodecs[2]).EnableRemb(); + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + // make sure REMB is not on offer and not on answer + ASSERT_EQ(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"), + std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"), + std::string::npos); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + UniquePtr<JsepVideoCodecDescription> codec; + ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); + ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); + ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); + ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); +} + +TEST_F(JsepTrackTest, VideoNegotiationOfferAnswerRemb) { + InitCodecs(); + // enable remb on the offer and answer codecs + ((JsepVideoCodecDescription&)*mOffCodecs[2]).EnableRemb(); + ((JsepVideoCodecDescription&)*mAnsCodecs[2]).EnableRemb(); + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + // make sure REMB is on offer and on answer + ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"), + std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"), + std::string::npos); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + UniquePtr<JsepVideoCodecDescription> codec; + ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 1U); + CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb); + ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 1U); + CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb); + ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 1U); + CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb); + ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 1U); + CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb); +} + +TEST_F(JsepTrackTest, VideoNegotiationOfferTransportCC) { + InitCodecs(); + // enable TransportCC on the offer codecs + ((JsepVideoCodecDescription&)*mOffCodecs[2]).EnableTransportCC(); + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + // make sure TransportCC is on offer and not on answer + ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 transport-cc"), + std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 transport-cc"), + std::string::npos); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + UniquePtr<JsepVideoCodecDescription> codec; + ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); + ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); + ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); + ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); +} + +TEST_F(JsepTrackTest, VideoNegotiationAnswerTransportCC) { + InitCodecs(); + // enable TransportCC on the answer codecs + ((JsepVideoCodecDescription&)*mAnsCodecs[2]).EnableTransportCC(); + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + // make sure TransportCC is not on offer and not on answer + ASSERT_EQ(mOffer->ToString().find("a=rtcp-fb:120 transport-cc"), + std::string::npos); + ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 transport-cc"), + std::string::npos); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + UniquePtr<JsepVideoCodecDescription> codec; + ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); + ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); + ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); + ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 0U); +} + +TEST_F(JsepTrackTest, VideoNegotiationOfferAnswerTransportCC) { + InitCodecs(); + // enable TransportCC on the offer and answer codecs + ((JsepVideoCodecDescription&)*mOffCodecs[2]).EnableTransportCC(); + ((JsepVideoCodecDescription&)*mAnsCodecs[2]).EnableTransportCC(); + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + // make sure TransportCC is on offer and on answer + ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 transport-cc"), + std::string::npos); + ASSERT_NE(mAnswer->ToString().find("a=rtcp-fb:120 transport-cc"), + std::string::npos); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + UniquePtr<JsepVideoCodecDescription> codec; + ASSERT_TRUE((codec = GetVideoCodec(mSendOff, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 1U); + CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC); + ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 1U); + CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC); + ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 1U); + CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC); + ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 2, 0))); + ASSERT_EQ(codec->mOtherFbTypes.size(), 1U); + CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC); +} + +TEST_F(JsepTrackTest, AudioOffSendonlyAnsRecvonly) { + Init(SdpMediaSection::kAudio); + GetOffer().SetDirection(SdpDirectionAttribute::kSendonly); + GetAnswer().SetDirection(SdpDirectionAttribute::kRecvonly); + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(0); +} + +TEST_F(JsepTrackTest, VideoOffSendonlyAnsRecvonly) { + Init(SdpMediaSection::kVideo); + GetOffer().SetDirection(SdpDirectionAttribute::kSendonly); + GetAnswer().SetDirection(SdpDirectionAttribute::kRecvonly); + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(0); +} + +TEST_F(JsepTrackTest, AudioOffSendrecvAnsRecvonly) { + Init(SdpMediaSection::kAudio); + GetAnswer().SetDirection(SdpDirectionAttribute::kRecvonly); + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(0); +} + +TEST_F(JsepTrackTest, VideoOffSendrecvAnsRecvonly) { + Init(SdpMediaSection::kVideo); + GetAnswer().SetDirection(SdpDirectionAttribute::kRecvonly); + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(0); +} + +TEST_F(JsepTrackTest, AudioOffRecvonlyAnsSendonly) { + Init(SdpMediaSection::kAudio); + GetOffer().SetDirection(SdpDirectionAttribute::kRecvonly); + GetAnswer().SetDirection(SdpDirectionAttribute::kSendonly); + OfferAnswer(); + CheckOffEncodingCount(0); + CheckAnsEncodingCount(1); +} + +TEST_F(JsepTrackTest, VideoOffRecvonlyAnsSendonly) { + Init(SdpMediaSection::kVideo); + GetOffer().SetDirection(SdpDirectionAttribute::kRecvonly); + GetAnswer().SetDirection(SdpDirectionAttribute::kSendonly); + OfferAnswer(); + CheckOffEncodingCount(0); + CheckAnsEncodingCount(1); +} + +TEST_F(JsepTrackTest, AudioOffSendrecvAnsSendonly) { + Init(SdpMediaSection::kAudio); + GetAnswer().SetDirection(SdpDirectionAttribute::kSendonly); + OfferAnswer(); + CheckOffEncodingCount(0); + CheckAnsEncodingCount(1); +} + +TEST_F(JsepTrackTest, VideoOffSendrecvAnsSendonly) { + Init(SdpMediaSection::kVideo); + GetAnswer().SetDirection(SdpDirectionAttribute::kSendonly); + OfferAnswer(); + CheckOffEncodingCount(0); + CheckAnsEncodingCount(1); +} + +TEST_F(JsepTrackTest, DataChannelDraft05) { + mOffCodecs = MakeCodecs(false, false, false); + mAnsCodecs = MakeCodecs(false, false, false); + InitTracks(SdpMediaSection::kApplication); + + mOffer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, ""))); + mOffer->AddMediaSection(SdpMediaSection::kApplication, + SdpDirectionAttribute::kSendrecv, 0, + SdpMediaSection::kDtlsSctp, sdp::kIPv4, "0.0.0.0"); + mAnswer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, ""))); + mAnswer->AddMediaSection(SdpMediaSection::kApplication, + SdpDirectionAttribute::kSendrecv, 0, + SdpMediaSection::kDtlsSctp, sdp::kIPv4, "0.0.0.0"); + + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_NE(std::string::npos, + mOffer->ToString().find("a=sctpmap:5999 webrtc-datachannel 256")); + ASSERT_NE(std::string::npos, + mAnswer->ToString().find("a=sctpmap:5999 webrtc-datachannel 256")); + // Note: this is testing for a workaround, see bug 1335262 for details + ASSERT_NE(std::string::npos, + mOffer->ToString().find("a=max-message-size:499")); + ASSERT_NE(std::string::npos, + mAnswer->ToString().find("a=max-message-size:499")); + ASSERT_EQ(std::string::npos, mOffer->ToString().find("a=sctp-port")); + ASSERT_EQ(std::string::npos, mAnswer->ToString().find("a=sctp-port")); +} + +TEST_F(JsepTrackTest, DataChannelDraft21) { + Init(SdpMediaSection::kApplication); + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_NE(std::string::npos, mOffer->ToString().find("a=sctp-port:5999")); + ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=sctp-port:5999")); + ASSERT_NE(std::string::npos, + mOffer->ToString().find("a=max-message-size:499")); + ASSERT_NE(std::string::npos, + mAnswer->ToString().find("a=max-message-size:499")); + ASSERT_EQ(std::string::npos, mOffer->ToString().find("a=sctpmap")); + ASSERT_EQ(std::string::npos, mAnswer->ToString().find("a=sctpmap")); +} + +TEST_F(JsepTrackTest, DataChannelDraft21AnswerWithDifferentPort) { + mOffCodecs = MakeCodecs(false, false, false); + mAnsCodecs = MakeCodecs(false, false, false); + + mOffCodecs.pop_back(); + mOffCodecs.emplace_back(new JsepApplicationCodecDescription( + "webrtc-datachannel", 256, 4555, 10544)); + + InitTracks(SdpMediaSection::kApplication); + InitSdp(SdpMediaSection::kApplication); + + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); + + ASSERT_NE(std::string::npos, mOffer->ToString().find("a=sctp-port:4555")); + ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=sctp-port:5999")); + ASSERT_NE(std::string::npos, + mOffer->ToString().find("a=max-message-size:10544")); + ASSERT_NE(std::string::npos, + mAnswer->ToString().find("a=max-message-size:499")); + ASSERT_EQ(std::string::npos, mOffer->ToString().find("a=sctpmap")); + ASSERT_EQ(std::string::npos, mAnswer->ToString().find("a=sctpmap")); +} + +TEST_F(JsepTrackTest, SimulcastRejected) { + Init(SdpMediaSection::kVideo); + std::vector<std::string> rids; + rids.push_back("foo"); + rids.push_back("bar"); + mSendOff.SetRids(rids); + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); +} + +TEST_F(JsepTrackTest, SimulcastPrevented) { + Init(SdpMediaSection::kVideo); + std::vector<std::string> rids; + rids.push_back("foo"); + rids.push_back("bar"); + mSendAns.SetRids(rids); + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); +} + +TEST_F(JsepTrackTest, SimulcastOfferer) { + Init(SdpMediaSection::kVideo); + std::vector<std::string> rids; + rids.push_back("foo"); + rids.push_back("bar"); + mSendOff.SetRids(rids); + CreateOffer(); + CreateAnswer(); + // Add simulcast/rid to answer + mRecvAns.AddToMsection(rids, sdp::kRecv, mSsrcGenerator, false, &GetAnswer()); + Negotiate(); + ASSERT_TRUE(mSendOff.GetNegotiatedDetails()); + ASSERT_EQ(2U, mSendOff.GetNegotiatedDetails()->GetEncodingCount()); + ASSERT_EQ("foo", mSendOff.GetNegotiatedDetails()->GetEncoding(0).mRid); + ASSERT_EQ("bar", mSendOff.GetNegotiatedDetails()->GetEncoding(1).mRid); + ASSERT_NE(std::string::npos, + mOffer->ToString().find("a=simulcast:send foo;bar")); + ASSERT_NE(std::string::npos, + mAnswer->ToString().find("a=simulcast:recv foo;bar")); + ASSERT_NE(std::string::npos, mOffer->ToString().find("a=rid:foo send")); + ASSERT_NE(std::string::npos, mOffer->ToString().find("a=rid:bar send")); + ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=rid:foo recv")); + ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=rid:bar recv")); +} + +TEST_F(JsepTrackTest, SimulcastOffererWithRtx) { + Init(SdpMediaSection::kVideo); + std::vector<std::string> rids; + rids.push_back("foo"); + rids.push_back("bar"); + rids.push_back("pop"); + mSendOff.SetRids(rids); + mSendOff.AddToMsection(rids, sdp::kSend, mSsrcGenerator, true, &GetOffer()); + mRecvOff.AddToMsection(rids, sdp::kSend, mSsrcGenerator, true, &GetOffer()); + CreateAnswer(); + // Add simulcast/rid to answer + mRecvAns.AddToMsection(rids, sdp::kRecv, mSsrcGenerator, false, &GetAnswer()); + Negotiate(); + + ASSERT_EQ(3U, mSendOff.GetSsrcs().size()); + const auto posSsrc0 = + mOffer->ToString().find(std::to_string(mSendOff.GetSsrcs()[0])); + const auto posSsrc1 = + mOffer->ToString().find(std::to_string(mSendOff.GetSsrcs()[1])); + const auto posSsrc2 = + mOffer->ToString().find(std::to_string(mSendOff.GetSsrcs()[2])); + ASSERT_NE(std::string::npos, posSsrc0); + ASSERT_NE(std::string::npos, posSsrc1); + ASSERT_NE(std::string::npos, posSsrc2); + ASSERT_GT(posSsrc1, posSsrc0); + ASSERT_GT(posSsrc2, posSsrc0); + ASSERT_GT(posSsrc2, posSsrc1); +} + +TEST_F(JsepTrackTest, SimulcastAnswerer) { + Init(SdpMediaSection::kVideo); + std::vector<std::string> rids; + rids.push_back("foo"); + rids.push_back("bar"); + mSendAns.SetRids(rids); + CreateOffer(); + // Add simulcast/rid to offer + mRecvOff.AddToMsection(rids, sdp::kRecv, mSsrcGenerator, false, &GetOffer()); + CreateAnswer(); + Negotiate(); + ASSERT_TRUE(mSendAns.GetNegotiatedDetails()); + ASSERT_EQ(2U, mSendAns.GetNegotiatedDetails()->GetEncodingCount()); + ASSERT_EQ("foo", mSendAns.GetNegotiatedDetails()->GetEncoding(0).mRid); + ASSERT_EQ("bar", mSendAns.GetNegotiatedDetails()->GetEncoding(1).mRid); + ASSERT_NE(std::string::npos, + mOffer->ToString().find("a=simulcast:recv foo;bar")); + ASSERT_NE(std::string::npos, + mAnswer->ToString().find("a=simulcast:send foo;bar")); + ASSERT_NE(std::string::npos, mOffer->ToString().find("a=rid:foo recv")); + ASSERT_NE(std::string::npos, mOffer->ToString().find("a=rid:bar recv")); + ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=rid:foo send")); + ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=rid:bar send")); +} + +#define VERIFY_OPUS_MAX_PLAYBACK_RATE(track, expectedRate) \ + { \ + JsepTrack& copy(track); \ + ASSERT_TRUE(copy.GetNegotiatedDetails()); \ + ASSERT_TRUE(copy.GetNegotiatedDetails()->GetEncodingCount()); \ + for (const auto& codec : \ + copy.GetNegotiatedDetails()->GetEncoding(0).GetCodecs()) { \ + if (codec->mName == "opus") { \ + JsepAudioCodecDescription& audioCodec = \ + static_cast<JsepAudioCodecDescription&>(*codec); \ + ASSERT_EQ((expectedRate), audioCodec.mMaxPlaybackRate); \ + } \ + }; \ + } + +#define VERIFY_OPUS_FORCE_MONO(track, expected) \ + { \ + JsepTrack& copy(track); \ + ASSERT_TRUE(copy.GetNegotiatedDetails()); \ + ASSERT_TRUE(copy.GetNegotiatedDetails()->GetEncodingCount()); \ + for (const auto& codec : \ + copy.GetNegotiatedDetails()->GetEncoding(0).GetCodecs()) { \ + if (codec->mName == "opus") { \ + JsepAudioCodecDescription& audioCodec = \ + static_cast<JsepAudioCodecDescription&>(*codec); \ + /* gtest has some compiler warnings when using ASSERT_EQ with \ + * booleans. */ \ + ASSERT_EQ((int)(expected), (int)audioCodec.mForceMono); \ + } \ + }; \ + } + +TEST_F(JsepTrackTest, DefaultOpusParameters) { + Init(SdpMediaSection::kAudio); + OfferAnswer(); + + VERIFY_OPUS_MAX_PLAYBACK_RATE( + mSendOff, SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate); + VERIFY_OPUS_MAX_PLAYBACK_RATE( + mSendAns, SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate); + VERIFY_OPUS_MAX_PLAYBACK_RATE(mRecvOff, 0U); + VERIFY_OPUS_FORCE_MONO(mRecvOff, false); + VERIFY_OPUS_MAX_PLAYBACK_RATE(mRecvAns, 0U); + VERIFY_OPUS_FORCE_MONO(mRecvAns, false); +} + +TEST_F(JsepTrackTest, NonDefaultOpusParameters) { + InitCodecs(); + for (auto& codec : mAnsCodecs) { + if (codec->mName == "opus") { + JsepAudioCodecDescription* audioCodec = + static_cast<JsepAudioCodecDescription*>(codec.get()); + audioCodec->mMaxPlaybackRate = 16000; + audioCodec->mForceMono = true; + } + } + InitTracks(SdpMediaSection::kAudio); + InitSdp(SdpMediaSection::kAudio); + OfferAnswer(); + + VERIFY_OPUS_MAX_PLAYBACK_RATE(mSendOff, 16000U); + VERIFY_OPUS_FORCE_MONO(mSendOff, true); + VERIFY_OPUS_MAX_PLAYBACK_RATE( + mSendAns, SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate); + VERIFY_OPUS_FORCE_MONO(mSendAns, false); + VERIFY_OPUS_MAX_PLAYBACK_RATE(mRecvOff, 0U); + VERIFY_OPUS_FORCE_MONO(mRecvOff, false); + VERIFY_OPUS_MAX_PLAYBACK_RATE(mRecvAns, 16000U); + VERIFY_OPUS_FORCE_MONO(mRecvAns, true); +} + +TEST_F(JsepTrackTest, RtcpFbWithPayloadTypeAsymmetry) { + std::vector<std::string> expectedAckFbTypes; + std::vector<std::string> expectedNackFbTypes{"", "pli"}; + std::vector<std::string> expectedCcmFbTypes{"fir"}; + std::vector<SdpRtcpFbAttributeList::Feedback> expectedOtherFbTypes{ + {"", SdpRtcpFbAttributeList::kRemb, "", ""}, + {"", SdpRtcpFbAttributeList::kTransportCC, "", ""}}; + + InitCodecs(); + + // On offerer, configure to support remb and transport-cc on video codecs + for (auto& codec : mOffCodecs) { + if (codec->Type() == SdpMediaSection::kVideo) { + auto& videoCodec = static_cast<JsepVideoCodecDescription&>(*codec); + videoCodec.EnableRemb(); + videoCodec.EnableTransportCC(); + } + } + + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + + CreateOffer(); + // We do not bother trying to bamboozle the answerer into doing asymmetric + // payload types, we just use a raw SDP. + const std::string answer = + "v=0\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "t=0 0\r\n" + "a=msid-semantic:WMS *\r\n" + "m=video 0 UDP/TLS/RTP/SAVPF 136\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=sendrecv\r\n" + "a=fmtp:136 " + "profile-level-id=42e00d;level-asymmetry-allowed=1;packetization-mode=" + "1\r\n" + "a=msid:stream_id\r\n" + "a=rtcp-fb:136 nack\r\n" + "a=rtcp-fb:136 nack pli\r\n" + "a=rtcp-fb:136 ccm fir\r\n" + "a=rtcp-fb:136 goog-remb\r\n" + "a=rtcp-fb:136 transport-cc\r\n" + "a=rtpmap:136 H264/90000\r\n" + "a=ssrc:2025549043 cname:\r\n"; + + UniquePtr<SdpParser> parser(new SipccSdpParser); + mAnswer = std::move(parser->Parse(answer)->Sdp()); + ASSERT_TRUE(mAnswer); + + mRecvOff.RecvTrackSetRemote(*mAnswer, GetAnswer()); + mRecvOff.Negotiate(GetAnswer(), GetAnswer(), GetOffer()); + mSendOff.Negotiate(GetAnswer(), GetAnswer(), GetOffer()); + + ASSERT_TRUE(mSendOff.GetNegotiatedDetails()); + ASSERT_TRUE(mRecvOff.GetNegotiatedDetails()); + + UniquePtr<JsepVideoCodecDescription> codec; + ASSERT_TRUE((codec = GetVideoCodec(mSendOff))); + ASSERT_EQ("136", codec->mDefaultPt) + << "Offerer should have seen answer asymmetry!"; + ASSERT_TRUE((codec = GetVideoCodec(mRecvOff))); + ASSERT_EQ("126", codec->mDefaultPt); + ASSERT_EQ(expectedAckFbTypes, codec->mAckFbTypes); + ASSERT_EQ(expectedNackFbTypes, codec->mNackFbTypes); + ASSERT_EQ(expectedCcmFbTypes, codec->mCcmFbTypes); + ASSERT_EQ(expectedOtherFbTypes, codec->mOtherFbTypes); +} + +TEST_F(JsepTrackTest, AudioSdpFmtpLine) { + mOffCodecs = MakeCodecs(true, true, true); + mAnsCodecs = MakeCodecs(true, true, true); + InitTracks(SdpMediaSection::kAudio); + InitSdp(SdpMediaSection::kAudio); + OfferAnswer(); + + // SanityCheck checks that the sdpFmtpLine for a local codec matches that of + // the corresponding remote codec. + UniquePtr<JsepAudioCodecDescription> codec; + EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 3, 0))); + EXPECT_EQ("opus", codec->mName); + EXPECT_EQ("maxplaybackrate=48000;stereo=1;useinbandfec=0", + codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 3, 0))); + EXPECT_EQ("opus", codec->mName); + EXPECT_EQ("maxplaybackrate=48000;stereo=1;useinbandfec=0", + codec->mSdpFmtpLine.valueOr("nothing")); + + EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 3, 1))); + EXPECT_EQ("G722", codec->mName); + EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 3, 1))); + EXPECT_EQ("G722", codec->mName); + EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing")); + + EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 3, 2))); + EXPECT_EQ("telephone-event", codec->mName); + EXPECT_EQ("0-15", codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 3, 2))); + EXPECT_EQ("telephone-event", codec->mName); + EXPECT_EQ("0-15", codec->mSdpFmtpLine.valueOr("nothing")); +} + +TEST_F(JsepTrackTest, NonDefaultAudioSdpFmtpLine) { + mOffCodecs = MakeCodecs(true, true, true); + mAnsCodecs = MakeCodecs(true, true, true); + + for (auto& codec : mOffCodecs) { + if (codec->mName == "opus") { + auto* audio = static_cast<JsepAudioCodecDescription*>(codec.get()); + audio->mForceMono = true; + audio->mMaxPlaybackRate = 32000; + } + } + + for (auto& codec : mAnsCodecs) { + if (codec->mName == "opus") { + auto* audio = static_cast<JsepAudioCodecDescription*>(codec.get()); + audio->mFECEnabled = true; + audio->mCbrEnabled = true; + audio->mDTXEnabled = true; + audio->mFrameSizeMs = 10; + audio->mMinFrameSizeMs = 5; + audio->mMaxFrameSizeMs = 20; + } + } + + InitTracks(SdpMediaSection::kAudio); + InitSdp(SdpMediaSection::kAudio); + + { + // telephone-event doesn't store any params in JsepAudioCodecDescription. + // Set them directly in the offer sdp instead. + auto params = MakeUnique<SdpFmtpAttributeList::TelephoneEventParameters>(); + params->dtmfTones = "2-9"; + GetOffer().SetFmtp({"101", *params}); + } + + { + // telephone-event doesn't store any params in JsepAudioCodecDescription. + // Set them directly in the answer sdp instead. + auto params = MakeUnique<SdpFmtpAttributeList::TelephoneEventParameters>(); + params->dtmfTones = "0-3,10"; + GetAnswer().SetFmtp({"101", *params}); + } + + OfferAnswer(); + + // SanityCheck checks that the sdpFmtpLine for a local codec matches that of + // the corresponding remote codec. + UniquePtr<JsepAudioCodecDescription> codec; + EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 3, 0))); + EXPECT_EQ("opus", codec->mName); + EXPECT_EQ( + "maxplaybackrate=48000;stereo=1;useinbandfec=1;usedtx=1;ptime=10;" + "minptime=5;maxptime=20;cbr=1", + codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 3, 0))); + EXPECT_EQ("opus", codec->mName); + EXPECT_EQ("maxplaybackrate=32000;stereo=0;useinbandfec=0", + codec->mSdpFmtpLine.valueOr("nothing")); + + EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 3, 1))); + EXPECT_EQ("G722", codec->mName); + EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 3, 1))); + EXPECT_EQ("G722", codec->mName); + EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing")); + + EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 3, 2))); + EXPECT_EQ("telephone-event", codec->mName); + EXPECT_EQ("0-3,10", codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 3, 2))); + EXPECT_EQ("telephone-event", codec->mName); + EXPECT_EQ("2-9", codec->mSdpFmtpLine.valueOr("nothing")); +} + +TEST_F(JsepTrackTest, VideoSdpFmtpLine) { + mOffCodecs = MakeCodecs(true, true, true); + mAnsCodecs = MakeCodecs(true, true, true); + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + // SanityCheck checks that the sdpFmtpLine for a local codec matches that of + // the corresponding remote codec. + UniquePtr<JsepVideoCodecDescription> codec; + EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 0))); + EXPECT_EQ("red", codec->mName); + EXPECT_EQ("120/126/123", codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 0))); + EXPECT_EQ("red", codec->mName); + EXPECT_EQ("120/126/123", codec->mSdpFmtpLine.valueOr("nothing")); + + EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 1))); + EXPECT_EQ("VP8", codec->mName); + EXPECT_EQ("max-fs=12288;max-fr=60", codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 1))); + EXPECT_EQ("VP8", codec->mName); + EXPECT_EQ("max-fs=12288;max-fr=60", codec->mSdpFmtpLine.valueOr("nothing")); + + EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 2))); + EXPECT_EQ("H264", codec->mName); + EXPECT_EQ( + "profile-level-id=42e00d;level-asymmetry-allowed=1;packetization-mode=1", + codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 2))); + EXPECT_EQ("H264", codec->mName); + EXPECT_EQ( + "profile-level-id=42e00d;level-asymmetry-allowed=1;packetization-mode=1", + codec->mSdpFmtpLine.valueOr("nothing")); + + EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 3))); + EXPECT_EQ("ulpfec", codec->mName); + EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 3))); + EXPECT_EQ("ulpfec", codec->mName); + EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing")); +} + +TEST_F(JsepTrackTest, NonDefaultVideoSdpFmtpLine) { + mOffCodecs = MakeCodecs(true, true, true); + mAnsCodecs = MakeCodecs(true, true, true); + + for (auto& codec : mOffCodecs) { + if (codec->mName == "VP8" || codec->mName == "H264") { + auto* video = static_cast<JsepVideoCodecDescription*>(codec.get()); + video->mConstraints.maxFs = 1200; + if (codec->mName == "VP8") { + video->mConstraints.maxFps = Some(15); + } else { + video->mConstraints.maxDpb = 6400; + video->mConstraints.maxBr = 1000; + JsepVideoCodecDescription::SetSaneH264Level(0x1F0, + &video->mProfileLevelId); + } + } + } + + for (auto& codec : mAnsCodecs) { + if (codec->mName == "VP8" || codec->mName == "H264") { + auto* video = static_cast<JsepVideoCodecDescription*>(codec.get()); + video->mConstraints.maxFs = 32400; + if (codec->mName == "VP8") { + video->mConstraints.maxFps = Some(60); + } else { + video->mConstraints.maxMbps = 1944000; + video->mConstraints.maxCpb = 800000; + video->mConstraints.maxDpb = 128000; + JsepVideoCodecDescription::SetSaneH264Level(0xAB, + &video->mProfileLevelId); + video->mPacketizationMode = 1; + } + } + } + + InitTracks(SdpMediaSection::kVideo); + InitSdp(SdpMediaSection::kVideo); + OfferAnswer(); + + // SanityCheck checks that the sdpFmtpLine for a local codec matches that of + // the corresponding remote codec. + UniquePtr<JsepVideoCodecDescription> codec; + EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 0))); + EXPECT_EQ("red", codec->mName); + EXPECT_EQ("120/126/123", codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 0))); + EXPECT_EQ("red", codec->mName); + EXPECT_EQ("120/126/123", codec->mSdpFmtpLine.valueOr("nothing")); + + EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 1))); + EXPECT_EQ("VP8", codec->mName); + EXPECT_EQ("max-fs=32400;max-fr=60", codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 1))); + EXPECT_EQ("VP8", codec->mName); + EXPECT_EQ("max-fs=1200;max-fr=15", codec->mSdpFmtpLine.valueOr("nothing")); + + EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 2))); + EXPECT_EQ("H264", codec->mName); + EXPECT_EQ( + "profile-level-id=42f00b;level-asymmetry-allowed=1;packetization-mode=1;" + "max-mbps=1944000;max-fs=32400;max-cpb=800000;max-dpb=128000", + codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 2))); + EXPECT_EQ("H264", codec->mName); + EXPECT_EQ( + "profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1;" + "max-fs=1200;max-dpb=6400;max-br=1000", + codec->mSdpFmtpLine.valueOr("nothing")); + + EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 4, 3))); + EXPECT_EQ("ulpfec", codec->mName); + EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing")); + EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 4, 3))); + EXPECT_EQ("ulpfec", codec->mName); + EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing")); +} + +} // namespace mozilla |