|  | /* | 
|  | *  Copyright 2019 The WebRTC project authors. All Rights Reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <cstring> | 
|  | #include <iterator> | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "absl/algorithm/container.h" | 
|  | #include "api/audio_codecs/builtin_audio_decoder_factory.h" | 
|  | #include "api/audio_codecs/builtin_audio_encoder_factory.h" | 
|  | #include "api/create_peerconnection_factory.h" | 
|  | #include "api/jsep.h" | 
|  | #include "api/media_types.h" | 
|  | #include "api/peer_connection_interface.h" | 
|  | #include "api/rtc_error.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/video/video_codec_constants.h" | 
|  | #include "api/video_codecs/video_decoder_factory_template.h" | 
|  | #include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h" | 
|  | #include "api/video_codecs/video_decoder_factory_template_libvpx_vp8_adapter.h" | 
|  | #include "api/video_codecs/video_decoder_factory_template_libvpx_vp9_adapter.h" | 
|  | #include "api/video_codecs/video_decoder_factory_template_open_h264_adapter.h" | 
|  | #include "api/video_codecs/video_encoder_factory_template.h" | 
|  | #include "api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h" | 
|  | #include "api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h" | 
|  | #include "api/video_codecs/video_encoder_factory_template_libvpx_vp9_adapter.h" | 
|  | #include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.h" | 
|  | #include "media/base/rid_description.h" | 
|  | #include "media/base/stream_params.h" | 
|  | #include "pc/peer_connection_wrapper.h" | 
|  | #include "pc/session_description.h" | 
|  | #include "pc/simulcast_description.h" | 
|  | #include "pc/test/fake_audio_capture_module.h" | 
|  | #include "pc/test/mock_peer_connection_observers.h" | 
|  | #include "pc/test/simulcast_layer_util.h" | 
|  | #include "rtc_base/checks.h" | 
|  | #include "rtc_base/thread.h" | 
|  | #include "test/gmock.h" | 
|  | #include "test/gtest.h" | 
|  |  | 
|  | namespace webrtc { | 
|  |  | 
|  | using ::testing::Contains; | 
|  | using ::testing::Each; | 
|  | using ::testing::ElementsAre; | 
|  | using ::testing::ElementsAreArray; | 
|  | using ::testing::Eq; | 
|  | using ::testing::Field; | 
|  | using ::testing::IsEmpty; | 
|  | using ::testing::Le; | 
|  | using ::testing::Ne; | 
|  | using ::testing::NotNull; | 
|  | using ::testing::Pair; | 
|  | using ::testing::Property; | 
|  | using ::testing::SizeIs; | 
|  | using ::testing::StartsWith; | 
|  |  | 
|  | class PeerConnectionSimulcastTests : public ::testing::Test { | 
|  | public: | 
|  | PeerConnectionSimulcastTests() | 
|  | : pc_factory_(CreatePeerConnectionFactory( | 
|  | Thread::Current(), | 
|  | Thread::Current(), | 
|  | Thread::Current(), | 
|  | FakeAudioCaptureModule::Create(), | 
|  | CreateBuiltinAudioEncoderFactory(), | 
|  | CreateBuiltinAudioDecoderFactory(), | 
|  | std::make_unique< | 
|  | VideoEncoderFactoryTemplate<LibvpxVp8EncoderTemplateAdapter, | 
|  | LibvpxVp9EncoderTemplateAdapter, | 
|  | OpenH264EncoderTemplateAdapter, | 
|  | LibaomAv1EncoderTemplateAdapter>>(), | 
|  | std::make_unique< | 
|  | VideoDecoderFactoryTemplate<LibvpxVp8DecoderTemplateAdapter, | 
|  | LibvpxVp9DecoderTemplateAdapter, | 
|  | OpenH264DecoderTemplateAdapter, | 
|  | Dav1dDecoderTemplateAdapter>>(), | 
|  | nullptr, | 
|  | nullptr)) {} | 
|  |  | 
|  | scoped_refptr<PeerConnectionInterface> CreatePeerConnection( | 
|  | MockPeerConnectionObserver* observer) { | 
|  | PeerConnectionInterface::RTCConfiguration config; | 
|  | config.sdp_semantics = SdpSemantics::kUnifiedPlan; | 
|  | PeerConnectionDependencies pcd(observer); | 
|  | auto result = | 
|  | pc_factory_->CreatePeerConnectionOrError(config, std::move(pcd)); | 
|  | EXPECT_TRUE(result.ok()); | 
|  | observer->SetPeerConnectionInterface(result.value().get()); | 
|  | return result.MoveValue(); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<PeerConnectionWrapper> CreatePeerConnectionWrapper() { | 
|  | auto observer = std::make_unique<MockPeerConnectionObserver>(); | 
|  | auto pc = CreatePeerConnection(observer.get()); | 
|  | return std::make_unique<PeerConnectionWrapper>(pc_factory_, pc, | 
|  | std::move(observer)); | 
|  | } | 
|  |  | 
|  | void ExchangeOfferAnswer(PeerConnectionWrapper* local, | 
|  | PeerConnectionWrapper* remote, | 
|  | const std::vector<SimulcastLayer>& answer_layers) { | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = | 
|  | local->CreateOfferAndSetAsLocal(); | 
|  | // Remove simulcast as the second peer connection won't support it. | 
|  | RemoveSimulcast(offer.get()); | 
|  | std::string err; | 
|  | EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &err)) << err; | 
|  | std::unique_ptr<SessionDescriptionInterface> answer = | 
|  | remote->CreateAnswerAndSetAsLocal(); | 
|  | // Setup the answer to look like a server response. | 
|  | auto mcd_answer = answer->description()->contents()[0].media_description(); | 
|  | auto& receive_layers = mcd_answer->simulcast_description().receive_layers(); | 
|  | for (const SimulcastLayer& layer : answer_layers) { | 
|  | receive_layers.AddLayer(layer); | 
|  | } | 
|  | EXPECT_TRUE(local->SetRemoteDescription(std::move(answer), &err)) << err; | 
|  | } | 
|  |  | 
|  | scoped_refptr<RtpTransceiverInterface> AddTransceiver( | 
|  | PeerConnectionWrapper* pc, | 
|  | const std::vector<SimulcastLayer>& layers, | 
|  | MediaType media_type = MediaType::VIDEO) { | 
|  | auto init = CreateTransceiverInit(layers); | 
|  | return pc->AddTransceiver(media_type, init); | 
|  | } | 
|  |  | 
|  | void AddRequestToReceiveSimulcast(const std::vector<SimulcastLayer>& layers, | 
|  | SessionDescriptionInterface* sd) { | 
|  | auto mcd = sd->description()->contents()[0].media_description(); | 
|  | SimulcastDescription simulcast; | 
|  | auto& receive_layers = simulcast.receive_layers(); | 
|  | for (const SimulcastLayer& layer : layers) { | 
|  | receive_layers.AddLayer(layer); | 
|  | } | 
|  | mcd->set_simulcast_description(simulcast); | 
|  | } | 
|  |  | 
|  | void ValidateTransceiverParameters( | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver, | 
|  | const std::vector<SimulcastLayer>& layers) { | 
|  | auto parameters = transceiver->sender()->GetParameters(); | 
|  | std::vector<SimulcastLayer> result_layers; | 
|  | absl::c_transform(parameters.encodings, std::back_inserter(result_layers), | 
|  | [](const RtpEncodingParameters& encoding) { | 
|  | return SimulcastLayer(encoding.rid, !encoding.active); | 
|  | }); | 
|  | EXPECT_THAT(result_layers, ElementsAreArray(layers)); | 
|  | } | 
|  |  | 
|  | private: | 
|  | scoped_refptr<PeerConnectionFactoryInterface> pc_factory_; | 
|  | }; | 
|  |  | 
|  | // Validates that RIDs are supported arguments when adding a transceiver. | 
|  | TEST_F(PeerConnectionSimulcastTests, CanCreateTransceiverWithRid) { | 
|  | auto pc = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"f"}, true); | 
|  | auto transceiver = AddTransceiver(pc.get(), layers); | 
|  | ASSERT_TRUE(transceiver); | 
|  | auto parameters = transceiver->sender()->GetParameters(); | 
|  | // Single RID should be removed. | 
|  | EXPECT_THAT(parameters.encodings, | 
|  | ElementsAre(Field("rid", &RtpEncodingParameters::rid, Eq("")))); | 
|  | } | 
|  |  | 
|  | TEST_F(PeerConnectionSimulcastTests, CanCreateTransceiverWithSimulcast) { | 
|  | auto pc = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"f", "h", "q"}, true); | 
|  | auto transceiver = AddTransceiver(pc.get(), layers); | 
|  | ASSERT_TRUE(transceiver); | 
|  | ValidateTransceiverParameters(transceiver, layers); | 
|  | } | 
|  |  | 
|  | TEST_F(PeerConnectionSimulcastTests, RidsAreAutogeneratedIfNotProvided) { | 
|  | auto pc = CreatePeerConnectionWrapper(); | 
|  | auto init = CreateTransceiverInit(CreateLayers({"f", "h", "q"}, true)); | 
|  | for (RtpEncodingParameters& parameters : init.send_encodings) { | 
|  | parameters.rid = ""; | 
|  | } | 
|  | auto transceiver = pc->AddTransceiver(MediaType::VIDEO, init); | 
|  | auto parameters = transceiver->sender()->GetParameters(); | 
|  | ASSERT_EQ(3u, parameters.encodings.size()); | 
|  | EXPECT_THAT(parameters.encodings, | 
|  | Each(Field("rid", &RtpEncodingParameters::rid, Ne("")))); | 
|  | } | 
|  |  | 
|  | // Validates that an error is returned when there is a mix of supplied and not | 
|  | // supplied RIDs in a call to AddTransceiver. | 
|  | TEST_F(PeerConnectionSimulcastTests, MustSupplyAllOrNoRidsInSimulcast) { | 
|  | auto pc_wrapper = CreatePeerConnectionWrapper(); | 
|  | auto pc = pc_wrapper->pc(); | 
|  | // Cannot create a layer with empty RID. Remove the RID after init is created. | 
|  | auto layers = CreateLayers({"f", "h", "remove"}, true); | 
|  | auto init = CreateTransceiverInit(layers); | 
|  | init.send_encodings[2].rid = ""; | 
|  | auto error = pc->AddTransceiver(MediaType::VIDEO, init); | 
|  | EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, error.error().type()); | 
|  | } | 
|  |  | 
|  | // Validates that an error is returned when illegal RIDs are supplied. | 
|  | TEST_F(PeerConnectionSimulcastTests, ChecksForIllegalRidValues) { | 
|  | auto pc_wrapper = CreatePeerConnectionWrapper(); | 
|  | auto pc = pc_wrapper->pc(); | 
|  | auto layers = CreateLayers({"f", "h", "~q"}, true); | 
|  | auto init = CreateTransceiverInit(layers); | 
|  | auto error = pc->AddTransceiver(MediaType::VIDEO, init); | 
|  | EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, error.error().type()); | 
|  | } | 
|  |  | 
|  | // Validates that a single RID is removed from the encoding layer. | 
|  | TEST_F(PeerConnectionSimulcastTests, SingleRidIsRemovedFromSessionDescription) { | 
|  | auto pc = CreatePeerConnectionWrapper(); | 
|  | auto transceiver = AddTransceiver(pc.get(), CreateLayers({"1"}, true)); | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = | 
|  | pc->CreateOfferAndSetAsLocal(); | 
|  | ASSERT_THAT(offer, NotNull()); | 
|  | auto contents = offer->description()->contents(); | 
|  | ASSERT_EQ(1u, contents.size()); | 
|  | EXPECT_THAT(contents[0].media_description()->streams(), | 
|  | ElementsAre(Property(&StreamParams::has_rids, false))); | 
|  | } | 
|  |  | 
|  | TEST_F(PeerConnectionSimulcastTests, SimulcastLayersRemovedFromTail) { | 
|  | static_assert( | 
|  | kMaxSimulcastStreams < 8, | 
|  | "Test assumes that the platform does not allow 8 simulcast layers"); | 
|  | auto pc = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"1", "2", "3", "4", "5", "6", "7", "8"}, true); | 
|  | std::vector<SimulcastLayer> expected_layers; | 
|  | std::copy_n(layers.begin(), kMaxSimulcastStreams, | 
|  | std::back_inserter(expected_layers)); | 
|  | auto transceiver = AddTransceiver(pc.get(), layers); | 
|  | ValidateTransceiverParameters(transceiver, expected_layers); | 
|  | } | 
|  |  | 
|  | // Checks that an offfer to send simulcast contains a SimulcastDescription. | 
|  | TEST_F(PeerConnectionSimulcastTests, SimulcastAppearsInSessionDescription) { | 
|  | auto pc = CreatePeerConnectionWrapper(); | 
|  | std::vector<std::string> rids({"f", "h", "q"}); | 
|  | auto layers = CreateLayers(rids, true); | 
|  | auto transceiver = AddTransceiver(pc.get(), layers); | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = pc->CreateOffer(); | 
|  | ASSERT_THAT(offer, NotNull()); | 
|  | auto contents = offer->description()->contents(); | 
|  | ASSERT_EQ(1u, contents.size()); | 
|  | auto content = contents[0]; | 
|  | auto mcd = content.media_description(); | 
|  | ASSERT_TRUE(mcd->HasSimulcast()); | 
|  | auto simulcast = mcd->simulcast_description(); | 
|  | EXPECT_THAT(simulcast.receive_layers(), IsEmpty()); | 
|  | // The size is validated separately because GetAllLayers() flattens the list. | 
|  | EXPECT_THAT(simulcast.send_layers(), SizeIs(3)); | 
|  | std::vector<SimulcastLayer> result = simulcast.send_layers().GetAllLayers(); | 
|  | EXPECT_THAT(result, ElementsAreArray(layers)); | 
|  | auto streams = mcd->streams(); | 
|  | ASSERT_EQ(1u, streams.size()); | 
|  | auto stream = streams[0]; | 
|  | EXPECT_FALSE(stream.has_ssrcs()); | 
|  | EXPECT_TRUE(stream.has_rids()); | 
|  | std::vector<std::string> result_rids; | 
|  | absl::c_transform(stream.rids(), std::back_inserter(result_rids), | 
|  | [](const RidDescription& rid) { return rid.rid; }); | 
|  | EXPECT_THAT(result_rids, ElementsAreArray(rids)); | 
|  | } | 
|  |  | 
|  | // Checks that Simulcast layers propagate to the sender parameters. | 
|  | TEST_F(PeerConnectionSimulcastTests, SimulcastLayersAreSetInSender) { | 
|  | auto local = CreatePeerConnectionWrapper(); | 
|  | auto remote = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"f", "h", "q"}, true); | 
|  | auto transceiver = AddTransceiver(local.get(), layers); | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = | 
|  | local->CreateOfferAndSetAsLocal(); | 
|  | { | 
|  | SCOPED_TRACE("after create offer"); | 
|  | ValidateTransceiverParameters(transceiver, layers); | 
|  | } | 
|  | // Remove simulcast as the second peer connection won't support it. | 
|  | auto simulcast = RemoveSimulcast(offer.get()); | 
|  | std::string error; | 
|  | EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &error)) << error; | 
|  | std::unique_ptr<SessionDescriptionInterface> answer = | 
|  | remote->CreateAnswerAndSetAsLocal(); | 
|  |  | 
|  | // Setup an answer that mimics a server accepting simulcast. | 
|  | auto mcd_answer = answer->description()->contents()[0].media_description(); | 
|  | mcd_answer->mutable_streams().clear(); | 
|  | auto simulcast_layers = simulcast.send_layers().GetAllLayers(); | 
|  | auto& receive_layers = mcd_answer->simulcast_description().receive_layers(); | 
|  | for (const auto& layer : simulcast_layers) { | 
|  | receive_layers.AddLayer(layer); | 
|  | } | 
|  | EXPECT_TRUE(local->SetRemoteDescription(std::move(answer), &error)) << error; | 
|  | { | 
|  | SCOPED_TRACE("after set remote"); | 
|  | ValidateTransceiverParameters(transceiver, layers); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Checks that paused Simulcast layers propagate to the sender parameters. | 
|  | TEST_F(PeerConnectionSimulcastTests, PausedSimulcastLayersAreDisabledInSender) { | 
|  | auto local = CreatePeerConnectionWrapper(); | 
|  | auto remote = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"f", "h", "q"}, {true, false, true}); | 
|  | auto server_layers = CreateLayers({"f", "h", "q"}, {true, false, false}); | 
|  | RTC_DCHECK_EQ(layers.size(), server_layers.size()); | 
|  | auto transceiver = AddTransceiver(local.get(), layers); | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = | 
|  | local->CreateOfferAndSetAsLocal(); | 
|  | { | 
|  | SCOPED_TRACE("after create offer"); | 
|  | ValidateTransceiverParameters(transceiver, layers); | 
|  | } | 
|  |  | 
|  | // Remove simulcast as the second peer connection won't support it. | 
|  | RemoveSimulcast(offer.get()); | 
|  | std::string error; | 
|  | EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &error)) << error; | 
|  | std::unique_ptr<SessionDescriptionInterface> answer = | 
|  | remote->CreateAnswerAndSetAsLocal(); | 
|  |  | 
|  | // Setup an answer that mimics a server accepting simulcast. | 
|  | auto mcd_answer = answer->description()->contents()[0].media_description(); | 
|  | mcd_answer->mutable_streams().clear(); | 
|  | auto& receive_layers = mcd_answer->simulcast_description().receive_layers(); | 
|  | for (const SimulcastLayer& layer : server_layers) { | 
|  | receive_layers.AddLayer(layer); | 
|  | } | 
|  | EXPECT_TRUE(local->SetRemoteDescription(std::move(answer), &error)) << error; | 
|  | { | 
|  | SCOPED_TRACE("after set remote"); | 
|  | ValidateTransceiverParameters(transceiver, server_layers); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Checks that when Simulcast is not supported by the remote party, then all | 
|  | // the layers (except the first) are removed. | 
|  | TEST_F(PeerConnectionSimulcastTests, SimulcastRejectedRemovesExtraLayers) { | 
|  | auto local = CreatePeerConnectionWrapper(); | 
|  | auto remote = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"1", "2", "3", "4"}, true); | 
|  | auto transceiver = AddTransceiver(local.get(), layers); | 
|  | ExchangeOfferAnswer(local.get(), remote.get(), {}); | 
|  | auto parameters = transceiver->sender()->GetParameters(); | 
|  | // Should only have the first layer. | 
|  | EXPECT_THAT(parameters.encodings, | 
|  | ElementsAre(Field("rid", &RtpEncodingParameters::rid, Eq("1")))); | 
|  | } | 
|  |  | 
|  | // Checks that if Simulcast is supported by remote party, but some layers are | 
|  | // rejected, then only rejected layers are removed from the sender. | 
|  | TEST_F(PeerConnectionSimulcastTests, RejectedSimulcastLayersAreDeactivated) { | 
|  | auto local = CreatePeerConnectionWrapper(); | 
|  | auto remote = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"1", "2", "3"}, true); | 
|  | auto expected_layers = CreateLayers({"2", "3"}, true); | 
|  | auto transceiver = AddTransceiver(local.get(), layers); | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = | 
|  | local->CreateOfferAndSetAsLocal(); | 
|  | { | 
|  | SCOPED_TRACE("after create offer"); | 
|  | ValidateTransceiverParameters(transceiver, layers); | 
|  | } | 
|  | // Remove simulcast as the second peer connection won't support it. | 
|  | auto removed_simulcast = RemoveSimulcast(offer.get()); | 
|  | std::string error; | 
|  | EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &error)) << error; | 
|  | std::unique_ptr<SessionDescriptionInterface> answer = | 
|  | remote->CreateAnswerAndSetAsLocal(); | 
|  | auto mcd_answer = answer->description()->contents()[0].media_description(); | 
|  | // Setup the answer to look like a server response. | 
|  | // Remove one of the layers to reject it in the answer. | 
|  | auto simulcast_layers = removed_simulcast.send_layers().GetAllLayers(); | 
|  | simulcast_layers.erase(simulcast_layers.begin()); | 
|  | auto& receive_layers = mcd_answer->simulcast_description().receive_layers(); | 
|  | for (const auto& layer : simulcast_layers) { | 
|  | receive_layers.AddLayer(layer); | 
|  | } | 
|  | ASSERT_TRUE(mcd_answer->HasSimulcast()); | 
|  | EXPECT_TRUE(local->SetRemoteDescription(std::move(answer), &error)) << error; | 
|  | { | 
|  | SCOPED_TRACE("after set remote"); | 
|  | ValidateTransceiverParameters(transceiver, expected_layers); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Checks that simulcast is set up correctly when the server sends an offer | 
|  | // requesting to receive simulcast. | 
|  | TEST_F(PeerConnectionSimulcastTests, ServerSendsOfferToReceiveSimulcast) { | 
|  | auto local = CreatePeerConnectionWrapper(); | 
|  | auto remote = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"f", "h", "q"}, true); | 
|  | AddTransceiver(local.get(), layers); | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = | 
|  | local->CreateOfferAndSetAsLocal(); | 
|  | // Remove simulcast as a sender and set it up as a receiver. | 
|  | RemoveSimulcast(offer.get()); | 
|  | AddRequestToReceiveSimulcast(layers, offer.get()); | 
|  | std::string error; | 
|  | EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &error)) << error; | 
|  | auto transceiver = remote->pc()->GetTransceivers()[0]; | 
|  | transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendRecv); | 
|  | EXPECT_TRUE(remote->CreateAnswerAndSetAsLocal()); | 
|  | ValidateTransceiverParameters(transceiver, layers); | 
|  | } | 
|  |  | 
|  | // Checks that SetRemoteDescription doesn't attempt to associate a transceiver | 
|  | // when simulcast is requested by the server. | 
|  | TEST_F(PeerConnectionSimulcastTests, TransceiverIsNotRecycledWithSimulcast) { | 
|  | auto local = CreatePeerConnectionWrapper(); | 
|  | auto remote = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"f", "h", "q"}, true); | 
|  | AddTransceiver(local.get(), layers); | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = | 
|  | local->CreateOfferAndSetAsLocal(); | 
|  | // Remove simulcast as a sender and set it up as a receiver. | 
|  | RemoveSimulcast(offer.get()); | 
|  | AddRequestToReceiveSimulcast(layers, offer.get()); | 
|  | // Call AddTrack so that a transceiver is created. | 
|  | remote->AddVideoTrack("fake_track"); | 
|  | std::string error; | 
|  | EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &error)) << error; | 
|  | auto transceivers = remote->pc()->GetTransceivers(); | 
|  | ASSERT_EQ(2u, transceivers.size()); | 
|  | auto transceiver = transceivers[1]; | 
|  | transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendRecv); | 
|  | EXPECT_TRUE(remote->CreateAnswerAndSetAsLocal()); | 
|  | ValidateTransceiverParameters(transceiver, layers); | 
|  | } | 
|  |  | 
|  | // Checks that if the number of layers changes during negotiation, then any | 
|  | // outstanding get/set parameters transaction is invalidated. | 
|  | TEST_F(PeerConnectionSimulcastTests, ParametersAreInvalidatedWhenLayersChange) { | 
|  | auto local = CreatePeerConnectionWrapper(); | 
|  | auto remote = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"1", "2", "3"}, true); | 
|  | auto transceiver = AddTransceiver(local.get(), layers); | 
|  | auto parameters = transceiver->sender()->GetParameters(); | 
|  | ASSERT_EQ(3u, parameters.encodings.size()); | 
|  | // Response will reject simulcast altogether. | 
|  | ExchangeOfferAnswer(local.get(), remote.get(), {}); | 
|  | auto result = transceiver->sender()->SetParameters(parameters); | 
|  | EXPECT_EQ(RTCErrorType::INVALID_STATE, result.type()); | 
|  | } | 
|  |  | 
|  | // Checks that even though negotiation modifies the sender's parameters, an | 
|  | // outstanding get/set parameters transaction is not invalidated. | 
|  | // This test negotiates twice because initial parameters before negotiation | 
|  | // is missing critical information and cannot be set on the sender. | 
|  | TEST_F(PeerConnectionSimulcastTests, | 
|  | NegotiationDoesNotInvalidateParameterTransactions) { | 
|  | auto local = CreatePeerConnectionWrapper(); | 
|  | auto remote = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"1", "2", "3"}, true); | 
|  | auto expected_layers = CreateLayers({"1", "2", "3"}, false); | 
|  | auto transceiver = AddTransceiver(local.get(), layers); | 
|  | ExchangeOfferAnswer(local.get(), remote.get(), expected_layers); | 
|  |  | 
|  | // Verify that negotiation does not invalidate the parameters. | 
|  | auto parameters = transceiver->sender()->GetParameters(); | 
|  | ExchangeOfferAnswer(local.get(), remote.get(), expected_layers); | 
|  |  | 
|  | auto result = transceiver->sender()->SetParameters(parameters); | 
|  | EXPECT_TRUE(result.ok()); | 
|  | ValidateTransceiverParameters(transceiver, expected_layers); | 
|  | } | 
|  |  | 
|  | // Tests that a simulcast answer is rejected if the RID extension is not | 
|  | // negotiated. | 
|  | TEST_F(PeerConnectionSimulcastTests, NegotiationDoesNotHaveRidExtensionFails) { | 
|  | auto local = CreatePeerConnectionWrapper(); | 
|  | auto remote = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"1", "2", "3"}, true); | 
|  | auto expected_layers = CreateLayers({"1"}, true); | 
|  | auto transceiver = AddTransceiver(local.get(), layers); | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = | 
|  | local->CreateOfferAndSetAsLocal(); | 
|  | // Remove simulcast as the second peer connection won't support it. | 
|  | RemoveSimulcast(offer.get()); | 
|  | std::string err; | 
|  | EXPECT_TRUE(remote->SetRemoteDescription(std::move(offer), &err)) << err; | 
|  | std::unique_ptr<SessionDescriptionInterface> answer = | 
|  | remote->CreateAnswerAndSetAsLocal(); | 
|  | // Setup the answer to look like a server response. | 
|  | // Drop the RID header extension. | 
|  | auto mcd_answer = answer->description()->contents()[0].media_description(); | 
|  | auto& receive_layers = mcd_answer->simulcast_description().receive_layers(); | 
|  | for (const SimulcastLayer& layer : layers) { | 
|  | receive_layers.AddLayer(layer); | 
|  | } | 
|  | RtpHeaderExtensions extensions; | 
|  | for (auto extension : mcd_answer->rtp_header_extensions()) { | 
|  | if (extension.uri != RtpExtension::kRidUri) { | 
|  | extensions.push_back(extension); | 
|  | } | 
|  | } | 
|  | mcd_answer->set_rtp_header_extensions(extensions); | 
|  | EXPECT_EQ(layers.size(), mcd_answer->simulcast_description() | 
|  | .receive_layers() | 
|  | .GetAllLayers() | 
|  | .size()); | 
|  | EXPECT_FALSE(local->SetRemoteDescription(std::move(answer), &err)) << err; | 
|  | } | 
|  |  | 
|  | TEST_F(PeerConnectionSimulcastTests, SimulcastAudioRejected) { | 
|  | auto local = CreatePeerConnectionWrapper(); | 
|  | auto remote = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"1", "2", "3", "4"}, true); | 
|  | auto transceiver = AddTransceiver(local.get(), layers, MediaType::AUDIO); | 
|  | // Should only have the first layer. | 
|  | auto parameters = transceiver->sender()->GetParameters(); | 
|  | EXPECT_EQ(1u, parameters.encodings.size()); | 
|  | EXPECT_THAT(parameters.encodings, | 
|  | ElementsAre(Field("rid", &RtpEncodingParameters::rid, Eq("")))); | 
|  | ExchangeOfferAnswer(local.get(), remote.get(), {}); | 
|  | // Still have a single layer after negotiation | 
|  | parameters = transceiver->sender()->GetParameters(); | 
|  | EXPECT_EQ(1u, parameters.encodings.size()); | 
|  | EXPECT_THAT(parameters.encodings, | 
|  | ElementsAre(Field("rid", &RtpEncodingParameters::rid, Eq("")))); | 
|  | } | 
|  |  | 
|  | // Check that modifying the offer to remove simulcast and at the same | 
|  | // time leaving in a RID line does not cause an exception. | 
|  | TEST_F(PeerConnectionSimulcastTests, SimulcastSldModificationRejected) { | 
|  | auto local = CreatePeerConnectionWrapper(); | 
|  | auto remote = CreatePeerConnectionWrapper(); | 
|  | auto layers = CreateLayers({"1", "2", "3"}, true); | 
|  | AddTransceiver(local.get(), layers); | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = local->CreateOffer(); | 
|  | std::string as_string; | 
|  | EXPECT_TRUE(offer->ToString(&as_string)); | 
|  | auto simulcast_marker = "a=rid:3 send\r\na=simulcast:send 1;2;3\r\n"; | 
|  | auto pos = as_string.find(simulcast_marker); | 
|  | EXPECT_NE(pos, std::string::npos); | 
|  | as_string.erase(pos, strlen(simulcast_marker)); | 
|  | SdpParseError parse_error; | 
|  | auto modified_offer = | 
|  | CreateSessionDescription(SdpType::kOffer, as_string, &parse_error); | 
|  | EXPECT_TRUE(modified_offer); | 
|  | EXPECT_TRUE(local->SetLocalDescription(std::move(modified_offer))); | 
|  | } | 
|  | }  // namespace webrtc |