diff --git a/media/webrtc/signaling/src/jsep/JsepTrack.cpp b/media/webrtc/signaling/src/jsep/JsepTrack.cpp index 36844ad904d2..e7b8111eb79f 100644 --- a/media/webrtc/signaling/src/jsep/JsepTrack.cpp +++ b/media/webrtc/signaling/src/jsep/JsepTrack.cpp @@ -103,6 +103,9 @@ void JsepTrack::AddToOffer(SdpMediaSection* offer) const { AddToMsection(mPrototypeCodecs.values, offer); + if (mDirection == sdp::kSend) { + AddToMsection(mJsEncodeConstraints, sdp::kSend, offer); + } } void @@ -119,6 +122,14 @@ JsepTrack::AddToAnswer(const SdpMediaSection& offer, } AddToMsection(codecs.values, answer); + + if (mDirection == sdp::kSend) { + std::vector constraints; + std::vector rids; + GetRids(offer, sdp::kRecv, &rids); + NegotiateRids(rids, &constraints); + AddToMsection(constraints, sdp::kSend, answer); + } } void @@ -143,59 +154,143 @@ JsepTrack::AddToMsection(const std::vector& codecs, } } +// Updates the |id| values in |constraintsList| with the rid values in |rids|, +// where necessary. void -JsepTrack::GetRids(const SdpMediaSection& msection, - std::vector* rids) const +JsepTrack::NegotiateRids(const std::vector& rids, + std::vector* constraintsList) const { - // TODO(bug 1192390): Get list of rids from |answer|; first rid from each - // simulcast version. + for (const SdpRidAttributeList::Rid& rid : rids) { + if (!FindConstraints(rid.id, *constraintsList)) { + // Pair up the first JsConstraints with an empty id, if it exists. + JsConstraints* constraints = FindConstraints("", *constraintsList); + if (constraints) { + constraints->rid = rid.id; + } + } + } +} + +/* static */ +void +JsepTrack::AddToMsection(const std::vector& constraintsList, + sdp::Direction direction, + SdpMediaSection* msection) +{ + UniquePtr simulcast(new SdpSimulcastAttribute); + UniquePtr rids(new SdpRidAttributeList); + for (const JsConstraints& constraints : constraintsList) { + if (!constraints.rid.empty()) { + SdpRidAttributeList::Rid rid; + rid.id = constraints.rid; + rid.direction = direction; + rids->mRids.push_back(rid); + + SdpSimulcastAttribute::Version version; + version.choices.push_back(constraints.rid); + if (direction == sdp::kSend) { + simulcast->sendVersions.push_back(version); + } else { + simulcast->recvVersions.push_back(version); + } + } + } + + if (!rids->mRids.empty()) { + msection->GetAttributeList().SetAttribute(simulcast.release()); + msection->GetAttributeList().SetAttribute(rids.release()); + } } void -JsepTrack::UpdateRidsFromAnswer( - const std::vector& rids) +JsepTrack::GetRids(const SdpMediaSection& msection, + sdp::Direction direction, + std::vector* rids) const { - // TODO(bug 1192390): For each rid, try to pair it with something in - // mEncodingParameters (either by matching rid, or just matching by index). - // Once these are paired up, update the ids in mEncodingParameters to match. + rids->clear(); + if (!msection.GetAttributeList().HasAttribute( + SdpAttribute::kSimulcastAttribute)) { + return; + } + + const SdpSimulcastAttribute& simulcast( + msection.GetAttributeList().GetSimulcast()); + + const SdpSimulcastAttribute::Versions* versions = nullptr; + switch (direction) { + case sdp::kSend: + versions = &simulcast.sendVersions; + break; + case sdp::kRecv: + versions = &simulcast.recvVersions; + break; + } + + if (!versions->IsSet()) { + return; + } + + if (versions->type != SdpSimulcastAttribute::Versions::kRid) { + // No support for PT-based simulcast, yet. + return; + } + + for (const SdpSimulcastAttribute::Version& version : *versions) { + if (!version.choices.empty()) { + // We validate that rids are present (and sane) elsewhere. + rids->push_back(*msection.FindRid(version.choices[0])); + } + } +} + +JsepTrack::JsConstraints* +JsepTrack::FindConstraints(const std::string& id, + std::vector& constraintsList) const +{ + for (JsConstraints& constraints : constraintsList) { + if (constraints.rid == id) { + return &constraints; + } + } + return nullptr; } void JsepTrack::CreateEncodings( - const SdpMediaSection& answer, + const SdpMediaSection& remote, const std::vector& negotiatedCodecs, JsepTrackNegotiatedDetails* negotiatedDetails) { - std::vector answerRids; - GetRids(answer, &answerRids); - UpdateRidsFromAnswer(answerRids); - if (answerRids.empty()) { + std::vector rids; + GetRids(remote, sdp::kRecv, &rids); // Get rids we will send + NegotiateRids(rids, &mJsEncodeConstraints); + if (rids.empty()) { // Add dummy value with an empty id to make sure we get a single unicast // stream. - answerRids.push_back(SdpRidAttributeList::Rid()); + rids.push_back(SdpRidAttributeList::Rid()); } - // For each rid in the answer, make sure we have an encoding, and configure + // For each rid in the remote, make sure we have an encoding, and configure // that encoding appropriately. - for (size_t i = 0; i < answerRids.size(); ++i) { - if (i >= negotiatedDetails->mEncodings.values.size()) { + for (size_t i = 0; i < rids.size(); ++i) { + if (i == negotiatedDetails->mEncodings.values.size()) { negotiatedDetails->mEncodings.values.push_back(new JsepTrackEncoding); } JsepTrackEncoding* encoding = negotiatedDetails->mEncodings.values[i]; for (const JsepCodecDescription* codec : negotiatedCodecs) { - if (answerRids[i].HasFormat(codec->mDefaultPt)) { + if (rids[i].HasFormat(codec->mDefaultPt)) { encoding->AddCodec(*codec); } } - encoding->mRid = answerRids[i].id; + encoding->mRid = rids[i].id; // If we end up supporting params for rid, we would handle that here. // Incorporate the corresponding JS encoding constraints, if they exist for (const JsConstraints& jsConstraints : mJsEncodeConstraints) { - if (jsConstraints.id == answerRids[i].id) { + if (jsConstraints.rid == rids[i].id) { encoding->mConstraints = jsConstraints.constraints; } } @@ -304,7 +399,7 @@ JsepTrack::Negotiate(const SdpMediaSection& answer, UniquePtr negotiatedDetails = MakeUnique(); - CreateEncodings(answer, negotiatedCodecs.values, negotiatedDetails.get()); + CreateEncodings(remote, negotiatedCodecs.values, negotiatedDetails.get()); if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) { for (auto& extmapAttr : answer.GetAttributeList().GetExtmap().mExtmaps) { diff --git a/media/webrtc/signaling/src/jsep/JsepTrack.h b/media/webrtc/signaling/src/jsep/JsepTrack.h index ce122ff587e7..f08db5435c54 100644 --- a/media/webrtc/signaling/src/jsep/JsepTrack.h +++ b/media/webrtc/signaling/src/jsep/JsepTrack.h @@ -174,6 +174,21 @@ public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JsepTrack); + struct JsConstraints + { + std::string rid; + EncodingConstraints constraints; + }; + + void SetJsConstraints(const std::vector& constraintsList) + { + mJsEncodeConstraints = constraintsList; + } + + static void AddToMsection(const std::vector& constraintsList, + sdp::Direction direction, + SdpMediaSection* msection); + protected: virtual ~JsepTrack() {} @@ -189,10 +204,10 @@ private: void AddToMsection(const std::vector& codecs, SdpMediaSection* msection) const; void GetRids(const SdpMediaSection& msection, + sdp::Direction direction, std::vector* rids) const; - void UpdateRidsFromAnswer(const std::vector& rids); void CreateEncodings( - const SdpMediaSection& answer, + const SdpMediaSection& remote, const std::vector& negotiatedCodecs, JsepTrackNegotiatedDetails* details); @@ -207,17 +222,18 @@ private: const SdpMediaSection* answer = nullptr, std::map* formatChanges = nullptr) const; + JsConstraints* FindConstraints( + const std::string& rid, + std::vector& constraintsList) const; + void NegotiateRids(const std::vector& rids, + std::vector* constraints) const; + const mozilla::SdpMediaSection::MediaType mType; std::string mStreamId; std::string mTrackId; std::string mCNAME; const sdp::Direction mDirection; PtrVector mPrototypeCodecs; - struct JsConstraints - { - std::string id; - EncodingConstraints constraints; - }; // Holds encoding params/constraints from JS. Simulcast happens when there are // multiple of these. If there are none, we assume unconstrained unicast with // no rid. diff --git a/media/webrtc/signaling/src/sdp/SdpHelper.h b/media/webrtc/signaling/src/sdp/SdpHelper.h index e6692ccb5eb2..421b2f497745 100644 --- a/media/webrtc/signaling/src/sdp/SdpHelper.h +++ b/media/webrtc/signaling/src/sdp/SdpHelper.h @@ -104,7 +104,7 @@ class SdpHelper { nsresult CopyStickyParams(const SdpMediaSection& source, SdpMediaSection* dest); bool HasRtcp(SdpMediaSection::Protocol proto) const; - SdpMediaSection::Protocol GetProtocolForMediaType( + static SdpMediaSection::Protocol GetProtocolForMediaType( SdpMediaSection::MediaType type); void appendSdpParseErrors( const std::vector >& aErrors, diff --git a/media/webrtc/signaling/src/sdp/SdpMediaSection.cpp b/media/webrtc/signaling/src/sdp/SdpMediaSection.cpp index 9d792c975419..14c04f477b0d 100644 --- a/media/webrtc/signaling/src/sdp/SdpMediaSection.cpp +++ b/media/webrtc/signaling/src/sdp/SdpMediaSection.cpp @@ -156,5 +156,21 @@ SdpMediaSection::AddMsid(const std::string& id, const std::string& appdata) GetAttributeList().SetAttribute(msids.release()); } +const SdpRidAttributeList::Rid* +SdpMediaSection::FindRid(const std::string& id) const +{ + if (!GetAttributeList().HasAttribute(SdpAttribute::kRidAttribute)) { + return nullptr; + } + + for (const auto& rid : GetAttributeList().GetRid().mRids) { + if (rid.id == id) { + return &rid; + } + } + + return nullptr; +} + } // namespace mozilla diff --git a/media/webrtc/signaling/src/sdp/SdpMediaSection.h b/media/webrtc/signaling/src/sdp/SdpMediaSection.h index 5353faeb63fd..ed70bd072daa 100644 --- a/media/webrtc/signaling/src/sdp/SdpMediaSection.h +++ b/media/webrtc/signaling/src/sdp/SdpMediaSection.h @@ -169,6 +169,7 @@ public: void SetSsrcs(const std::vector& ssrcs, const std::string& cname); void AddMsid(const std::string& id, const std::string& appdata); + const SdpRidAttributeList::Rid* FindRid(const std::string& id) const; private: size_t mLevel; diff --git a/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp b/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp index 2d6f1a1d2b35..33c2a9e0d55e 100644 --- a/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp +++ b/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp @@ -136,6 +136,10 @@ SipccSdpMediaSection::Load(sdp_t* sdp, uint16_t level, return false; } + if (!ValidateSimulcast(sdp, level, errorHolder)) { + return false; + } + if (!mBandwidths.Load(sdp, level, errorHolder)) { return false; } @@ -223,6 +227,68 @@ SipccSdpMediaSection::LoadFormats(sdp_t* sdp, return true; } +bool +SipccSdpMediaSection::ValidateSimulcast(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) const +{ + if (!GetAttributeList().HasAttribute(SdpAttribute::kSimulcastAttribute)) { + return true; + } + + const SdpSimulcastAttribute& simulcast(GetAttributeList().GetSimulcast()); + if (!ValidateSimulcastVersions( + sdp, level, simulcast.sendVersions, sdp::kSend, errorHolder)) { + return false; + } + if (!ValidateSimulcastVersions( + sdp, level, simulcast.recvVersions, sdp::kRecv, errorHolder)) { + return false; + } + return true; +} + +bool +SipccSdpMediaSection::ValidateSimulcastVersions( + sdp_t* sdp, + uint16_t level, + const SdpSimulcastAttribute::Versions& versions, + sdp::Direction direction, + SdpErrorHolder& errorHolder) const +{ + if (versions.IsSet() && !(direction & GetDirectionAttribute().mValue)) { + errorHolder.AddParseError(sdp_get_media_line_number(sdp, level), + "simulcast attribute has a direction that is " + "inconsistent with the direction of this media " + "section."); + return false; + } + + for (const SdpSimulcastAttribute::Version& version : versions) { + for (const std::string& id : version.choices) { + if (versions.type == SdpSimulcastAttribute::Versions::kRid) { + const SdpRidAttributeList::Rid* ridAttr = FindRid(id); + if (!ridAttr || (ridAttr->direction != direction)) { + std::ostringstream os; + os << "No rid attribute for \'" << id << "\'"; + errorHolder.AddParseError(sdp_get_media_line_number(sdp, level), + os.str()); + return false; + } + } else if (versions.type == SdpSimulcastAttribute::Versions::kPt) { + if (std::find(mFormats.begin(), mFormats.end(), id) + == mFormats.end()) { + std::ostringstream os; + os << "No pt for \'" << id << "\'"; + errorHolder.AddParseError(sdp_get_media_line_number(sdp, level), + os.str()); + return false; + } + } + } + } + return true; +} + bool SipccSdpMediaSection::LoadConnection(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder) diff --git a/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h b/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h index db34efe0c5e4..6d2dafa7b752 100644 --- a/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h +++ b/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h @@ -76,6 +76,14 @@ private: bool LoadConnection(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); bool LoadProtocol(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); bool LoadFormats(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + bool ValidateSimulcast(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) const; + bool ValidateSimulcastVersions( + sdp_t* sdp, + uint16_t level, + const SdpSimulcastAttribute::Versions& versions, + sdp::Direction direction, + SdpErrorHolder& errorHolder) const; // the following values are cached on first get MediaType mMediaType; diff --git a/media/webrtc/signaling/test/jsep_track_unittest.cpp b/media/webrtc/signaling/test/jsep_track_unittest.cpp new file mode 100644 index 000000000000..c2c36857f053 --- /dev/null +++ b/media/webrtc/signaling/test/jsep_track_unittest.cpp @@ -0,0 +1,475 @@ +/* -*- 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/. */ + +#define GTEST_HAS_RTTI 0 +#include "gtest/gtest.h" +#include "gtest_utils.h" + +// Magic linker includes :( +#include "FakeMediaStreams.h" +#include "FakeMediaStreamsImpl.h" + +#include "signaling/src/jsep/JsepTrack.h" +#include "signaling/src/sdp/SipccSdp.h" +#include "signaling/src/sdp/SdpHelper.h" + +#include "mtransport_test_utils.h" + +namespace mozilla { + +class JsepTrackTest : public ::testing::Test +{ + public: + JsepTrackTest() {} + + std::vector + MakeCodecs() const + { + std::vector results; + results.push_back( + new JsepAudioCodecDescription("1", "opus", 48000, 2, 960, 40000)); + results.push_back( + new JsepAudioCodecDescription("9", "G722", 8000, 1, 320, 64000)); + + JsepVideoCodecDescription* vp8 = + new JsepVideoCodecDescription("120", "VP8", 90000); + vp8->mConstraints.maxFs = 12288; + vp8->mConstraints.maxFps = 60; + results.push_back(vp8); + + JsepVideoCodecDescription* h264 = + new JsepVideoCodecDescription("126", "H264", 90000); + h264->mPacketizationMode = 1; + h264->mProfileLevelId = 0x42E00D; + results.push_back(h264); + + results.push_back( + new JsepApplicationCodecDescription( + "5000", + "webrtc-datachannel", + 16 + )); + + return results; + } + + void Init(SdpMediaSection::MediaType type) { + InitCodecs(); + InitTracks(type); + InitSdp(type); + } + + void InitCodecs() { + mOffCodecs.values = MakeCodecs(); + mAnsCodecs.values = MakeCodecs(); + } + + void InitTracks(SdpMediaSection::MediaType type) + { + mSendOff = new JsepTrack(type, "stream_id", "track_id", sdp::kSend); + mRecvOff = new JsepTrack(type, "stream_id", "track_id", sdp::kRecv); + mSendOff->PopulateCodecs(mOffCodecs.values); + mRecvOff->PopulateCodecs(mOffCodecs.values); + + mSendAns = new JsepTrack(type, "stream_id", "track_id", sdp::kSend); + mRecvAns = new JsepTrack(type, "stream_id", "track_id", sdp::kRecv); + mSendAns->PopulateCodecs(mAnsCodecs.values); + mRecvAns->PopulateCodecs(mAnsCodecs.values); + } + + void InitSdp(SdpMediaSection::MediaType type) + { + mOffer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, ""))); + mOffer->AddMediaSection( + type, + SdpDirectionAttribute::kInactive, + 0, + SdpHelper::GetProtocolForMediaType(type), + sdp::kIPv4, + "0.0.0.0"); + mAnswer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, ""))); + mAnswer->AddMediaSection( + type, + SdpDirectionAttribute::kInactive, + 0, + SdpHelper::GetProtocolForMediaType(type), + sdp::kIPv4, + "0.0.0.0"); + } + + SdpMediaSection& GetOffer() + { + return mOffer->GetMediaSection(0); + } + + SdpMediaSection& GetAnswer() + { + return mAnswer->GetMediaSection(0); + } + + void CreateOffer() + { + if (mSendOff) { + mSendOff->AddToOffer(&GetOffer()); + } + + if (mRecvOff) { + mRecvOff->AddToOffer(&GetOffer()); + } + } + + void CreateAnswer() + { + if (mSendAns && GetOffer().IsReceiving()) { + mSendAns->AddToAnswer(GetOffer(), &GetAnswer()); + } + + if (mRecvAns && GetOffer().IsSending()) { + mRecvAns->AddToAnswer(GetOffer(), &GetAnswer()); + } + } + + void Negotiate() + { + std::cerr << "Offer SDP: " << std::endl; + mOffer->Serialize(std::cerr); + + std::cerr << "Answer SDP: " << std::endl; + mAnswer->Serialize(std::cerr); + + if (mSendAns && GetAnswer().IsSending()) { + mSendAns->Negotiate(GetAnswer(), GetOffer()); + } + + if (mRecvAns && GetAnswer().IsReceiving()) { + mRecvAns->Negotiate(GetAnswer(), GetOffer()); + } + + if (mSendOff && GetAnswer().IsReceiving()) { + mSendOff->Negotiate(GetAnswer(), GetAnswer()); + } + + if (mRecvOff && GetAnswer().IsSending()) { + mRecvOff->Negotiate(GetAnswer(), GetAnswer()); + } + } + + void OfferAnswer() + { + CreateOffer(); + CreateAnswer(); + Negotiate(); + SanityCheck(); + } + + static size_t EncodingCount(const RefPtr& track) + { + return track->GetNegotiatedDetails()->GetEncodingCount(); + } + + // 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 RefPtr& send, + const RefPtr& recv) + { + if (expected) { + ASSERT_TRUE(!!send); + ASSERT_TRUE(send->GetNegotiatedDetails()); + ASSERT_TRUE(!!recv); + ASSERT_TRUE(recv->GetNegotiatedDetails()); + } + + if (send && send->GetNegotiatedDetails()) { + ASSERT_EQ(expected, send->GetNegotiatedDetails()->GetEncodingCount()); + } + + if (recv && 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); + } + + void SanityCheckCodecs(const JsepCodecDescription& a, + const JsepCodecDescription& b) const + { + ASSERT_EQ(a.mType, b.mType); + ASSERT_EQ(a.mDefaultPt, b.mDefaultPt); + ASSERT_EQ(a.mName, b.mName); + ASSERT_EQ(a.mClock, b.mClock); + ASSERT_EQ(a.mChannels, b.mChannels); + ASSERT_NE(a.mDirection, b.mDirection); + // These constraints are for fmtp and rid, which _are_ signaled + ASSERT_EQ(a.mConstraints, b.mConstraints); + } + + 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.GetStreamId(), b.GetStreamId()); + ASSERT_EQ(a.GetTrackId(), b.GetTrackId()); + 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 + { + if (mSendOff && mRecvAns) { + SanityCheckTracks(*mSendOff, *mRecvAns); + } + if (mRecvOff && mSendAns) { + SanityCheckTracks(*mRecvOff, *mSendAns); + } + } + + protected: + RefPtr mSendOff; + RefPtr mRecvOff; + RefPtr mSendAns; + RefPtr mRecvAns; + PtrVector mOffCodecs; + PtrVector mAnsCodecs; + UniquePtr mOffer; + UniquePtr mAnswer; +}; + +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); +} + +TEST_F(JsepTrackTest, AudioOffSendonlyAnsRecvonly) +{ + Init(SdpMediaSection::kAudio); + mRecvOff = nullptr; + mSendAns = nullptr; + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(0); +} + +TEST_F(JsepTrackTest, VideoOffSendonlyAnsRecvonly) +{ + Init(SdpMediaSection::kVideo); + mRecvOff = nullptr; + mSendAns = nullptr; + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(0); +} + +TEST_F(JsepTrackTest, AudioOffSendrecvAnsRecvonly) +{ + Init(SdpMediaSection::kAudio); + mSendAns = nullptr; + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(0); +} + +TEST_F(JsepTrackTest, VideoOffSendrecvAnsRecvonly) +{ + Init(SdpMediaSection::kVideo); + mSendAns = nullptr; + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(0); +} + +TEST_F(JsepTrackTest, AudioOffRecvonlyAnsSendrecv) +{ + Init(SdpMediaSection::kAudio); + mSendOff = nullptr; + OfferAnswer(); + CheckOffEncodingCount(0); + CheckAnsEncodingCount(1); +} + +TEST_F(JsepTrackTest, VideoOffRecvonlyAnsSendrecv) +{ + Init(SdpMediaSection::kVideo); + mSendOff = nullptr; + OfferAnswer(); + CheckOffEncodingCount(0); + CheckAnsEncodingCount(1); +} + +TEST_F(JsepTrackTest, AudioOffSendrecvAnsSendonly) +{ + Init(SdpMediaSection::kAudio); + mRecvAns = nullptr; + OfferAnswer(); + CheckOffEncodingCount(0); + CheckAnsEncodingCount(1); +} + +TEST_F(JsepTrackTest, VideoOffSendrecvAnsSendonly) +{ + Init(SdpMediaSection::kVideo); + mRecvAns = nullptr; + OfferAnswer(); + CheckOffEncodingCount(0); + CheckAnsEncodingCount(1); +} + +static JsepTrack::JsConstraints +MakeConstraints(const std::string& rid, uint32_t maxBitrate) +{ + JsepTrack::JsConstraints constraints; + constraints.rid = rid; + constraints.constraints.maxBr = maxBitrate; + return constraints; +} + +TEST_F(JsepTrackTest, SimulcastRejected) +{ + Init(SdpMediaSection::kVideo); + std::vector constraints; + constraints.push_back(MakeConstraints("foo", 40000)); + constraints.push_back(MakeConstraints("bar", 10000)); + mSendOff->SetJsConstraints(constraints); + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); +} + +TEST_F(JsepTrackTest, SimulcastPrevented) +{ + Init(SdpMediaSection::kVideo); + std::vector constraints; + constraints.push_back(MakeConstraints("foo", 40000)); + constraints.push_back(MakeConstraints("bar", 10000)); + mSendAns->SetJsConstraints(constraints); + OfferAnswer(); + CheckOffEncodingCount(1); + CheckAnsEncodingCount(1); +} + +TEST_F(JsepTrackTest, SimulcastOfferer) +{ + Init(SdpMediaSection::kVideo); + std::vector constraints; + constraints.push_back(MakeConstraints("foo", 40000)); + constraints.push_back(MakeConstraints("bar", 10000)); + mSendOff->SetJsConstraints(constraints); + CreateOffer(); + CreateAnswer(); + // Add simulcast/rid to answer + JsepTrack::AddToMsection(constraints, sdp::kRecv, &GetAnswer()); + Negotiate(); + ASSERT_TRUE(mSendOff->GetNegotiatedDetails()); + ASSERT_EQ(2U, mSendOff->GetNegotiatedDetails()->GetEncodingCount()); + ASSERT_EQ("foo", mSendOff->GetNegotiatedDetails()->GetEncoding(0).mRid); + ASSERT_EQ(40000U, + mSendOff->GetNegotiatedDetails()->GetEncoding(0).mConstraints.maxBr); + ASSERT_EQ("bar", mSendOff->GetNegotiatedDetails()->GetEncoding(1).mRid); + ASSERT_EQ(10000U, + mSendOff->GetNegotiatedDetails()->GetEncoding(1).mConstraints.maxBr); +} + +TEST_F(JsepTrackTest, SimulcastAnswerer) +{ + Init(SdpMediaSection::kVideo); + std::vector constraints; + constraints.push_back(MakeConstraints("foo", 40000)); + constraints.push_back(MakeConstraints("bar", 10000)); + mSendAns->SetJsConstraints(constraints); + CreateOffer(); + // Add simulcast/rid to offer + JsepTrack::AddToMsection(constraints, sdp::kRecv, &GetOffer()); + CreateAnswer(); + Negotiate(); + ASSERT_TRUE(mSendAns->GetNegotiatedDetails()); + ASSERT_EQ(2U, mSendAns->GetNegotiatedDetails()->GetEncodingCount()); + ASSERT_EQ("foo", mSendAns->GetNegotiatedDetails()->GetEncoding(0).mRid); + ASSERT_EQ(40000U, + mSendAns->GetNegotiatedDetails()->GetEncoding(0).mConstraints.maxBr); + ASSERT_EQ("bar", mSendAns->GetNegotiatedDetails()->GetEncoding(1).mRid); + ASSERT_EQ(10000U, + mSendAns->GetNegotiatedDetails()->GetEncoding(1).mConstraints.maxBr); +} + +} // namespace mozilla + +int +main(int argc, char** argv) +{ + // Prevents some log spew + ScopedXPCOM xpcom("jsep_track_unittest"); + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + diff --git a/media/webrtc/signaling/test/moz.build b/media/webrtc/signaling/test/moz.build index 0f2c71980513..ee86665392e8 100644 --- a/media/webrtc/signaling/test/moz.build +++ b/media/webrtc/signaling/test/moz.build @@ -8,6 +8,7 @@ if CONFIG['OS_TARGET'] != 'WINNT' and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk' and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'uikit': GeckoCppUnitTests([ 'jsep_session_unittest', + 'jsep_track_unittest', 'mediaconduit_unittests', 'mediapipeline_unittest', 'sdp_file_parser', diff --git a/media/webrtc/signaling/test/sdp_unittests.cpp b/media/webrtc/signaling/test/sdp_unittests.cpp index 1f77e2903b28..aa27fadc57ad 100644 --- a/media/webrtc/signaling/test/sdp_unittests.cpp +++ b/media/webrtc/signaling/test/sdp_unittests.cpp @@ -1195,8 +1195,7 @@ const std::string kBasicAudioVideoOffer = "a=ssrc:1111 foo:bar" CRLF "a=imageattr:120 send * recv *" CRLF "a=imageattr:121 send [x=640,y=480] recv [x=640,y=480]" CRLF -"a=simulcast:send pt=120;121" CRLF -"a=rid:foo send" CRLF +"a=simulcast:recv pt=120;121" CRLF "a=rid:bar recv pt=96;max-width=800;max-height=600" CRLF "m=audio 9 RTP/SAVPF 0" CRLF "a=mid:third" CRLF @@ -1778,16 +1777,13 @@ TEST_P(NewSdpTest, CheckRid) const SdpRidAttributeList& rids = mSdp->GetMediaSection(1).GetAttributeList().GetRid(); - ASSERT_EQ(2U, rids.mRids.size()); - ASSERT_EQ("foo", rids.mRids[0].id); - ASSERT_EQ(sdp::kSend, rids.mRids[0].direction); - ASSERT_EQ(0U, rids.mRids[0].formats.size()); - ASSERT_EQ("bar", rids.mRids[1].id); - ASSERT_EQ(sdp::kRecv, rids.mRids[1].direction); - ASSERT_EQ(1U, rids.mRids[1].formats.size()); - ASSERT_EQ(96U, rids.mRids[1].formats[0]); - ASSERT_EQ(800U, rids.mRids[1].constraints.maxWidth); - ASSERT_EQ(600U, rids.mRids[1].constraints.maxHeight); + ASSERT_EQ(1U, rids.mRids.size()); + ASSERT_EQ("bar", rids.mRids[0].id); + ASSERT_EQ(sdp::kRecv, rids.mRids[0].direction); + ASSERT_EQ(1U, rids.mRids[0].formats.size()); + ASSERT_EQ(96U, rids.mRids[0].formats[0]); + ASSERT_EQ(800U, rids.mRids[0].constraints.maxWidth); + ASSERT_EQ(600U, rids.mRids[0].constraints.maxHeight); } TEST_P(NewSdpTest, CheckMediaLevelIceUfrag) { @@ -2099,14 +2095,14 @@ TEST_P(NewSdpTest, CheckSimulcast) const SdpSimulcastAttribute& simulcast = mSdp->GetMediaSection(1).GetAttributeList().GetSimulcast(); - ASSERT_EQ(0U, simulcast.recvVersions.size()); - ASSERT_EQ(2U, simulcast.sendVersions.size()); - ASSERT_EQ(1U, simulcast.sendVersions[0].choices.size()); - ASSERT_EQ("120", simulcast.sendVersions[0].choices[0]); - ASSERT_EQ(1U, simulcast.sendVersions[1].choices.size()); - ASSERT_EQ("121", simulcast.sendVersions[1].choices[0]); + ASSERT_EQ(2U, simulcast.recvVersions.size()); + ASSERT_EQ(0U, simulcast.sendVersions.size()); + ASSERT_EQ(1U, simulcast.recvVersions[0].choices.size()); + ASSERT_EQ("120", simulcast.recvVersions[0].choices[0]); + ASSERT_EQ(1U, simulcast.recvVersions[1].choices.size()); + ASSERT_EQ("121", simulcast.recvVersions[1].choices[0]); ASSERT_EQ(SdpSimulcastAttribute::Versions::kPt, - simulcast.sendVersions.type); + simulcast.recvVersions.type); } TEST_P(NewSdpTest, CheckSctpmap) { @@ -2613,6 +2609,81 @@ TEST_P(NewSdpTest, CheckMalformedImageattr) ASSERT_NE("", GetParseErrors()); } +TEST_P(NewSdpTest, ParseInvalidSimulcastNoSuchSendRid) { + ParseSdp("v=0" CRLF + "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF + "s=SIP Call" CRLF + "c=IN IP4 198.51.100.7" CRLF + "b=CT:5000" CRLF + "t=0 0" CRLF + "m=video 56436 RTP/SAVPF 120" CRLF + "a=rtpmap:120 VP8/90000" CRLF + "a=sendrecv" CRLF + "a=simulcast: send rid=9" CRLF, + false); + ASSERT_NE("", GetParseErrors()); +} + +TEST_P(NewSdpTest, ParseInvalidSimulcastNoSuchRecvRid) { + ParseSdp("v=0" CRLF + "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF + "s=SIP Call" CRLF + "c=IN IP4 198.51.100.7" CRLF + "b=CT:5000" CRLF + "t=0 0" CRLF + "m=video 56436 RTP/SAVPF 120" CRLF + "a=rtpmap:120 VP8/90000" CRLF + "a=sendrecv" CRLF + "a=simulcast: recv rid=9" CRLF, + false); + ASSERT_NE("", GetParseErrors()); +} + +TEST_P(NewSdpTest, ParseInvalidSimulcastNoSuchPt) { + ParseSdp("v=0" CRLF + "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF + "s=SIP Call" CRLF + "c=IN IP4 198.51.100.7" CRLF + "b=CT:5000" CRLF + "t=0 0" CRLF + "m=video 56436 RTP/SAVPF 120" CRLF + "a=rtpmap:120 VP8/90000" CRLF + "a=sendrecv" CRLF + "a=simulcast: send pt=9" CRLF, + false); + ASSERT_NE("", GetParseErrors()); +} + +TEST_P(NewSdpTest, ParseInvalidSimulcastNotSending) { + ParseSdp("v=0" CRLF + "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF + "s=SIP Call" CRLF + "c=IN IP4 198.51.100.7" CRLF + "b=CT:5000" CRLF + "t=0 0" CRLF + "m=video 56436 RTP/SAVPF 120" CRLF + "a=rtpmap:120 VP8/90000" CRLF + "a=recvonly" CRLF + "a=simulcast: send pt=120" CRLF, + false); + ASSERT_NE("", GetParseErrors()); +} + +TEST_P(NewSdpTest, ParseInvalidSimulcastNotReceiving) { + ParseSdp("v=0" CRLF + "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF + "s=SIP Call" CRLF + "c=IN IP4 198.51.100.7" CRLF + "b=CT:5000" CRLF + "t=0 0" CRLF + "m=video 56436 RTP/SAVPF 120" CRLF + "a=rtpmap:120 VP8/90000" CRLF + "a=sendonly" CRLF + "a=simulcast: recv pt=120" CRLF, + false); + ASSERT_NE("", GetParseErrors()); +} + const std::string kNoAttributes = "v=0" CRLF "o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF diff --git a/testing/cppunittest.ini b/testing/cppunittest.ini index 0ab29bf1e8c4..57b967c17de9 100644 --- a/testing/cppunittest.ini +++ b/testing/cppunittest.ini @@ -101,6 +101,7 @@ skip-if = os == 'b2g' || os == 'android' # Bug 919646 [rlogringbuffer_unittest] [runnable_utils_unittest] [sctp_unittest] +[jsep_track_unittest] [jsep_session_unittest] skip-if = os == 'android' # Bug 1147631 [sdp_unittests]