/*
 *  Copyright (c) 2024 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */
#include "media/base/codec_comparators.h"

#include <string>

#include "api/audio_codecs/audio_format.h"
#include "api/rtp_parameters.h"
#include "api/video_codecs/scalability_mode.h"
#include "api/video_codecs/sdp_video_format.h"
#include "api/video_codecs/vp9_profile.h"
#include "media/base/codec.h"
#include "media/base/media_constants.h"
#include "test/gtest.h"

namespace webrtc {

using ::testing::TestWithParam;
using ::testing::ValuesIn;

TEST(CodecComparatorsTest, CodecMatchesItself) {
  Codec codec = CreateVideoCodec("custom");
  EXPECT_TRUE(MatchesWithCodecRules(codec, codec));
}

TEST(CodecComparatorsTest, MismatchedBasicParameters) {
  Codec codec = CreateAudioCodec(SdpAudioFormat("opus", 48000, 2));
  Codec nonmatch_codec = codec;
  nonmatch_codec.name = "g711";
  EXPECT_FALSE(MatchesWithCodecRules(nonmatch_codec, codec));
  nonmatch_codec = codec;
  nonmatch_codec.clockrate = 8000;
  EXPECT_FALSE(MatchesWithCodecRules(nonmatch_codec, codec));
  nonmatch_codec = codec;
  nonmatch_codec.channels = 1;
  EXPECT_FALSE(MatchesWithCodecRules(nonmatch_codec, codec));
}

TEST(CodecComparatorsTest, H264PacketizationModeMismatch) {
  Codec pt_mode_1 = CreateVideoCodec(kH264CodecName);
  Codec pt_mode_0 = pt_mode_1;
  pt_mode_0.SetParam(kH264FmtpPacketizationMode, "0");
  EXPECT_FALSE(MatchesWithCodecRules(pt_mode_1, pt_mode_0));
  EXPECT_FALSE(MatchesWithCodecRules(pt_mode_0, pt_mode_1));
  Codec no_pt_mode = pt_mode_1;
  no_pt_mode.RemoveParam(kH264FmtpPacketizationMode);
  EXPECT_TRUE(MatchesWithCodecRules(pt_mode_0, no_pt_mode));
  EXPECT_TRUE(MatchesWithCodecRules(no_pt_mode, pt_mode_0));
  EXPECT_FALSE(MatchesWithCodecRules(no_pt_mode, pt_mode_1));
}

TEST(CodecComparatorsTest, AudioParametersIgnored) {
  // Currently, all parameters on audio codecs are ignored for matching.
  Codec basic_opus = CreateAudioCodec(SdpAudioFormat("opus", 48000, 2));
  Codec opus_with_parameters = basic_opus;
  opus_with_parameters.SetParam("stereo", "0");
  EXPECT_TRUE(MatchesWithCodecRules(basic_opus, opus_with_parameters));
  EXPECT_TRUE(MatchesWithCodecRules(opus_with_parameters, basic_opus));
  opus_with_parameters.SetParam("nonsense", "stuff");
  EXPECT_TRUE(MatchesWithCodecRules(basic_opus, opus_with_parameters));
  EXPECT_TRUE(MatchesWithCodecRules(opus_with_parameters, basic_opus));
}

TEST(CodecComparatorsTest, StaticPayloadTypesIgnoreName) {
  // This is the IANA registered format for PT 8
  Codec codec_1 = CreateAudioCodec(8, "pcma", 8000, 1);
  Codec codec_2 = CreateAudioCodec(8, "nonsense", 8000, 1);
  EXPECT_TRUE(MatchesWithCodecRules(codec_1, codec_2));
}

TEST(CodecComparatorsTest, MatchesWithReferenceAttributesRed) {
  // Test that RED codecs' reference attributes get parsed correctly.
  Codec codec_1 = CreateAudioCodec(101, kRedCodecName, 48000, 2);
  codec_1.SetParam(kCodecParamNotInNameValueFormat, "100/100");
  Codec codec_2 = CreateAudioCodec(102, kRedCodecName, 48000, 2);
  codec_2.SetParam(kCodecParamNotInNameValueFormat, "101/101");
  // Mixed codecs in RED
  Codec codec_3 = CreateAudioCodec(103, kRedCodecName, 48000, 2);
  codec_3.SetParam(kCodecParamNotInNameValueFormat, "100/101");
  // Identical codecs always match.
  EXPECT_TRUE(MatchesWithReferenceAttributes(codec_1, codec_1));
  EXPECT_TRUE(MatchesWithReferenceAttributes(codec_2, codec_2));
  EXPECT_TRUE(MatchesWithReferenceAttributes(codec_3, codec_3));
  // Mismatched reference codec lists.
  EXPECT_FALSE(MatchesWithReferenceAttributes(codec_1, codec_2));
  EXPECT_FALSE(MatchesWithReferenceAttributes(codec_1, codec_3));
  EXPECT_FALSE(MatchesWithReferenceAttributes(codec_2, codec_3));
  // Overflow of longer lists are ignored.
  // Overlong list - overflow should be ignored.
  Codec codec_4 = CreateAudioCodec(103, kRedCodecName, 48000, 2);
  codec_4.SetParam(kCodecParamNotInNameValueFormat, "100/100/101/102");
  EXPECT_TRUE(MatchesWithReferenceAttributes(codec_4, codec_4));
  EXPECT_TRUE(MatchesWithReferenceAttributes(codec_1, codec_4));
  // Broken syntax will cause a non-match with anything except itself.
  Codec codec_5 = CreateAudioCodec(103, kRedCodecName, 48000, 2);
  codec_5.SetParam(kCodecParamNotInNameValueFormat, "");
  EXPECT_TRUE(MatchesWithReferenceAttributes(codec_5, codec_5));
  EXPECT_FALSE(MatchesWithReferenceAttributes(codec_1, codec_5));
}

TEST(CodecComparatorsTest, RedParametersMismatch) {
  Codec with_params = CreateAudioCodec(101, kRedCodecName, 48000, 2);
  with_params.SetParam(kCodecParamNotInNameValueFormat, "111/111");

  Codec no_params = CreateAudioCodec(102, kRedCodecName, 48000, 2);

  // Case 1: Exactly one lacks parameters, both have IDs -> SHOULD NOT match for
  // audio RED.
  EXPECT_FALSE(MatchesWithReferenceAttributes(with_params, no_params));
  EXPECT_FALSE(MatchesWithReferenceAttributes(no_params, with_params));

  // Case 2: Exactly one lacks parameters, the one WITHOUT parameters is
  // unassigned -> SHOULD match.
  // This is the case with late assignment.
  Codec no_params_unassigned = no_params;
  no_params_unassigned.id = Codec::kIdNotSet;
  EXPECT_TRUE(
      MatchesWithReferenceAttributes(with_params, no_params_unassigned));
  EXPECT_TRUE(
      MatchesWithReferenceAttributes(no_params_unassigned, with_params));

  // Case 3: Exactly one lacks parameters, the one WITH parameters is
  // unassigned -> SHOULD match for audio RED because one side is unassigned.
  Codec with_params_unassigned = with_params;
  with_params_unassigned.id = Codec::kIdNotSet;
  EXPECT_TRUE(
      MatchesWithReferenceAttributes(with_params_unassigned, no_params));
  EXPECT_TRUE(
      MatchesWithReferenceAttributes(no_params, with_params_unassigned));

  // Case 4: Exactly one lacks parameters, both are unassigned -> SHOULD match.
  EXPECT_TRUE(MatchesWithReferenceAttributes(with_params_unassigned,
                                             no_params_unassigned));
  EXPECT_TRUE(MatchesWithReferenceAttributes(no_params_unassigned,
                                             with_params_unassigned));

  // Case 5: Both lack parameters -> SHOULD match.
  Codec no_params_2 = CreateAudioCodec(103, kRedCodecName, 48000, 2);
  EXPECT_TRUE(MatchesWithReferenceAttributes(no_params, no_params_2));

  // A video RED codec should not match any audio RED codec,
  // independent of parameters.
  Codec video_red_codec = CreateVideoCodec(107, kRedCodecName);
  EXPECT_FALSE(MatchesWithReferenceAttributes(no_params, video_red_codec));
  EXPECT_FALSE(MatchesWithReferenceAttributes(with_params, video_red_codec));

  // Two video RED codecs with different IDs and one lacking parameters
  // should NOT match.
  Codec video_red_with_params = CreateVideoCodec(108, kRedCodecName);
  video_red_with_params.SetParam(kCodecParamNotInNameValueFormat, "120/120");
  Codec video_red_no_params = CreateVideoCodec(109, kRedCodecName);
  EXPECT_FALSE(MatchesWithReferenceAttributes(video_red_with_params,
                                              video_red_no_params));
  EXPECT_FALSE(MatchesWithReferenceAttributes(video_red_no_params,
                                              video_red_with_params));
}

struct TestParams {
  std::string name;
  SdpVideoFormat codec1;
  SdpVideoFormat codec2;
  bool expected_result;
};

using IsSameRtpCodecTest = TestWithParam<TestParams>;

TEST_P(IsSameRtpCodecTest, IsSameRtpCodec) {
  TestParams param = GetParam();
  Codec codec1 = CreateVideoCodec(param.codec1);
  Codec codec2 = CreateVideoCodec(param.codec2);

  EXPECT_EQ(IsSameRtpCodec(codec1, codec2.ToCodecParameters()),
            param.expected_result);
}

INSTANTIATE_TEST_SUITE_P(
    CodecTest,
    IsSameRtpCodecTest,
    ValuesIn<TestParams>({
        {.name = "CodecWithDifferentName",
         .codec1 = {"VP9", {}},
         .codec2 = {"VP8", {}},
         .expected_result = false},
        {.name = "Vp8WithoutParameters",
         .codec1 = {"vp8", {}},
         .codec2 = {"VP8", {}},
         .expected_result = true},
        {.name = "Vp8WithSameParameters",
         .codec1 = {"VP8", {{"x", "1"}}},
         .codec2 = {"VP8", {{"x", "1"}}},
         .expected_result = true},
        {.name = "Vp8WithDifferentParameters",
         .codec1 = {"VP8", {}},
         .codec2 = {"VP8", {{"x", "1"}}},
         .expected_result = false},
        {.name = "Av1WithoutParameters",
         .codec1 = {"AV1", {}},
         .codec2 = {"AV1", {}},
         .expected_result = true},
        {.name = "Av1WithSameProfile",
         .codec1 = {"AV1", SdpVideoFormat::AV1Profile0().parameters},
         .codec2 = {"AV1", SdpVideoFormat::AV1Profile0().parameters},
         .expected_result = true},
        {.name = "Av1WithoutParametersTreatedAsProfile0",
         .codec1 = {"AV1", SdpVideoFormat::AV1Profile0().parameters},
         .codec2 = {"AV1", {}},
         .expected_result = true},
        {.name = "Av1WithoutProfileTreatedAsProfile0",
         .codec1 = {"AV1", {{kAv1FmtpProfile, "0"}, {"x", "1"}}},
         .codec2 = {"AV1", {{"x", "1"}}},
         .expected_result = true},
        {.name = "Av1WithDifferentProfile",
         .codec1 = {"AV1", SdpVideoFormat::AV1Profile0().parameters},
         .codec2 = {"AV1", SdpVideoFormat::AV1Profile1().parameters},
         .expected_result = false},
        {.name = "Av1WithDifferentParameters",
         .codec1 = {"AV1", {{kAv1FmtpProfile, "0"}, {"x", "1"}}},
         .codec2 = {"AV1", {{kAv1FmtpProfile, "0"}, {"x", "2"}}},
         .expected_result = false},
        {.name = "Vp9WithSameProfile",
         .codec1 = {"VP9", SdpVideoFormat::VP9Profile0().parameters},
         .codec2 = {"VP9", SdpVideoFormat::VP9Profile0().parameters},
         .expected_result = true},
        {.name = "Vp9WithoutProfileTreatedAsProfile0",
         .codec1 = {"VP9", {{kVP9FmtpProfileId, "0"}, {"x", "1"}}},
         .codec2 = {"VP9", {{"x", "1"}}},
         .expected_result = true},
        {.name = "Vp9WithDifferentProfile",
         .codec1 = {"VP9", SdpVideoFormat::VP9Profile0().parameters},
         .codec2 = {"VP9", SdpVideoFormat::VP9Profile1().parameters},
         .expected_result = false},
        {.name = "H264WithSamePacketizationMode",
         .codec1 = {"H264", {{kH264FmtpPacketizationMode, "0"}}},
         .codec2 = {"H264", {{kH264FmtpPacketizationMode, "0"}}},
         .expected_result = true},
        {.name = "H264WithoutPacketizationModeTreatedAsMode0",
         .codec1 = {"H264", {{kH264FmtpPacketizationMode, "0"}, {"x", "1"}}},
         .codec2 = {"H264", {{"x", "1"}}},
         .expected_result = true},
        {.name = "H264WithDifferentPacketizationMode",
         .codec1 = {"H264", {{kH264FmtpPacketizationMode, "0"}}},
         .codec2 = {"H264", {{kH264FmtpPacketizationMode, "1"}}},
         .expected_result = false},
#ifdef RTC_ENABLE_H265
        {.name = "H265WithSameProfile",
         .codec1 = {"H265",
                    {{kH265FmtpProfileId, "1"},
                     {kH265FmtpTierFlag, "0"},
                     {kH265FmtpLevelId, "93"},
                     {kH265FmtpTxMode, "SRST"}}},
         .codec2 = {"H265",
                    {{kH265FmtpProfileId, "1"},
                     {kH265FmtpTierFlag, "0"},
                     {kH265FmtpLevelId, "93"},
                     {kH265FmtpTxMode, "SRST"}}},
         .expected_result = true},
        {.name = "H265WithoutParametersTreatedAsDefault",
         .codec1 = {"H265",
                    {{kH265FmtpProfileId, "1"},
                     {kH265FmtpTierFlag, "0"},
                     {kH265FmtpLevelId, "93"},
                     {kH265FmtpTxMode, "SRST"}}},
         .codec2 = {"H265", {}},
         .expected_result = true},
        {.name = "H265WithDifferentProfile",
         .codec1 = {"H265",
                    {{kH265FmtpProfileId, "1"},
                     {kH265FmtpTierFlag, "0"},
                     {kH265FmtpLevelId, "93"},
                     {kH265FmtpTxMode, "SRST"}}},
         .codec2 = {"H265",
                    {{kH265FmtpProfileId, "1"},
                     {kH265FmtpTierFlag, "1"},
                     {kH265FmtpLevelId, "93"},
                     {kH265FmtpTxMode, "SRST"}}},
         .expected_result = false},
#endif
    }),
    [](const testing::TestParamInfo<IsSameRtpCodecTest::ParamType>& info) {
      return info.param.name;
    });

// For H264, the profile and level IDs are entangled into a single
// "profile-level-id" attribute, so let's test many different versions.
// See https://cconcolato.github.io/media-mime-support/ for inspiration.
TEST(IsSameRtpCodecIgnoringLevelTest, IgnoresH264Levels) {
  // AVC Baseline Level 3.1
  Codec baseline_3_1 =
      CreateVideoCodec(SdpVideoFormat("H264",
                                      {{kH264FmtpLevelAsymmetryAllowed, "1"},
                                       {kH264FmtpPacketizationMode, "1"},
                                       {kH264FmtpProfileLevelId, "42001f"}},
                                      {ScalabilityMode::kL1T1}));
  // AVC Baseline Level 5.2
  Codec baseline_5_2 =
      CreateVideoCodec(SdpVideoFormat("H264",
                                      {{kH264FmtpLevelAsymmetryAllowed, "1"},
                                       {kH264FmtpPacketizationMode, "1"},
                                       {kH264FmtpProfileLevelId, "420034"}},
                                      {ScalabilityMode::kL1T1}));
  // AVC High Level 3.1
  Codec high_3_1 =
      CreateVideoCodec(SdpVideoFormat("H264",
                                      {{kH264FmtpLevelAsymmetryAllowed, "1"},
                                       {kH264FmtpPacketizationMode, "1"},
                                       {kH264FmtpProfileLevelId, "64001f"}},
                                      {ScalabilityMode::kL1T1}));
  // AVC High Level 5.2
  Codec high_5_2 =
      CreateVideoCodec(SdpVideoFormat("H264",
                                      {{kH264FmtpLevelAsymmetryAllowed, "1"},
                                       {kH264FmtpPacketizationMode, "1"},
                                       {kH264FmtpProfileLevelId, "640034"}},
                                      {ScalabilityMode::kL1T1}));
  // AVC High 4:4:4 Predictive Level 3.1
  Codec high_444_predictive_3_1 =
      CreateVideoCodec(SdpVideoFormat("H264",
                                      {{kH264FmtpLevelAsymmetryAllowed, "1"},
                                       {kH264FmtpPacketizationMode, "1"},
                                       {kH264FmtpProfileLevelId, "f4001f"}},
                                      {ScalabilityMode::kL1T1}));

  // AVC Baseline Level 5.2 is compatible with AVC Baseline Level 3.1.
  EXPECT_TRUE(IsSameRtpCodecIgnoringLevel(baseline_5_2,
                                          baseline_3_1.ToCodecParameters()));
  // AVC High is NOT compatible with AVC Baseline.
  EXPECT_FALSE(
      IsSameRtpCodecIgnoringLevel(baseline_3_1, high_3_1.ToCodecParameters()));
  EXPECT_FALSE(
      IsSameRtpCodecIgnoringLevel(baseline_3_1, high_5_2.ToCodecParameters()));
  EXPECT_FALSE(
      IsSameRtpCodecIgnoringLevel(baseline_5_2, high_3_1.ToCodecParameters()));
  EXPECT_FALSE(
      IsSameRtpCodecIgnoringLevel(baseline_5_2, high_5_2.ToCodecParameters()));
  // AVC High 5.2 is compatible with AVC High 3.1
  EXPECT_TRUE(
      IsSameRtpCodecIgnoringLevel(high_5_2, high_3_1.ToCodecParameters()));
  // 4:4:4 Predictive is NOT compatible with either High or Baseline.
  EXPECT_FALSE(IsSameRtpCodecIgnoringLevel(high_444_predictive_3_1,
                                           high_3_1.ToCodecParameters()));
  EXPECT_FALSE(IsSameRtpCodecIgnoringLevel(high_444_predictive_3_1,
                                           high_5_2.ToCodecParameters()));
  EXPECT_FALSE(IsSameRtpCodecIgnoringLevel(high_444_predictive_3_1,
                                           baseline_3_1.ToCodecParameters()));
  EXPECT_FALSE(IsSameRtpCodecIgnoringLevel(high_444_predictive_3_1,
                                           baseline_3_1.ToCodecParameters()));
}

#ifdef RTC_ENABLE_H265
// For H265, the "profile-id" and "level-id" are separate so test can be simple.
// The level-id value for Level X.Y is calculated as (X * 10 + Y) * 3.
// The lowest Level, 1.0, is thus (1 * 10 + 0) * 3 = 30.
TEST(IsSameRtpCodecIgnoringLevelTest, IgnoresH265Levels) {
  // Profile 1, Level 5.2
  Codec profile_1_level_5_2 =
      CreateVideoCodec(SdpVideoFormat("H265",
                                      {{kH265FmtpProfileId, "1"},
                                       {kH265FmtpTierFlag, "0"},
                                       {kH265FmtpLevelId, "156"},
                                       {kH265FmtpTxMode, "SRST"}},
                                      {ScalabilityMode::kL1T1}));
  // Profile 1, Level 6.0
  Codec profile_1_level_6_0 =
      CreateVideoCodec(SdpVideoFormat("H265",
                                      {{kH265FmtpProfileId, "1"},
                                       {kH265FmtpTierFlag, "0"},
                                       {kH265FmtpLevelId, "180"},
                                       {kH265FmtpTxMode, "SRST"}},
                                      {ScalabilityMode::kL1T1}));
  // Profile 2, Level 6.0
  Codec profile_2_level_6_0 =
      CreateVideoCodec(SdpVideoFormat("H265",
                                      {{kH265FmtpProfileId, "2"},
                                       {kH265FmtpTierFlag, "0"},
                                       {kH265FmtpLevelId, "180"},
                                       {kH265FmtpTxMode, "SRST"}},
                                      {ScalabilityMode::kL1T1}));
  // Profile 1 codecs are compatible with each other.
  EXPECT_TRUE(IsSameRtpCodecIgnoringLevel(
      profile_1_level_5_2, profile_1_level_6_0.ToCodecParameters()));
  // Profile 2 codecs are NOT compatible with profile 1 codecs.
  EXPECT_FALSE(IsSameRtpCodecIgnoringLevel(
      profile_2_level_6_0, profile_1_level_5_2.ToCodecParameters()));
  EXPECT_FALSE(IsSameRtpCodecIgnoringLevel(
      profile_2_level_6_0, profile_1_level_6_0.ToCodecParameters()));
}
#endif  // RTC_ENABLE_H265

TEST(CodecTest, TestCodecMatches) {
  // Test a codec with a static payload type.
  Codec c0 = CreateAudioCodec(34, "A", 44100, 1);
  EXPECT_TRUE(c0.Matches(CreateAudioCodec(34, "", 44100, 1)));
  EXPECT_TRUE(c0.Matches(CreateAudioCodec(34, "", 44100, 0)));
  EXPECT_TRUE(c0.Matches(CreateAudioCodec(34, "", 44100, 0)));
  EXPECT_FALSE(c0.Matches(CreateAudioCodec(96, "A", 44100, 1)));
  EXPECT_FALSE(c0.Matches(CreateAudioCodec(96, "", 44100, 1)));
  EXPECT_FALSE(c0.Matches(CreateAudioCodec(95, "", 55100, 1)));
  EXPECT_FALSE(c0.Matches(CreateAudioCodec(95, "", 44100, 1)));
  EXPECT_FALSE(c0.Matches(CreateAudioCodec(95, "", 44100, 2)));
  EXPECT_FALSE(c0.Matches(CreateAudioCodec(95, "", 55100, 2)));

  // Test a codec with a dynamic payload type.
  Codec c1 = CreateAudioCodec(96, "A", 44100, 1);
  EXPECT_TRUE(c1.Matches(CreateAudioCodec(96, "A", 44100, 0)));
  EXPECT_TRUE(c1.Matches(CreateAudioCodec(97, "A", 44100, 0)));
  EXPECT_TRUE(c1.Matches(CreateAudioCodec(96, "a", 44100, 0)));
  EXPECT_TRUE(c1.Matches(CreateAudioCodec(97, "a", 44100, 0)));
  EXPECT_TRUE(c1.Matches(CreateAudioCodec(35, "a", 44100, 0)));
  EXPECT_TRUE(c1.Matches(CreateAudioCodec(42, "a", 44100, 0)));
  EXPECT_TRUE(c1.Matches(CreateAudioCodec(63, "a", 44100, 0)));
  EXPECT_FALSE(c1.Matches(CreateAudioCodec(64, "a", 44100, 0)));
  EXPECT_FALSE(c1.Matches(CreateAudioCodec(95, "A", 44100, 0)));
  EXPECT_FALSE(c1.Matches(CreateAudioCodec(34, "A", 44100, 0)));
  EXPECT_FALSE(c1.Matches(CreateAudioCodec(96, "", 44100, 2)));
  EXPECT_FALSE(c1.Matches(CreateAudioCodec(96, "A", 55100, 1)));

  // Test a codec with a dynamic payload type, and auto bitrate.
  Codec c2 = CreateAudioCodec(97, "A", 16000, 1);
  // Use default bitrate.
  EXPECT_TRUE(c2.Matches(CreateAudioCodec(97, "A", 16000, 1)));
  EXPECT_TRUE(c2.Matches(CreateAudioCodec(97, "A", 16000, 0)));
  // Use explicit bitrate.
  EXPECT_TRUE(c2.Matches(CreateAudioCodec(97, "A", 16000, 1)));
  // Backward compatibility with clients that might send "-1" (for default).
  EXPECT_TRUE(c2.Matches(CreateAudioCodec(97, "A", 16000, 1)));

  // Stereo doesn't match channels = 0.
  Codec c3 = CreateAudioCodec(96, "A", 44100, 2);
  EXPECT_TRUE(c3.Matches(CreateAudioCodec(96, "A", 44100, 2)));
  EXPECT_FALSE(c3.Matches(CreateAudioCodec(96, "A", 44100, 1)));
  EXPECT_FALSE(c3.Matches(CreateAudioCodec(96, "A", 44100, 0)));
}

TEST(CodecTest, TestOpusAudioCodecWithDifferentParameters) {
  Codec opus_with_fec = CreateAudioCodec(96, "opus", 48000, 2);
  opus_with_fec.params["useinbandfec"] = "1";
  Codec opus_without_fec = CreateAudioCodec(96, "opus", 48000, 2);

  EXPECT_TRUE(opus_with_fec != opus_without_fec);
  // Matches does not compare parameters for audio.
  EXPECT_TRUE(opus_with_fec.Matches(opus_without_fec));

  RtpCodecParameters rtp_opus_with_fec = opus_with_fec.ToCodecParameters();
  // MatchesRtpCodec takes parameters into account.
  EXPECT_TRUE(opus_with_fec.MatchesRtpCodec(rtp_opus_with_fec));
  EXPECT_FALSE(opus_without_fec.MatchesRtpCodec(rtp_opus_with_fec));
}

TEST(CodecTest, TestVideoCodecMatches) {
  // Test a codec with a static payload type.
  Codec c0 = CreateVideoCodec(34, "V");
  EXPECT_TRUE(c0.Matches(CreateVideoCodec(34, "")));
  EXPECT_FALSE(c0.Matches(CreateVideoCodec(96, "")));
  EXPECT_FALSE(c0.Matches(CreateVideoCodec(96, "V")));

  // Test a codec with a dynamic payload type.
  Codec c1 = CreateVideoCodec(96, "V");
  EXPECT_TRUE(c1.Matches(CreateVideoCodec(96, "V")));
  EXPECT_TRUE(c1.Matches(CreateVideoCodec(97, "V")));
  EXPECT_TRUE(c1.Matches(CreateVideoCodec(96, "v")));
  EXPECT_TRUE(c1.Matches(CreateVideoCodec(97, "v")));
  EXPECT_TRUE(c1.Matches(CreateVideoCodec(35, "v")));
  EXPECT_TRUE(c1.Matches(CreateVideoCodec(42, "v")));
  EXPECT_TRUE(c1.Matches(CreateVideoCodec(63, "v")));
  EXPECT_FALSE(c1.Matches(CreateVideoCodec(64, "v")));
  EXPECT_FALSE(c1.Matches(CreateVideoCodec(96, "")));
  EXPECT_FALSE(c1.Matches(CreateVideoCodec(95, "V")));
  EXPECT_FALSE(c1.Matches(CreateVideoCodec(34, "V")));
}

TEST(CodecTest, TestVideoCodecMatchesWithDifferentPacketization) {
  Codec c0 = CreateVideoCodec(100, kVp8CodecName);
  Codec c1 = CreateVideoCodec(101, kVp8CodecName);
  c1.packetization = "raw";

  EXPECT_TRUE(c0.Matches(c1));
  EXPECT_TRUE(c1.Matches(c0));
}

// AV1 codecs do not compare profile information.
TEST(CodecTest, TestAV1CodecMatches) {
  const char kProfile0[] = "0";
  const char kProfile1[] = "1";
  const char kProfile2[] = "2";

  Codec c_no_profile = CreateVideoCodec(95, kAv1CodecName);
  Codec c_profile0 = CreateVideoCodec(95, kAv1CodecName);
  c_profile0.params[kAv1FmtpProfile] = kProfile0;
  Codec c_profile1 = CreateVideoCodec(95, kAv1CodecName);
  c_profile1.params[kAv1FmtpProfile] = kProfile1;
  Codec c_profile2 = CreateVideoCodec(95, kAv1CodecName);
  c_profile2.params[kAv1FmtpProfile] = kProfile2;

  // An AV1 entry with no profile specified should be treated as profile-0.
  EXPECT_TRUE(c_profile0.Matches(c_no_profile));

  {
    // Two AV1 entries without a profile specified are treated as duplicates.
    Codec c_no_profile_eq = CreateVideoCodec(95, kAv1CodecName);
    EXPECT_TRUE(c_no_profile.Matches(c_no_profile_eq));
  }

  {
    // Two AV1 entries with profile 0 specified are treated as duplicates.
    Codec c_profile0_eq = CreateVideoCodec(95, kAv1CodecName);
    c_profile0_eq.params[kAv1FmtpProfile] = kProfile0;
    EXPECT_TRUE(c_profile0.Matches(c_profile0_eq));
  }

  {
    // Two AV1 entries with profile 1 specified are treated as duplicates.
    Codec c_profile1_eq = CreateVideoCodec(95, kAv1CodecName);
    c_profile1_eq.params[kAv1FmtpProfile] = kProfile1;
    EXPECT_TRUE(c_profile1.Matches(c_profile1_eq));
  }

  // AV1 entries with different profiles (0 and 1) are seen as distinct.
  EXPECT_FALSE(c_profile0.Matches(c_profile1));
  EXPECT_FALSE(c_no_profile.Matches(c_profile1));

  // AV1 entries with different profiles (0 and 2) are seen as distinct.
  EXPECT_FALSE(c_profile0.Matches(c_profile2));
  EXPECT_FALSE(c_no_profile.Matches(c_profile2));

  // AV1 entries with same profile and different tier are seen as equal.
  Codec c_tier0 = CreateVideoCodec(95, kAv1CodecName);
  c_tier0.params[kAv1FmtpProfile] = kProfile0;
  c_tier0.params[kAv1FmtpTier] = "0";
  Codec c_tier1 = CreateVideoCodec(95, kAv1CodecName);
  c_tier1.params[kAv1FmtpProfile] = kProfile0;
  c_tier1.params[kAv1FmtpTier] = "1";
  EXPECT_TRUE(c_tier0.Matches(c_tier1));

  // AV1 entries with profile and different level are seen as equal.
  Codec c_level0 = CreateVideoCodec(95, kAv1CodecName);
  c_level0.params[kAv1FmtpProfile] = kProfile0;
  c_level0.params[kAv1FmtpLevelIdx] = "0";
  Codec c_level1 = CreateVideoCodec(95, kAv1CodecName);
  c_level1.params[kAv1FmtpProfile] = kProfile0;
  c_level1.params[kAv1FmtpLevelIdx] = "1";
  EXPECT_TRUE(c_level0.Matches(c_level1));
}

// VP9 codecs compare profile information.
TEST(CodecTest, TestVP9CodecMatches) {
  const char kProfile0[] = "0";
  const char kProfile2[] = "2";

  Codec c_no_profile = CreateVideoCodec(95, kVp9CodecName);
  Codec c_profile0 = CreateVideoCodec(95, kVp9CodecName);
  c_profile0.params[kVP9FmtpProfileId] = kProfile0;

  EXPECT_TRUE(c_profile0.Matches(c_no_profile));

  {
    Codec c_profile0_eq = CreateVideoCodec(95, kVp9CodecName);
    c_profile0_eq.params[kVP9FmtpProfileId] = kProfile0;
    EXPECT_TRUE(c_profile0.Matches(c_profile0_eq));
  }

  {
    Codec c_profile2 = CreateVideoCodec(95, kVp9CodecName);
    c_profile2.params[kVP9FmtpProfileId] = kProfile2;
    EXPECT_FALSE(c_profile0.Matches(c_profile2));
    EXPECT_FALSE(c_no_profile.Matches(c_profile2));
  }

  {
    Codec c_no_profile_eq = CreateVideoCodec(95, kVp9CodecName);
    EXPECT_TRUE(c_no_profile.Matches(c_no_profile_eq));
  }
}

// Matching H264 codecs also need to have matching profile-level-id and
// packetization-mode.
TEST(CodecTest, TestH264CodecMatches) {
  const char kProfileLevelId1[] = "42e01f";
  const char kProfileLevelId2[] = "42a01e";
  const char kProfileLevelId3[] = "42e01e";

  Codec pli_1_pm_0 = CreateVideoCodec(95, "H264");
  pli_1_pm_0.params[kH264FmtpProfileLevelId] = kProfileLevelId1;
  pli_1_pm_0.params[kH264FmtpPacketizationMode] = "0";

  {
    Codec pli_1_pm_blank = CreateVideoCodec(95, "H264");
    pli_1_pm_blank.params[kH264FmtpProfileLevelId] = kProfileLevelId1;
    pli_1_pm_blank.params.erase(
        pli_1_pm_blank.params.find(kH264FmtpPacketizationMode));

    // Matches since if packetization-mode is not specified it defaults to "0".
    EXPECT_TRUE(pli_1_pm_0.Matches(pli_1_pm_blank));

    // MatchesRtpCodec does exact comparison of parameters.
    EXPECT_FALSE(
        pli_1_pm_0.MatchesRtpCodec(pli_1_pm_blank.ToCodecParameters()));
  }

  {
    Codec pli_1_pm_1 = CreateVideoCodec(95, "H264");
    pli_1_pm_1.params[kH264FmtpProfileLevelId] = kProfileLevelId1;
    pli_1_pm_1.params[kH264FmtpPacketizationMode] = "1";

    // Does not match since packetization-mode is different.
    EXPECT_FALSE(pli_1_pm_0.Matches(pli_1_pm_1));

    EXPECT_FALSE(pli_1_pm_0.MatchesRtpCodec(pli_1_pm_1.ToCodecParameters()));
  }

  {
    Codec pli_2_pm_0 = CreateVideoCodec(95, "H264");
    pli_2_pm_0.params[kH264FmtpProfileLevelId] = kProfileLevelId2;
    pli_2_pm_0.params[kH264FmtpPacketizationMode] = "0";

    // Does not match since profile-level-id is different.
    EXPECT_FALSE(pli_1_pm_0.Matches(pli_2_pm_0));

    EXPECT_FALSE(pli_1_pm_0.MatchesRtpCodec(pli_2_pm_0.ToCodecParameters()));
  }

  {
    Codec pli_3_pm_0_asym = CreateVideoCodec(95, "H264");
    pli_3_pm_0_asym.params[kH264FmtpProfileLevelId] = kProfileLevelId3;
    pli_3_pm_0_asym.params[kH264FmtpPacketizationMode] = "0";

    // Does match, profile-level-id is different but the level is not compared.
    // and the profile matches.
    EXPECT_TRUE(pli_1_pm_0.Matches(pli_3_pm_0_asym));

    EXPECT_FALSE(
        pli_1_pm_0.MatchesRtpCodec(pli_3_pm_0_asym.ToCodecParameters()));

    //
  }
}

#ifdef RTC_ENABLE_H265
// Matching H.265 codecs should have matching profile/tier/level and tx-mode.
TEST(CodecTest, TestH265CodecMatches) {
  constexpr char kProfile1[] = "1";
  constexpr char kTier1[] = "1";
  constexpr char kLevel3_1[] = "93";
  constexpr char kLevel4[] = "120";
  constexpr char kTxMrst[] = "MRST";

  Codec c_ptl_blank = CreateVideoCodec(95, kH265CodecName);

  {
    Codec c_profile_1 = CreateVideoCodec(95, kH265CodecName);
    c_profile_1.params[kH265FmtpProfileId] = kProfile1;

    // Matches since profile-id unspecified defaults to "1".
    EXPECT_TRUE(c_ptl_blank.Matches(c_profile_1));
  }

  {
    Codec c_tier_flag_1 = CreateVideoCodec(95, kH265CodecName);
    c_tier_flag_1.params[kH265FmtpTierFlag] = kTier1;

    // Does not match since profile-space unspecified defaults to "0".
    EXPECT_FALSE(c_ptl_blank.Matches(c_tier_flag_1));
  }

  {
    Codec c_level_id_3_1 = CreateVideoCodec(95, kH265CodecName);
    c_level_id_3_1.params[kH265FmtpLevelId] = kLevel3_1;

    // Matches since level-id unspecified defaults to "93".
    EXPECT_TRUE(c_ptl_blank.Matches(c_level_id_3_1));
  }

  {
    Codec c_level_id_4 = CreateVideoCodec(95, kH265CodecName);
    c_level_id_4.params[kH265FmtpLevelId] = kLevel4;

    // Matches since we ignore level-id when matching H.265 codecs.
    EXPECT_TRUE(c_ptl_blank.Matches(c_level_id_4));
  }

  {
    Codec c_tx_mode_mrst = CreateVideoCodec(95, kH265CodecName);
    c_tx_mode_mrst.params[kH265FmtpTxMode] = kTxMrst;

    // Does not match since tx-mode implies to "SRST" and must be not specified
    // when it is the only mode supported:
    // https://datatracker.ietf.org/doc/html/draft-ietf-avtcore-hevc-webrtc
    EXPECT_FALSE(c_ptl_blank.Matches(c_tx_mode_mrst));
  }
}
#endif

TEST(CodecTest, TestMatchesRtpCodecRtx) {
  const Codec rtx_codec_1 = CreateVideoRtxCodec(96, 120);
  const Codec rtx_codec_2 = CreateVideoRtxCodec(96, 121);
  EXPECT_TRUE(rtx_codec_1.Matches(rtx_codec_2));
  // MatchesRtpCodec ignores the different associated payload type (apt) for
  // RTX.
  EXPECT_TRUE(rtx_codec_1.MatchesRtpCodec(rtx_codec_2.ToCodecParameters()));
}

}  // namespace webrtc
