/* * Copyright 2004 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 "pc/codec_vendor.h" #include #include #include "absl/strings/string_view.h" #include "api/environment/environment.h" #include "api/environment/environment_factory.h" #include "api/field_trials.h" #include "api/media_types.h" #include "api/rtc_error.h" #include "api/rtp_transceiver_direction.h" #include "api/test/rtc_error_matchers.h" #include "call/fake_payload_type_suggester.h" #include "media/base/codec.h" #include "media/base/codec_list.h" #include "media/base/fake_media_engine.h" #include "media/base/media_constants.h" #include "media/base/test_utils.h" #include "pc/media_options.h" #include "pc/rtp_parameters_conversion.h" #include "pc/session_description.h" #include "rtc_base/checks.h" #include "test/create_test_field_trials.h" #include "test/gmock.h" #include "test/gtest.h" namespace webrtc { namespace { using ::testing::Contains; using ::testing::ElementsAre; using ::testing::Eq; using ::testing::Field; using ::testing::Ne; using ::testing::Not; using ::testing::Pair; Codec CreateRedAudioCodec(absl::string_view encoding_id) { Codec red = CreateAudioCodec(63, "red", 48000, 2); red.SetParam(kCodecParamNotInNameValueFormat, std::string(encoding_id) + '/' + std::string(encoding_id)); return red; } const Codec kAudioCodecs1[] = {CreateAudioCodec(111, "opus", 48000, 2), CreateRedAudioCodec("111"), CreateAudioCodec(102, "G722", 16000, 1), CreateAudioCodec(0, "PCMU", 8000, 1), CreateAudioCodec(8, "PCMA", 8000, 1), CreateAudioCodec(107, "CN", 48000, 1)}; const Codec kAudioCodecs2[] = { CreateAudioCodec(126, "foo", 16000, 1), CreateAudioCodec(0, "PCMU", 8000, 1), CreateAudioCodec(127, "G722", 16000, 1), }; const Codec kAudioCodecsAnswer[] = { CreateAudioCodec(102, "G722", 16000, 1), CreateAudioCodec(0, "PCMU", 8000, 1), }; TEST(CodecVendorTest, TestSetAudioCodecs) { FieldTrials trials = CreateTestFieldTrials(); std::vector send_codecs = MAKE_VECTOR(kAudioCodecs1); std::vector recv_codecs = MAKE_VECTOR(kAudioCodecs2); // The merged list of codecs should contain any send codecs that are also // nominally in the receive codecs list. Payload types should be picked from // the send codecs and a number-of-channels of 0 and 1 should be equivalent // (set to 1). This equals what happens when the send codecs are used in an // offer and the receive codecs are used in the following answer. const std::vector sendrecv_codecs = MAKE_VECTOR(kAudioCodecsAnswer); RTC_CHECK_EQ(send_codecs[2].name, "G722") << "Please don't change shared test data!"; RTC_CHECK_EQ(recv_codecs[2].name, "G722") << "Please don't change shared test data!"; // Alter iLBC send codec to have zero channels, to test that that is handled // properly. send_codecs[2].channels = 0; // Alter PCMU receive codec to be lowercase, to test that case conversions // are handled properly. recv_codecs[1].name = "pcmu"; // Test proper merge FakeMediaEngine media_engine; media_engine.SetAudioSendCodecs(send_codecs); media_engine.SetAudioRecvCodecs(recv_codecs); { CodecVendor codec_vendor(&media_engine, false, trials); EXPECT_EQ(send_codecs, codec_vendor.audio_send_codecs().codecs()); EXPECT_EQ(recv_codecs, codec_vendor.audio_recv_codecs().codecs()); EXPECT_EQ(sendrecv_codecs, codec_vendor.audio_sendrecv_codecs().codecs()); } // Test empty send codecs list CodecList no_codecs; media_engine.SetAudioSendCodecs(no_codecs.codecs()); media_engine.SetAudioRecvCodecs(recv_codecs); { CodecVendor codec_vendor(&media_engine, false, trials); EXPECT_EQ(no_codecs.codecs(), codec_vendor.audio_send_codecs().codecs()); EXPECT_EQ(recv_codecs, codec_vendor.audio_recv_codecs().codecs()); EXPECT_EQ(no_codecs.codecs(), codec_vendor.audio_sendrecv_codecs().codecs()); } // Test empty recv codecs list media_engine.SetAudioSendCodecs(send_codecs); media_engine.SetAudioRecvCodecs(no_codecs.codecs()); { CodecVendor codec_vendor(&media_engine, false, trials); EXPECT_EQ(send_codecs, codec_vendor.audio_send_codecs().codecs()); EXPECT_EQ(no_codecs.codecs(), codec_vendor.audio_recv_codecs().codecs()); EXPECT_EQ(no_codecs.codecs(), codec_vendor.audio_sendrecv_codecs().codecs()); } // Test all empty codec lists media_engine.SetAudioSendCodecs(no_codecs.codecs()); media_engine.SetAudioRecvCodecs(no_codecs.codecs()); { CodecVendor codec_vendor(&media_engine, false, trials); EXPECT_EQ(no_codecs, codec_vendor.audio_send_codecs()); EXPECT_EQ(no_codecs, codec_vendor.audio_recv_codecs()); EXPECT_EQ(no_codecs, codec_vendor.audio_sendrecv_codecs()); } } TEST(CodecVendorTest, VideoRtxIsIncludedWhenAskedFor) { Environment env = CreateEnvironment(); FakeMediaEngine media_engine; std::vector video_codecs({ CreateVideoCodec(97, "vp8"), CreateVideoRtxCodec(98, 97), }); media_engine.SetVideoSendCodecs(video_codecs); CodecVendor codec_vendor(&media_engine, /* rtx_enabled= */ true, env.field_trials()); FakePayloadTypeSuggester pt_suggester; RTCErrorOr> offered_codecs = codec_vendor.GetNegotiatedCodecsForOffer( MediaDescriptionOptions(MediaType::VIDEO, "mid", RtpTransceiverDirection::kSendOnly, false), MediaSessionOptions(), nullptr, pt_suggester); EXPECT_THAT(offered_codecs.value(), Contains(Field("name", &Codec::name, "rtx"))); } TEST(CodecVendorTest, VideoRtxIsExcludedWhenNotAskedFor) { Environment env = CreateEnvironment(); FakeMediaEngine media_engine; std::vector video_codecs({ CreateVideoCodec(97, "vp8"), CreateVideoRtxCodec(98, 97), }); media_engine.SetVideoSendCodecs(video_codecs); CodecVendor codec_vendor(&media_engine, /* rtx_enabled= */ false, env.field_trials()); FakePayloadTypeSuggester pt_suggester; RTCErrorOr> offered_codecs = codec_vendor.GetNegotiatedCodecsForOffer( MediaDescriptionOptions(MediaType::VIDEO, "mid", RtpTransceiverDirection::kSendOnly, false), MediaSessionOptions(), nullptr, pt_suggester); EXPECT_THAT(offered_codecs.value(), Not(Contains(Field("name", &Codec::name, "rtx")))); } TEST(CodecVendorTest, PreferencesAffectCodecChoice) { Environment env = CreateEnvironment(); FakeMediaEngine media_engine; std::vector video_codecs({ CreateVideoCodec(97, "vp8"), CreateVideoRtxCodec(98, 97), CreateVideoCodec(99, "vp9"), CreateVideoRtxCodec(100, 99), }); media_engine.SetVideoSendCodecs(video_codecs); CodecVendor codec_vendor(&media_engine, /* rtx_enabled= */ false, env.field_trials()); MediaDescriptionOptions options(MediaType::VIDEO, "mid", RtpTransceiverDirection::kSendOnly, false); options.codec_preferences = { ToRtpCodecCapability(CreateVideoCodec(-1, "vp9")), }; FakePayloadTypeSuggester pt_suggester; RTCErrorOr> offered_codecs = codec_vendor.GetNegotiatedCodecsForOffer(options, MediaSessionOptions(), nullptr, pt_suggester); ASSERT_TRUE(offered_codecs.ok()); EXPECT_THAT(offered_codecs.value(), Contains(Field("name", &Codec::name, "vp9"))); EXPECT_THAT(offered_codecs.value(), Not(Contains(Field("name", &Codec::name, "vp8")))); EXPECT_THAT(offered_codecs.value().size(), Eq(1)); } TEST(CodecVendorTest, GetNegotiatedCodecsForAnswerSimple) { Environment env = CreateEnvironment(); FakeMediaEngine media_engine; std::vector video_codecs({ CreateVideoCodec(97, "vp8"), CreateVideoRtxCodec(98, 97), CreateVideoCodec(99, "vp9"), CreateVideoRtxCodec(100, 99), }); media_engine.SetVideoSendCodecs(video_codecs); CodecVendor codec_vendor(&media_engine, /* rtx_enabled= */ true, env.field_trials()); MediaDescriptionOptions options(MediaType::VIDEO, "mid", RtpTransceiverDirection::kSendOnly, false); FakePayloadTypeSuggester pt_suggester; ContentInfo* current_content = nullptr; RTCErrorOr> answered_codecs = codec_vendor.GetNegotiatedCodecsForAnswer( options, MediaSessionOptions(), RtpTransceiverDirection::kSendOnly, RtpTransceiverDirection::kSendOnly, current_content, video_codecs, pt_suggester); EXPECT_THAT(answered_codecs, IsRtcOkAndHolds(video_codecs)); } TEST(CodecVendorTest, GetNegotiatedCodecsForAnswerWithCollision) { Environment env = CreateEnvironment(); FakeMediaEngine media_engine; std::vector video_codecs({ CreateVideoCodec(97, "vp8"), CreateVideoCodec(99, "vp9"), CreateVideoCodec(101, "av1"), }); std::vector remote_codecs({ CreateVideoCodec(97, "av1"), CreateVideoCodec(99, "vp9"), }); media_engine.SetVideoSendCodecs(video_codecs); CodecVendor codec_vendor(&media_engine, /* rtx_enabled= */ false, env.field_trials()); MediaDescriptionOptions options(MediaType::VIDEO, "mid", RtpTransceiverDirection::kSendOnly, false); FakePayloadTypeSuggester pt_suggester; ContentInfo* current_content = nullptr; RTCErrorOr> answered_codecs = codec_vendor.GetNegotiatedCodecsForAnswer( options, MediaSessionOptions(), RtpTransceiverDirection::kSendOnly, RtpTransceiverDirection::kSendOnly, current_content, remote_codecs, pt_suggester); EXPECT_THAT(answered_codecs, IsRtcOkAndHolds(remote_codecs)); } TEST(CodecVendorMergeTest, BasicTestSetup) { CodecList reference_codecs; const std::string mid = "mid"; CodecList merged_codecs; FakePayloadTypeSuggester pt_suggester; RTCError error = MergeCodecsForTesting(reference_codecs, mid, merged_codecs, pt_suggester); EXPECT_TRUE(error.ok()); } TEST(CodecVendorMergeTest, IdenticalListsMergeWithNoChange) { CodecList reference_codecs; const std::string mid = "mid"; CodecList merged_codecs; FakePayloadTypeSuggester pt_suggester; Codec some_codec = CreateVideoCodec(97, "foo"); auto pt_or_error = pt_suggester.SuggestPayloadType(mid, some_codec); ASSERT_THAT(pt_or_error.value(), Eq(97)); reference_codecs.push_back(some_codec); merged_codecs.push_back(some_codec); RTCError error = MergeCodecsForTesting(reference_codecs, mid, merged_codecs, pt_suggester); EXPECT_TRUE(error.ok()); EXPECT_THAT(merged_codecs.size(), Eq(1)); EXPECT_THAT(merged_codecs[0].id, Eq(97)); } TEST(CodecVendorMergeTest, MergeRenumbersAdditionalCodecs) { CodecList reference_codecs; const std::string mid = "mid"; CodecList merged_codecs; FakePayloadTypeSuggester pt_suggester; Codec some_codec = CreateVideoCodec(97, "foo"); auto pt_or_error = pt_suggester.SuggestPayloadType(mid, some_codec); ASSERT_THAT(pt_or_error.value(), Eq(97)); merged_codecs.push_back(some_codec); // Use the same PT for a reference codec. This should be renumbered. Codec some_other_codec = CreateVideoCodec(97, "bar"); reference_codecs.push_back(some_other_codec); RTCError error = MergeCodecsForTesting(reference_codecs, mid, merged_codecs, pt_suggester); EXPECT_TRUE(error.ok()); EXPECT_THAT(merged_codecs.size(), Eq(2)); // Both foo and bar should be present EXPECT_THAT(merged_codecs.codecs(), UnorderedElementsAre(Field("name", &Codec::name, "foo"), Field("name", &Codec::name, "bar"))); // Foo should retain 97 EXPECT_THAT(merged_codecs.codecs(), Contains(AllOf(Field("name", &Codec::name, "foo"), Field("id", &Codec::id, 97)))); // Bar should not have 97 EXPECT_THAT(merged_codecs.codecs(), Contains(AllOf(Field("name", &Codec::name, "bar"), Field("id", &Codec::id, Ne(97))))); } TEST(CodecVendorMergeTest, MergeRenumbersRedCodecArgument) { CodecList reference_codecs; const std::string mid = "mid"; CodecList merged_codecs; FakePayloadTypeSuggester pt_suggester; Codec some_codec = CreateAudioCodec(100, "foo", 8000, 1); merged_codecs.push_back(some_codec); // Push into "reference" with a different ID some_codec.id = 102; reference_codecs.push_back(some_codec); Codec red_codec = CreateAudioCodec(101, "red", 8000, 1); ASSERT_EQ(red_codec.GetResiliencyType(), Codec::ResiliencyType::kRed); red_codec.params[kCodecParamNotInNameValueFormat] = "102/102"; reference_codecs.push_back(red_codec); // Merging should add the RED codec with parameter 100/100 RTCError error = MergeCodecsForTesting(reference_codecs, mid, merged_codecs, pt_suggester); EXPECT_TRUE(error.ok()); EXPECT_THAT(merged_codecs.size(), Eq(2)); EXPECT_THAT( merged_codecs.codecs(), Contains(AllOf(Field("name", &Codec::name, "red"), Field("params", &Codec::params, ElementsAre(Pair(kCodecParamNotInNameValueFormat, "100/100")))))); } TEST(CodecVendorMergeTest, MergeRenumbersRedCodecArgumentAndMerges) { CodecList reference_codecs; const std::string mid = "mid"; CodecList merged_codecs; FakePayloadTypeSuggester pt_suggester; Codec some_codec = CreateAudioCodec(100, "foo", 8000, 1); merged_codecs.push_back(some_codec); // Push into "reference" with a different ID some_codec.id = 102; reference_codecs.push_back(some_codec); Codec red_codec = CreateAudioCodec(101, "red", 8000, 1); ASSERT_EQ(red_codec.GetResiliencyType(), Codec::ResiliencyType::kRed); red_codec.params[kCodecParamNotInNameValueFormat] = "102/102"; reference_codecs.push_back(red_codec); // Push the same red codec into `merged_codecs` with the 100 id red_codec.params[kCodecParamNotInNameValueFormat] = "100/100"; merged_codecs.push_back(red_codec); // Merging should note the duplication and not add another codec. RTCError error = MergeCodecsForTesting(reference_codecs, mid, merged_codecs, pt_suggester); EXPECT_TRUE(error.ok()); EXPECT_THAT(merged_codecs.size(), Eq(2)); EXPECT_THAT( merged_codecs.codecs(), Contains(AllOf(Field("name", &Codec::name, "red"), Field("params", &Codec::params, ElementsAre(Pair(kCodecParamNotInNameValueFormat, "100/100")))))); } TEST(CodecVendorMergeTest, MergeWithBrokenReferenceRedErrors) { CodecList reference_codecs; const std::string mid = "mid"; CodecList merged_codecs; FakePayloadTypeSuggester pt_suggester; Codec some_codec = CreateAudioCodec(100, "foo", 8000, 1); Codec red_codec = CreateAudioCodec(101, "red", 8000, 1); // Adds a RED codec that refers to codec 102, which does not exist. red_codec.params[kCodecParamNotInNameValueFormat] = "100/102"; reference_codecs.push_back(some_codec); reference_codecs.push_back(red_codec); // The bogus RED codec should result in an error return. RTCError error = MergeCodecsForTesting(reference_codecs, mid, merged_codecs, pt_suggester); EXPECT_FALSE(error.ok()); EXPECT_THAT(error.type(), Eq(RTCErrorType::INTERNAL_ERROR)); } } // namespace } // namespace webrtc