Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/media/webrtc/signaling/gtest/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 64 kB image not shown  

Quelle  jsep_track_unittest.cpp   Sprache: C

 
/* -*- 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 = falsebool preferRed = false,
      bool addDtmfCodec = falseconst {
    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));
    results.emplace_back(JsepVideoCodecDescription::CreateDefaultAV1(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));

    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(bool offerCodecsMatchAnswer = true) {
    CreateOffer();
    CreateAnswer();
    Negotiate();
    SanityCheck(offerCodecsMatchAnswer);
  }

  // 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,
                                    bool codecsMustMatch) const {
    ASSERT_EQ(a.GetEncodingCount(), b.GetEncodingCount());
    if (codecsMustMatch) {
      for (size_t i = 0; i < a.GetEncodingCount(); ++i) {
        SanityCheckEncodings(a.GetEncoding(i), b.GetEncoding(i));
      }
    }

    ASSERT_EQ(a.GetUniqueReceivePayloadTypes().size(),
              b.GetUniqueReceivePayloadTypes().size());
    for (size_t i = 0; i < a.GetUniqueReceivePayloadTypes().size(); ++i) {
      ASSERT_EQ(a.GetUniqueReceivePayloadTypes()[i],
                b.GetUniqueReceivePayloadTypes()[i]);
    }
  }

  void SanityCheckTracks(const JsepTrack& a, const JsepTrack& b,
                         bool codecsMustMatch) 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(), codecsMustMatch);
  }

  void SanityCheck(bool offerCodecsMatchAnswer = trueconst {
    SanityCheckTracks(mSendOff, mRecvAns, true);
    SanityCheckTracks(mRecvOff, mSendAns, offerCodecsMatchAnswer);
  }

 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(falsefalsetrue);
  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(falsefalsetrue);
  mAnsCodecs = MakeCodecs(falsefalsefalse);

  InitTracks(SdpMediaSection::kAudio);
  InitSdp(SdpMediaSection::kAudio);
  OfferAnswer(false);

  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, 3, 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, 3, 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(falsefalsefalse);
  mAnsCodecs = MakeCodecs(falsefalsetrue);

  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(falsefalsetrue);
  mAnsCodecs = MakeCodecs(falsefalsetrue);

  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(falsefalsetrue);
  mAnsCodecs = MakeCodecs(falsefalsetrue);

  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(falsefalsetrue);
  mAnsCodecs = MakeCodecs(falsefalsetrue);

  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(falsefalsetrue);
  mAnsCodecs = MakeCodecs(falsefalsetrue);

  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(false);

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

  UniquePtr<JsepVideoCodecDescription> track;
  ASSERT_TRUE((track = GetVideoCodec(mSendOff, 3, 0)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 5, 0)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendAns, 3, 0)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 3, 0)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendOff, 3, 1)));
  ASSERT_EQ("126", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 5, 1)));
  ASSERT_EQ("126", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendAns, 3, 1)));
  ASSERT_EQ("126", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 3, 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);

  UniquePtr<JsepVideoCodecDescription> track;
  ASSERT_TRUE((track = GetVideoCodec(mSendOff, 3, 0)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 3, 0)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendAns, 3, 0)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 3, 0)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendOff, 3, 1)));
  ASSERT_EQ("126", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 3, 1)));
  ASSERT_EQ("126", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendAns, 3, 1)));
  ASSERT_EQ("126", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 3, 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);

  UniquePtr<JsepVideoCodecDescription> track;
  ASSERT_TRUE((track = GetVideoCodec(mSendOff, 5)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 5)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendAns, 5)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 5)));
  ASSERT_EQ("120", track->mDefaultPt);
}

TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECPreferred) {
  mOffCodecs = MakeCodecs(truetrue);
  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);

  UniquePtr<JsepVideoCodecDescription> track;
  // We should have 4 codecs, the first of which is VP8, because having a
  // pseudo codec come first is silly.
  ASSERT_TRUE((track = GetVideoCodec(mSendOff, 5)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 5)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendAns, 5)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 5)));
  ASSERT_EQ("120", track->mDefaultPt);
}

// Make sure we only put the right things in the fmtp:122 120/.... line
TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECMismatch) {
  mOffCodecs = MakeCodecs(truetrue);
  mAnsCodecs = MakeCodecs(true);
  // remove h264 & AV1 from answer codecs
  ASSERT_EQ("H264", mAnsCodecs[3]->mName);
  ASSERT_EQ("AV1", mAnsCodecs[4]->mName);
  mAnsCodecs.erase(mAnsCodecs.begin() + 4);
  mAnsCodecs.erase(mAnsCodecs.begin() + 3);

  InitTracks(SdpMediaSection::kVideo);
  InitSdp(SdpMediaSection::kVideo);
  OfferAnswer(false);

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

  // We should have 3 codecs, the first of which is VP8, because having a
  // pseudo codec come first is silly.
  UniquePtr<JsepVideoCodecDescription> track;
  ASSERT_TRUE((track = GetVideoCodec(mSendOff, 3)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 5)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendAns, 3)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 3)));
  ASSERT_EQ("120", 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(9U, mOffCodecs.size());
  JsepVideoCodecDescription& red =
      static_cast<JsepVideoCodecDescription&>(*mOffCodecs[5]);
  ASSERT_EQ("red", red.mName);

  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);
}

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, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 3, 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, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 3, 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, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb);
  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 3, 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, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 3, 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, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 3, 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, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 3, 0)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 3, 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(falsefalsefalse);
  mAnsCodecs = MakeCodecs(falsefalsefalse);
  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(falsefalsefalse);
  mAnsCodecs = MakeCodecs(falsefalsefalse);

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

  ASSERT_EQ(3U, mSendOff.GetRtxSsrcs().size());
  const auto posRtxSsrc0 =
      mOffer->ToString().find(std::to_string(mSendOff.GetRtxSsrcs()[0]));
  const auto posRtxSsrc1 =
      mOffer->ToString().find(std::to_string(mSendOff.GetRtxSsrcs()[1]));
  const auto posRtxSsrc2 =
      mOffer->ToString().find(std::to_string(mSendOff.GetRtxSsrcs()[2]));
  ASSERT_NE(std::string::npos, posRtxSsrc0);
  ASSERT_NE(std::string::npos, posRtxSsrc1);
  ASSERT_NE(std::string::npos, posRtxSsrc2);
  ASSERT_GT(posRtxSsrc1, posRtxSsrc0);
  ASSERT_GT(posRtxSsrc2, posRtxSsrc0);
  ASSERT_GT(posRtxSsrc2, posRtxSsrc1);
}

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=42e01f;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, 3, 0)));
  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(truetruetrue);
  mAnsCodecs = MakeCodecs(truetruetrue);
  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(truetruetrue);
  mAnsCodecs = MakeCodecs(truetruetrue);

  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(truetruetrue);
  mAnsCodecs = MakeCodecs(truetruetrue);
  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, 5, 0)));
  EXPECT_EQ("VP8", codec->mName);
  EXPECT_EQ("max-fs=12288;max-fr=60", codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 5, 0)));
  EXPECT_EQ("VP8", codec->mName);
  EXPECT_EQ("max-fs=12288;max-fr=60", codec->mSdpFmtpLine.valueOr("nothing"));

  EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 5, 1)));
  EXPECT_EQ("H264", codec->mName);
  EXPECT_EQ(
      "profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1",
      codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 5, 1)));
  EXPECT_EQ("H264", codec->mName);
  EXPECT_EQ(
      "profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1",
      codec->mSdpFmtpLine.valueOr("nothing"));

  EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 5, 3)));
  EXPECT_EQ("red", codec->mName);
  EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 5, 3)));
  EXPECT_EQ("red", codec->mName);
  EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));

  EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 5, 4)));
  EXPECT_EQ("ulpfec", codec->mName);
  EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 5, 4)));
  EXPECT_EQ("ulpfec", codec->mName);
  EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
}

TEST_F(JsepTrackTest, NonDefaultVideoSdpFmtpLine) {
  mOffCodecs = MakeCodecs(truetruetrue);
  mAnsCodecs = MakeCodecs(truetruetrue);

  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, 5, 0)));
  EXPECT_EQ("VP8", codec->mName);
  EXPECT_EQ("max-fs=32400;max-fr=60", codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 5, 0)));
  EXPECT_EQ("VP8", codec->mName);
  EXPECT_EQ("max-fs=1200;max-fr=15", codec->mSdpFmtpLine.valueOr("nothing"));

  EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 5, 1)));
  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, 5, 1)));
  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, 5, 3)));
  EXPECT_EQ("red", codec->mName);
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=93 G=94

¤ Dauer der Verarbeitung: 0.20 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.