Bug 1192390 - Part 2: Simulcast and RID negotiation. r=mt

--HG--
extra : transplant_source : %D1%CAj%05%C7%7B%92%D9%CDV%24j%FF%CB%24B%D4%03%92%5E
This commit is contained in:
Byron Campen [:bwc] 2015-11-02 09:32:16 -06:00
parent dac8417d25
commit bc30932b74
11 changed files with 799 additions and 49 deletions

View File

@ -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<JsConstraints> constraints;
std::vector<SdpRidAttributeList::Rid> rids;
GetRids(offer, sdp::kRecv, &rids);
NegotiateRids(rids, &constraints);
AddToMsection(constraints, sdp::kSend, answer);
}
}
void
@ -143,59 +154,143 @@ JsepTrack::AddToMsection(const std::vector<JsepCodecDescription*>& codecs,
}
}
// Updates the |id| values in |constraintsList| with the rid values in |rids|,
// where necessary.
void
JsepTrack::GetRids(const SdpMediaSection& msection,
std::vector<SdpRidAttributeList::Rid>* rids) const
JsepTrack::NegotiateRids(const std::vector<SdpRidAttributeList::Rid>& rids,
std::vector<JsConstraints>* 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<JsConstraints>& constraintsList,
sdp::Direction direction,
SdpMediaSection* msection)
{
UniquePtr<SdpSimulcastAttribute> simulcast(new SdpSimulcastAttribute);
UniquePtr<SdpRidAttributeList> 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<SdpRidAttributeList::Rid>& rids)
JsepTrack::GetRids(const SdpMediaSection& msection,
sdp::Direction direction,
std::vector<SdpRidAttributeList::Rid>* 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<JsConstraints>& 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<JsepCodecDescription*>& negotiatedCodecs,
JsepTrackNegotiatedDetails* negotiatedDetails)
{
std::vector<SdpRidAttributeList::Rid> answerRids;
GetRids(answer, &answerRids);
UpdateRidsFromAnswer(answerRids);
if (answerRids.empty()) {
std::vector<SdpRidAttributeList::Rid> 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<JsepTrackNegotiatedDetails> negotiatedDetails =
MakeUnique<JsepTrackNegotiatedDetails>();
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) {

View File

@ -174,6 +174,21 @@ public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JsepTrack);
struct JsConstraints
{
std::string rid;
EncodingConstraints constraints;
};
void SetJsConstraints(const std::vector<JsConstraints>& constraintsList)
{
mJsEncodeConstraints = constraintsList;
}
static void AddToMsection(const std::vector<JsConstraints>& constraintsList,
sdp::Direction direction,
SdpMediaSection* msection);
protected:
virtual ~JsepTrack() {}
@ -189,10 +204,10 @@ private:
void AddToMsection(const std::vector<JsepCodecDescription*>& codecs,
SdpMediaSection* msection) const;
void GetRids(const SdpMediaSection& msection,
sdp::Direction direction,
std::vector<SdpRidAttributeList::Rid>* rids) const;
void UpdateRidsFromAnswer(const std::vector<SdpRidAttributeList::Rid>& rids);
void CreateEncodings(
const SdpMediaSection& answer,
const SdpMediaSection& remote,
const std::vector<JsepCodecDescription*>& negotiatedCodecs,
JsepTrackNegotiatedDetails* details);
@ -207,17 +222,18 @@ private:
const SdpMediaSection* answer = nullptr,
std::map<std::string, std::string>* formatChanges = nullptr) const;
JsConstraints* FindConstraints(
const std::string& rid,
std::vector<JsConstraints>& constraintsList) const;
void NegotiateRids(const std::vector<SdpRidAttributeList::Rid>& rids,
std::vector<JsConstraints>* constraints) const;
const mozilla::SdpMediaSection::MediaType mType;
std::string mStreamId;
std::string mTrackId;
std::string mCNAME;
const sdp::Direction mDirection;
PtrVector<JsepCodecDescription> 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.

View File

@ -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<std::pair<size_t, std::string> >& aErrors,

View File

@ -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

View File

@ -169,6 +169,7 @@ public:
void SetSsrcs(const std::vector<uint32_t>& 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;

View File

@ -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)

View File

@ -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;

View File

@ -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<JsepCodecDescription*>
MakeCodecs() const
{
std::vector<JsepCodecDescription*> 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<JsepTrack>& 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<JsepTrack>& send,
const RefPtr<JsepTrack>& 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<JsepTrack> mSendOff;
RefPtr<JsepTrack> mRecvOff;
RefPtr<JsepTrack> mSendAns;
RefPtr<JsepTrack> mRecvAns;
PtrVector<JsepCodecDescription> mOffCodecs;
PtrVector<JsepCodecDescription> mAnsCodecs;
UniquePtr<Sdp> mOffer;
UniquePtr<Sdp> 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<JsepTrack::JsConstraints> 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<JsepTrack::JsConstraints> 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<JsepTrack::JsConstraints> 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<JsepTrack::JsConstraints> 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();
}

View File

@ -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',

View File

@ -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

View File

@ -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]