Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/media/webrtc/signaling/gtest/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 92 kB image not shown  

Quelle  jsep_track_unittest.cpp

  Sprache: C
 

/* 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 "gmock/gmock.h"
#include "gtest/gtest.h"

#include "CodecConfig.h"
#include "MockJsepCodecPreferences.h"
#include "jsapi/DefaultCodecPreferences.h"
#include "jsapi/RTCRtpTransceiver.h"
#include "jsep/JsepTrack.h"
#include "sdp/SipccSdp.h"
#include "sdp/SipccSdpParser.h"
#include "sdp/SdpHelper.h"

#include <tuple>

using testing::UnorderedElementsAre;

namespace mozilla {

class JsepTrackTestBase : public ::testing::Test {
 public:
  static void SetUpTestCase() {
    NSS_NoDB_Init(nullptr);
    NSS_SetDomesticPolicy();
  }
};

struct CodecOverrides {
  bool addFecCodecs = false;
  bool preferRed = false;
  bool addDtmfCodec = false;
  bool enableRemb = true;
  bool enableTransportCC = true;
  bool enableAudioTransportCC = true;
  bool enableRtx = true;
  void ApplyToPrefs(MockJsepCodecPreferences& aPrefs) const {
    aPrefs.mUseRemb = enableRemb;
    aPrefs.mUseTransportCC = enableTransportCC;
    aPrefs.mUseAudioTransportCC = enableAudioTransportCC;
    aPrefs.mUseRtx = enableRtx;
  }
};

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: \n";
        mOffer->Serialize(std::cerr);
      }

      if (mAnswer) {
        std::cerr << "Answer SDP: \n";
        mAnswer->Serialize(std::cerr);
      }
    }
  }

  std::vector<UniquePtr<JsepCodecDescription>> MakeCodecs(
      const CodecOverrides overrides) const {
    MockJsepCodecPreferences prefs;
    overrides.ApplyToPrefs(prefs);

    prefs.mUseRemb = overrides.enableRemb;
    prefs.mUseTransportCC = overrides.enableTransportCC;
    JsepCodecPreferences& prefsRef = prefs;
    std::cout << "CodecPrefrences: " << prefsRef << "\n";
    std::vector<UniquePtr<JsepCodecDescription>> results;
    results.emplace_back(JsepAudioCodecDescription::CreateDefaultOpus(prefs));
    results.emplace_back(JsepAudioCodecDescription::CreateDefaultG722(prefs));
    if (overrides.addDtmfCodec) {
      results.emplace_back(
          JsepAudioCodecDescription::CreateDefaultTelephoneEvent());
    }

    if (overrides.addFecCodecs && overrides.preferRed) {
      results.emplace_back(JsepVideoCodecDescription::CreateDefaultRed(prefs));
    }
    results.emplace_back(JsepVideoCodecDescription::CreateDefaultVP8(prefs));
    results.emplace_back(JsepVideoCodecDescription::CreateDefaultH264_1(prefs));
    results.emplace_back(JsepVideoCodecDescription::CreateDefaultAV1(prefs));

    if (overrides.addFecCodecs) {
      if (!overrides.preferRed) {
        results.emplace_back(
            JsepVideoCodecDescription::CreateDefaultRed(prefs));
      }
      results.emplace_back(
          JsepVideoCodecDescription::CreateDefaultUlpFec(prefs));
    }

    results.emplace_back(new JsepApplicationCodecDescription(
        "webrtc-datachannel"2565999499));

    return results;
  }

  void Init(SdpMediaSection::MediaType type) {
    InitCodecs(CodecOverrides{});
    InitTracks(type);
    InitSdp(type);
  }

  struct SplitOverrides {
    CodecOverrides offer = {};
    CodecOverrides answer = {};
  };

  void InitCodecs(const CodecOverrides& overrides) {
    mOffCodecs = MakeCodecs(overrides);
    mAnsCodecs = MakeCodecs(overrides);
  }
  void InitCodecs(const SplitOverrides& overrides) {
    mOffCodecs = MakeCodecs(overrides.offer);
    mAnsCodecs = MakeCodecs(overrides.answer);
  }

  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(""00, 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(""00, 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<JsepCodecDescription> GetCodec(const JsepTrack& track,
                                           const std::string& name) const {
    if (!track.GetNegotiatedDetails() ||
        track.GetNegotiatedDetails()->GetEncodingCount() != 1U) {
      return nullptr;
    }

    const auto& codecs =
        track.GetNegotiatedDetails()->GetEncoding(0).GetCodecs();
    for (const auto& codec : codecs) {
      if (codec->mName == name) {
        return UniquePtr<JsepCodecDescription>(codec->Clone());
      }
    }
    return nullptr;
  }

  UniquePtr<JsepAudioCodecDescription> AsAudio(
      UniquePtr<JsepCodecDescription> codec) {
    if (codec && codec->Type() == SdpMediaSection::kAudio) {
      return UniquePtr<JsepAudioCodecDescription>(
          static_cast<JsepAudioCodecDescription*>(codec.release()));
    }
    return nullptr;
  }

  UniquePtr<JsepVideoCodecDescription> AsVideo(
      UniquePtr<JsepCodecDescription> codec) {
    if (codec && codec->Type() == SdpMediaSection::kVideo) {
      return UniquePtr<JsepVideoCodecDescription>(
          static_cast<JsepVideoCodecDescription*>(codec.release()));
    }
    return nullptr;
  }

  UniquePtr<JsepVideoCodecDescription> GetVideoCodec(
      const JsepTrack& track, size_t expectedSize = 1,
      size_t codecIndex = 0const {
    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 = 0const {
    auto codec =
        GetCodec(track, SdpMediaSection::kAudio, expectedSize, codecIndex);
    return UniquePtr<JsepAudioCodecDescription>(
        static_cast<JsepAudioCodecDescription*>(codec.release()));
  }

  template <typename T>
  void CheckOtherFbExists(const T& codec,
                          SdpRtcpFbAttributeList::Type type) const {
    for (const auto& fb : codec.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 JsepTrack& aTrack,
                                    const JsepTrack& bTrack,
                                    bool codecsMustMatch) const {
    const auto aDetails = *aTrack.GetNegotiatedDetails();
    const auto bDetails = *bTrack.GetNegotiatedDetails();
    ASSERT_EQ(aDetails.GetEncodingCount(), bDetails.GetEncodingCount());
    if (codecsMustMatch) {
      for (size_t i = 0; i < aDetails.GetEncodingCount(); ++i) {
        SanityCheckEncodings(aDetails.GetEncoding(i), bDetails.GetEncoding(i));
      }
    }

    ASSERT_EQ(aTrack.GetUniqueReceivePayloadTypes().size(),
              bTrack.GetUniqueReceivePayloadTypes().size());
    for (size_t i = 0; i < aTrack.GetUniqueReceivePayloadTypes().size(); ++i) {
      ASSERT_EQ(aTrack.GetUniqueReceivePayloadTypes()[i],
                bTrack.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, b, codecsMustMatch);
  }

  void SanityCheck(bool offerCodecsMatchAnswer = true) const {
    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, CheckForAnsweringWithExtmapAllowMixedWhenNotOffered) {
  Init(SdpMediaSection::kAudio);

  CreateOffer();
  mOffer->GetMediaSection(0).GetAttributeList().RemoveAttribute(
      SdpAttribute::kExtmapAllowMixedAttribute);
  CreateAnswer();

  ASSERT_FALSE(mAnswer->GetMediaSection(0).GetAttributeList().HasAttribute(
      SdpAttribute::kExtmapAllowMixedAttribute));
  Negotiate();
  SanityCheck();
}

TEST_F(JsepTrackTest, CheckForMismatchedAudioCodecAndVideoTrack) {
  std::vector<UniquePtr<JsepCodecDescription>> offerCodecs;

  // make codecs including telephone-event (an audio codec)
  offerCodecs = MakeCodecs({.addDtmfCodec = true});
  JsepTrack videoTrack(SdpMediaSection::kVideo, sdp::kSend);
  videoTrack.UpdateStreamIds(std::vector<std::string>(1"stream_id"));
  // populate codecs and then make sure we don't have any audio codecs
  // in the video track
  videoTrack.PopulateCodecs(offerCodecs);

  bool found = false;
  videoTrack.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
  ASSERT_FALSE(found);

  found = false;
  videoTrack.ForEachCodec(CheckForCodecType(SdpMediaSection::kVideo, &found));
  ASSERT_TRUE(found);  // for sanity, make sure we did find video codecs
}

TEST_F(JsepTrackTest, CheckVideoTrackWithHackedDtmfSdp) {
  Init(SdpMediaSection::kVideo);
  CreateOffer();
  // make sure we don't find sdp containing telephone-event in video track
  ASSERT_EQ(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
            std::string::npos);
  // force audio codec telephone-event into video m= section of offer
  GetOffer().AddCodec("101""telephone-event"80001);
  // 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"80001);
  // 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) {
  InitCodecs(
      {.offer = {.addDtmfCodec = true}, .answer = {.addDtmfCodec = false}});

  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, 20)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 20)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 20)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 21)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 21)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 21)));
  ASSERT_EQ("9", track->mDefaultPt);
}

TEST_F(JsepTrackTest, AudioNegotiationAnswererDtmf) {
  InitCodecs(
      {.offer = {.addDtmfCodec = false}, .answer = {.addDtmfCodec = true}});

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

  CheckOffEncodingCount(1);
  CheckAnsEncodingCount(1);

  ASSERT_EQ(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
            std::string::npos);
  ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
            std::string::npos);

  ASSERT_EQ(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
  ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);

  UniquePtr<JsepAudioCodecDescription> track;
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 20)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 20)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 20)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 20)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 21)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 21)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 21)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 21)));
  ASSERT_EQ("9", track->mDefaultPt);
}

TEST_F(JsepTrackTest, AudioNegotiationOffererAnswererDtmf) {
  InitCodecs(
      {.offer = {.addDtmfCodec = true}, .answer = {.addDtmfCodec = true}});

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

  CheckOffEncodingCount(1);
  CheckAnsEncodingCount(1);

  ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
            std::string::npos);
  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
            std::string::npos);

  ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
  ASSERT_NE(mAnswer->ToString().find("a=fmtp:101 0-15"), std::string::npos);

  UniquePtr<JsepAudioCodecDescription> track;
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
}

TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererNoFmtpAnswererFmtp) {
  InitCodecs(
      {.offer = {.addDtmfCodec = true}, .answer = {.addDtmfCodec = true}});

  mExpectDifferingFmtp = true;

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

  CreateOffer();
  GetOffer().RemoveFmtp("101");

  CreateAnswer();

  Negotiate();
  SanityCheck();

  CheckOffEncodingCount(1);
  CheckAnsEncodingCount(1);

  ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
            std::string::npos);
  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
            std::string::npos);

  ASSERT_EQ(mOffer->ToString().find("a=fmtp:101"), std::string::npos);
  ASSERT_NE(mAnswer->ToString().find("a=fmtp:101 0-15"), std::string::npos);

  UniquePtr<JsepAudioCodecDescription> track;
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_EQ("0-15", track->mSdpFmtpLine.valueOr("nothing"));
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_EQ("0-15", track->mSdpFmtpLine.valueOr("nothing"));
}

TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererFmtpAnswererNoFmtp) {
  InitCodecs(
      {.offer = {.addDtmfCodec = true}, .answer = {.addDtmfCodec = true}});

  mExpectDifferingFmtp = true;

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

  CreateOffer();

  CreateAnswer();
  GetAnswer().RemoveFmtp("101");

  Negotiate();
  SanityCheck();

  CheckOffEncodingCount(1);
  CheckAnsEncodingCount(1);

  ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
            std::string::npos);
  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
            std::string::npos);

  ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
  ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);

  UniquePtr<JsepAudioCodecDescription> track;
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_EQ("0-15", track->mSdpFmtpLine.valueOr("nothing"));
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_EQ("0-15", track->mSdpFmtpLine.valueOr("nothing"));
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
}

TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererNoFmtpAnswererNoFmtp) {
  InitCodecs(
      {.offer = {.addDtmfCodec = true}, .answer = {.addDtmfCodec = true}});

  mExpectDifferingFmtp = true;

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

  CreateOffer();
  GetOffer().RemoveFmtp("101");

  CreateAnswer();
  GetAnswer().RemoveFmtp("101");

  Negotiate();
  SanityCheck();

  CheckOffEncodingCount(1);
  CheckAnsEncodingCount(1);

  ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
            std::string::npos);
  ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
            std::string::npos);

  ASSERT_EQ(mOffer->ToString().find("a=fmtp:101"), std::string::npos);
  ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);

  UniquePtr<JsepAudioCodecDescription> track;
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 30)));
  ASSERT_EQ("109", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 31)));
  ASSERT_EQ("9", track->mDefaultPt);
  ASSERT_TRUE((track = GetAudioCodec(mSendOff, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
  ASSERT_TRUE((track = GetAudioCodec(mRecvOff, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
  ASSERT_TRUE((track = GetAudioCodec(mSendAns, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
  ASSERT_TRUE((track = GetAudioCodec(mRecvAns, 32)));
  ASSERT_EQ("101", track->mDefaultPt);
  ASSERT_EQ("nothing", track->mSdpFmtpLine.valueOr("nothing"));
}

TEST_F(JsepTrackTest, VideoNegotationOffererFEC) {
  InitCodecs(
      {.offer = {.addFecCodecs = true}, .answer = {.addFecCodecs = 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, 30)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 50)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendAns, 30)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 30)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendOff, 31)));
  ASSERT_EQ("126", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 51)));
  ASSERT_EQ("126", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendAns, 31)));
  ASSERT_EQ("126", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 31)));
  ASSERT_EQ("126", track->mDefaultPt);
}

TEST_F(JsepTrackTest, VideoNegotationAnswererFEC) {
  InitCodecs(
      {.offer = {.addFecCodecs = false}, .answer = {.addFecCodecs = 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, 30)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 30)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendAns, 30)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 30)));
  ASSERT_EQ("120", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendOff, 31)));
  ASSERT_EQ("126", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvOff, 31)));
  ASSERT_EQ("126", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mSendAns, 31)));
  ASSERT_EQ("126", track->mDefaultPt);
  ASSERT_TRUE((track = GetVideoCodec(mRecvAns, 31)));
  ASSERT_EQ("126", track->mDefaultPt);
}

TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFEC) {
  InitCodecs(
      {.offer = {.addFecCodecs = true}, .answer = {.addFecCodecs = 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) {
  InitCodecs({.offer = {.addFecCodecs = true, .preferRed = true},
              .answer = {.addFecCodecs = 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) {
  InitCodecs({.offer = {.addFecCodecs = true, .preferRed = true},
              .answer = {.addFecCodecs = 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) {
  MockJsepCodecPreferences prefs;
  mOffCodecs = MakeCodecs({.addFecCodecs = true});
  auto vp9 = JsepVideoCodecDescription::CreateDefaultVP9(prefs);
  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({.addFecCodecs = 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) {
  // enable remb on the offer codecs
  InitCodecs({.offer = {.enableRemb = true, .enableTransportCC = false},
              .answer = {.enableRemb = false, .enableTransportCC = false}});
  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, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);

  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
}

TEST_F(JsepTrackTest, VideoNegotiationAnswerRemb) {
  InitCodecs({.offer = {.enableRemb = false, .enableTransportCC = false},
              .answer = {.enableRemb = true, .enableTransportCC = false}});
  // 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, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
}

TEST_F(JsepTrackTest, VideoNegotiationOfferAnswerRemb) {
  InitCodecs({.offer = {.enableRemb = true, .enableTransportCC = false},
              .answer = {.enableRemb = true, .enableTransportCC = false}});
  // 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, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb);
  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kRemb);
}

TEST_F(JsepTrackTest, AudioNegotiationOfferTransportCC) {
  InitCodecs({.enableAudioTransportCC = false});
  // enable TransportCC on the offer codecs
  ((JsepAudioCodecDescription&)*mOffCodecs[0]).EnableTransportCC();
  InitTracks(SdpMediaSection::kAudio);
  InitSdp(SdpMediaSection::kAudio);
  OfferAnswer();

  // make sure TransportCC is on offer and not on answer
  ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:109 transport-cc"),
            std::string::npos);
  ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:109 transport-cc"),
            std::string::npos);
  CheckOffEncodingCount(1);
  CheckAnsEncodingCount(1);

  UniquePtr<JsepAudioCodecDescription> codec;
  ASSERT_TRUE((codec = GetAudioCodec(mSendOff, 20)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetAudioCodec(mRecvAns, 20)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetAudioCodec(mSendAns, 20)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetAudioCodec(mRecvOff, 20)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
}

TEST_F(JsepTrackTest, AudioNegotiationAnswerTransportCC) {
  InitCodecs({.enableAudioTransportCC = false});
  // enable TransportCC on the answer codecs
  ((JsepAudioCodecDescription&)*mAnsCodecs[0]).EnableTransportCC();
  InitTracks(SdpMediaSection::kAudio);
  InitSdp(SdpMediaSection::kAudio);
  OfferAnswer();

  // make sure TransportCC is not on offer and not on answer
  ASSERT_EQ(mOffer->ToString().find("a=rtcp-fb:109 transport-cc"),
            std::string::npos);
  ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:109 transport-cc"),
            std::string::npos);
  CheckOffEncodingCount(1);
  CheckAnsEncodingCount(1);

  UniquePtr<JsepAudioCodecDescription> codec;
  ASSERT_TRUE((codec = GetAudioCodec(mSendOff, 20)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetAudioCodec(mRecvAns, 20)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetAudioCodec(mSendAns, 20)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetAudioCodec(mRecvOff, 20)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
}

TEST_F(JsepTrackTest, AudioNegotiationOfferAnswerTransportCC) {
  InitCodecs({.enableAudioTransportCC = false});
  // enable TransportCC on the offer and answer codecs
  ((JsepAudioCodecDescription&)*mOffCodecs[0]).EnableTransportCC();
  ((JsepAudioCodecDescription&)*mAnsCodecs[0]).EnableTransportCC();
  InitTracks(SdpMediaSection::kAudio);
  InitSdp(SdpMediaSection::kAudio);
  OfferAnswer();

  // make sure TransportCC is on offer and on answer
  ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:109 transport-cc"),
            std::string::npos);
  ASSERT_NE(mAnswer->ToString().find("a=rtcp-fb:109 transport-cc"),
            std::string::npos);
  CheckOffEncodingCount(1);
  CheckAnsEncodingCount(1);

  UniquePtr<JsepAudioCodecDescription> codec;
  ASSERT_TRUE((codec = GetAudioCodec(mSendOff, 20)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists<JsepAudioCodecDescription>(
      *codec, SdpRtcpFbAttributeList::kTransportCC);
  ASSERT_TRUE((codec = GetAudioCodec(mRecvAns, 20)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists<JsepAudioCodecDescription>(
      *codec, SdpRtcpFbAttributeList::kTransportCC);
  ASSERT_TRUE((codec = GetAudioCodec(mSendAns, 20)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists<JsepAudioCodecDescription>(
      *codec, SdpRtcpFbAttributeList::kTransportCC);
  ASSERT_TRUE((codec = GetAudioCodec(mRecvOff, 20)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists<JsepAudioCodecDescription>(
      *codec, SdpRtcpFbAttributeList::kTransportCC);
}

TEST_F(JsepTrackTest, AudioTransportCCFbSetUnsetWhenAnswerRejects) {
  InitCodecs({.enableAudioTransportCC = false});
  // Offer enables TransportCC, answer does not. After negotiation TC is
  // dropped; AudioCodecConfig::mTransportCCFbSet must reflect that even though
  // JsepAudioCodecDescription::mTransportCCEnabled stays true on the offerer.
  ((JsepAudioCodecDescription&)*mOffCodecs[0]).EnableTransportCC();
  InitTracks(SdpMediaSection::kAudio);
  InitSdp(SdpMediaSection::kAudio);
  OfferAnswer();

  for (const JsepTrack* track : {&mSendOff, &mRecvAns, &mSendAns, &mRecvOff}) {
    ASSERT_TRUE(track->GetNegotiatedDetails());
    std::vector<AudioCodecConfig> configs;
    dom::RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs(
        *track->GetNegotiatedDetails(), &configs);
    ASSERT_FALSE(configs.empty());
    for (const auto& config : configs) {
      EXPECT_FALSE(config.mTransportCCFbSet)
          << "codec " << config.mName
          << " has mTransportCCFbSet=true despite negotiation rejecting TC";
    }
  }
}

TEST_F(JsepTrackTest, AudioTransportCCFbSetWhenBothSidesNegotiate) {
  InitCodecs(CodecOverrides{});
  ((JsepAudioCodecDescription&)*mOffCodecs[0]).EnableTransportCC();
  ((JsepAudioCodecDescription&)*mAnsCodecs[0]).EnableTransportCC();
  InitTracks(SdpMediaSection::kAudio);
  InitSdp(SdpMediaSection::kAudio);
  OfferAnswer();

  for (const JsepTrack* track : {&mSendOff, &mRecvAns, &mSendAns, &mRecvOff}) {
    ASSERT_TRUE(track->GetNegotiatedDetails());
    std::vector<AudioCodecConfig> configs;
    dom::RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs(
        *track->GetNegotiatedDetails(), &configs);
    bool sawOpusWithTC = false;
    for (const auto& config : configs) {
      if (config.mName == "opus") {
        sawOpusWithTC = config.mTransportCCFbSet;
      }
    }
    EXPECT_TRUE(sawOpusWithTC)
        << "opus should have mTransportCCFbSet=true after both sides negotiate "
           "transport-cc";
  }
}

TEST_F(JsepTrackTest, VideoNegotiationOfferTransportCC) {
  InitCodecs({.offer = {.enableRemb = false, .enableTransportCC = true},
              .answer = {.enableRemb = false, .enableTransportCC = false}});
  // 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, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
}

TEST_F(JsepTrackTest, VideoNegotiationAnswerTransportCC) {
  InitCodecs({.offer = {.enableRemb = false, .enableTransportCC = false},
              .answer = {.enableRemb = false, .enableTransportCC = true}});
  // 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, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 0U);
}

TEST_F(JsepTrackTest, VideoNegotiationOfferAnswerTransportCC) {
  InitCodecs({.enableRemb = false, .enableTransportCC = true});
  // 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, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvAns, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
  ASSERT_TRUE((codec = GetVideoCodec(mSendAns, 30)));
  ASSERT_EQ(codec->mOtherFbTypes.size(), 1U);
  CheckOtherFbExists(*codec, SdpRtcpFbAttributeList::kTransportCC);
  ASSERT_TRUE((codec = GetVideoCodec(mRecvOff, 30)));
  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) {
  InitCodecs(CodecOverrides{});
  InitTracks(SdpMediaSection::kApplication);

  mOffer.reset(new SipccSdp(SdpOrigin(""00, sdp::kIPv4, "")));
  mOffer->AddMediaSection(SdpMediaSection::kApplication,
                          SdpDirectionAttribute::kSendrecv, 0,
                          SdpMediaSection::kDtlsSctp, sdp::kIPv4, "0.0.0.0");
  mAnswer.reset(new SipccSdp(SdpOrigin(""00, 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) {
  InitCodecs(CodecOverrides{});

  mOffCodecs.pop_back();
  mOffCodecs.emplace_back(new JsepApplicationCodecDescription(
      "webrtc-datachannel"256455510544));

  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(CodecOverrides{});
  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(CodecOverrides{});

  // 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, 30)));
  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, OfferRedUlpfecNoRtx) {
  InitCodecs({.offer = {.addFecCodecs = true, .enableRtx = false},
              .answer = {.addFecCodecs = true, .enableRtx = true}});
  InitTracks(SdpMediaSection::kVideo);
  InitSdp(SdpMediaSection::kVideo);
  OfferAnswer();

  CheckOffEncodingCount(1);
  CheckAnsEncodingCount(1);

  for (const auto& type : {"VP8""AV1""H264"}) {
    for (const auto& [description, track] :
         {std::tuple{"Offerer send", &mSendOff},
          std::tuple{"Offerer recv", &mRecvOff},
          std::tuple{"Answerer send", &mSendAns},
          std::tuple{"Answerer recv", &mRecvAns}}) {
      auto codec = AsVideo(GetCodec(*track, type));
      ASSERT_TRUE(codec)
      << description << " track has codec for " << type;
      ASSERT_TRUE(codec->mFECEnabled)
      << description << " " << type << " has fec";
      ASSERT_FALSE(codec->mULPFECPayloadType.empty())
      << description << " " << type << " has ulpfec";
      ASSERT_FALSE(codec->mREDPayloadType.empty())
      << description << " " << type << " has red";
      ASSERT_TRUE(codec->mREDRTXPayloadType.empty())
      << description << " " << type << " does not have red/rtx";
    }
  }
}

TEST_F(JsepTrackTest, AnswerRedUlpfecNoRtx) {
  InitCodecs({.offer = {.addFecCodecs = true, .enableRtx = true},
              .answer = {.addFecCodecs = true, .enableRtx = false}});
  InitTracks(SdpMediaSection::kVideo);
  InitSdp(SdpMediaSection::kVideo);
  OfferAnswer();

  CheckOffEncodingCount(1);
  CheckAnsEncodingCount(1);

  for (const auto& type : {"VP8""AV1""H264"}) {
    for (const auto& [description, track] :
         {std::tuple{"Offerer send", &mSendOff},
          std::tuple{"Offerer recv", &mRecvOff},
          std::tuple{"Answerer send", &mSendAns},
          std::tuple{"Answerer recv", &mRecvAns}}) {
      auto codec = AsVideo(GetCodec(*track, type));
      ASSERT_TRUE(codec)
      << description << " track has codec for " << type;
      ASSERT_TRUE(codec->mFECEnabled)
      << description << " " << type << " has fec";
      ASSERT_FALSE(codec->mULPFECPayloadType.empty())
      << description << " " << type << " has ulpfec";
      ASSERT_FALSE(codec->mREDPayloadType.empty())
      << description << " " << type << " has red";
      if (track != &mRecvOff) {
        ASSERT_TRUE(codec->mREDRTXPayloadType.empty())
        << description << " " << type << " does not have red/rtx";
      } else {
        ASSERT_FALSE(codec->mREDRTXPayloadType.empty())
        << description << " " << type
        << " has red/rtx, since it offered to receive it";
      }
    }
  }
}

TEST_F(JsepTrackTest, AudioSdpFmtpLine) {
  mOffCodecs = MakeCodecs(
      {.addFecCodecs = true, .preferRed = true, .addDtmfCodec = true});
  mAnsCodecs = MakeCodecs(
      {.addFecCodecs = true, .preferRed = true, .addDtmfCodec = true});
  InitTracks(SdpMediaSection::kAudio);
  InitSdp(SdpMediaSection::kAudio);
  OfferAnswer();

  // SanityCheck checks that the sdpFmtpLine for a local codec matches that of
  // the corresponding remote codec.
  UniquePtr<JsepAudioCodecDescription> codec;
  EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 30)));
  EXPECT_EQ("opus", codec->mName);
  EXPECT_EQ("maxplaybackrate=48000;stereo=1;useinbandfec=1",
            codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 30)));
  EXPECT_EQ("opus", codec->mName);
  EXPECT_EQ("maxplaybackrate=48000;stereo=1;useinbandfec=1",
            codec->mSdpFmtpLine.valueOr("nothing"));

  EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 31)));
  EXPECT_EQ("G722", codec->mName);
  EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 31)));
  EXPECT_EQ("G722", codec->mName);
  EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));

  EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 32)));
  EXPECT_EQ("telephone-event", codec->mName);
  EXPECT_EQ("0-15", codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 32)));
  EXPECT_EQ("telephone-event", codec->mName);
  EXPECT_EQ("0-15", codec->mSdpFmtpLine.valueOr("nothing"));
}

TEST_F(JsepTrackTest, NonDefaultAudioSdpFmtpLine) {
  mOffCodecs = MakeCodecs(
      {.addFecCodecs = true, .preferRed = true, .addDtmfCodec = true});
  mAnsCodecs = MakeCodecs(
      {.addFecCodecs = true, .preferRed = true, .addDtmfCodec = true});

  for (auto& codec : mOffCodecs) {
    if (codec->mName == "opus") {
      auto* audio = static_cast<JsepAudioCodecDescription*>(codec.get());
      audio->mForceMono = true;
      audio->mMaxPlaybackRate = 32000;
    }
  }

  for (auto& codec : mAnsCodecs) {
    if (codec->mName == "opus") {
      auto* audio = static_cast<JsepAudioCodecDescription*>(codec.get());
      audio->mFECEnabled = true;
      audio->mCbrEnabled = true;
      audio->mDTXEnabled = true;
      audio->mFrameSizeMs = 10;
      audio->mMinFrameSizeMs = 5;
      audio->mMaxFrameSizeMs = 20;
    }
  }

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

  {
    // telephone-event doesn't store any params in JsepAudioCodecDescription.
    // Set them directly in the offer sdp instead.
    auto params = MakeUnique<SdpFmtpAttributeList::TelephoneEventParameters>();
    params->dtmfTones = "2-9";
    GetOffer().SetFmtp({"101", *params});
  }

  {
    // telephone-event doesn't store any params in JsepAudioCodecDescription.
    // Set them directly in the answer sdp instead.
    auto params = MakeUnique<SdpFmtpAttributeList::TelephoneEventParameters>();
    params->dtmfTones = "0-3,10";
    GetAnswer().SetFmtp({"101", *params});
  }

  OfferAnswer();

  // SanityCheck checks that the sdpFmtpLine for a local codec matches that of
  // the corresponding remote codec.
  UniquePtr<JsepAudioCodecDescription> codec;
  EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 30)));
  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, 30)));
  EXPECT_EQ("opus", codec->mName);
  EXPECT_EQ("maxplaybackrate=32000;stereo=0;useinbandfec=1",
            codec->mSdpFmtpLine.valueOr("nothing"));

  EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 31)));
  EXPECT_EQ("G722", codec->mName);
  EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 31)));
  EXPECT_EQ("G722", codec->mName);
  EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));

  EXPECT_TRUE((codec = GetAudioCodec(mSendOff, 32)));
  EXPECT_EQ("telephone-event", codec->mName);
  EXPECT_EQ("0-3,10", codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetAudioCodec(mSendAns, 32)));
  EXPECT_EQ("telephone-event", codec->mName);
  EXPECT_EQ("2-9", codec->mSdpFmtpLine.valueOr("nothing"));
}

TEST_F(JsepTrackTest, VideoSdpFmtpLine) {
  mOffCodecs = MakeCodecs(
      {.addFecCodecs = true, .preferRed = true, .addDtmfCodec = true});
  mAnsCodecs = MakeCodecs(
      {.addFecCodecs = true, .preferRed = true, .addDtmfCodec = true});
  InitTracks(SdpMediaSection::kVideo);
  InitSdp(SdpMediaSection::kVideo);
  OfferAnswer();

  // SanityCheck checks that the sdpFmtpLine for a local codec matches that of
  // the corresponding remote codec.
  UniquePtr<JsepVideoCodecDescription> codec;
  EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 50)));
  EXPECT_EQ("VP8", codec->mName);
  EXPECT_EQ("max-fs=12288;max-fr=60", codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 50)));
  EXPECT_EQ("VP8", codec->mName);
  EXPECT_EQ("max-fs=12288;max-fr=60", codec->mSdpFmtpLine.valueOr("nothing"));

  EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 51)));
  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, 51)));
  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, 53)));
  EXPECT_EQ("red", codec->mName);
  EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 53)));
  EXPECT_EQ("red", codec->mName);
  EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));

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

TEST_F(JsepTrackTest, NonDefaultVideoSdpFmtpLine) {
  mOffCodecs = MakeCodecs(
      {.addFecCodecs = true, .preferRed = true, .addDtmfCodec = true});
  mAnsCodecs = MakeCodecs(
      {.addFecCodecs = true, .preferRed = true, .addDtmfCodec = true});

  for (auto& codec : mOffCodecs) {
    if (codec->mName == "VP8" || codec->mName == "H264") {
      auto* video = static_cast<JsepVideoCodecDescription*>(codec.get());
      video->mConstraints.maxFs = 1200;
      if (codec->mName == "VP8") {
        video->mConstraints.maxFps = Some(15);
      } else {
        video->mConstraints.maxDpb = 6400;
        video->mConstraints.maxBr = 1000;
        JsepVideoCodecDescription::SetSaneH264Level(0x1F0,
                                                    &video->mProfileLevelId);
      }
    }
  }

  for (auto& codec : mAnsCodecs) {
    if (codec->mName == "VP8" || codec->mName == "H264") {
      auto* video = static_cast<JsepVideoCodecDescription*>(codec.get());
      video->mConstraints.maxFs = 32400;
      if (codec->mName == "VP8") {
        video->mConstraints.maxFps = Some(60);
      } else {
        video->mConstraints.maxMbps = 1944000;
        video->mConstraints.maxCpb = 800000;
        video->mConstraints.maxDpb = 128000;
        JsepVideoCodecDescription::SetSaneH264Level(0xAB,
                                                    &video->mProfileLevelId);
        video->mPacketizationMode = 1;
      }
    }
  }

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

  // SanityCheck checks that the sdpFmtpLine for a local codec matches that of
  // the corresponding remote codec.
  UniquePtr<JsepVideoCodecDescription> codec;
  EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 50)));
  EXPECT_EQ("VP8", codec->mName);
  EXPECT_EQ("max-fs=32400;max-fr=60", codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 50)));
  EXPECT_EQ("VP8", codec->mName);
  EXPECT_EQ("max-fs=1200;max-fr=15", codec->mSdpFmtpLine.valueOr("nothing"));

  EXPECT_TRUE((codec = GetVideoCodec(mSendOff, 51)));
  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, 51)));
  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, 53)));
  EXPECT_EQ("red", codec->mName);
  EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));
  EXPECT_TRUE((codec = GetVideoCodec(mSendAns, 53)));
  EXPECT_EQ("red", codec->mName);
  EXPECT_EQ("nothing", codec->mSdpFmtpLine.valueOr("nothing"));

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

TEST(JsepTrackRecvPayloadTypesTest, SingleTrackPTsAreUnique)
{
  constexpr auto audio = SdpMediaSection::MediaType::kAudio;

  std::vector<UniquePtr<JsepCodecDescription>> codecs;
  codecs.emplace_back(
      MakeUnique<JsepAudioCodecDescription>("1""codec1"480001));

  SipccSdp offer1(SdpOrigin(""00, sdp::kIPv4, ""));
  SdpMediaSection& offer1Msection1 = offer1.AddMediaSection(
      audio, SdpDirectionAttribute::kRecvonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");

  SipccSdp answer1(SdpOrigin(""00, sdp::kIPv4, ""));
  SdpMediaSection& answer1Msection1 = answer1.AddMediaSection(
      audio, SdpDirectionAttribute::kSendonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");

  for (const auto& codec : codecs) {
    codec->mDirection = sdp::kSend;
    offer1Msection1.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock,
                             codec->mChannels);
    UniquePtr<JsepCodecDescription> clone = codec->Clone();
    clone->mDirection = sdp::kRecv;
    clone->AddToMediaSection(answer1Msection1);
  }

  JsepTrack t1{audio, sdp::Direction::kRecv};
  t1.PopulateCodecs(codecs, false);
  t1.RecvTrackSetLocal(offer1Msection1);
  t1.RecvTrackSetRemote(answer1, answer1Msection1);
  ASSERT_EQ(t1.Negotiate(answer1Msection1, answer1Msection1, offer1Msection1),
            NS_OK);

  std::vector tracks{&t1};
  JsepTrack::SetReceivePayloadTypes(tracks);
  EXPECT_THAT(t1.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(1));
  EXPECT_THAT(t1.GetOtherReceivePayloadTypes(), UnorderedElementsAre());
}

TEST(JsepTrackRecvPayloadTypesTest, DoubleTrackPTsAreUnique)
{
  constexpr auto audio = SdpMediaSection::MediaType::kAudio;

  std::vector<UniquePtr<JsepCodecDescription>> codecs1;
  codecs1.emplace_back(
      MakeUnique<JsepAudioCodecDescription>("1""codec1"480001));

  std::vector<UniquePtr<JsepCodecDescription>> codecs2;
  codecs2.emplace_back(
      MakeUnique<JsepAudioCodecDescription>("2""codec1"480001));

  SipccSdp offer1(SdpOrigin(""00, sdp::kIPv4, ""));
  SdpMediaSection& offer1Msection1 = offer1.AddMediaSection(
      audio, SdpDirectionAttribute::kRecvonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");
  SdpMediaSection& offer1Msection2 = offer1.AddMediaSection(
      audio, SdpDirectionAttribute::kRecvonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");

  SipccSdp answer1(SdpOrigin(""00, sdp::kIPv4, ""));
  SdpMediaSection& answer1Msection1 = answer1.AddMediaSection(
      audio, SdpDirectionAttribute::kSendonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");
  SdpMediaSection& answer1Msection2 = answer1.AddMediaSection(
      audio, SdpDirectionAttribute::kSendonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");

  for (const auto& codec : codecs1) {
    codec->mDirection = sdp::kSend;
    offer1Msection1.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock,
                             codec->mChannels);
    UniquePtr<JsepCodecDescription> clone = codec->Clone();
    clone->mDirection = sdp::kRecv;
    clone->AddToMediaSection(answer1Msection1);
  }

  for (const auto& codec : codecs2) {
    codec->mDirection = sdp::kSend;
    offer1Msection2.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock,
                             codec->mChannels);
    UniquePtr<JsepCodecDescription> clone = codec->Clone();
    clone->mDirection = sdp::kRecv;
    clone->AddToMediaSection(answer1Msection2);
  }

  JsepTrack t1{audio, sdp::Direction::kRecv};
  t1.PopulateCodecs(codecs1, false);
  t1.RecvTrackSetLocal(offer1Msection1);
  t1.RecvTrackSetRemote(answer1, answer1Msection1);
  ASSERT_EQ(t1.Negotiate(answer1Msection1, answer1Msection1, offer1Msection1),
            NS_OK);

  JsepTrack t2{audio, sdp::Direction::kRecv};
  t2.PopulateCodecs(codecs2, false);
  t2.RecvTrackSetLocal(offer1Msection2);
  t2.RecvTrackSetRemote(answer1, answer1Msection2);
  ASSERT_EQ(t2.Negotiate(answer1Msection2, answer1Msection2, offer1Msection2),
            NS_OK);

  std::vector tracks{&t1, &t2};
  JsepTrack::SetReceivePayloadTypes(tracks);
  EXPECT_THAT(t1.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(1));
  EXPECT_THAT(t1.GetOtherReceivePayloadTypes(), UnorderedElementsAre(2));
  EXPECT_THAT(t2.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(2));
  EXPECT_THAT(t2.GetOtherReceivePayloadTypes(), UnorderedElementsAre(1));
}

TEST(JsepTrackRecvPayloadTypesTest, DoubleTrackPTsAreDuplicates)
{
  constexpr auto audio = SdpMediaSection::MediaType::kAudio;

  std::vector<UniquePtr<JsepCodecDescription>> codecs1;
  codecs1.emplace_back(
      MakeUnique<JsepAudioCodecDescription>("1""codec1"480001));

  std::vector<UniquePtr<JsepCodecDescription>> codecs2;
  codecs2.emplace_back(
      MakeUnique<JsepAudioCodecDescription>("1""codec1"480001));

  SipccSdp offer1(SdpOrigin(""00, sdp::kIPv4, ""));
  SdpMediaSection& offer1Msection1 = offer1.AddMediaSection(
      audio, SdpDirectionAttribute::kRecvonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");
  SdpMediaSection& offer1Msection2 = offer1.AddMediaSection(
      audio, SdpDirectionAttribute::kRecvonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");

  SipccSdp answer1(SdpOrigin(""00, sdp::kIPv4, ""));
  SdpMediaSection& answer1Msection1 = answer1.AddMediaSection(
      audio, SdpDirectionAttribute::kSendonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");
  SdpMediaSection& answer1Msection2 = answer1.AddMediaSection(
      audio, SdpDirectionAttribute::kSendonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");

  for (const auto& codec : codecs1) {
    codec->mDirection = sdp::kSend;
    offer1Msection1.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock,
                             codec->mChannels);
    UniquePtr<JsepCodecDescription> clone = codec->Clone();
    clone->mDirection = sdp::kRecv;
    clone->AddToMediaSection(answer1Msection1);
  }
  for (const auto& codec : codecs2) {
    codec->mDirection = sdp::kSend;
    offer1Msection2.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock,
                             codec->mChannels);
    UniquePtr<JsepCodecDescription> clone = codec->Clone();
    clone->mDirection = sdp::kRecv;
    clone->AddToMediaSection(answer1Msection2);
  }

  JsepTrack t1{audio, sdp::Direction::kRecv};
  t1.PopulateCodecs(codecs1, false);
  t1.RecvTrackSetLocal(offer1Msection1);
  t1.RecvTrackSetRemote(answer1, answer1Msection1);
  ASSERT_EQ(t1.Negotiate(answer1Msection1, answer1Msection1, offer1Msection1),
            NS_OK);

  JsepTrack t2{audio, sdp::Direction::kRecv};
  t2.PopulateCodecs(codecs2, false);
  t2.RecvTrackSetLocal(offer1Msection2);
  t2.RecvTrackSetRemote(answer1, answer1Msection2);
  ASSERT_EQ(t2.Negotiate(answer1Msection2, answer1Msection2, offer1Msection2),
            NS_OK);

  std::vector tracks{&t1, &t2};
  JsepTrack::SetReceivePayloadTypes(tracks);
  EXPECT_THAT(t1.GetUniqueReceivePayloadTypes(), UnorderedElementsAre());
  EXPECT_THAT(t1.GetOtherReceivePayloadTypes(), UnorderedElementsAre(1));
  EXPECT_THAT(t2.GetUniqueReceivePayloadTypes(), UnorderedElementsAre());
  EXPECT_THAT(t2.GetOtherReceivePayloadTypes(), UnorderedElementsAre(1));
}

TEST(JsepTrackRecvPayloadTypesTest, DoubleTrackPTsOverlap)
{
  constexpr auto audio = SdpMediaSection::MediaType::kAudio;

  std::vector<UniquePtr<JsepCodecDescription>> codecs1;
  codecs1.emplace_back(
      MakeUnique<JsepAudioCodecDescription>("1""codec1"480001));
  codecs1.emplace_back(
      MakeUnique<JsepAudioCodecDescription>("2""codec2"480001));

  std::vector<UniquePtr<JsepCodecDescription>> codecs2;
  codecs2.emplace_back(
      MakeUnique<JsepAudioCodecDescription>("1""codec1"480001));
  codecs2.emplace_back(
      MakeUnique<JsepAudioCodecDescription>("3""codec2"480001));

  SipccSdp offer1(SdpOrigin(""00, sdp::kIPv4, ""));
  SdpMediaSection& offer1Msection1 = offer1.AddMediaSection(
      audio, SdpDirectionAttribute::kRecvonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");
  SdpMediaSection& offer1Msection2 = offer1.AddMediaSection(
      audio, SdpDirectionAttribute::kRecvonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");

  SipccSdp answer1(SdpOrigin(""00, sdp::kIPv4, ""));
  SdpMediaSection& answer1Msection1 = answer1.AddMediaSection(
      audio, SdpDirectionAttribute::kSendonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");
  SdpMediaSection& answer1Msection2 = answer1.AddMediaSection(
      audio, SdpDirectionAttribute::kSendonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");

  for (const auto& codec : codecs1) {
    codec->mDirection = sdp::kSend;
    offer1Msection1.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock,
                             codec->mChannels);
    UniquePtr<JsepCodecDescription> clone = codec->Clone();
    clone->mDirection = sdp::kRecv;
    clone->AddToMediaSection(answer1Msection1);
  }

  for (const auto& codec : codecs2) {
    codec->mDirection = sdp::kSend;
    offer1Msection2.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock,
                             codec->mChannels);
    UniquePtr<JsepCodecDescription> clone = codec->Clone();
    clone->mDirection = sdp::kRecv;
    clone->AddToMediaSection(answer1Msection2);
  }

  JsepTrack t1{audio, sdp::Direction::kRecv};
  t1.PopulateCodecs(codecs1, false);
  t1.RecvTrackSetLocal(offer1Msection1);
  t1.RecvTrackSetRemote(answer1, answer1Msection1);
  ASSERT_EQ(t1.Negotiate(answer1Msection1, answer1Msection1, offer1Msection1),
            NS_OK);

  JsepTrack t2{audio, sdp::Direction::kRecv};
  t2.PopulateCodecs(codecs2, false);
  t2.RecvTrackSetLocal(offer1Msection2);
  t2.RecvTrackSetRemote(answer1, answer1Msection2);
  ASSERT_EQ(t2.Negotiate(answer1Msection2, answer1Msection2, offer1Msection2),
            NS_OK);

  std::vector tracks{&t1, &t2};
  JsepTrack::SetReceivePayloadTypes(tracks);
  EXPECT_THAT(t1.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(2));
  EXPECT_THAT(t1.GetOtherReceivePayloadTypes(), UnorderedElementsAre(13));
  EXPECT_THAT(t2.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(3));
  EXPECT_THAT(t2.GetOtherReceivePayloadTypes(), UnorderedElementsAre(12));
}

TEST(JsepTrackRecvPayloadTypesTest, DoubleTrackPTsDuplicateAfterRenegotiation)
{
  constexpr auto audio = SdpMediaSection::MediaType::kAudio;

  std::vector<UniquePtr<JsepCodecDescription>> codecs1;
  codecs1.emplace_back(
      MakeUnique<JsepAudioCodecDescription>("1""codec1"480001));
  codecs1.emplace_back(
      MakeUnique<JsepAudioCodecDescription>("2""codec2"480001));

  std::vector<UniquePtr<JsepCodecDescription>> codecs2;
  codecs2.emplace_back(
      MakeUnique<JsepAudioCodecDescription>("3""codec1"480001));
  codecs2.emplace_back(
      MakeUnique<JsepAudioCodecDescription>("4""codec2"480001));

  // First negotiation.
  SipccSdp offer1(SdpOrigin(""00, sdp::kIPv4, ""));
  SdpMediaSection& offer1Msection1 = offer1.AddMediaSection(
      audio, SdpDirectionAttribute::kRecvonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");
  SdpMediaSection& offer1Msection2 = offer1.AddMediaSection(
      audio, SdpDirectionAttribute::kRecvonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");

  SipccSdp answer1(SdpOrigin(""00, sdp::kIPv4, ""));
  SdpMediaSection& answer1Msection1 = answer1.AddMediaSection(
      audio, SdpDirectionAttribute::kSendonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");
  SdpMediaSection& answer1Msection2 = answer1.AddMediaSection(
      audio, SdpDirectionAttribute::kSendonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");

  for (const auto& codec : codecs1) {
    codec->mDirection = sdp::kSend;
    offer1Msection1.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock,
                             codec->mChannels);
    UniquePtr<JsepCodecDescription> clone = codec->Clone();
    clone->mDirection = sdp::kRecv;
    clone->AddToMediaSection(answer1Msection1);
  }

  for (const auto& codec : codecs2) {
    codec->mDirection = sdp::kSend;
    offer1Msection2.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock,
                             codec->mChannels);
    UniquePtr<JsepCodecDescription> clone = codec->Clone();
    clone->mDirection = sdp::kRecv;
    clone->AddToMediaSection(answer1Msection2);
  }

  // t1 and t2 use distinct payload types in the first negotiation.
  JsepTrack t1{audio, sdp::Direction::kRecv};
  t1.PopulateCodecs(codecs1, false);
  t1.RecvTrackSetLocal(offer1Msection1);
  t1.RecvTrackSetRemote(answer1, answer1Msection1);
  ASSERT_EQ(t1.Negotiate(answer1Msection1, answer1Msection1, offer1Msection1),
            NS_OK);

  JsepTrack t2{audio, sdp::Direction::kRecv};
  t2.PopulateCodecs(codecs2, false);
  t2.RecvTrackSetLocal(offer1Msection2);
  t2.RecvTrackSetRemote(answer1, answer1Msection2);
  ASSERT_EQ(t2.Negotiate(answer1Msection2, answer1Msection2, offer1Msection2),
            NS_OK);

  std::vector tracks{&t1, &t2};
  JsepTrack::SetReceivePayloadTypes(tracks);
  EXPECT_THAT(t1.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(12));
  EXPECT_THAT(t1.GetOtherReceivePayloadTypes(), UnorderedElementsAre(34));
  EXPECT_THAT(t2.GetUniqueReceivePayloadTypes(), UnorderedElementsAre(34));
  EXPECT_THAT(t2.GetOtherReceivePayloadTypes(), UnorderedElementsAre(12));

  // Second negotiation.
  SipccSdp offer2(SdpOrigin(""00, sdp::kIPv4, ""));
  SdpMediaSection& offer2Msection1 = offer2.AddMediaSection(
      audio, SdpDirectionAttribute::kRecvonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");
  SdpMediaSection& offer2Msection2 = offer2.AddMediaSection(
      audio, SdpDirectionAttribute::kRecvonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");

  SipccSdp answer2(SdpOrigin(""00, sdp::kIPv4, ""));
  SdpMediaSection& answer2Msection1 = answer2.AddMediaSection(
      audio, SdpDirectionAttribute::kSendonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");
  SdpMediaSection& answer2Msection2 = answer2.AddMediaSection(
      audio, SdpDirectionAttribute::kSendonly, 0,
      SdpHelper::GetProtocolForMediaType(audio), sdp::kIPv4, "0.0.0.0");

  for (const auto& codec : codecs1) {
    codec->mDirection = sdp::kSend;
    offer2Msection1.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock,
                             codec->mChannels);
    UniquePtr<JsepCodecDescription> clone = codec->Clone();
    clone->mDirection = sdp::kRecv;
    clone->AddToMediaSection(answer2Msection1);
  }

  for (const auto& codec : codecs2) {
    codec->mDirection = sdp::kSend;
    offer2Msection2.AddCodec(codec->mDefaultPt, codec->mName, codec->mClock,
                             codec->mChannels);
    UniquePtr<JsepCodecDescription> clone = codec->Clone();
    clone->mDirection = sdp::kRecv;
    clone->AddToMediaSection(answer2Msection2);
  }

  t1.PopulateCodecs(codecs1, false);
  t1.RecvTrackSetLocal(offer2Msection1);
  t1.RecvTrackSetRemote(answer2, answer2Msection1);
  ASSERT_EQ(t1.Negotiate(answer2Msection1, answer2Msection1, offer2Msection1),
            NS_OK);

  // Change t2 to use the same payload types as t1. Both tracks should now mark
  // all their payload types as duplicates.
  t2.PopulateCodecs(codecs1, false);
  t2.RecvTrackSetLocal(offer2Msection2);
  t2.RecvTrackSetRemote(answer2, answer2Msection2);
  ASSERT_EQ(t2.Negotiate(answer2Msection2, answer2Msection2, offer2Msection2),
            NS_OK);

  std::vector newTracks{&t1, &t2};
  JsepTrack::SetReceivePayloadTypes(newTracks);
  EXPECT_THAT(t1.GetUniqueReceivePayloadTypes(), UnorderedElementsAre());
  EXPECT_THAT(t1.GetOtherReceivePayloadTypes(), UnorderedElementsAre(12));
  EXPECT_THAT(t2.GetUniqueReceivePayloadTypes(), UnorderedElementsAre());
  EXPECT_THAT(t2.GetOtherReceivePayloadTypes(), UnorderedElementsAre(12));
}
}  // namespace mozilla

Messung V0.5 in Prozent
C=91 H=94 G=92

¤ Dauer der Verarbeitung: 0.34 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

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.