Bug 1160280: Put ssrc attributes in recvonly m-sections. r=ekr
authorByron Campen [:bwc] <docfaraday@gmail.com>
Thu, 30 Apr 2015 13:03:16 -0700
changeset 245243 8a17732972887b2f9bf77704f96684038125738a
parent 245242 95f9ab2b60e1cd4cbf24b31ab7d29535231f2909
child 245244 61511c9486a9e5e379dbe4aa6e77ed67327fed79
push id28799
push userphilringnalda@gmail.com
push dateSat, 23 May 2015 20:31:44 +0000
treeherdermozilla-central@c3c0928c3cde [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersekr
bugs1160280
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1160280: Put ssrc attributes in recvonly m-sections. r=ekr
media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
media/webrtc/signaling/src/jsep/JsepSessionImpl.h
media/webrtc/signaling/src/jsep/JsepTrack.h
media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
media/webrtc/signaling/test/jsep_session_unittest.cpp
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -106,26 +106,18 @@ JsepSessionImpl::AddTrack(const RefPtr<J
   mLastError.clear();
   MOZ_ASSERT(track->GetDirection() == JsepTrack::kJsepTrackSending);
 
   if (track->GetMediaType() != SdpMediaSection::kApplication) {
     track->SetCNAME(mCNAME);
 
     if (track->GetSsrcs().empty()) {
       uint32_t ssrc;
-      do {
-        SECStatus rv = PK11_GenerateRandom(
-            reinterpret_cast<unsigned char*>(&ssrc), sizeof(ssrc));
-        if (rv != SECSuccess) {
-          JSEP_SET_ERROR("Failed to generate SSRC, error=" << rv);
-          return NS_ERROR_FAILURE;
-        }
-      } while (mSsrcs.count(ssrc));
-
-      mSsrcs.insert(ssrc);
+      nsresult rv = CreateSsrc(&ssrc);
+      NS_ENSURE_SUCCESS(rv, rv);
       track->AddSsrc(ssrc);
     }
   }
 
   JsepSendingTrack strack;
   strack.mTrack = track;
   strack.mSetInLocalDescription = false;
 
@@ -398,17 +390,17 @@ JsepSessionImpl::BindLocalTracks(SdpMedi
 }
 
 nsresult
 JsepSessionImpl::BindTrackToMsection(
     JsepSendingTrack* track,
     SdpMediaSection* msection)
 {
   if (msection->GetMediaType() != SdpMediaSection::kApplication) {
-    AddLocalSsrcs(*track->mTrack, msection);
+    SetSsrcs(track->mTrack->GetSsrcs(), msection);
     AddLocalIds(*track->mTrack, msection);
   }
   msection->SetSending(true);
   track->mAssignedMLine = Some(msection->GetLevel());
   track->mSetInLocalDescription = false;
   return NS_OK;
 }
 
@@ -836,31 +828,34 @@ void
 JsepSessionImpl::AddMid(const std::string& mid,
                         SdpMediaSection* msection) const
 {
   msection->GetAttributeList().SetAttribute(new SdpStringAttribute(
         SdpAttribute::kMidAttribute, mid));
 }
 
 void
-JsepSessionImpl::AddLocalSsrcs(const JsepTrack& track,
-                               SdpMediaSection* msection) const
+JsepSessionImpl::SetSsrcs(const std::vector<uint32_t>& ssrcs,
+                          SdpMediaSection* msection) const
 {
-  UniquePtr<SdpSsrcAttributeList> ssrcs(new SdpSsrcAttributeList);
-  for (auto i = track.GetSsrcs().begin(); i != track.GetSsrcs().end(); ++i) {
+  if (ssrcs.empty()) {
+    msection->GetAttributeList().RemoveAttribute(SdpAttribute::kSsrcAttribute);
+    return;
+  }
+
+  UniquePtr<SdpSsrcAttributeList> ssrcAttr(new SdpSsrcAttributeList);
+  for (auto ssrc : ssrcs) {
     // When using ssrc attributes, we are required to at least have a cname.
     // (See https://tools.ietf.org/html/rfc5576#section-6.1)
     std::string cnameAttr("cname:");
-    cnameAttr += track.GetCNAME();
-    ssrcs->PushEntry(*i, cnameAttr);
+    cnameAttr += mCNAME;
+    ssrcAttr->PushEntry(ssrc, cnameAttr);
   }
 
-  if (!ssrcs->mSsrcs.empty()) {
-    msection->GetAttributeList().SetAttribute(ssrcs.release());
-  }
+  msection->GetAttributeList().SetAttribute(ssrcAttr.release());
 }
 
 void
 JsepSessionImpl::AddLocalIds(const JsepTrack& track,
                              SdpMediaSection* msection) const
 {
   if (track.GetMediaType() == SdpMediaSection::kApplication) {
     return;
@@ -1019,25 +1014,17 @@ JsepSessionImpl::CreateAnswer(const Jsep
       i->mAssignedMLine.reset();
     }
   }
 
   size_t numMsections = offer.GetMediaSectionCount();
 
   for (size_t i = 0; i < numMsections; ++i) {
     const SdpMediaSection& remoteMsection = offer.GetMediaSection(i);
-    SdpMediaSection& msection =
-        sdp->AddMediaSection(remoteMsection.GetMediaType(),
-                             SdpDirectionAttribute::kInactive,
-                             9,
-                             remoteMsection.GetProtocol(),
-                             sdp::kIPv4,
-                             "0.0.0.0");
-
-    rv = CreateAnswerMSection(options, i, remoteMsection, &msection, sdp.get());
+    rv = CreateAnswerMSection(options, i, remoteMsection, sdp.get());
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   *answer = sdp->ToString();
   mGeneratedLocalDescription = Move(sdp);
 
   return NS_OK;
 }
@@ -1096,17 +1083,17 @@ JsepSessionImpl::CreateOfferMSection(Sdp
                                      SdpDirectionAttribute::Direction dir,
                                      Sdp* sdp)
 {
   SdpMediaSection::Protocol proto = GetProtocolForMediaType(mediatype);
 
   SdpMediaSection* msection =
       &sdp->AddMediaSection(mediatype, dir, 0, proto, sdp::kIPv4, "0.0.0.0");
 
-  return EnableMsection(msection);
+  return EnableOfferMsection(msection);
 }
 
 nsresult
 JsepSessionImpl::GetFreeMsectionForSend(
     SdpMediaSection::MediaType type,
     Sdp* sdp,
     SdpMediaSection** msectionOut)
 {
@@ -1121,17 +1108,17 @@ JsepSessionImpl::GetFreeMsectionForSend(
 
     if (FindTrackByLevel(mLocalTracks, i) != mLocalTracks.end()) {
       // Not free
       continue;
     }
 
     if (MsectionIsDisabled(msection)) {
       // Was disabled; revive
-      nsresult rv = EnableMsection(&msection);
+      nsresult rv = EnableOfferMsection(&msection);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     *msectionOut = &msection;
     return NS_OK;
   }
 
   // Ok, no pre-existing m-section. Make a new one.
@@ -1143,77 +1130,104 @@ JsepSessionImpl::GetFreeMsectionForSend(
   *msectionOut = &sdp->GetMediaSection(sdp->GetMediaSectionCount() - 1);
   return NS_OK;
 }
 
 nsresult
 JsepSessionImpl::CreateAnswerMSection(const JsepAnswerOptions& options,
                                       size_t mlineIndex,
                                       const SdpMediaSection& remoteMsection,
-                                      SdpMediaSection* msection,
                                       Sdp* sdp)
 {
-  nsresult rv = CopyStickyParams(remoteMsection, msection);
+  SdpMediaSection& msection =
+      sdp->AddMediaSection(remoteMsection.GetMediaType(),
+                           SdpDirectionAttribute::kInactive,
+                           9,
+                           remoteMsection.GetProtocol(),
+                           sdp::kIPv4,
+                           "0.0.0.0");
+
+  nsresult rv = CopyStickyParams(remoteMsection, &msection);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (MsectionIsDisabled(remoteMsection)) {
-    DisableMsection(sdp, msection);
+    DisableMsection(sdp, &msection);
     return NS_OK;
   }
 
   SdpSetupAttribute::Role role;
   rv = DetermineAnswererSetupRole(remoteMsection, &role);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = AddTransportAttributes(msection, role);
+  rv = AddTransportAttributes(&msection, role);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = SetRecvonlySsrc(&msection);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Only attempt to match up local tracks if the offerer has elected to
   // receive traffic.
   if (remoteMsection.IsReceiving()) {
-    rv = BindMatchingLocalTrackForAnswer(msection);
+    rv = BindMatchingLocalTrackForAnswer(&msection);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (remoteMsection.IsSending()) {
-    msection->SetReceiving(true);
+    msection.SetReceiving(true);
   }
 
   // Now add the codecs.
   PtrVector<JsepCodecDescription> matchingCodecs(
       GetCommonCodecs(remoteMsection));
 
   for (const JsepCodecDescription* codec : matchingCodecs.values) {
     UniquePtr<JsepCodecDescription> negotiated(
         codec->MakeNegotiatedCodec(remoteMsection));
     if (negotiated) {
-      negotiated->AddToMediaSection(*msection);
+      negotiated->AddToMediaSection(msection);
       // TODO(bug 1099351): Once bug 1073475 is fixed on all supported
       // versions, we can remove this limitation.
       break;
     }
   }
 
   // Add extmap attributes.
-  AddCommonExtmaps(remoteMsection, msection);
-
-  if (!msection->IsReceiving() && !msection->IsSending()) {
-    DisableMsection(sdp, msection);
+  AddCommonExtmaps(remoteMsection, &msection);
+
+  if (!msection.IsReceiving() && !msection.IsSending()) {
+    DisableMsection(sdp, &msection);
     return NS_OK;
   }
 
-  if (msection->GetFormats().empty()) {
+  if (msection.GetFormats().empty()) {
     // Could not negotiate anything. Disable m-section.
-    DisableMsection(sdp, msection);
+    DisableMsection(sdp, &msection);
   }
 
   return NS_OK;
 }
 
 nsresult
+JsepSessionImpl::SetRecvonlySsrc(SdpMediaSection* msection)
+{
+  // If previous m-sections are disabled, we do not call this function for them
+  while (mRecvonlySsrcs.size() <= msection->GetLevel()) {
+    uint32_t ssrc;
+    nsresult rv = CreateSsrc(&ssrc);
+    NS_ENSURE_SUCCESS(rv, rv);
+    mRecvonlySsrcs.push_back(ssrc);
+  }
+
+  std::vector<uint32_t> ssrcs;
+  ssrcs.push_back(mRecvonlySsrcs[msection->GetLevel()]);
+  SetSsrcs(ssrcs, msection);
+  return NS_OK;
+}
+
+nsresult
 JsepSessionImpl::BindMatchingLocalTrackForAnswer(SdpMediaSection* msection)
 {
   auto track = FindTrackByLevel(mLocalTracks, msection->GetLevel());
 
   if (track == mLocalTracks.end()) {
     track = FindUnassignedTrackByType(mLocalTracks, msection->GetMediaType());
   }
 
@@ -1593,16 +1607,20 @@ JsepSessionImpl::MakeNegotiatedTrackPair
   MOZ_MTLOG(ML_DEBUG, "Negotiated m= line"
                           << " index=" << local.GetLevel()
                           << " type=" << local.GetMediaType()
                           << " sending=" << sending
                           << " receiving=" << receiving);
 
   trackPairOut->mLevel = local.GetLevel();
 
+  MOZ_ASSERT(mRecvonlySsrcs.size() > local.GetLevel(),
+             "Failed to set the default ssrc for an active m-section");
+  trackPairOut->mRecvonlySsrc = mRecvonlySsrcs[local.GetLevel()];
+
   if (usingBundle) {
     trackPairOut->mBundleLevel = Some(transportLevel);
   }
 
   if (sending) {
     auto sendTrack = FindTrackByLevel(mLocalTracks, local.GetLevel());
     if (sendTrack == mLocalTracks.end()) {
       JSEP_SET_ERROR("Failed to find local track for level " <<
@@ -2358,16 +2376,32 @@ JsepSessionImpl::SetupIds()
   if (!mUuidGen->Generate(&mCNAME)) {
     JSEP_SET_ERROR("Failed to generate CNAME");
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+nsresult
+JsepSessionImpl::CreateSsrc(uint32_t* ssrc)
+{
+  do {
+    SECStatus rv = PK11_GenerateRandom(
+        reinterpret_cast<unsigned char*>(ssrc), sizeof(uint32_t));
+    if (rv != SECSuccess) {
+      JSEP_SET_ERROR("Failed to generate SSRC, error=" << rv);
+      return NS_ERROR_FAILURE;
+    }
+  } while (mSsrcs.count(*ssrc));
+  mSsrcs.insert(*ssrc);
+
+  return NS_OK;
+}
+
 void
 JsepSessionImpl::SetupDefaultCodecs()
 {
   // Supported audio codecs.
   mCodecs.values.push_back(new JsepAudioCodecDescription(
       "109",
       "opus",
       48000,
@@ -2831,17 +2865,17 @@ JsepSessionImpl::DisableMsection(Sdp* sd
 
   auto* direction =
     new SdpDirectionAttribute(SdpDirectionAttribute::kInactive);
   msection->GetAttributeList().SetAttribute(direction);
   msection->SetPort(0);
 }
 
 nsresult
-JsepSessionImpl::EnableMsection(SdpMediaSection* msection)
+JsepSessionImpl::EnableOfferMsection(SdpMediaSection* msection)
 {
   // We assert here because adding rtcp-mux to a non-disabled m-section that
   // did not already have rtcp-mux can cause problems.
   MOZ_ASSERT(MsectionIsDisabled(*msection));
 
   msection->SetPort(9);
 
   // We don't do this in AddTransportAttributes because that is also used for
@@ -2850,16 +2884,19 @@ JsepSessionImpl::EnableMsection(SdpMedia
     // Set RTCP-MUX.
     msection->GetAttributeList().SetAttribute(
         new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
   }
 
   nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = SetRecvonlySsrc(msection);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   AddCodecs(msection);
 
   AddExtmap(msection);
 
   std::ostringstream osMid;
   osMid << "sdparta_" << msection->GetLevel();
   AddMid(osMid.str(), msection);
 
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
@@ -172,29 +172,31 @@ private:
     Maybe<size_t> mAssignedMLine;
   };
 
   // Non-const so it can set mLastError
   nsresult CreateGenericSDP(UniquePtr<Sdp>* sdp);
   void AddCodecs(SdpMediaSection* msection) const;
   void AddExtmap(SdpMediaSection* msection) const;
   void AddMid(const std::string& mid, SdpMediaSection* msection) const;
-  void AddLocalSsrcs(const JsepTrack& track, SdpMediaSection* msection) const;
+  void SetSsrcs(const std::vector<uint32_t>& ssrcs,
+                SdpMediaSection* msection) const;
   void AddLocalIds(const JsepTrack& track, SdpMediaSection* msection) const;
   JsepCodecDescription* FindMatchingCodec(
       const std::string& pt,
       const SdpMediaSection& msection) const;
   const std::vector<SdpExtmapAttributeList::Extmap>* GetRtpExtensions(
       SdpMediaSection::MediaType type) const;
 
   PtrVector<JsepCodecDescription> GetCommonCodecs(
       const SdpMediaSection& remoteMsection);
   void AddCommonExtmaps(const SdpMediaSection& remoteMsection,
                         SdpMediaSection* msection);
   nsresult SetupIds();
+  nsresult CreateSsrc(uint32_t* ssrc);
   void SetupDefaultCodecs();
   void SetupDefaultRtpExtensions();
   void SetState(JsepSignalingState state);
   // Non-const so it can set mLastError
   nsresult ParseSdp(const std::string& sdp, UniquePtr<Sdp>* parsedp);
   nsresult SetLocalDescriptionOffer(UniquePtr<Sdp> offer);
   nsresult SetLocalDescriptionAnswer(JsepSdpType type, UniquePtr<Sdp> answer);
   nsresult SetRemoteDescriptionOffer(UniquePtr<Sdp> offer);
@@ -257,18 +259,18 @@ private:
                                SdpDirectionAttribute::Direction direction,
                                Sdp* sdp);
   nsresult GetFreeMsectionForSend(SdpMediaSection::MediaType type,
                                   Sdp* sdp,
                                   SdpMediaSection** msection);
   nsresult CreateAnswerMSection(const JsepAnswerOptions& options,
                                 size_t mlineIndex,
                                 const SdpMediaSection& remoteMsection,
-                                SdpMediaSection* msection,
                                 Sdp* sdp);
+  nsresult SetRecvonlySsrc(SdpMediaSection* msection);
   nsresult BindMatchingLocalTrackForAnswer(SdpMediaSection* msection);
   nsresult DetermineAnswererSetupRole(const SdpMediaSection& remoteMsection,
                                       SdpSetupAttribute::Role* rolep);
   nsresult MakeNegotiatedTrackPair(const SdpMediaSection& remote,
                                    const SdpMediaSection& local,
                                    const RefPtr<JsepTransport>& transport,
                                    bool usingBundle,
                                    size_t transportLevel,
@@ -303,17 +305,17 @@ private:
 
   nsresult GetBundleInfo(const Sdp& sdp,
                          std::set<std::string>* bundleMids,
                          const SdpMediaSection** bundleMsection);
 
   bool IsBundleSlave(const Sdp& localSdp, uint16_t level);
 
   void DisableMsection(Sdp* sdp, SdpMediaSection* msection) const;
-  nsresult EnableMsection(SdpMediaSection* msection);
+  nsresult EnableOfferMsection(SdpMediaSection* msection);
 
   nsresult SetUniquePayloadTypes();
   nsresult GetAllPayloadTypes(const JsepTrackNegotiatedDetails& trackDetails,
                               std::vector<uint8_t>* payloadTypesOut);
   std::string GetCNAME(const SdpMediaSection& msection) const;
   bool MsectionIsDisabled(const SdpMediaSection& msection) const;
   const Sdp* GetAnswer() const;
 
@@ -338,16 +340,19 @@ private:
   std::vector<SdpExtmapAttributeList::Extmap> mVideoRtpExtensions;
   UniquePtr<JsepUuidGenerator> mUuidGen;
   std::string mDefaultRemoteStreamId;
   std::map<size_t, std::string> mDefaultRemoteTrackIdsByLevel;
   std::string mCNAME;
   // Used to prevent duplicate local SSRCs. Not used to prevent local/remote or
   // remote-only duplication, which will be important for EKT but not now.
   std::set<uint32_t> mSsrcs;
+  // When an m-section doesn't have a local track, it still needs an ssrc, which
+  // is stored here.
+  std::vector<uint32_t> mRecvonlySsrcs;
   UniquePtr<Sdp> mGeneratedLocalDescription; // Created but not set.
   UniquePtr<Sdp> mCurrentLocalDescription;
   UniquePtr<Sdp> mCurrentRemoteDescription;
   UniquePtr<Sdp> mPendingLocalDescription;
   UniquePtr<Sdp> mPendingRemoteDescription;
   PtrVector<JsepCodecDescription> mCodecs;
   std::string mLastError;
   SipccSdpParser mParser;
--- a/media/webrtc/signaling/src/jsep/JsepTrack.h
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.h
@@ -157,16 +157,17 @@ private:
   std::vector<uint32_t> mSsrcs;
 };
 
 // Need a better name for this.
 struct JsepTrackPair {
   size_t mLevel;
   // Is this track pair sharing a transport with another?
   Maybe<size_t> mBundleLevel;
+  uint32_t mRecvonlySsrc;
   RefPtr<JsepTrack> mSending;
   RefPtr<JsepTrack> mReceiving;
   RefPtr<JsepTransport> mRtpTransport;
   RefPtr<JsepTransport> mRtcpTransport;
 };
 
 } // namespace mozilla
 
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
@@ -107,17 +107,41 @@ WebrtcAudioConduit::~WebrtcAudioConduit(
   if(mVoiceEngine)
   {
     webrtc::VoiceEngine::Delete(mVoiceEngine);
   }
 }
 
 bool WebrtcAudioConduit::SetLocalSSRC(unsigned int ssrc)
 {
-  return !mPtrRTP->SetLocalSSRC(mChannel, ssrc);
+  unsigned int oldSsrc;
+  if (!GetLocalSSRC(&oldSsrc)) {
+    MOZ_ASSERT(false, "GetLocalSSRC failed");
+    return false;
+  }
+
+  if (oldSsrc == ssrc) {
+    return true;
+  }
+
+  bool wasTransmitting = mEngineTransmitting;
+  if (StopTransmitting() != kMediaConduitNoError) {
+    return false;
+  }
+
+  if (mPtrRTP->SetLocalSSRC(mChannel, ssrc)) {
+    return false;
+  }
+
+  if (wasTransmitting) {
+    if (StartTransmitting() != kMediaConduitNoError) {
+      return false;
+    }
+  }
+  return true;
 }
 
 bool WebrtcAudioConduit::GetLocalSSRC(unsigned int* ssrc) {
   return !mPtrRTP->GetLocalSSRC(mChannel, *ssrc);
 }
 
 bool WebrtcAudioConduit::GetRemoteSSRC(unsigned int* ssrc) {
   return !mPtrRTP->GetRemoteSSRC(mChannel, *ssrc);
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
@@ -158,17 +158,41 @@ WebrtcVideoConduit::~WebrtcVideoConduit(
   if(mVideoEngine)
   {
     webrtc::VideoEngine::Delete(mVideoEngine);
   }
 }
 
 bool WebrtcVideoConduit::SetLocalSSRC(unsigned int ssrc)
 {
-  return !mPtrRTP->SetLocalSSRC(mChannel, ssrc);
+  unsigned int oldSsrc;
+  if (!GetLocalSSRC(&oldSsrc)) {
+    MOZ_ASSERT(false, "GetLocalSSRC failed");
+    return false;
+  }
+
+  if (oldSsrc == ssrc) {
+    return true;
+  }
+
+  bool wasTransmitting = mEngineTransmitting;
+  if (StopTransmitting() != kMediaConduitNoError) {
+    return false;
+  }
+
+  if (mPtrRTP->SetLocalSSRC(mChannel, ssrc)) {
+    return false;
+  }
+
+  if (wasTransmitting) {
+    if (StartTransmitting() != kMediaConduitNoError) {
+      return false;
+    }
+  }
+  return true;
 }
 
 bool WebrtcVideoConduit::GetLocalSSRC(unsigned int* ssrc)
 {
   return !mPtrRTP->GetLocalSSRC(mChannel, *ssrc);
 }
 
 bool WebrtcVideoConduit::GetRemoteSSRC(unsigned int* ssrc)
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -360,16 +360,30 @@ MediaPipelineFactory::CreateOrUpdateMedi
 
   if (!stream->HasTrack(aTrack.GetTrackId())) {
     MOZ_MTLOG(ML_ERROR, "Negotiated " << (receiving ? "recv" : "send")
               << " track id " << aTrack.GetTrackId() << " was never added");
     MOZ_ASSERT(false);
     return NS_ERROR_FAILURE;
   }
 
+  RefPtr<MediaSessionConduit> conduit;
+  if (aTrack.GetMediaType() == SdpMediaSection::kAudio) {
+    rv = GetOrCreateAudioConduit(aTrackPair, aTrack, &conduit);
+    if (NS_FAILED(rv))
+      return rv;
+  } else if (aTrack.GetMediaType() == SdpMediaSection::kVideo) {
+    rv = GetOrCreateVideoConduit(aTrackPair, aTrack, &conduit);
+    if (NS_FAILED(rv))
+      return rv;
+  } else {
+    // We've created the TransportFlow, nothing else to do here.
+    return NS_OK;
+  }
+
   RefPtr<MediaPipeline> pipeline =
     stream->GetPipelineByTrackId_m(aTrack.GetTrackId());
 
   if (pipeline && pipeline->level() != static_cast<int>(level)) {
     MOZ_MTLOG(ML_WARNING, "Track " << aTrack.GetTrackId() <<
                           " has moved from level " << pipeline->level() <<
                           " to level " << level <<
                           ". This requires re-creating the MediaPipeline.");
@@ -386,30 +400,16 @@ MediaPipelineFactory::CreateOrUpdateMedi
   }
 
   MOZ_MTLOG(ML_DEBUG,
             "Creating media pipeline"
                 << " m-line index=" << aTrackPair.mLevel
                 << " type=" << aTrack.GetMediaType()
                 << " direction=" << aTrack.GetDirection());
 
-  RefPtr<MediaSessionConduit> conduit;
-  if (aTrack.GetMediaType() == SdpMediaSection::kAudio) {
-    rv = GetOrCreateAudioConduit(aTrackPair, aTrack, &conduit);
-    if (NS_FAILED(rv))
-      return rv;
-  } else if (aTrack.GetMediaType() == SdpMediaSection::kVideo) {
-    rv = GetOrCreateVideoConduit(aTrackPair, aTrack, &conduit);
-    if (NS_FAILED(rv))
-      return rv;
-  } else {
-    // We've created the TransportFlow, nothing else to do here.
-    return NS_OK;
-  }
-
   if (receiving) {
     rv = CreateMediaPipelineReceiving(aTrackPair, aTrack,
                                       level, rtpFlow, rtcpFlow, filter,
                                       conduit);
     if (NS_FAILED(rv))
       return rv;
   } else {
     rv = CreateMediaPipelineSending(aTrackPair, aTrack,
@@ -614,16 +614,25 @@ MediaPipelineFactory::GetOrCreateAudioCo
     }
 
     auto error = conduit->ConfigureRecvMediaCodecs(configs.values);
 
     if (error) {
       MOZ_MTLOG(ML_ERROR, "ConfigureRecvMediaCodecs failed: " << error);
       return NS_ERROR_FAILURE;
     }
+
+    if (!aTrackPair.mSending) {
+      // No send track, but we still need to configure an SSRC for receiver
+      // reports.
+      if (!conduit->SetLocalSSRC(aTrackPair.mRecvonlySsrc)) {
+        MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed");
+        return NS_ERROR_FAILURE;
+      }
+    }
   } else {
     // For now we only expect to have one ssrc per local track.
     auto ssrcs = aTrack.GetSsrcs();
     if (!ssrcs.empty()) {
       if (!conduit->SetLocalSSRC(ssrcs.front())) {
         MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed");
         return NS_ERROR_FAILURE;
       }
@@ -735,16 +744,25 @@ MediaPipelineFactory::GetOrCreateVideoCo
     }
 
     auto error = conduit->ConfigureRecvMediaCodecs(configs.values);
 
     if (error) {
       MOZ_MTLOG(ML_ERROR, "ConfigureRecvMediaCodecs failed: " << error);
       return NS_ERROR_FAILURE;
     }
+
+    if (!aTrackPair.mSending) {
+      // No send track, but we still need to configure an SSRC for receiver
+      // reports.
+      if (!conduit->SetLocalSSRC(aTrackPair.mRecvonlySsrc)) {
+        MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed");
+        return NS_ERROR_FAILURE;
+      }
+    }
   } else {
     // For now we only expect to have one ssrc per local track.
     auto ssrcs = aTrack.GetSsrcs();
     if (!ssrcs.empty()) {
       if (!conduit->SetLocalSSRC(ssrcs.front())) {
         MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed");
         return NS_ERROR_FAILURE;
       }
--- a/media/webrtc/signaling/test/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/test/jsep_session_unittest.cpp
@@ -2037,62 +2037,82 @@ TEST_F(JsepSessionTest, OfferAnswerRecvO
 {
   JsepOfferOptions options;
   options.mOfferToReceiveAudio = Some(static_cast<size_t>(1U));
   options.mOfferToReceiveVideo = Some(static_cast<size_t>(2U));
   options.mDontOfferDataChannel = Some(true);
   std::string offer = CreateOffer(Some(options));
 
   SipccSdpParser parser;
-  auto outputSdp = parser.Parse(offer);
-  ASSERT_TRUE(!!outputSdp) << "Should have valid SDP" << std::endl
+  UniquePtr<Sdp> parsedOffer = parser.Parse(offer);
+  ASSERT_TRUE(!!parsedOffer) << "Should have valid SDP" << std::endl
                            << "Errors were: " << GetParseErrors(parser);
 
-  ASSERT_EQ(3U, outputSdp->GetMediaSectionCount());
+  ASSERT_EQ(3U, parsedOffer->GetMediaSectionCount());
   ASSERT_EQ(SdpMediaSection::kAudio,
-            outputSdp->GetMediaSection(0).GetMediaType());
+            parsedOffer->GetMediaSection(0).GetMediaType());
   ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
-            outputSdp->GetMediaSection(0).GetAttributeList().GetDirection());
+            parsedOffer->GetMediaSection(0).GetAttributeList().GetDirection());
+  ASSERT_TRUE(parsedOffer->GetMediaSection(0).GetAttributeList().HasAttribute(
+        SdpAttribute::kSsrcAttribute));
+
   ASSERT_EQ(SdpMediaSection::kVideo,
-            outputSdp->GetMediaSection(1).GetMediaType());
+            parsedOffer->GetMediaSection(1).GetMediaType());
   ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
-            outputSdp->GetMediaSection(1).GetAttributeList().GetDirection());
+            parsedOffer->GetMediaSection(1).GetAttributeList().GetDirection());
+  ASSERT_TRUE(parsedOffer->GetMediaSection(1).GetAttributeList().HasAttribute(
+        SdpAttribute::kSsrcAttribute));
+
   ASSERT_EQ(SdpMediaSection::kVideo,
-            outputSdp->GetMediaSection(2).GetMediaType());
+            parsedOffer->GetMediaSection(2).GetMediaType());
   ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
-            outputSdp->GetMediaSection(2).GetAttributeList().GetDirection());
-
-  ASSERT_TRUE(outputSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+            parsedOffer->GetMediaSection(2).GetAttributeList().GetDirection());
+  ASSERT_TRUE(parsedOffer->GetMediaSection(2).GetAttributeList().HasAttribute(
+        SdpAttribute::kSsrcAttribute));
+
+  ASSERT_TRUE(parsedOffer->GetMediaSection(0).GetAttributeList().HasAttribute(
       SdpAttribute::kRtcpMuxAttribute));
-  ASSERT_TRUE(outputSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+  ASSERT_TRUE(parsedOffer->GetMediaSection(1).GetAttributeList().HasAttribute(
       SdpAttribute::kRtcpMuxAttribute));
-  ASSERT_TRUE(outputSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+  ASSERT_TRUE(parsedOffer->GetMediaSection(2).GetAttributeList().HasAttribute(
       SdpAttribute::kRtcpMuxAttribute));
 
   SetLocalOffer(offer, CHECK_SUCCESS);
 
   AddTracks(mSessionAns, "audio,video");
   SetRemoteOffer(offer, CHECK_SUCCESS);
 
   std::string answer = CreateAnswer();
-  outputSdp = parser.Parse(answer);
-
-  ASSERT_EQ(3U, outputSdp->GetMediaSectionCount());
+  UniquePtr<Sdp> parsedAnswer = parser.Parse(answer);
+
+  ASSERT_EQ(3U, parsedAnswer->GetMediaSectionCount());
   ASSERT_EQ(SdpMediaSection::kAudio,
-            outputSdp->GetMediaSection(0).GetMediaType());
+            parsedAnswer->GetMediaSection(0).GetMediaType());
   ASSERT_EQ(SdpDirectionAttribute::kSendonly,
-            outputSdp->GetMediaSection(0).GetAttributeList().GetDirection());
+            parsedAnswer->GetMediaSection(0).GetAttributeList().GetDirection());
+  ASSERT_EQ(SdpMediaSection::kVideo,
+            parsedAnswer->GetMediaSection(1).GetMediaType());
+  ASSERT_EQ(SdpDirectionAttribute::kSendonly,
+            parsedAnswer->GetMediaSection(1).GetAttributeList().GetDirection());
   ASSERT_EQ(SdpMediaSection::kVideo,
-            outputSdp->GetMediaSection(1).GetMediaType());
-  ASSERT_EQ(SdpDirectionAttribute::kSendonly,
-            outputSdp->GetMediaSection(1).GetAttributeList().GetDirection());
-  ASSERT_EQ(SdpMediaSection::kVideo,
-            outputSdp->GetMediaSection(2).GetMediaType());
+            parsedAnswer->GetMediaSection(2).GetMediaType());
   ASSERT_EQ(SdpDirectionAttribute::kInactive,
-            outputSdp->GetMediaSection(2).GetAttributeList().GetDirection());
+            parsedAnswer->GetMediaSection(2).GetAttributeList().GetDirection());
+
+  SetLocalAnswer(answer, CHECK_SUCCESS);
+  SetRemoteAnswer(answer, CHECK_SUCCESS);
+
+  std::vector<JsepTrackPair> trackPairs(mSessionOff.GetNegotiatedTrackPairs());
+  ASSERT_EQ(2U, trackPairs.size());
+  for (auto pair : trackPairs) {
+    auto ssrcs = parsedOffer->GetMediaSection(pair.mLevel).GetAttributeList()
+                 .GetSsrc().mSsrcs;
+    ASSERT_EQ(1U, ssrcs.size());
+    ASSERT_EQ(pair.mRecvonlySsrc, ssrcs.front().ssrc);
+  }
 }
 
 TEST_F(JsepSessionTest, OfferAnswerSendOnlyLines)
 {
   AddTracks(mSessionOff, "audio,video,video");
 
   JsepOfferOptions options;
   options.mOfferToReceiveAudio = Some(static_cast<size_t>(0U));