| /* | 
 |  *  Copyright 2017 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. | 
 |  */ | 
 |  | 
 | // This file contains tests that check the interaction between the | 
 | // PeerConnection and the underlying media engine, as well as tests that check | 
 | // the media-related aspects of SDP. | 
 |  | 
 | #include <algorithm> | 
 | #include <functional> | 
 | #include <iterator> | 
 | #include <map> | 
 | #include <memory> | 
 | #include <set> | 
 | #include <string> | 
 | #include <tuple> | 
 | #include <type_traits> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "absl/algorithm/container.h" | 
 | #include "absl/types/optional.h" | 
 | #include "api/audio_options.h" | 
 | #include "api/jsep.h" | 
 | #include "api/media_types.h" | 
 | #include "api/peer_connection_interface.h" | 
 | #include "api/rtc_error.h" | 
 | #include "api/rtc_event_log/rtc_event_log_factory.h" | 
 | #include "api/rtc_event_log/rtc_event_log_factory_interface.h" | 
 | #include "api/rtp_parameters.h" | 
 | #include "api/rtp_sender_interface.h" | 
 | #include "api/rtp_transceiver_direction.h" | 
 | #include "api/rtp_transceiver_interface.h" | 
 | #include "api/scoped_refptr.h" | 
 | #include "api/task_queue/default_task_queue_factory.h" | 
 | #include "api/task_queue/task_queue_factory.h" | 
 | #include "media/base/codec.h" | 
 | #include "media/base/fake_media_engine.h" | 
 | #include "media/base/media_channel.h" | 
 | #include "media/base/media_constants.h" | 
 | #include "media/base/media_engine.h" | 
 | #include "media/base/stream_params.h" | 
 | #include "p2p/base/fake_port_allocator.h" | 
 | #include "p2p/base/p2p_constants.h" | 
 | #include "p2p/base/port_allocator.h" | 
 | #include "p2p/base/transport_info.h" | 
 | #include "pc/channel_interface.h" | 
 | #include "pc/media_session.h" | 
 | #include "pc/peer_connection_wrapper.h" | 
 | #include "pc/rtp_media_utils.h" | 
 | #include "pc/rtp_transceiver.h" | 
 | #include "pc/session_description.h" | 
 | #include "pc/test/enable_fake_media.h" | 
 | #include "pc/test/mock_peer_connection_observers.h" | 
 | #include "rtc_base/checks.h" | 
 | #include "rtc_base/rtc_certificate_generator.h" | 
 | #include "rtc_base/thread.h" | 
 | #include "test/gtest.h" | 
 | #include "test/scoped_key_value_config.h" | 
 | #ifdef WEBRTC_ANDROID | 
 | #include "pc/test/android_test_initializer.h" | 
 | #endif | 
 | #include "rtc_base/virtual_socket_server.h" | 
 | #include "test/gmock.h" | 
 |  | 
 | namespace webrtc { | 
 |  | 
 | using cricket::FakeMediaEngine; | 
 | using RTCConfiguration = PeerConnectionInterface::RTCConfiguration; | 
 | using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions; | 
 | using ::testing::Bool; | 
 | using ::testing::Combine; | 
 | using ::testing::ElementsAre; | 
 | using ::testing::HasSubstr; | 
 | using ::testing::NotNull; | 
 | using ::testing::Values; | 
 |  | 
 | cricket::MediaSendChannelInterface* SendChannelInternal( | 
 |     rtc::scoped_refptr<RtpTransceiverInterface> transceiver) { | 
 |   auto transceiver_with_internal = static_cast< | 
 |       RefCountedObject<RtpTransceiverProxyWithInternal<RtpTransceiver>>*>( | 
 |       transceiver.get()); | 
 |   auto transceiver_internal = | 
 |       static_cast<RtpTransceiver*>(transceiver_with_internal->internal()); | 
 |   return transceiver_internal->channel()->media_send_channel(); | 
 | } | 
 |  | 
 | cricket::MediaReceiveChannelInterface* ReceiveChannelInternal( | 
 |     rtc::scoped_refptr<RtpTransceiverInterface> transceiver) { | 
 |   auto transceiver_with_internal = static_cast< | 
 |       RefCountedObject<RtpTransceiverProxyWithInternal<RtpTransceiver>>*>( | 
 |       transceiver.get()); | 
 |   auto transceiver_internal = | 
 |       static_cast<RtpTransceiver*>(transceiver_with_internal->internal()); | 
 |   return transceiver_internal->channel()->media_receive_channel(); | 
 | } | 
 |  | 
 | cricket::FakeVideoMediaSendChannel* VideoMediaSendChannel( | 
 |     rtc::scoped_refptr<RtpTransceiverInterface> transceiver) { | 
 |   return static_cast<cricket::FakeVideoMediaSendChannel*>( | 
 |       SendChannelInternal(transceiver)); | 
 | } | 
 | cricket::FakeVideoMediaReceiveChannel* VideoMediaReceiveChannel( | 
 |     rtc::scoped_refptr<RtpTransceiverInterface> transceiver) { | 
 |   return static_cast<cricket::FakeVideoMediaReceiveChannel*>( | 
 |       ReceiveChannelInternal(transceiver)); | 
 | } | 
 | cricket::FakeVoiceMediaSendChannel* VoiceMediaSendChannel( | 
 |     rtc::scoped_refptr<RtpTransceiverInterface> transceiver) { | 
 |   return static_cast<cricket::FakeVoiceMediaSendChannel*>( | 
 |       SendChannelInternal(transceiver)); | 
 | } | 
 | cricket::FakeVoiceMediaReceiveChannel* VoiceMediaReceiveChannel( | 
 |     rtc::scoped_refptr<RtpTransceiverInterface> transceiver) { | 
 |   return static_cast<cricket::FakeVoiceMediaReceiveChannel*>( | 
 |       ReceiveChannelInternal(transceiver)); | 
 | } | 
 |  | 
 | class PeerConnectionWrapperForMediaTest : public PeerConnectionWrapper { | 
 |  public: | 
 |   using PeerConnectionWrapper::PeerConnectionWrapper; | 
 |  | 
 |   FakeMediaEngine* media_engine() { return media_engine_; } | 
 |   void set_media_engine(FakeMediaEngine* media_engine) { | 
 |     media_engine_ = media_engine; | 
 |   } | 
 |  | 
 |  private: | 
 |   FakeMediaEngine* media_engine_; | 
 | }; | 
 |  | 
 | class PeerConnectionMediaBaseTest : public ::testing::Test { | 
 |  protected: | 
 |   typedef std::unique_ptr<PeerConnectionWrapperForMediaTest> WrapperPtr; | 
 |  | 
 |   explicit PeerConnectionMediaBaseTest(SdpSemantics sdp_semantics) | 
 |       : vss_(new rtc::VirtualSocketServer()), | 
 |         main_(vss_.get()), | 
 |         sdp_semantics_(sdp_semantics) { | 
 | #ifdef WEBRTC_ANDROID | 
 |     InitializeAndroidObjects(); | 
 | #endif | 
 |   } | 
 |  | 
 |   WrapperPtr CreatePeerConnection() { | 
 |     return CreatePeerConnection(RTCConfiguration()); | 
 |   } | 
 |  | 
 |   WrapperPtr CreatePeerConnection(const RTCConfiguration& config) { | 
 |     return CreatePeerConnection(config, std::make_unique<FakeMediaEngine>()); | 
 |   } | 
 |  | 
 |   WrapperPtr CreatePeerConnection( | 
 |       std::unique_ptr<FakeMediaEngine> media_engine) { | 
 |     return CreatePeerConnection(RTCConfiguration(), std::move(media_engine)); | 
 |   } | 
 |  | 
 |   // Creates PeerConnectionFactory and PeerConnection for given configuration. | 
 |   WrapperPtr CreatePeerConnection( | 
 |       const RTCConfiguration& config, | 
 |       std::unique_ptr<FakeMediaEngine> media_engine) { | 
 |     auto* media_engine_ptr = media_engine.get(); | 
 |  | 
 |     PeerConnectionFactoryDependencies factory_dependencies; | 
 |  | 
 |     factory_dependencies.network_thread = rtc::Thread::Current(); | 
 |     factory_dependencies.worker_thread = rtc::Thread::Current(); | 
 |     factory_dependencies.signaling_thread = rtc::Thread::Current(); | 
 |     factory_dependencies.task_queue_factory = CreateDefaultTaskQueueFactory(); | 
 |     EnableFakeMedia(factory_dependencies, std::move(media_engine)); | 
 |     factory_dependencies.event_log_factory = | 
 |         std::make_unique<RtcEventLogFactory>(); | 
 |     factory_dependencies.trials = std::move(field_trials_); | 
 |     auto pc_factory = | 
 |         CreateModularPeerConnectionFactory(std::move(factory_dependencies)); | 
 |  | 
 |     auto fake_port_allocator = std::make_unique<cricket::FakePortAllocator>( | 
 |         rtc::Thread::Current(), | 
 |         std::make_unique<rtc::BasicPacketSocketFactory>(vss_.get()), nullptr); | 
 |     auto observer = std::make_unique<MockPeerConnectionObserver>(); | 
 |     auto modified_config = config; | 
 |     modified_config.sdp_semantics = sdp_semantics_; | 
 |     PeerConnectionDependencies pc_dependencies(observer.get()); | 
 |     pc_dependencies.allocator = std::move(fake_port_allocator); | 
 |  | 
 |     auto result = pc_factory->CreatePeerConnectionOrError( | 
 |         modified_config, std::move(pc_dependencies)); | 
 |     if (!result.ok()) { | 
 |       return nullptr; | 
 |     } | 
 |  | 
 |     auto pc = result.MoveValue(); | 
 |     observer->SetPeerConnectionInterface(pc.get()); | 
 |     auto wrapper = std::make_unique<PeerConnectionWrapperForMediaTest>( | 
 |         pc_factory, pc, std::move(observer)); | 
 |     wrapper->set_media_engine(media_engine_ptr); | 
 |     return wrapper; | 
 |   } | 
 |  | 
 |   // Accepts the same arguments as CreatePeerConnection and adds default audio | 
 |   // track (but no video). | 
 |   template <typename... Args> | 
 |   WrapperPtr CreatePeerConnectionWithAudio(Args&&... args) { | 
 |     auto wrapper = CreatePeerConnection(std::forward<Args>(args)...); | 
 |     if (!wrapper) { | 
 |       return nullptr; | 
 |     } | 
 |     wrapper->AddAudioTrack("a"); | 
 |     return wrapper; | 
 |   } | 
 |  | 
 |   // Accepts the same arguments as CreatePeerConnection and adds default video | 
 |   // track (but no audio). | 
 |   template <typename... Args> | 
 |   WrapperPtr CreatePeerConnectionWithVideo(Args&&... args) { | 
 |     auto wrapper = CreatePeerConnection(std::forward<Args>(args)...); | 
 |     if (!wrapper) { | 
 |       return nullptr; | 
 |     } | 
 |     wrapper->AddVideoTrack("v"); | 
 |     return wrapper; | 
 |   } | 
 |  | 
 |   // Accepts the same arguments as CreatePeerConnection and adds default audio | 
 |   // and video tracks. | 
 |   template <typename... Args> | 
 |   WrapperPtr CreatePeerConnectionWithAudioVideo(Args&&... args) { | 
 |     auto wrapper = CreatePeerConnection(std::forward<Args>(args)...); | 
 |     if (!wrapper) { | 
 |       return nullptr; | 
 |     } | 
 |     wrapper->AddAudioTrack("a"); | 
 |     wrapper->AddVideoTrack("v"); | 
 |     return wrapper; | 
 |   } | 
 |  | 
 |   RtpTransceiverDirection GetMediaContentDirection( | 
 |       const SessionDescriptionInterface* sdesc, | 
 |       cricket::MediaType media_type) { | 
 |     auto* content = | 
 |         cricket::GetFirstMediaContent(sdesc->description(), media_type); | 
 |     RTC_DCHECK(content); | 
 |     return content->media_description()->direction(); | 
 |   } | 
 |  | 
 |   bool IsUnifiedPlan() const { | 
 |     return sdp_semantics_ == SdpSemantics::kUnifiedPlan; | 
 |   } | 
 |  | 
 |   std::unique_ptr<test::ScopedKeyValueConfig> field_trials_; | 
 |   std::unique_ptr<rtc::VirtualSocketServer> vss_; | 
 |   rtc::AutoSocketServerThread main_; | 
 |   const SdpSemantics sdp_semantics_; | 
 | }; | 
 |  | 
 | class PeerConnectionMediaTest | 
 |     : public PeerConnectionMediaBaseTest, | 
 |       public ::testing::WithParamInterface<SdpSemantics> { | 
 |  protected: | 
 |   PeerConnectionMediaTest() : PeerConnectionMediaBaseTest(GetParam()) {} | 
 | }; | 
 |  | 
 | class PeerConnectionMediaTestUnifiedPlan : public PeerConnectionMediaBaseTest { | 
 |  protected: | 
 |   PeerConnectionMediaTestUnifiedPlan() | 
 |       : PeerConnectionMediaBaseTest(SdpSemantics::kUnifiedPlan) {} | 
 | }; | 
 |  | 
 | class PeerConnectionMediaTestPlanB : public PeerConnectionMediaBaseTest { | 
 |  protected: | 
 |   PeerConnectionMediaTestPlanB() | 
 |       : PeerConnectionMediaBaseTest(SdpSemantics::kPlanB_DEPRECATED) {} | 
 | }; | 
 |  | 
 | TEST_P(PeerConnectionMediaTest, | 
 |        FailToSetRemoteDescriptionIfCreateMediaChannelFails) { | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnectionWithAudioVideo(); | 
 |   callee->media_engine()->set_fail_create_channel(true); | 
 |  | 
 |   std::string error; | 
 |   ASSERT_FALSE(callee->SetRemoteDescription(caller->CreateOffer(), &error)); | 
 |   EXPECT_THAT(error, | 
 |               HasSubstr("Failed to set remote offer sdp: Failed to create")); | 
 | } | 
 |  | 
 | TEST_P(PeerConnectionMediaTest, | 
 |        FailToSetLocalDescriptionIfCreateMediaChannelFails) { | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   caller->media_engine()->set_fail_create_channel(true); | 
 |  | 
 |   std::string error; | 
 |   ASSERT_FALSE(caller->SetLocalDescription(caller->CreateOffer(), &error)); | 
 |   EXPECT_THAT(error, | 
 |               HasSubstr("Failed to set local offer sdp: Failed to create")); | 
 | } | 
 |  | 
 | std::vector<std::string> GetIds( | 
 |     const std::vector<cricket::StreamParams>& streams) { | 
 |   std::vector<std::string> ids; | 
 |   ids.reserve(streams.size()); | 
 |   for (const auto& stream : streams) { | 
 |     ids.push_back(stream.id); | 
 |   } | 
 |   return ids; | 
 | } | 
 |  | 
 | // Test that exchanging an offer and answer with each side having an audio and | 
 | // video stream creates the appropriate send/recv streams in the underlying | 
 | // media engine on both sides. | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        AudioVideoOfferAnswerCreateSendRecvStreams) { | 
 |   const std::string kCallerAudioId = "caller_a"; | 
 |   const std::string kCallerVideoId = "caller_v"; | 
 |   const std::string kCalleeAudioId = "callee_a"; | 
 |   const std::string kCalleeVideoId = "callee_v"; | 
 |  | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack(kCallerAudioId); | 
 |   caller->AddVideoTrack(kCallerVideoId); | 
 |  | 
 |   auto callee = CreatePeerConnection(); | 
 |   callee->AddAudioTrack(kCalleeAudioId); | 
 |   callee->AddVideoTrack(kCalleeVideoId); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |   ASSERT_TRUE( | 
 |       caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); | 
 |  | 
 |   auto* caller_voice_send = | 
 |       VoiceMediaSendChannel(caller->pc()->GetTransceivers()[0]); | 
 |   auto* caller_voice_receive = | 
 |       VoiceMediaReceiveChannel(caller->pc()->GetTransceivers()[0]); | 
 |   EXPECT_THAT(GetIds(caller_voice_receive->recv_streams()), | 
 |               ElementsAre(kCalleeAudioId)); | 
 |   EXPECT_THAT(GetIds(caller_voice_send->send_streams()), | 
 |               ElementsAre(kCallerAudioId)); | 
 |  | 
 |   auto* caller_video_send = | 
 |       VideoMediaSendChannel(caller->pc()->GetTransceivers()[1]); | 
 |   auto* caller_video_receive = | 
 |       VideoMediaReceiveChannel(caller->pc()->GetTransceivers()[1]); | 
 |   EXPECT_THAT(GetIds(caller_video_receive->recv_streams()), | 
 |               ElementsAre(kCalleeVideoId)); | 
 |   EXPECT_THAT(GetIds(caller_video_send->send_streams()), | 
 |               ElementsAre(kCallerVideoId)); | 
 |  | 
 |   auto* callee_voice_send = | 
 |       VoiceMediaSendChannel(callee->pc()->GetTransceivers()[0]); | 
 |   auto* callee_voice_receive = | 
 |       VoiceMediaReceiveChannel(callee->pc()->GetTransceivers()[0]); | 
 |   EXPECT_THAT(GetIds(callee_voice_receive->recv_streams()), | 
 |               ElementsAre(kCallerAudioId)); | 
 |   EXPECT_THAT(GetIds(callee_voice_send->send_streams()), | 
 |               ElementsAre(kCalleeAudioId)); | 
 |  | 
 |   auto* callee_video_send = | 
 |       VideoMediaSendChannel(callee->pc()->GetTransceivers()[1]); | 
 |   auto* callee_video_receive = | 
 |       VideoMediaReceiveChannel(callee->pc()->GetTransceivers()[1]); | 
 |   EXPECT_THAT(GetIds(callee_video_receive->recv_streams()), | 
 |               ElementsAre(kCallerVideoId)); | 
 |   EXPECT_THAT(GetIds(callee_video_send->send_streams()), | 
 |               ElementsAre(kCalleeVideoId)); | 
 | } | 
 |  | 
 | // Test that stopping the caller transceivers causes the media channels on the | 
 | // callee to be destroyed after calling SetRemoteDescription on the generated | 
 | // offer. | 
 | // See next test for equivalent behavior with Plan B semantics. | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        StoppedRemoteTransceiversRemovesMediaChannels) { | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   // Stop both audio and video transceivers on the caller. | 
 |   auto transceivers = caller->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(2u, transceivers.size()); | 
 |   transceivers[0]->StopInternal(); | 
 |   transceivers[1]->StopInternal(); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   ASSERT_TRUE(callee->pc()->GetTransceivers().empty()); | 
 | } | 
 |  | 
 | // Test that removing streams from a subsequent offer causes the receive streams | 
 | // on the callee to be removed. | 
 | // See previous test for equivalent behavior with Unified Plan semantics. | 
 | TEST_F(PeerConnectionMediaTestPlanB, EmptyRemoteOfferRemovesRecvStreams) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   auto caller_audio_track = caller->AddAudioTrack("a"); | 
 |   auto caller_video_track = caller->AddVideoTrack("v"); | 
 |   auto callee = CreatePeerConnectionWithAudioVideo(); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   // Remove both tracks from caller. | 
 |   caller->pc()->RemoveTrackOrError(caller_audio_track); | 
 |   caller->pc()->RemoveTrackOrError(caller_video_track); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   EXPECT_TRUE(callee->pc()->GetReceivers().empty()); | 
 |   EXPECT_EQ(2u, callee->pc()->GetSenders().size()); | 
 | } | 
 |  | 
 | // Test enabling of simulcast with Plan B semantics. | 
 | // This test creating an offer. | 
 | TEST_F(PeerConnectionMediaTestPlanB, SimulcastOffer) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   auto caller_video_track = caller->AddVideoTrack("v"); | 
 |   RTCOfferAnswerOptions options; | 
 |   options.num_simulcast_layers = 3; | 
 |   auto offer = caller->CreateOffer(options); | 
 |   auto* description = cricket::GetFirstMediaContent(offer->description(), | 
 |                                                     cricket::MEDIA_TYPE_VIDEO) | 
 |                           ->media_description(); | 
 |   ASSERT_EQ(1u, description->streams().size()); | 
 |   ASSERT_TRUE(description->streams()[0].get_ssrc_group("SIM")); | 
 |   EXPECT_EQ(3u, description->streams()[0].get_ssrc_group("SIM")->ssrcs.size()); | 
 |  | 
 |   // Check that it actually creates simulcast aswell. | 
 |   caller->SetLocalDescription(std::move(offer)); | 
 |   auto senders = caller->pc()->GetSenders(); | 
 |   ASSERT_EQ(1u, senders.size()); | 
 |   EXPECT_EQ(cricket::MediaType::MEDIA_TYPE_VIDEO, senders[0]->media_type()); | 
 |   EXPECT_EQ(3u, senders[0]->GetParameters().encodings.size()); | 
 | } | 
 |  | 
 | // Test enabling of simulcast with Plan B semantics. | 
 | // This test creating an answer. | 
 | TEST_F(PeerConnectionMediaTestPlanB, SimulcastAnswer) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddVideoTrack("v0"); | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto callee = CreatePeerConnection(); | 
 |   auto callee_video_track = callee->AddVideoTrack("v1"); | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | 
 |   RTCOfferAnswerOptions options; | 
 |   options.num_simulcast_layers = 3; | 
 |   auto answer = callee->CreateAnswer(options); | 
 |   auto* description = cricket::GetFirstMediaContent(answer->description(), | 
 |                                                     cricket::MEDIA_TYPE_VIDEO) | 
 |                           ->media_description(); | 
 |   ASSERT_EQ(1u, description->streams().size()); | 
 |   ASSERT_TRUE(description->streams()[0].get_ssrc_group("SIM")); | 
 |   EXPECT_EQ(3u, description->streams()[0].get_ssrc_group("SIM")->ssrcs.size()); | 
 |  | 
 |   // Check that it actually creates simulcast aswell. | 
 |   callee->SetLocalDescription(std::move(answer)); | 
 |   auto senders = callee->pc()->GetSenders(); | 
 |   ASSERT_EQ(1u, senders.size()); | 
 |   EXPECT_EQ(cricket::MediaType::MEDIA_TYPE_VIDEO, senders[0]->media_type()); | 
 |   EXPECT_EQ(3u, senders[0]->GetParameters().encodings.size()); | 
 | } | 
 |  | 
 | // Test that stopping the callee transceivers causes the media channels to be | 
 | // destroyed on the callee after calling SetLocalDescription on the local | 
 | // answer. | 
 | // See next test for equivalent behavior with Plan B semantics. | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        StoppedLocalTransceiversRemovesMediaChannels) { | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnectionWithAudioVideo(); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   // Stop both audio and video transceivers on the callee. | 
 |   auto transceivers = callee->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(2u, transceivers.size()); | 
 |   transceivers[0]->StopInternal(); | 
 |   transceivers[1]->StopInternal(); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   EXPECT_TRUE(callee->pc()->GetTransceivers().empty()); | 
 | } | 
 |  | 
 | // Test that removing streams from a subsequent answer causes the send streams | 
 | // on the callee to be removed when applied locally. | 
 | // See previous test for equivalent behavior with Unified Plan semantics. | 
 | TEST_F(PeerConnectionMediaTestPlanB, EmptyLocalAnswerRemovesSendStreams) { | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnection(); | 
 |   auto callee_audio_track = callee->AddAudioTrack("a"); | 
 |   auto callee_video_track = callee->AddVideoTrack("v"); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   // Remove both tracks from callee. | 
 |   callee->pc()->RemoveTrackOrError(callee_audio_track); | 
 |   callee->pc()->RemoveTrackOrError(callee_video_track); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   EXPECT_TRUE(callee->pc()->GetSenders().empty()); | 
 |   EXPECT_EQ(2u, callee->pc()->GetReceivers().size()); | 
 | } | 
 |  | 
 | // Test that a new stream in a subsequent offer causes a new receive stream to | 
 | // be created on the callee. | 
 | TEST_P(PeerConnectionMediaTest, NewStreamInRemoteOfferAddsRecvStreams) { | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   // Add second set of tracks to the caller. | 
 |   caller->AddAudioTrack("a2"); | 
 |   caller->AddVideoTrack("v2"); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   if (IsUnifiedPlan()) { | 
 |     auto a1 = VoiceMediaReceiveChannel(callee->pc()->GetTransceivers()[0]); | 
 |     auto a2 = VoiceMediaReceiveChannel(callee->pc()->GetTransceivers()[2]); | 
 |     auto v1 = VideoMediaReceiveChannel(callee->pc()->GetTransceivers()[1]); | 
 |     auto v2 = VideoMediaReceiveChannel(callee->pc()->GetTransceivers()[3]); | 
 |  | 
 |     ASSERT_TRUE(a1); | 
 |     EXPECT_EQ(1u, a1->recv_streams().size()); | 
 |     ASSERT_TRUE(a2); | 
 |     EXPECT_EQ(1u, a2->recv_streams().size()); | 
 |     ASSERT_TRUE(v1); | 
 |     EXPECT_EQ(1u, v1->recv_streams().size()); | 
 |     ASSERT_TRUE(v2); | 
 |     EXPECT_EQ(1u, v2->recv_streams().size()); | 
 |   } else { | 
 |     EXPECT_EQ(4u, callee->pc()->GetReceivers().size()); | 
 |   } | 
 | } | 
 |  | 
 | // Test that a new stream in a subsequent answer causes a new send stream to be | 
 | // created on the callee when added locally. | 
 | TEST_P(PeerConnectionMediaTest, NewStreamInLocalAnswerAddsSendStreams) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   auto callee = CreatePeerConnectionWithAudioVideo(); | 
 |  | 
 |   RTCOfferAnswerOptions offer_options; | 
 |   offer_options.offer_to_receive_audio = | 
 |       RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; | 
 |   offer_options.offer_to_receive_video = | 
 |       RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; | 
 |   RTCOfferAnswerOptions answer_options; | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get(), offer_options, | 
 |                                               answer_options)); | 
 |  | 
 |   // Add second set of tracks to the callee. | 
 |   callee->AddAudioTrack("a2"); | 
 |   callee->AddVideoTrack("v2"); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get(), offer_options, | 
 |                                               answer_options)); | 
 |  | 
 |   if (IsUnifiedPlan()) { | 
 |     auto callee_voice = | 
 |         VoiceMediaSendChannel(callee->pc()->GetTransceivers()[0]); | 
 |     ASSERT_TRUE(callee_voice); | 
 |     auto callee_video = | 
 |         VideoMediaSendChannel(callee->pc()->GetTransceivers()[1]); | 
 |     ASSERT_TRUE(callee_video); | 
 |  | 
 |     EXPECT_EQ(1u, callee_voice->send_streams().size()); | 
 |     EXPECT_EQ(1u, callee_video->send_streams().size()); | 
 |   } else { | 
 |     EXPECT_EQ(4u, callee->pc()->GetSenders().size()); | 
 |   } | 
 | } | 
 |  | 
 | // A PeerConnection with no local streams and no explicit answer constraints | 
 | // should not reject any offered media sections. | 
 | TEST_P(PeerConnectionMediaTest, | 
 |        CreateAnswerWithNoStreamsAndDefaultOptionsDoesNotReject) { | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnection(); | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |   auto answer = callee->CreateAnswer(); | 
 |  | 
 |   const auto* audio_content = | 
 |       cricket::GetFirstAudioContent(answer->description()); | 
 |   ASSERT_TRUE(audio_content); | 
 |   EXPECT_FALSE(audio_content->rejected); | 
 |  | 
 |   const auto* video_content = | 
 |       cricket::GetFirstVideoContent(answer->description()); | 
 |   ASSERT_TRUE(video_content); | 
 |   EXPECT_FALSE(video_content->rejected); | 
 | } | 
 |  | 
 | // Test that raw packetization is not set in the offer by default. | 
 | TEST_P(PeerConnectionMediaTest, RawPacketizationNotSetInOffer) { | 
 |   std::vector<cricket::Codec> fake_codecs; | 
 |   fake_codecs.push_back(cricket::CreateVideoCodec(111, cricket::kVp8CodecName)); | 
 |   fake_codecs.push_back(cricket::CreateVideoRtxCodec(112, 111)); | 
 |   fake_codecs.push_back(cricket::CreateVideoCodec(113, cricket::kVp9CodecName)); | 
 |   fake_codecs.push_back( | 
 |       cricket::CreateVideoCodec(114, cricket::kH264CodecName)); | 
 |   fake_codecs.push_back(cricket::CreateVideoCodec(115, "HEVC")); | 
 |   auto caller_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   caller_fake_engine->SetVideoCodecs(fake_codecs); | 
 |  | 
 |   auto caller = CreatePeerConnectionWithVideo(std::move(caller_fake_engine)); | 
 |   auto offer = caller->CreateOfferAndSetAsLocal(); | 
 |   auto* offer_description = | 
 |       cricket::GetFirstVideoContentDescription(offer->description()); | 
 |   for (const auto& codec : offer_description->codecs()) { | 
 |     EXPECT_EQ(codec.packetization, absl::nullopt); | 
 |   } | 
 | } | 
 |  | 
 | // Test that raw packetization is set in the offer and answer for all | 
 | // video payload when raw_packetization_for_video is true. | 
 | TEST_P(PeerConnectionMediaTest, RawPacketizationSetInOfferAndAnswer) { | 
 |   std::vector<cricket::Codec> fake_codecs; | 
 |   fake_codecs.push_back(cricket::CreateVideoCodec(111, cricket::kVp8CodecName)); | 
 |   fake_codecs.push_back(cricket::CreateVideoRtxCodec(112, 111)); | 
 |   fake_codecs.push_back(cricket::CreateVideoCodec(113, cricket::kVp9CodecName)); | 
 |   fake_codecs.push_back( | 
 |       cricket::CreateVideoCodec(114, cricket::kH264CodecName)); | 
 |   fake_codecs.push_back(cricket::CreateVideoCodec(115, "HEVC")); | 
 |   auto caller_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   caller_fake_engine->SetVideoCodecs(fake_codecs); | 
 |   auto callee_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   callee_fake_engine->SetVideoCodecs(fake_codecs); | 
 |  | 
 |   RTCOfferAnswerOptions options; | 
 |   options.raw_packetization_for_video = true; | 
 |  | 
 |   auto caller = CreatePeerConnectionWithVideo(std::move(caller_fake_engine)); | 
 |   auto offer = caller->CreateOfferAndSetAsLocal(options); | 
 |   auto* offer_description = | 
 |       cricket::GetFirstVideoContentDescription(offer->description()); | 
 |   for (const auto& codec : offer_description->codecs()) { | 
 |     if (codec.IsMediaCodec()) { | 
 |       EXPECT_EQ(codec.packetization, cricket::kPacketizationParamRaw); | 
 |     } | 
 |   } | 
 |  | 
 |   auto callee = CreatePeerConnectionWithVideo(std::move(callee_fake_engine)); | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | 
 |   auto answer = callee->CreateAnswerAndSetAsLocal(options); | 
 |   auto* answer_description = | 
 |       cricket::GetFirstVideoContentDescription(answer->description()); | 
 |   for (const auto& codec : answer_description->codecs()) { | 
 |     if (codec.IsMediaCodec()) { | 
 |       EXPECT_EQ(codec.packetization, cricket::kPacketizationParamRaw); | 
 |     } | 
 |   } | 
 |  | 
 |   ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer))); | 
 | } | 
 |  | 
 | // Test that raw packetization is not set in the answer when | 
 | // raw_packetization_for_video is true if it was not set in the offer. | 
 | TEST_P(PeerConnectionMediaTest, | 
 |        RawPacketizationNotSetInAnswerWhenNotSetInOffer) { | 
 |   std::vector<cricket::Codec> fake_codecs; | 
 |   fake_codecs.push_back(cricket::CreateVideoCodec(111, cricket::kVp8CodecName)); | 
 |   fake_codecs.push_back(cricket::CreateVideoRtxCodec(112, 111)); | 
 |   fake_codecs.push_back(cricket::CreateVideoCodec(113, cricket::kVp9CodecName)); | 
 |   fake_codecs.push_back( | 
 |       cricket::CreateVideoCodec(114, cricket::kH264CodecName)); | 
 |   fake_codecs.push_back(cricket::CreateVideoCodec(115, "HEVC")); | 
 |   auto caller_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   caller_fake_engine->SetVideoCodecs(fake_codecs); | 
 |   auto callee_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   callee_fake_engine->SetVideoCodecs(fake_codecs); | 
 |  | 
 |   RTCOfferAnswerOptions caller_options; | 
 |   caller_options.raw_packetization_for_video = false; | 
 |   RTCOfferAnswerOptions callee_options; | 
 |   callee_options.raw_packetization_for_video = true; | 
 |  | 
 |   auto caller = CreatePeerConnectionWithVideo(std::move(caller_fake_engine)); | 
 |   auto offer = caller->CreateOfferAndSetAsLocal(caller_options); | 
 |  | 
 |   auto callee = CreatePeerConnectionWithVideo(std::move(callee_fake_engine)); | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | 
 |   auto answer = callee->CreateAnswerAndSetAsLocal(callee_options); | 
 |  | 
 |   auto* answer_description = | 
 |       cricket::GetFirstVideoContentDescription(answer->description()); | 
 |   for (const auto& codec : answer_description->codecs()) { | 
 |     EXPECT_EQ(codec.packetization, absl::nullopt); | 
 |   } | 
 |  | 
 |   ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer))); | 
 | } | 
 |  | 
 | class PeerConnectionMediaOfferDirectionTest | 
 |     : public PeerConnectionMediaBaseTest, | 
 |       public ::testing::WithParamInterface< | 
 |           std::tuple<SdpSemantics, | 
 |                      std::tuple<bool, int, RtpTransceiverDirection>>> { | 
 |  protected: | 
 |   PeerConnectionMediaOfferDirectionTest() | 
 |       : PeerConnectionMediaBaseTest(std::get<0>(GetParam())) { | 
 |     auto param = std::get<1>(GetParam()); | 
 |     send_media_ = std::get<0>(param); | 
 |     offer_to_receive_ = std::get<1>(param); | 
 |     expected_direction_ = std::get<2>(param); | 
 |   } | 
 |  | 
 |   bool send_media_; | 
 |   int offer_to_receive_; | 
 |   RtpTransceiverDirection expected_direction_; | 
 | }; | 
 |  | 
 | // Tests that the correct direction is set on the media description according | 
 | // to the presence of a local media track and the offer_to_receive setting. | 
 | TEST_P(PeerConnectionMediaOfferDirectionTest, VerifyDirection) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   if (send_media_) { | 
 |     caller->AddAudioTrack("a"); | 
 |   } | 
 |  | 
 |   RTCOfferAnswerOptions options; | 
 |   options.offer_to_receive_audio = offer_to_receive_; | 
 |   auto offer = caller->CreateOffer(options); | 
 |  | 
 |   auto* content = cricket::GetFirstMediaContent(offer->description(), | 
 |                                                 cricket::MEDIA_TYPE_AUDIO); | 
 |   if (expected_direction_ == RtpTransceiverDirection::kInactive) { | 
 |     EXPECT_FALSE(content); | 
 |   } else { | 
 |     EXPECT_EQ(expected_direction_, content->media_description()->direction()); | 
 |   } | 
 | } | 
 |  | 
 | // Note that in these tests, MD_INACTIVE indicates that no media section is | 
 | // included in the offer, not that the media direction is inactive. | 
 | INSTANTIATE_TEST_SUITE_P( | 
 |     PeerConnectionMediaTest, | 
 |     PeerConnectionMediaOfferDirectionTest, | 
 |     Combine( | 
 |         Values(SdpSemantics::kPlanB_DEPRECATED, SdpSemantics::kUnifiedPlan), | 
 |         Values(std::make_tuple(false, -1, RtpTransceiverDirection::kInactive), | 
 |                std::make_tuple(false, 0, RtpTransceiverDirection::kInactive), | 
 |                std::make_tuple(false, 1, RtpTransceiverDirection::kRecvOnly), | 
 |                std::make_tuple(true, -1, RtpTransceiverDirection::kSendRecv), | 
 |                std::make_tuple(true, 0, RtpTransceiverDirection::kSendOnly), | 
 |                std::make_tuple(true, 1, RtpTransceiverDirection::kSendRecv)))); | 
 |  | 
 | class PeerConnectionMediaAnswerDirectionTest | 
 |     : public PeerConnectionMediaBaseTest, | 
 |       public ::testing::WithParamInterface< | 
 |           std::tuple<SdpSemantics, RtpTransceiverDirection, bool, int>> { | 
 |  protected: | 
 |   PeerConnectionMediaAnswerDirectionTest() | 
 |       : PeerConnectionMediaBaseTest(std::get<0>(GetParam())) { | 
 |     offer_direction_ = std::get<1>(GetParam()); | 
 |     send_media_ = std::get<2>(GetParam()); | 
 |     offer_to_receive_ = std::get<3>(GetParam()); | 
 |   } | 
 |  | 
 |   RtpTransceiverDirection offer_direction_; | 
 |   bool send_media_; | 
 |   int offer_to_receive_; | 
 | }; | 
 |  | 
 | // Tests that the direction in an answer is correct according to direction sent | 
 | // in the offer, the presence of a local media track on the receive side and the | 
 | // offer_to_receive setting. | 
 | TEST_P(PeerConnectionMediaAnswerDirectionTest, VerifyDirection) { | 
 |   if (IsUnifiedPlan() && | 
 |       offer_to_receive_ != RTCOfferAnswerOptions::kUndefined) { | 
 |     // offer_to_receive_ is not implemented when creating answers with Unified | 
 |     // Plan semantics specified. | 
 |     return; | 
 |   } | 
 |  | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |  | 
 |   // Create the offer with an audio section and set its direction. | 
 |   auto offer = caller->CreateOffer(); | 
 |   cricket::GetFirstAudioContentDescription(offer->description()) | 
 |       ->set_direction(offer_direction_); | 
 |  | 
 |   auto callee = CreatePeerConnection(); | 
 |   if (send_media_) { | 
 |     callee->AddAudioTrack("a"); | 
 |   } | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | 
 |  | 
 |   // Create the answer according to the test parameters. | 
 |   RTCOfferAnswerOptions options; | 
 |   options.offer_to_receive_audio = offer_to_receive_; | 
 |   auto answer = callee->CreateAnswer(options); | 
 |  | 
 |   // The expected direction in the answer is the intersection of each side's | 
 |   // capability to send/recv media. | 
 |   // For the offerer, the direction is given in the offer (offer_direction_). | 
 |   // For the answerer, the direction has two components: | 
 |   // 1. Send if the answerer has a local track to send. | 
 |   // 2. Receive if the answerer has explicitly set the offer_to_receive to 1 or | 
 |   //    if it has been left as default. | 
 |   bool offer_send = RtpTransceiverDirectionHasSend(offer_direction_); | 
 |   bool offer_recv = RtpTransceiverDirectionHasRecv(offer_direction_); | 
 |  | 
 |   // The negotiated components determine the direction set in the answer. | 
 |   bool negotiate_send = (send_media_ && offer_recv); | 
 |   bool negotiate_recv = ((offer_to_receive_ != 0) && offer_send); | 
 |  | 
 |   auto expected_direction = | 
 |       RtpTransceiverDirectionFromSendRecv(negotiate_send, negotiate_recv); | 
 |   EXPECT_EQ(expected_direction, | 
 |             GetMediaContentDirection(answer.get(), cricket::MEDIA_TYPE_AUDIO)); | 
 | } | 
 |  | 
 | // Tests that the media section is rejected if and only if the callee has no | 
 | // local media track and has set offer_to_receive to 0, no matter which | 
 | // direction the caller indicated in the offer. | 
 | TEST_P(PeerConnectionMediaAnswerDirectionTest, VerifyRejected) { | 
 |   if (IsUnifiedPlan() && | 
 |       offer_to_receive_ != RTCOfferAnswerOptions::kUndefined) { | 
 |     // offer_to_receive_ is not implemented when creating answers with Unified | 
 |     // Plan semantics specified. | 
 |     return; | 
 |   } | 
 |  | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |  | 
 |   // Create the offer with an audio section and set its direction. | 
 |   auto offer = caller->CreateOffer(); | 
 |   cricket::GetFirstAudioContentDescription(offer->description()) | 
 |       ->set_direction(offer_direction_); | 
 |  | 
 |   auto callee = CreatePeerConnection(); | 
 |   if (send_media_) { | 
 |     callee->AddAudioTrack("a"); | 
 |   } | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | 
 |  | 
 |   // Create the answer according to the test parameters. | 
 |   RTCOfferAnswerOptions options; | 
 |   options.offer_to_receive_audio = offer_to_receive_; | 
 |   auto answer = callee->CreateAnswer(options); | 
 |  | 
 |   // The media section is rejected if and only if offer_to_receive is explicitly | 
 |   // set to 0 and there is no media to send. | 
 |   auto* audio_content = cricket::GetFirstAudioContent(answer->description()); | 
 |   ASSERT_TRUE(audio_content); | 
 |   EXPECT_EQ((offer_to_receive_ == 0 && !send_media_), audio_content->rejected); | 
 | } | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(PeerConnectionMediaTest, | 
 |                          PeerConnectionMediaAnswerDirectionTest, | 
 |                          Combine(Values(SdpSemantics::kPlanB_DEPRECATED, | 
 |                                         SdpSemantics::kUnifiedPlan), | 
 |                                  Values(RtpTransceiverDirection::kInactive, | 
 |                                         RtpTransceiverDirection::kSendOnly, | 
 |                                         RtpTransceiverDirection::kRecvOnly, | 
 |                                         RtpTransceiverDirection::kSendRecv), | 
 |                                  Bool(), | 
 |                                  Values(-1, 0, 1))); | 
 |  | 
 | TEST_P(PeerConnectionMediaTest, OfferHasDifferentDirectionForAudioVideo) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddVideoTrack("v"); | 
 |  | 
 |   RTCOfferAnswerOptions options; | 
 |   options.offer_to_receive_audio = 1; | 
 |   options.offer_to_receive_video = 0; | 
 |   auto offer = caller->CreateOffer(options); | 
 |  | 
 |   EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, | 
 |             GetMediaContentDirection(offer.get(), cricket::MEDIA_TYPE_AUDIO)); | 
 |   EXPECT_EQ(RtpTransceiverDirection::kSendOnly, | 
 |             GetMediaContentDirection(offer.get(), cricket::MEDIA_TYPE_VIDEO)); | 
 | } | 
 |  | 
 | TEST_P(PeerConnectionMediaTest, AnswerHasDifferentDirectionsForAudioVideo) { | 
 |   if (IsUnifiedPlan()) { | 
 |     // offer_to_receive_ is not implemented when creating answers with Unified | 
 |     // Plan semantics specified. | 
 |     return; | 
 |   } | 
 |  | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnection(); | 
 |   callee->AddVideoTrack("v"); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   RTCOfferAnswerOptions options; | 
 |   options.offer_to_receive_audio = 1; | 
 |   options.offer_to_receive_video = 0; | 
 |   auto answer = callee->CreateAnswer(options); | 
 |  | 
 |   EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, | 
 |             GetMediaContentDirection(answer.get(), cricket::MEDIA_TYPE_AUDIO)); | 
 |   EXPECT_EQ(RtpTransceiverDirection::kSendOnly, | 
 |             GetMediaContentDirection(answer.get(), cricket::MEDIA_TYPE_VIDEO)); | 
 | } | 
 |  | 
 | void AddComfortNoiseCodecsToSend(cricket::FakeMediaEngine* media_engine) { | 
 |   const cricket::Codec kComfortNoiseCodec8k = | 
 |       cricket::CreateAudioCodec(102, cricket::kCnCodecName, 8000, 1); | 
 |   const cricket::Codec kComfortNoiseCodec16k = | 
 |       cricket::CreateAudioCodec(103, cricket::kCnCodecName, 16000, 1); | 
 |  | 
 |   auto codecs = media_engine->voice().send_codecs(); | 
 |   codecs.push_back(kComfortNoiseCodec8k); | 
 |   codecs.push_back(kComfortNoiseCodec16k); | 
 |   media_engine->SetAudioCodecs(codecs); | 
 | } | 
 |  | 
 | bool HasAnyComfortNoiseCodecs(const cricket::SessionDescription* desc) { | 
 |   const auto* audio_desc = cricket::GetFirstAudioContentDescription(desc); | 
 |   for (const auto& codec : audio_desc->codecs()) { | 
 |     if (codec.name == cricket::kCnCodecName) { | 
 |       return true; | 
 |     } | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | bool HasPayloadTypeConflict(const cricket::SessionDescription* desc) { | 
 |   std::set<int> payload_types; | 
 |   const auto* audio_desc = cricket::GetFirstAudioContentDescription(desc); | 
 |   if (audio_desc) { | 
 |     for (const auto& codec : audio_desc->codecs()) { | 
 |       if (payload_types.count(codec.id) > 0) { | 
 |         return true; | 
 |       } | 
 |       payload_types.insert(codec.id); | 
 |     } | 
 |   } | 
 |   const auto* video_desc = cricket::GetFirstVideoContentDescription(desc); | 
 |   if (video_desc) { | 
 |     for (const auto& codec : video_desc->codecs()) { | 
 |       if (payload_types.count(codec.id) > 0) { | 
 |         return true; | 
 |       } | 
 |       payload_types.insert(codec.id); | 
 |     } | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | TEST_P(PeerConnectionMediaTest, | 
 |        CreateOfferWithNoVoiceActivityDetectionIncludesNoComfortNoiseCodecs) { | 
 |   auto fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   AddComfortNoiseCodecsToSend(fake_engine.get()); | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(std::move(fake_engine)); | 
 |  | 
 |   RTCOfferAnswerOptions options; | 
 |   options.voice_activity_detection = false; | 
 |   auto offer = caller->CreateOffer(options); | 
 |  | 
 |   EXPECT_FALSE(HasAnyComfortNoiseCodecs(offer->description())); | 
 | } | 
 |  | 
 | TEST_P(PeerConnectionMediaTest, | 
 |        CreateOfferWithVoiceActivityDetectionIncludesComfortNoiseCodecs) { | 
 |   auto fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   AddComfortNoiseCodecsToSend(fake_engine.get()); | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(std::move(fake_engine)); | 
 |  | 
 |   RTCOfferAnswerOptions options; | 
 |   options.voice_activity_detection = true; | 
 |   auto offer = caller->CreateOffer(options); | 
 |  | 
 |   EXPECT_TRUE(HasAnyComfortNoiseCodecs(offer->description())); | 
 | } | 
 |  | 
 | TEST_P(PeerConnectionMediaTest, | 
 |        CreateAnswerWithVoiceActivityDetectionIncludesNoComfortNoiseCodecs) { | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |  | 
 |   auto callee_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   AddComfortNoiseCodecsToSend(callee_fake_engine.get()); | 
 |   auto callee = | 
 |       CreatePeerConnectionWithAudioVideo(std::move(callee_fake_engine)); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   RTCOfferAnswerOptions options; | 
 |   options.voice_activity_detection = true; | 
 |   auto answer = callee->CreateAnswer(options); | 
 |  | 
 |   EXPECT_FALSE(HasAnyComfortNoiseCodecs(answer->description())); | 
 | } | 
 |  | 
 | TEST_P(PeerConnectionMediaTest, | 
 |        CreateAnswerWithNoVoiceActivityDetectionIncludesNoComfortNoiseCodecs) { | 
 |   auto caller_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   AddComfortNoiseCodecsToSend(caller_fake_engine.get()); | 
 |   auto caller = | 
 |       CreatePeerConnectionWithAudioVideo(std::move(caller_fake_engine)); | 
 |  | 
 |   auto callee_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   AddComfortNoiseCodecsToSend(callee_fake_engine.get()); | 
 |   auto callee = | 
 |       CreatePeerConnectionWithAudioVideo(std::move(callee_fake_engine)); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   RTCOfferAnswerOptions options; | 
 |   options.voice_activity_detection = false; | 
 |   auto answer = callee->CreateAnswer(options); | 
 |  | 
 |   EXPECT_FALSE(HasAnyComfortNoiseCodecs(answer->description())); | 
 | } | 
 |  | 
 | // The following test group verifies that we reject answers with invalid media | 
 | // sections as per RFC 3264. | 
 |  | 
 | class PeerConnectionMediaInvalidMediaTest | 
 |     : public PeerConnectionMediaBaseTest, | 
 |       public ::testing::WithParamInterface<std::tuple< | 
 |           SdpSemantics, | 
 |           std::tuple<std::string, | 
 |                      std::function<void(cricket::SessionDescription*)>, | 
 |                      std::string>>> { | 
 |  protected: | 
 |   PeerConnectionMediaInvalidMediaTest() | 
 |       : PeerConnectionMediaBaseTest(std::get<0>(GetParam())) { | 
 |     auto param = std::get<1>(GetParam()); | 
 |     mutator_ = std::get<1>(param); | 
 |     expected_error_ = std::get<2>(param); | 
 |   } | 
 |  | 
 |   std::function<void(cricket::SessionDescription*)> mutator_; | 
 |   std::string expected_error_; | 
 | }; | 
 |  | 
 | TEST_P(PeerConnectionMediaInvalidMediaTest, FailToSetRemoteAnswer) { | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnectionWithAudioVideo(); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   auto answer = callee->CreateAnswer(); | 
 |   mutator_(answer->description()); | 
 |  | 
 |   std::string error; | 
 |   ASSERT_FALSE(caller->SetRemoteDescription(std::move(answer), &error)); | 
 |   EXPECT_EQ("Failed to set remote answer sdp: " + expected_error_, error); | 
 | } | 
 |  | 
 | TEST_P(PeerConnectionMediaInvalidMediaTest, FailToSetLocalAnswer) { | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnectionWithAudioVideo(); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   auto answer = callee->CreateAnswer(); | 
 |   mutator_(answer->description()); | 
 |  | 
 |   std::string error; | 
 |   ASSERT_FALSE(callee->SetLocalDescription(std::move(answer), &error)); | 
 |   EXPECT_EQ("Failed to set local answer sdp: " + expected_error_, error); | 
 | } | 
 |  | 
 | void RemoveVideoContentAndUnbundle(cricket::SessionDescription* desc) { | 
 |   // Removing BUNDLE is easier than removing the content in there. | 
 |   desc->RemoveGroupByName("BUNDLE"); | 
 |   auto content_name = cricket::GetFirstVideoContent(desc)->name; | 
 |   desc->RemoveContentByName(content_name); | 
 |   desc->RemoveTransportInfoByName(content_name); | 
 | } | 
 |  | 
 | void RenameVideoContentAndUnbundle(cricket::SessionDescription* desc) { | 
 |   // Removing BUNDLE is easier than renaming the content in there. | 
 |   desc->RemoveGroupByName("BUNDLE"); | 
 |   auto* video_content = cricket::GetFirstVideoContent(desc); | 
 |   auto* transport_info = desc->GetTransportInfoByName(video_content->name); | 
 |   video_content->name = "video_renamed"; | 
 |   transport_info->content_name = video_content->name; | 
 | } | 
 |  | 
 | void ReverseMediaContent(cricket::SessionDescription* desc) { | 
 |   absl::c_reverse(desc->contents()); | 
 |   absl::c_reverse(desc->transport_infos()); | 
 | } | 
 |  | 
 | void ChangeMediaTypeAudioToVideo(cricket::SessionDescription* desc) { | 
 |   std::string audio_mid = cricket::GetFirstAudioContent(desc)->name; | 
 |   desc->RemoveContentByName(audio_mid); | 
 |   auto* video_content = cricket::GetFirstVideoContent(desc); | 
 |   desc->AddContent(audio_mid, video_content->type, | 
 |                    video_content->media_description()->Clone()); | 
 | } | 
 |  | 
 | constexpr char kMLinesOutOfOrder[] = | 
 |     "The order of m-lines in answer doesn't match order in offer. Rejecting " | 
 |     "answer."; | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P( | 
 |     PeerConnectionMediaTest, | 
 |     PeerConnectionMediaInvalidMediaTest, | 
 |     Combine(Values(SdpSemantics::kPlanB_DEPRECATED, SdpSemantics::kUnifiedPlan), | 
 |             Values(std::make_tuple("remove video", | 
 |                                    RemoveVideoContentAndUnbundle, | 
 |                                    kMLinesOutOfOrder), | 
 |                    std::make_tuple("rename video", | 
 |                                    RenameVideoContentAndUnbundle, | 
 |                                    kMLinesOutOfOrder), | 
 |                    std::make_tuple("reverse media sections", | 
 |                                    ReverseMediaContent, | 
 |                                    kMLinesOutOfOrder), | 
 |                    std::make_tuple("change audio type to video type", | 
 |                                    ChangeMediaTypeAudioToVideo, | 
 |                                    kMLinesOutOfOrder)))); | 
 |  | 
 | // Tests that if the underlying video encoder fails to be initialized (signaled | 
 | // by failing to set send codecs), the PeerConnection signals the error to the | 
 | // client. | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        MediaEngineErrorPropagatedToClients) { | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnectionWithAudioVideo(); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   auto video_channel = | 
 |       VideoMediaSendChannel(caller->pc()->GetTransceivers()[1]); | 
 |   video_channel->set_fail_set_send_codecs(true); | 
 |  | 
 |   std::string error; | 
 |   ASSERT_FALSE(caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal(), | 
 |                                             &error)); | 
 |   EXPECT_EQ(std::string("Failed to set remote answer sdp: Failed to set remote " | 
 |                         "video description " | 
 |                         "send parameters for m-section with mid='") + | 
 |                 (IsUnifiedPlan() ? "1" : "video") + "'.", | 
 |             error); | 
 | } | 
 |  | 
 | // Tests that if the underlying video encoder fails once then subsequent | 
 | // attempts at setting the local/remote description will also fail, even if | 
 | // SetSendCodecs no longer fails. | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        FailToApplyDescriptionIfVideoEncoderHasEverFailed) { | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnectionWithAudioVideo(); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   auto video_channel = | 
 |       VideoMediaSendChannel(caller->pc()->GetTransceivers()[1]); | 
 |   video_channel->set_fail_set_send_codecs(true); | 
 |  | 
 |   EXPECT_FALSE( | 
 |       caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); | 
 |  | 
 |   video_channel->set_fail_set_send_codecs(false); | 
 |  | 
 |   EXPECT_FALSE(caller->SetRemoteDescription(callee->CreateAnswer())); | 
 |   EXPECT_FALSE(caller->SetLocalDescription(caller->CreateOffer())); | 
 | } | 
 |  | 
 | void RenameContent(cricket::SessionDescription* desc, | 
 |                    cricket::MediaType media_type, | 
 |                    const std::string& new_name) { | 
 |   auto* content = cricket::GetFirstMediaContent(desc, media_type); | 
 |   RTC_DCHECK(content); | 
 |   std::string old_name = content->name; | 
 |   content->name = new_name; | 
 |   auto* transport = desc->GetTransportInfoByName(old_name); | 
 |   RTC_DCHECK(transport); | 
 |   transport->content_name = new_name; | 
 |  | 
 |   // Rename the content name in the BUNDLE group. | 
 |   cricket::ContentGroup new_bundle_group = | 
 |       *desc->GetGroupByName(cricket::GROUP_TYPE_BUNDLE); | 
 |   new_bundle_group.RemoveContentName(old_name); | 
 |   new_bundle_group.AddContentName(new_name); | 
 |   desc->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); | 
 |   desc->AddGroup(new_bundle_group); | 
 | } | 
 |  | 
 | // Tests that an answer responds with the same MIDs as the offer. | 
 | TEST_P(PeerConnectionMediaTest, AnswerHasSameMidsAsOffer) { | 
 |   const std::string kAudioMid = "notdefault1"; | 
 |   const std::string kVideoMid = "notdefault2"; | 
 |  | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnectionWithAudioVideo(); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   RenameContent(offer->description(), cricket::MEDIA_TYPE_AUDIO, kAudioMid); | 
 |   RenameContent(offer->description(), cricket::MEDIA_TYPE_VIDEO, kVideoMid); | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | 
 |  | 
 |   auto answer = callee->CreateAnswer(); | 
 |   EXPECT_EQ(kAudioMid, | 
 |             cricket::GetFirstAudioContent(answer->description())->name); | 
 |   EXPECT_EQ(kVideoMid, | 
 |             cricket::GetFirstVideoContent(answer->description())->name); | 
 | } | 
 |  | 
 | // Test that if the callee creates a re-offer, the MIDs are the same as the | 
 | // original offer. | 
 | TEST_P(PeerConnectionMediaTest, ReOfferHasSameMidsAsFirstOffer) { | 
 |   const std::string kAudioMid = "notdefault1"; | 
 |   const std::string kVideoMid = "notdefault2"; | 
 |  | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnectionWithAudioVideo(); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   RenameContent(offer->description(), cricket::MEDIA_TYPE_AUDIO, kAudioMid); | 
 |   RenameContent(offer->description(), cricket::MEDIA_TYPE_VIDEO, kVideoMid); | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | 
 |   ASSERT_TRUE(callee->SetLocalDescription(callee->CreateAnswer())); | 
 |  | 
 |   auto reoffer = callee->CreateOffer(); | 
 |   EXPECT_EQ(kAudioMid, | 
 |             cricket::GetFirstAudioContent(reoffer->description())->name); | 
 |   EXPECT_EQ(kVideoMid, | 
 |             cricket::GetFirstVideoContent(reoffer->description())->name); | 
 | } | 
 |  | 
 | // Test that SetRemoteDescription returns an error if there are two m= sections | 
 | // with the same MID value. | 
 | TEST_P(PeerConnectionMediaTest, SetRemoteDescriptionFailsWithDuplicateMids) { | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(); | 
 |   auto callee = CreatePeerConnectionWithAudioVideo(); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   RenameContent(offer->description(), cricket::MEDIA_TYPE_AUDIO, "same"); | 
 |   RenameContent(offer->description(), cricket::MEDIA_TYPE_VIDEO, "same"); | 
 |  | 
 |   std::string error; | 
 |   EXPECT_FALSE(callee->SetRemoteDescription(std::move(offer), &error)); | 
 |   EXPECT_EQ(error, | 
 |             "Failed to set remote offer sdp: Duplicate a=mid value 'same'."); | 
 | } | 
 |  | 
 | // Test that if a RED codec refers to another codec in its fmtp line, but that | 
 | // codec's payload type was reassigned for some reason (either the remote | 
 | // endpoint selected a different payload type or there was a conflict), the RED | 
 | // fmtp line is modified to refer to the correct payload type. | 
 | TEST_P(PeerConnectionMediaTest, RedFmtpPayloadTypeReassigned) { | 
 |   std::vector<cricket::Codec> caller_fake_codecs; | 
 |   caller_fake_codecs.push_back(cricket::CreateAudioCodec(100, "foo", 0, 1)); | 
 |   auto caller_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   caller_fake_engine->SetAudioCodecs(caller_fake_codecs); | 
 |   auto caller = CreatePeerConnectionWithAudio(std::move(caller_fake_engine)); | 
 |  | 
 |   std::vector<cricket::Codec> callee_fake_codecs; | 
 |   callee_fake_codecs.push_back(cricket::CreateAudioCodec(120, "foo", 0, 1)); | 
 |   callee_fake_codecs.push_back( | 
 |       cricket::CreateAudioCodec(121, cricket::kRedCodecName, 0, 1)); | 
 |   callee_fake_codecs.back().SetParam(cricket::kCodecParamNotInNameValueFormat, | 
 |                                      "120/120"); | 
 |   auto callee_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   callee_fake_engine->SetAudioCodecs(callee_fake_codecs); | 
 |   auto callee = CreatePeerConnectionWithAudio(std::move(callee_fake_engine)); | 
 |  | 
 |   // Offer from the caller establishes 100 as the "foo" payload type. | 
 |   auto offer = caller->CreateOfferAndSetAsLocal(); | 
 |   callee->SetRemoteDescription(std::move(offer)); | 
 |   auto answer = callee->CreateAnswerAndSetAsLocal(); | 
 |   auto answer_description = | 
 |       cricket::GetFirstAudioContentDescription(answer->description()); | 
 |   ASSERT_EQ(1u, answer_description->codecs().size()); | 
 |  | 
 |   // Offer from the callee should respect the established payload type, and | 
 |   // attempt to add RED, which should refer to the correct payload type. | 
 |   offer = callee->CreateOfferAndSetAsLocal(); | 
 |   auto* offer_description = | 
 |       cricket::GetFirstAudioContentDescription(offer->description()); | 
 |   ASSERT_EQ(2u, offer_description->codecs().size()); | 
 |   for (const auto& codec : offer_description->codecs()) { | 
 |     if (codec.name == "foo") { | 
 |       ASSERT_EQ(100, codec.id); | 
 |     } else if (codec.name == cricket::kRedCodecName) { | 
 |       std::string fmtp; | 
 |       ASSERT_TRUE(codec.GetParam("", &fmtp)); | 
 |       EXPECT_EQ("100/100", fmtp); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | // Test that RED without fmtp does match RED without fmtp. | 
 | TEST_P(PeerConnectionMediaTest, RedFmtpPayloadTypeNoFmtpMatchNoFmtp) { | 
 |   std::vector<cricket::Codec> caller_fake_codecs; | 
 |   caller_fake_codecs.push_back(cricket::CreateAudioCodec(100, "foo", 0, 1)); | 
 |   caller_fake_codecs.push_back( | 
 |       cricket::CreateAudioCodec(101, cricket::kRedCodecName, 0, 1)); | 
 |   auto caller_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   caller_fake_engine->SetAudioCodecs(caller_fake_codecs); | 
 |   auto caller = CreatePeerConnectionWithAudio(std::move(caller_fake_engine)); | 
 |  | 
 |   std::vector<cricket::Codec> callee_fake_codecs; | 
 |   callee_fake_codecs.push_back(cricket::CreateAudioCodec(120, "foo", 0, 1)); | 
 |   callee_fake_codecs.push_back( | 
 |       cricket::CreateAudioCodec(121, cricket::kRedCodecName, 0, 1)); | 
 |   auto callee_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   callee_fake_engine->SetAudioCodecs(callee_fake_codecs); | 
 |   auto callee = CreatePeerConnectionWithAudio(std::move(callee_fake_engine)); | 
 |  | 
 |   // Offer from the caller establishes 100 as the "foo" payload type. | 
 |   // Red (without fmtp) is negotiated. | 
 |   auto offer = caller->CreateOfferAndSetAsLocal(); | 
 |   callee->SetRemoteDescription(std::move(offer)); | 
 |   auto answer = callee->CreateAnswerAndSetAsLocal(); | 
 |   auto answer_description = | 
 |       cricket::GetFirstAudioContentDescription(answer->description()); | 
 |   ASSERT_EQ(2u, answer_description->codecs().size()); | 
 |  | 
 |   // Offer from the callee should respect the established payload type, and | 
 |   // attempt to add RED. | 
 |   offer = callee->CreateOfferAndSetAsLocal(); | 
 |   auto* offer_description = | 
 |       cricket::GetFirstAudioContentDescription(offer->description()); | 
 |   ASSERT_EQ(2u, offer_description->codecs().size()); | 
 |   for (const auto& codec : offer_description->codecs()) { | 
 |     if (codec.name == "foo") { | 
 |       ASSERT_EQ(100, codec.id); | 
 |     } else if (codec.name == cricket::kRedCodecName) { | 
 |       ASSERT_EQ(101, codec.id); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | // Test that RED without fmtp does not match RED with fmtp. | 
 | TEST_P(PeerConnectionMediaTest, RedFmtpPayloadTypeNoFmtpNoMatchFmtp) { | 
 |   std::vector<cricket::Codec> caller_fake_codecs; | 
 |   caller_fake_codecs.push_back(cricket::CreateAudioCodec(100, "foo", 0, 1)); | 
 |   caller_fake_codecs.push_back( | 
 |       cricket::CreateAudioCodec(101, cricket::kRedCodecName, 0, 1)); | 
 |   auto caller_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   caller_fake_engine->SetAudioCodecs(caller_fake_codecs); | 
 |   auto caller = CreatePeerConnectionWithAudio(std::move(caller_fake_engine)); | 
 |  | 
 |   std::vector<cricket::Codec> callee_fake_codecs; | 
 |   callee_fake_codecs.push_back(cricket::CreateAudioCodec(120, "foo", 0, 1)); | 
 |   callee_fake_codecs.push_back( | 
 |       cricket::CreateAudioCodec(121, cricket::kRedCodecName, 0, 1)); | 
 |   callee_fake_codecs.back().SetParam(cricket::kCodecParamNotInNameValueFormat, | 
 |                                      "120/120"); | 
 |   auto callee_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   callee_fake_engine->SetAudioCodecs(callee_fake_codecs); | 
 |   auto callee = CreatePeerConnectionWithAudio(std::move(callee_fake_engine)); | 
 |  | 
 |   // Offer from the caller establishes 100 as the "foo" payload type. | 
 |   // It should not negotiate RED. | 
 |   auto offer = caller->CreateOfferAndSetAsLocal(); | 
 |   callee->SetRemoteDescription(std::move(offer)); | 
 |   auto answer = callee->CreateAnswerAndSetAsLocal(); | 
 |   auto answer_description = | 
 |       cricket::GetFirstAudioContentDescription(answer->description()); | 
 |   ASSERT_EQ(1u, answer_description->codecs().size()); | 
 |  | 
 |   // Offer from the callee should respect the established payload type, and | 
 |   // attempt to add RED, which should refer to the correct payload type. | 
 |   offer = callee->CreateOfferAndSetAsLocal(); | 
 |   auto* offer_description = | 
 |       cricket::GetFirstAudioContentDescription(offer->description()); | 
 |   ASSERT_EQ(2u, offer_description->codecs().size()); | 
 |   for (const auto& codec : offer_description->codecs()) { | 
 |     if (codec.name == "foo") { | 
 |       ASSERT_EQ(100, codec.id); | 
 |     } else if (codec.name == cricket::kRedCodecName) { | 
 |       std::string fmtp; | 
 |       ASSERT_TRUE( | 
 |           codec.GetParam(cricket::kCodecParamNotInNameValueFormat, &fmtp)); | 
 |       EXPECT_EQ("100/100", fmtp); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | // Test that RED with fmtp must match base codecs. | 
 | TEST_P(PeerConnectionMediaTest, RedFmtpPayloadTypeMustMatchBaseCodecs) { | 
 |   std::vector<cricket::Codec> caller_fake_codecs; | 
 |   caller_fake_codecs.push_back(cricket::CreateAudioCodec(100, "foo", 0, 1)); | 
 |   caller_fake_codecs.push_back( | 
 |       cricket::CreateAudioCodec(101, cricket::kRedCodecName, 0, 1)); | 
 |   caller_fake_codecs.back().SetParam(cricket::kCodecParamNotInNameValueFormat, | 
 |                                      "100/100"); | 
 |   auto caller_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   caller_fake_engine->SetAudioCodecs(caller_fake_codecs); | 
 |   auto caller = CreatePeerConnectionWithAudio(std::move(caller_fake_engine)); | 
 |  | 
 |   std::vector<cricket::Codec> callee_fake_codecs; | 
 |   callee_fake_codecs.push_back(cricket::CreateAudioCodec(120, "foo", 0, 1)); | 
 |   callee_fake_codecs.push_back( | 
 |       cricket::CreateAudioCodec(121, cricket::kRedCodecName, 0, 1)); | 
 |   callee_fake_codecs.push_back(cricket::CreateAudioCodec(122, "bar", 0, 1)); | 
 |   callee_fake_codecs.back().SetParam(cricket::kCodecParamNotInNameValueFormat, | 
 |                                      "122/122"); | 
 |   auto callee_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   callee_fake_engine->SetAudioCodecs(callee_fake_codecs); | 
 |   auto callee = CreatePeerConnectionWithAudio(std::move(callee_fake_engine)); | 
 |  | 
 |   // Offer from the caller establishes 100 as the "foo" payload type. | 
 |   // It should not negotiate RED since RED is associated with foo, not bar. | 
 |   auto offer = caller->CreateOfferAndSetAsLocal(); | 
 |   callee->SetRemoteDescription(std::move(offer)); | 
 |   auto answer = callee->CreateAnswerAndSetAsLocal(); | 
 |   auto answer_description = | 
 |       cricket::GetFirstAudioContentDescription(answer->description()); | 
 |   ASSERT_EQ(1u, answer_description->codecs().size()); | 
 | } | 
 |  | 
 | // Test behaviour when the RED fmtp attempts to specify different codecs | 
 | // which is not supported. | 
 | TEST_P(PeerConnectionMediaTest, RedFmtpPayloadMixed) { | 
 |   std::vector<cricket::Codec> caller_fake_codecs; | 
 |   caller_fake_codecs.push_back(cricket::CreateAudioCodec(100, "foo", 0, 1)); | 
 |   caller_fake_codecs.push_back(cricket::CreateAudioCodec(102, "bar", 0, 1)); | 
 |   caller_fake_codecs.push_back( | 
 |       cricket::CreateAudioCodec(101, cricket::kRedCodecName, 0, 1)); | 
 |   caller_fake_codecs.back().SetParam(cricket::kCodecParamNotInNameValueFormat, | 
 |                                      "100/102"); | 
 |   auto caller_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   caller_fake_engine->SetAudioCodecs(caller_fake_codecs); | 
 |   auto caller = CreatePeerConnectionWithAudio(std::move(caller_fake_engine)); | 
 |  | 
 |   std::vector<cricket::Codec> callee_fake_codecs; | 
 |   callee_fake_codecs.push_back(cricket::CreateAudioCodec(120, "foo", 0, 1)); | 
 |   callee_fake_codecs.push_back( | 
 |       cricket::CreateAudioCodec(121, cricket::kRedCodecName, 0, 1)); | 
 |   callee_fake_codecs.back().SetParam(cricket::kCodecParamNotInNameValueFormat, | 
 |                                      "120/120"); | 
 |   auto callee_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   callee_fake_engine->SetAudioCodecs(callee_fake_codecs); | 
 |   auto callee = CreatePeerConnectionWithAudio(std::move(callee_fake_engine)); | 
 |  | 
 |   // Offer from the caller establishes 100 as the "foo" payload type. | 
 |   auto offer = caller->CreateOfferAndSetAsLocal(); | 
 |   callee->SetRemoteDescription(std::move(offer)); | 
 |   auto answer = callee->CreateAnswerAndSetAsLocal(); | 
 |   auto answer_description = | 
 |       cricket::GetFirstAudioContentDescription(answer->description()); | 
 |   // RED is not negotiated. | 
 |   ASSERT_EQ(1u, answer_description->codecs().size()); | 
 | } | 
 |  | 
 | // Test behaviour when the RED fmtp attempts to negotiate different levels of | 
 | // redundancy. | 
 | TEST_P(PeerConnectionMediaTest, RedFmtpPayloadDifferentRedundancy) { | 
 |   std::vector<cricket::Codec> caller_fake_codecs; | 
 |   caller_fake_codecs.push_back(cricket::CreateAudioCodec(100, "foo", 0, 1)); | 
 |   caller_fake_codecs.push_back( | 
 |       cricket::CreateAudioCodec(101, cricket::kRedCodecName, 0, 1)); | 
 |   caller_fake_codecs.back().SetParam(cricket::kCodecParamNotInNameValueFormat, | 
 |                                      "100/100"); | 
 |   auto caller_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   caller_fake_engine->SetAudioCodecs(caller_fake_codecs); | 
 |   auto caller = CreatePeerConnectionWithAudio(std::move(caller_fake_engine)); | 
 |  | 
 |   std::vector<cricket::Codec> callee_fake_codecs; | 
 |   callee_fake_codecs.push_back(cricket::CreateAudioCodec(120, "foo", 0, 1)); | 
 |   callee_fake_codecs.push_back( | 
 |       cricket::CreateAudioCodec(121, cricket::kRedCodecName, 0, 1)); | 
 |   callee_fake_codecs.back().SetParam(cricket::kCodecParamNotInNameValueFormat, | 
 |                                      "120/120/120"); | 
 |   auto callee_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   callee_fake_engine->SetAudioCodecs(callee_fake_codecs); | 
 |   auto callee = CreatePeerConnectionWithAudio(std::move(callee_fake_engine)); | 
 |  | 
 |   // Offer from the caller establishes 100 as the "foo" payload type. | 
 |   auto offer = caller->CreateOfferAndSetAsLocal(); | 
 |   callee->SetRemoteDescription(std::move(offer)); | 
 |   auto answer = callee->CreateAnswerAndSetAsLocal(); | 
 |   auto answer_description = | 
 |       cricket::GetFirstAudioContentDescription(answer->description()); | 
 |   // RED is negotiated. | 
 |   ASSERT_EQ(2u, answer_description->codecs().size()); | 
 |  | 
 |   // Offer from the callee should respect the established payload type, and | 
 |   // attempt to add RED, which should refer to the correct payload type. | 
 |   offer = callee->CreateOfferAndSetAsLocal(); | 
 |   auto* offer_description = | 
 |       cricket::GetFirstAudioContentDescription(offer->description()); | 
 |   ASSERT_EQ(2u, offer_description->codecs().size()); | 
 |   for (const auto& codec : offer_description->codecs()) { | 
 |     if (codec.name == "foo") { | 
 |       ASSERT_EQ(100, codec.id); | 
 |     } else if (codec.name == cricket::kRedCodecName) { | 
 |       std::string fmtp; | 
 |       ASSERT_TRUE( | 
 |           codec.GetParam(cricket::kCodecParamNotInNameValueFormat, &fmtp)); | 
 |       EXPECT_EQ("100/100", fmtp); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | template <typename C> | 
 | bool CompareCodecs(const std::vector<RtpCodecCapability>& capabilities, | 
 |                    const std::vector<C>& codecs) { | 
 |   bool capability_has_rtx = | 
 |       absl::c_any_of(capabilities, [](const RtpCodecCapability& codec) { | 
 |         return codec.name == cricket::kRtxCodecName; | 
 |       }); | 
 |   bool codecs_has_rtx = absl::c_any_of(codecs, [](const C& codec) { | 
 |     return codec.name == cricket::kRtxCodecName; | 
 |   }); | 
 |  | 
 |   std::vector<C> codecs_no_rtx; | 
 |   absl::c_copy_if( | 
 |       codecs, std::back_inserter(codecs_no_rtx), | 
 |       [](const C& codec) { return codec.name != cricket::kRtxCodecName; }); | 
 |  | 
 |   std::vector<RtpCodecCapability> capabilities_no_rtx; | 
 |   absl::c_copy_if(capabilities, std::back_inserter(capabilities_no_rtx), | 
 |                   [](const RtpCodecCapability& codec) { | 
 |                     return codec.name != cricket::kRtxCodecName; | 
 |                   }); | 
 |  | 
 |   return capability_has_rtx == codecs_has_rtx && | 
 |          absl::c_equal( | 
 |              capabilities_no_rtx, codecs_no_rtx, | 
 |              [](const RtpCodecCapability& capability, const C& codec) { | 
 |                return codec.MatchesRtpCodec(capability); | 
 |              }); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesAudioMissingRecvCodec) { | 
 |   auto fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   auto send_codecs = fake_engine->voice().send_codecs(); | 
 |   send_codecs.push_back(cricket::CreateAudioCodec(send_codecs.back().id + 1, | 
 |                                                   "send_only_codec", 0, 1)); | 
 |   fake_engine->SetAudioSendCodecs(send_codecs); | 
 |  | 
 |   auto caller = CreatePeerConnectionWithAudio(std::move(fake_engine)); | 
 |  | 
 |   auto transceiver = caller->pc()->GetTransceivers().front(); | 
 |   auto capabilities = caller->pc_factory()->GetRtpSenderCapabilities( | 
 |       cricket::MediaType::MEDIA_TYPE_AUDIO); | 
 |  | 
 |   std::vector<RtpCodecCapability> codecs; | 
 |   absl::c_copy_if(capabilities.codecs, std::back_inserter(codecs), | 
 |                   [](const RtpCodecCapability& codec) { | 
 |                     return codec.name.find("_only_") != std::string::npos; | 
 |                   }); | 
 |  | 
 |   auto result = transceiver->SetCodecPreferences(codecs); | 
 |   EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, result.type()); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesAudioRejectsVideoCodec) { | 
 |   auto caller = CreatePeerConnectionWithAudio(); | 
 |  | 
 |   auto transceiver = caller->pc()->GetTransceivers().front(); | 
 |   auto video_codecs = | 
 |       caller->pc_factory() | 
 |           ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_VIDEO) | 
 |           .codecs; | 
 |   auto codecs = | 
 |       caller->pc_factory() | 
 |           ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_AUDIO) | 
 |           .codecs; | 
 |   codecs.insert(codecs.end(), video_codecs.begin(), video_codecs.end()); | 
 |   auto result = transceiver->SetCodecPreferences(codecs); | 
 |   EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, result.type()); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesAudioRejectsOnlyRtxRedFec) { | 
 |   auto fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   auto audio_codecs = fake_engine->voice().send_codecs(); | 
 |   audio_codecs.push_back(cricket::CreateAudioRtxCodec( | 
 |       audio_codecs.back().id + 1, audio_codecs.back().id)); | 
 |   audio_codecs.push_back(cricket::CreateAudioCodec( | 
 |       audio_codecs.back().id + 1, cricket::kRedCodecName, 0, 1)); | 
 |   audio_codecs.push_back(cricket::CreateAudioCodec( | 
 |       audio_codecs.back().id + 1, cricket::kUlpfecCodecName, 0, 1)); | 
 |   fake_engine->SetAudioCodecs(audio_codecs); | 
 |  | 
 |   auto caller = CreatePeerConnectionWithAudio(std::move(fake_engine)); | 
 |  | 
 |   auto transceiver = caller->pc()->GetTransceivers().front(); | 
 |   auto codecs = | 
 |       caller->pc_factory() | 
 |           ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_AUDIO) | 
 |           .codecs; | 
 |   auto codecs_only_rtx_red_fec = codecs; | 
 |   auto it = std::remove_if(codecs_only_rtx_red_fec.begin(), | 
 |                            codecs_only_rtx_red_fec.end(), | 
 |                            [](const RtpCodecCapability& codec) { | 
 |                              return !(codec.name == cricket::kRtxCodecName || | 
 |                                       codec.name == cricket::kRedCodecName || | 
 |                                       codec.name == cricket::kUlpfecCodecName); | 
 |                            }); | 
 |   codecs_only_rtx_red_fec.erase(it, codecs_only_rtx_red_fec.end()); | 
 |  | 
 |   auto result = transceiver->SetCodecPreferences(codecs_only_rtx_red_fec); | 
 |   EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, result.type()); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, SetCodecPreferencesAllAudioCodecs) { | 
 |   auto caller = CreatePeerConnectionWithAudio(); | 
 |  | 
 |   auto sender_audio_codecs = | 
 |       caller->pc_factory() | 
 |           ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO) | 
 |           .codecs; | 
 |  | 
 |   auto audio_transceiver = caller->pc()->GetTransceivers().front(); | 
 |  | 
 |   // Normal case, set all capabilities as preferences | 
 |   EXPECT_TRUE(audio_transceiver->SetCodecPreferences(sender_audio_codecs).ok()); | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto codecs = offer->description() | 
 |                     ->contents()[0] | 
 |                     .media_description() | 
 |                     ->codecs(); | 
 |   EXPECT_TRUE(CompareCodecs(sender_audio_codecs, codecs)); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesResetAudioCodecs) { | 
 |   auto caller = CreatePeerConnectionWithAudio(); | 
 |  | 
 |   auto sender_audio_codecs = | 
 |       caller->pc_factory() | 
 |           ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO) | 
 |           .codecs; | 
 |   std::vector<RtpCodecCapability> empty_codecs = {}; | 
 |  | 
 |   auto audio_transceiver = caller->pc()->GetTransceivers().front(); | 
 |  | 
 |   // Normal case, reset codec preferences | 
 |   EXPECT_TRUE(audio_transceiver->SetCodecPreferences(empty_codecs).ok()); | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto codecs = offer->description() | 
 |                     ->contents()[0] | 
 |                     .media_description() | 
 |                     ->codecs(); | 
 |   EXPECT_TRUE(CompareCodecs(sender_audio_codecs, codecs)); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesAudioSendOnlyKillswitch) { | 
 |   field_trials_ = std::make_unique<test::ScopedKeyValueConfig>( | 
 |       "WebRTC-SetCodecPreferences-ReceiveOnlyFilterInsteadOfThrow/Disabled/"); | 
 |   auto fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   auto send_codecs = fake_engine->voice().send_codecs(); | 
 |   send_codecs.push_back(cricket::CreateAudioCodec(send_codecs.back().id + 1, | 
 |                                                   "send_only_codec", 0, 1)); | 
 |   fake_engine->SetAudioSendCodecs(send_codecs); | 
 |  | 
 |   auto caller = CreatePeerConnectionWithAudio(std::move(fake_engine)); | 
 |  | 
 |   auto transceiver = caller->pc()->GetTransceivers().front(); | 
 |   auto send_capabilities = caller->pc_factory()->GetRtpSenderCapabilities( | 
 |       cricket::MediaType::MEDIA_TYPE_AUDIO); | 
 |  | 
 |   EXPECT_TRUE(transceiver->SetCodecPreferences(send_capabilities.codecs).ok()); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesVideoRejectsAudioCodec) { | 
 |   auto caller = CreatePeerConnectionWithVideo(); | 
 |  | 
 |   auto transceiver = caller->pc()->GetTransceivers().front(); | 
 |   auto audio_codecs = | 
 |       caller->pc_factory() | 
 |           ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_AUDIO) | 
 |           .codecs; | 
 |   auto codecs = | 
 |       caller->pc_factory() | 
 |           ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_VIDEO) | 
 |           .codecs; | 
 |   codecs.insert(codecs.end(), audio_codecs.begin(), audio_codecs.end()); | 
 |   auto result = transceiver->SetCodecPreferences(codecs); | 
 |   EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, result.type()); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesVideoRejectsOnlyRtxRedFec) { | 
 |   auto fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   auto video_codecs = fake_engine->video().send_codecs(); | 
 |   video_codecs.push_back(cricket::CreateVideoRtxCodec( | 
 |       video_codecs.back().id + 1, video_codecs.back().id)); | 
 |   video_codecs.push_back(cricket::CreateVideoCodec(video_codecs.back().id + 1, | 
 |                                                    cricket::kRedCodecName)); | 
 |   video_codecs.push_back(cricket::CreateVideoCodec(video_codecs.back().id + 1, | 
 |                                                    cricket::kUlpfecCodecName)); | 
 |   fake_engine->SetVideoCodecs(video_codecs); | 
 |  | 
 |   auto caller = CreatePeerConnectionWithVideo(std::move(fake_engine)); | 
 |  | 
 |   auto transceiver = caller->pc()->GetTransceivers().front(); | 
 |   auto codecs = | 
 |       caller->pc_factory() | 
 |           ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_VIDEO) | 
 |           .codecs; | 
 |   auto codecs_only_rtx_red_fec = codecs; | 
 |   auto it = std::remove_if(codecs_only_rtx_red_fec.begin(), | 
 |                            codecs_only_rtx_red_fec.end(), | 
 |                            [](const RtpCodecCapability& codec) { | 
 |                              return !(codec.name == cricket::kRtxCodecName || | 
 |                                       codec.name == cricket::kRedCodecName || | 
 |                                       codec.name == cricket::kUlpfecCodecName); | 
 |                            }); | 
 |   codecs_only_rtx_red_fec.erase(it, codecs_only_rtx_red_fec.end()); | 
 |  | 
 |   auto result = transceiver->SetCodecPreferences(codecs_only_rtx_red_fec); | 
 |   EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, result.type()); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, SetCodecPreferencesAllVideoCodecs) { | 
 |   auto caller = CreatePeerConnectionWithVideo(); | 
 |  | 
 |   auto sender_video_codecs = | 
 |       caller->pc_factory() | 
 |           ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO) | 
 |           .codecs; | 
 |  | 
 |   auto video_transceiver = caller->pc()->GetTransceivers().front(); | 
 |  | 
 |   // Normal case, setting preferences to normal capabilities | 
 |   EXPECT_TRUE(video_transceiver->SetCodecPreferences(sender_video_codecs).ok()); | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto codecs = offer->description() | 
 |                     ->contents()[0] | 
 |                     .media_description() | 
 |                     ->codecs(); | 
 |   EXPECT_TRUE(CompareCodecs(sender_video_codecs, codecs)); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesResetVideoCodecs) { | 
 |   auto caller = CreatePeerConnectionWithVideo(); | 
 |  | 
 |   auto sender_video_codecs = | 
 |       caller->pc_factory() | 
 |           ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO) | 
 |           .codecs; | 
 |  | 
 |   std::vector<RtpCodecCapability> empty_codecs = {}; | 
 |  | 
 |   auto video_transceiver = caller->pc()->GetTransceivers().front(); | 
 |  | 
 |   // Normal case, resetting preferences with empty list of codecs | 
 |   EXPECT_TRUE(video_transceiver->SetCodecPreferences(empty_codecs).ok()); | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto codecs = offer->description() | 
 |                     ->contents()[0] | 
 |                     .media_description() | 
 |                     ->codecs(); | 
 |   EXPECT_TRUE(CompareCodecs(sender_video_codecs, codecs)); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesVideoSendOnlyKillswitch) { | 
 |   field_trials_ = std::make_unique<test::ScopedKeyValueConfig>( | 
 |       "WebRTC-SetCodecPreferences-ReceiveOnlyFilterInsteadOfThrow/Disabled/"); | 
 |   auto fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   auto send_codecs = fake_engine->voice().send_codecs(); | 
 |   send_codecs.push_back( | 
 |       cricket::CreateVideoCodec(send_codecs.back().id + 1, "send_only_codec")); | 
 |   fake_engine->SetAudioSendCodecs(send_codecs); | 
 |  | 
 |   auto caller = CreatePeerConnectionWithAudio(std::move(fake_engine)); | 
 |  | 
 |   auto transceiver = caller->pc()->GetTransceivers().front(); | 
 |   auto send_capabilities = caller->pc_factory()->GetRtpSenderCapabilities( | 
 |       cricket::MediaType::MEDIA_TYPE_AUDIO); | 
 |  | 
 |   EXPECT_TRUE(transceiver->SetCodecPreferences(send_capabilities.codecs).ok()); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesVideoCodecDuplicatesRemoved) { | 
 |   auto caller = CreatePeerConnectionWithVideo(); | 
 |  | 
 |   auto sender_video_codecs = | 
 |       caller->pc_factory() | 
 |           ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO) | 
 |           .codecs; | 
 |  | 
 |   auto video_transceiver = caller->pc()->GetTransceivers().front(); | 
 |  | 
 |   // Check duplicates are removed | 
 |   auto single_codec = sender_video_codecs; | 
 |   single_codec.resize(1); | 
 |   auto duplicate_codec = single_codec; | 
 |   duplicate_codec.push_back(duplicate_codec.front()); | 
 |   duplicate_codec.push_back(duplicate_codec.front()); | 
 |   duplicate_codec.push_back(duplicate_codec.front()); | 
 |  | 
 |   EXPECT_TRUE(video_transceiver->SetCodecPreferences(duplicate_codec).ok()); | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto codecs = offer->description() | 
 |                     ->contents()[0] | 
 |                     .media_description() | 
 |                     ->codecs(); | 
 |   EXPECT_TRUE(CompareCodecs(single_codec, codecs)); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, SetCodecPreferencesVideoWithRtx) { | 
 |   auto caller_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   auto caller_video_codecs = caller_fake_engine->video().send_codecs(); | 
 |   caller_video_codecs.push_back(cricket::CreateVideoCodec( | 
 |       caller_video_codecs.back().id + 1, cricket::kVp8CodecName)); | 
 |   caller_video_codecs.push_back(cricket::CreateVideoRtxCodec( | 
 |       caller_video_codecs.back().id + 1, caller_video_codecs.back().id)); | 
 |   caller_video_codecs.push_back(cricket::CreateVideoCodec( | 
 |       caller_video_codecs.back().id + 1, cricket::kVp9CodecName)); | 
 |   caller_video_codecs.push_back(cricket::CreateVideoRtxCodec( | 
 |       caller_video_codecs.back().id + 1, caller_video_codecs.back().id)); | 
 |   caller_fake_engine->SetVideoCodecs(caller_video_codecs); | 
 |  | 
 |   auto caller = CreatePeerConnectionWithVideo(std::move(caller_fake_engine)); | 
 |  | 
 |   auto sender_video_codecs = | 
 |       caller->pc_factory() | 
 |           ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO) | 
 |           .codecs; | 
 |  | 
 |   auto video_transceiver = caller->pc()->GetTransceivers().front(); | 
 |  | 
 |   // Check that RTX codec is properly added | 
 |   auto video_codecs_vpx_rtx = sender_video_codecs; | 
 |   auto it = | 
 |       std::remove_if(video_codecs_vpx_rtx.begin(), video_codecs_vpx_rtx.end(), | 
 |                      [](const RtpCodecCapability& codec) { | 
 |                        return codec.name != cricket::kRtxCodecName && | 
 |                               codec.name != cricket::kVp8CodecName && | 
 |                               codec.name != cricket::kVp9CodecName; | 
 |                      }); | 
 |   video_codecs_vpx_rtx.erase(it, video_codecs_vpx_rtx.end()); | 
 |   absl::c_reverse(video_codecs_vpx_rtx); | 
 |   EXPECT_EQ(video_codecs_vpx_rtx.size(), 3u);  // VP8, VP9, RTX | 
 |   EXPECT_TRUE( | 
 |       video_transceiver->SetCodecPreferences(video_codecs_vpx_rtx).ok()); | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto codecs = offer->description() | 
 |                     ->contents()[0] | 
 |                     .media_description() | 
 |                     ->codecs(); | 
 |  | 
 |   EXPECT_TRUE(CompareCodecs(video_codecs_vpx_rtx, codecs)); | 
 |   EXPECT_EQ(codecs.size(), 4u); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesVideoCodecsNegotiation) { | 
 |   auto caller_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   auto caller_video_codecs = caller_fake_engine->video().send_codecs(); | 
 |   caller_video_codecs.push_back(cricket::CreateVideoCodec( | 
 |       caller_video_codecs.back().id + 1, cricket::kVp8CodecName)); | 
 |   caller_video_codecs.push_back(cricket::CreateVideoRtxCodec( | 
 |       caller_video_codecs.back().id + 1, caller_video_codecs.back().id)); | 
 |   caller_video_codecs.push_back(cricket::CreateVideoCodec( | 
 |       caller_video_codecs.back().id + 1, cricket::kVp9CodecName)); | 
 |   caller_video_codecs.push_back(cricket::CreateVideoRtxCodec( | 
 |       caller_video_codecs.back().id + 1, caller_video_codecs.back().id)); | 
 |   caller_fake_engine->SetVideoCodecs(caller_video_codecs); | 
 |  | 
 |   auto callee_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   callee_fake_engine->SetVideoCodecs(caller_video_codecs); | 
 |  | 
 |   auto caller = CreatePeerConnectionWithVideo(std::move(caller_fake_engine)); | 
 |   auto callee = CreatePeerConnection(std::move(callee_fake_engine)); | 
 |  | 
 |   auto video_codecs = caller->pc_factory() | 
 |                           ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO) | 
 |                           .codecs; | 
 |  | 
 |   auto send_transceiver = caller->pc()->GetTransceivers().front(); | 
 |  | 
 |   auto video_codecs_vpx = video_codecs; | 
 |   auto it = std::remove_if(video_codecs_vpx.begin(), video_codecs_vpx.end(), | 
 |                            [](const RtpCodecCapability& codec) { | 
 |                              return codec.name != cricket::kVp8CodecName && | 
 |                                     codec.name != cricket::kVp9CodecName; | 
 |                            }); | 
 |   video_codecs_vpx.erase(it, video_codecs_vpx.end()); | 
 |   EXPECT_EQ(video_codecs_vpx.size(), 2u);  // VP8, VP9 | 
 |   EXPECT_TRUE(send_transceiver->SetCodecPreferences(video_codecs_vpx).ok()); | 
 |  | 
 |   auto offer = caller->CreateOfferAndSetAsLocal(); | 
 |   auto codecs = offer->description() | 
 |                     ->contents()[0] | 
 |                     .media_description() | 
 |                     ->codecs(); | 
 |  | 
 |   EXPECT_EQ(codecs.size(), 2u);  // VP8, VP9 | 
 |   EXPECT_TRUE(CompareCodecs(video_codecs_vpx, codecs)); | 
 |  | 
 |   callee->SetRemoteDescription(std::move(offer)); | 
 |  | 
 |   auto recv_transceiver = callee->pc()->GetTransceivers().front(); | 
 |   auto video_codecs_vp8_rtx = video_codecs; | 
 |   it = std::remove_if(video_codecs_vp8_rtx.begin(), video_codecs_vp8_rtx.end(), | 
 |                       [](const RtpCodecCapability& codec) { | 
 |                         bool r = codec.name != cricket::kVp8CodecName && | 
 |                                  codec.name != cricket::kRtxCodecName; | 
 |                         return r; | 
 |                       }); | 
 |   video_codecs_vp8_rtx.erase(it, video_codecs_vp8_rtx.end()); | 
 |   EXPECT_EQ(video_codecs_vp8_rtx.size(), 2u);  // VP8, RTX | 
 |   recv_transceiver->SetCodecPreferences(video_codecs_vp8_rtx); | 
 |  | 
 |   auto answer = callee->CreateAnswerAndSetAsLocal(); | 
 |  | 
 |   auto recv_codecs = answer->description() | 
 |                          ->contents()[0] | 
 |                          .media_description() | 
 |                          ->codecs(); | 
 |   EXPECT_EQ(recv_codecs.size(), 1u);  // VP8 | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesVideoCodecsNegotiationReverseOrder) { | 
 |   auto caller_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   auto caller_video_codecs = caller_fake_engine->video().send_codecs(); | 
 |   caller_video_codecs.push_back(cricket::CreateVideoCodec( | 
 |       caller_video_codecs.back().id + 1, cricket::kVp8CodecName)); | 
 |   caller_video_codecs.push_back(cricket::CreateVideoRtxCodec( | 
 |       caller_video_codecs.back().id + 1, caller_video_codecs.back().id)); | 
 |   caller_video_codecs.push_back(cricket::CreateVideoCodec( | 
 |       caller_video_codecs.back().id + 1, cricket::kVp9CodecName)); | 
 |   caller_video_codecs.push_back(cricket::CreateVideoRtxCodec( | 
 |       caller_video_codecs.back().id + 1, caller_video_codecs.back().id)); | 
 |   caller_fake_engine->SetVideoCodecs(caller_video_codecs); | 
 |  | 
 |   auto callee_fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   callee_fake_engine->SetVideoCodecs(caller_video_codecs); | 
 |  | 
 |   auto caller = CreatePeerConnectionWithVideo(std::move(caller_fake_engine)); | 
 |   auto callee = CreatePeerConnection(std::move(callee_fake_engine)); | 
 |  | 
 |   auto video_codecs = caller->pc_factory() | 
 |                           ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO) | 
 |                           .codecs; | 
 |  | 
 |   auto send_transceiver = caller->pc()->GetTransceivers().front(); | 
 |  | 
 |   auto video_codecs_vpx = video_codecs; | 
 |   auto it = std::remove_if(video_codecs_vpx.begin(), video_codecs_vpx.end(), | 
 |                            [](const RtpCodecCapability& codec) { | 
 |                              return codec.name != cricket::kVp8CodecName && | 
 |                                     codec.name != cricket::kVp9CodecName; | 
 |                            }); | 
 |   video_codecs_vpx.erase(it, video_codecs_vpx.end()); | 
 |   EXPECT_EQ(video_codecs_vpx.size(), 2u);  // VP8, VP9 | 
 |   EXPECT_TRUE(send_transceiver->SetCodecPreferences(video_codecs_vpx).ok()); | 
 |  | 
 |   auto video_codecs_vpx_reverse = video_codecs_vpx; | 
 |   absl::c_reverse(video_codecs_vpx_reverse); | 
 |  | 
 |   auto offer = caller->CreateOfferAndSetAsLocal(); | 
 |   auto codecs = offer->description() | 
 |                     ->contents()[0] | 
 |                     .media_description() | 
 |                     ->codecs(); | 
 |   EXPECT_EQ(codecs.size(), 2u);  // VP9, VP8 | 
 |   EXPECT_TRUE(CompareCodecs(video_codecs_vpx, codecs)); | 
 |  | 
 |   callee->SetRemoteDescription(std::move(offer)); | 
 |  | 
 |   auto recv_transceiver = callee->pc()->GetTransceivers().front(); | 
 |   recv_transceiver->SetCodecPreferences(video_codecs_vpx_reverse); | 
 |  | 
 |   auto answer = callee->CreateAnswerAndSetAsLocal(); | 
 |  | 
 |   auto recv_codecs = answer->description() | 
 |                          ->contents()[0] | 
 |                          .media_description() | 
 |                          ->codecs(); | 
 |  | 
 |   EXPECT_TRUE(CompareCodecs(video_codecs_vpx_reverse, recv_codecs)); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesVoiceActivityDetection) { | 
 |   auto fake_engine = std::make_unique<FakeMediaEngine>(); | 
 |   AddComfortNoiseCodecsToSend(fake_engine.get()); | 
 |   auto caller = CreatePeerConnectionWithAudio(std::move(fake_engine)); | 
 |  | 
 |   RTCOfferAnswerOptions options; | 
 |   auto offer = caller->CreateOffer(options); | 
 |   EXPECT_TRUE(HasAnyComfortNoiseCodecs(offer->description())); | 
 |  | 
 |   auto transceiver = caller->pc()->GetTransceivers().front(); | 
 |   auto capabilities = caller->pc_factory()->GetRtpSenderCapabilities( | 
 |       cricket::MediaType::MEDIA_TYPE_AUDIO); | 
 |   EXPECT_TRUE(transceiver->SetCodecPreferences(capabilities.codecs).ok()); | 
 |  | 
 |   options.voice_activity_detection = false; | 
 |   offer = caller->CreateOffer(options); | 
 |   EXPECT_FALSE(HasAnyComfortNoiseCodecs(offer->description())); | 
 | } | 
 |  | 
 | // If the "default" payload types of audio/video codecs are the same, and | 
 | // audio/video are bundled (as is the default), payload types should be | 
 | // remapped to avoid conflict, as normally happens without using | 
 | // SetCodecPreferences. | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesAvoidsPayloadTypeConflictInOffer) { | 
 |   auto fake_engine = std::make_unique<cricket::FakeMediaEngine>(); | 
 |  | 
 |   std::vector<cricket::Codec> audio_codecs; | 
 |   audio_codecs.emplace_back(cricket::CreateAudioCodec(100, "foo", 0, 1)); | 
 |   audio_codecs.emplace_back(cricket::CreateAudioRtxCodec(101, 100)); | 
 |   fake_engine->SetAudioCodecs(audio_codecs); | 
 |  | 
 |   std::vector<cricket::Codec> video_codecs; | 
 |   video_codecs.emplace_back(cricket::CreateVideoCodec(100, "bar")); | 
 |   video_codecs.emplace_back(cricket::CreateVideoRtxCodec(101, 100)); | 
 |   fake_engine->SetVideoCodecs(video_codecs); | 
 |  | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(std::move(fake_engine)); | 
 |   auto transceivers = caller->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(2u, transceivers.size()); | 
 |  | 
 |   auto audio_transceiver = caller->pc()->GetTransceivers()[0]; | 
 |   auto capabilities = caller->pc_factory()->GetRtpSenderCapabilities( | 
 |       cricket::MediaType::MEDIA_TYPE_AUDIO); | 
 |   EXPECT_TRUE(audio_transceiver->SetCodecPreferences(capabilities.codecs).ok()); | 
 |  | 
 |   auto video_transceiver = caller->pc()->GetTransceivers()[1]; | 
 |   capabilities = caller->pc_factory()->GetRtpSenderCapabilities( | 
 |       cricket::MediaType::MEDIA_TYPE_VIDEO); | 
 |   EXPECT_TRUE(video_transceiver->SetCodecPreferences(capabilities.codecs).ok()); | 
 |  | 
 |   RTCOfferAnswerOptions options; | 
 |   auto offer = caller->CreateOffer(options); | 
 |   EXPECT_FALSE(HasPayloadTypeConflict(offer->description())); | 
 |   // Sanity check that we got the primary codec and RTX. | 
 |   EXPECT_EQ(2u, cricket::GetFirstAudioContentDescription(offer->description()) | 
 |                     ->codecs() | 
 |                     .size()); | 
 |   EXPECT_EQ(2u, cricket::GetFirstVideoContentDescription(offer->description()) | 
 |                     ->codecs() | 
 |                     .size()); | 
 | } | 
 |  | 
 | // Same as above, but preferences set for the answer. | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesAvoidsPayloadTypeConflictInAnswer) { | 
 |   auto fake_engine = std::make_unique<cricket::FakeMediaEngine>(); | 
 |  | 
 |   std::vector<cricket::Codec> audio_codecs; | 
 |   audio_codecs.emplace_back(cricket::CreateAudioCodec(100, "foo", 0, 1)); | 
 |   audio_codecs.emplace_back(cricket::CreateAudioRtxCodec(101, 100)); | 
 |   fake_engine->SetAudioCodecs(audio_codecs); | 
 |  | 
 |   std::vector<cricket::Codec> video_codecs; | 
 |   video_codecs.emplace_back(cricket::CreateVideoCodec(100, "bar")); | 
 |   video_codecs.emplace_back(cricket::CreateVideoRtxCodec(101, 100)); | 
 |   fake_engine->SetVideoCodecs(video_codecs); | 
 |  | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(std::move(fake_engine)); | 
 |  | 
 |   RTCOfferAnswerOptions options; | 
 |   caller->SetRemoteDescription(caller->CreateOffer(options)); | 
 |  | 
 |   auto transceivers = caller->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(2u, transceivers.size()); | 
 |  | 
 |   auto audio_transceiver = caller->pc()->GetTransceivers()[0]; | 
 |   auto capabilities = caller->pc_factory()->GetRtpSenderCapabilities( | 
 |       cricket::MediaType::MEDIA_TYPE_AUDIO); | 
 |   EXPECT_TRUE(audio_transceiver->SetCodecPreferences(capabilities.codecs).ok()); | 
 |  | 
 |   auto video_transceiver = caller->pc()->GetTransceivers()[1]; | 
 |   capabilities = caller->pc_factory()->GetRtpSenderCapabilities( | 
 |       cricket::MediaType::MEDIA_TYPE_VIDEO); | 
 |   EXPECT_TRUE(video_transceiver->SetCodecPreferences(capabilities.codecs).ok()); | 
 |  | 
 |   auto answer = caller->CreateAnswer(options); | 
 |  | 
 |   EXPECT_FALSE(HasPayloadTypeConflict(answer->description())); | 
 |   // Sanity check that we got the primary codec and RTX. | 
 |   EXPECT_EQ(2u, cricket::GetFirstAudioContentDescription(answer->description()) | 
 |                     ->codecs() | 
 |                     .size()); | 
 |   EXPECT_EQ(2u, cricket::GetFirstVideoContentDescription(answer->description()) | 
 |                     ->codecs() | 
 |                     .size()); | 
 | } | 
 |  | 
 | // Same as above, but preferences set for a subsequent offer. | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesAvoidsPayloadTypeConflictInSubsequentOffer) { | 
 |   auto fake_engine = std::make_unique<cricket::FakeMediaEngine>(); | 
 |  | 
 |   std::vector<cricket::Codec> audio_codecs; | 
 |   audio_codecs.emplace_back(cricket::CreateAudioCodec(100, "foo", 0, 1)); | 
 |   audio_codecs.emplace_back(cricket::CreateAudioRtxCodec(101, 100)); | 
 |   fake_engine->SetAudioCodecs(audio_codecs); | 
 |  | 
 |   std::vector<cricket::Codec> video_codecs; | 
 |   video_codecs.emplace_back(cricket::CreateVideoCodec(100, "bar")); | 
 |   video_codecs.emplace_back(cricket::CreateVideoRtxCodec(101, 100)); | 
 |   fake_engine->SetVideoCodecs(video_codecs); | 
 |  | 
 |   auto caller = CreatePeerConnectionWithAudioVideo(std::move(fake_engine)); | 
 |  | 
 |   RTCOfferAnswerOptions options; | 
 |   caller->SetRemoteDescription(caller->CreateOffer(options)); | 
 |   caller->SetLocalDescription(caller->CreateAnswer(options)); | 
 |  | 
 |   auto transceivers = caller->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(2u, transceivers.size()); | 
 |  | 
 |   auto audio_transceiver = caller->pc()->GetTransceivers()[0]; | 
 |   auto capabilities = caller->pc_factory()->GetRtpSenderCapabilities( | 
 |       cricket::MediaType::MEDIA_TYPE_AUDIO); | 
 |   EXPECT_TRUE(audio_transceiver->SetCodecPreferences(capabilities.codecs).ok()); | 
 |  | 
 |   auto video_transceiver = caller->pc()->GetTransceivers()[1]; | 
 |   capabilities = caller->pc_factory()->GetRtpSenderCapabilities( | 
 |       cricket::MediaType::MEDIA_TYPE_VIDEO); | 
 |   EXPECT_TRUE(video_transceiver->SetCodecPreferences(capabilities.codecs).ok()); | 
 |  | 
 |   auto reoffer = caller->CreateOffer(options); | 
 |   ASSERT_THAT(reoffer, NotNull()); | 
 |  | 
 |   EXPECT_FALSE(HasPayloadTypeConflict(reoffer->description())); | 
 |   // Sanity check that we got the primary codec and RTX. | 
 |   EXPECT_EQ(2u, cricket::GetFirstAudioContentDescription(reoffer->description()) | 
 |                     ->codecs() | 
 |                     .size()); | 
 |   EXPECT_EQ(2u, cricket::GetFirstVideoContentDescription(reoffer->description()) | 
 |                     ->codecs() | 
 |                     .size()); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionMediaTestUnifiedPlan, | 
 |        SetCodecPreferencesReceiveOnlyWithSendOnlyTransceiverStops) { | 
 |   auto fake_engine = std::make_unique<cricket::FakeMediaEngine>(); | 
 |  | 
 |   std::vector<cricket::Codec> audio_codecs; | 
 |   audio_codecs.emplace_back(cricket::CreateAudioCodec(100, "foo", 0, 1)); | 
 |   fake_engine->SetAudioRecvCodecs(audio_codecs); | 
 |  | 
 |   auto caller = CreatePeerConnectionWithAudio(std::move(fake_engine)); | 
 |  | 
 |   auto transceivers = caller->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(1u, transceivers.size()); | 
 |  | 
 |   auto audio_transceiver = caller->pc()->GetTransceivers()[0]; | 
 |   auto error = audio_transceiver->SetDirectionWithError( | 
 |       RtpTransceiverDirection::kSendOnly); | 
 |   ASSERT_TRUE(error.ok()); | 
 |   auto capabilities = caller->pc_factory()->GetRtpReceiverCapabilities( | 
 |       cricket::MediaType::MEDIA_TYPE_AUDIO); | 
 |   EXPECT_TRUE(audio_transceiver->SetCodecPreferences(capabilities.codecs).ok()); | 
 |   RTCOfferAnswerOptions options; | 
 |   EXPECT_TRUE(caller->SetLocalDescription(caller->CreateOffer(options))); | 
 |   EXPECT_EQ(audio_transceiver->direction(), RtpTransceiverDirection::kStopped); | 
 | } | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(PeerConnectionMediaTest, | 
 |                          PeerConnectionMediaTest, | 
 |                          Values(SdpSemantics::kPlanB_DEPRECATED, | 
 |                                 SdpSemantics::kUnifiedPlan)); | 
 |  | 
 | }  // namespace webrtc |