Default streams: don't block media even if on different transceiver.

This fixes some edge cases where early media could cause default
stream that block the actual signaled media from beind delivered.

Bug: webrtc:11477
Change-Id: I8b26df63a690861bd19f083102d1395e882f8733
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/183120
Commit-Queue: Taylor <deadbeef@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#32030}
This commit is contained in:
Taylor Brandstetter
2020-09-02 13:25:31 -07:00
committed by Commit Bot
parent 0ade98316c
commit c03a187391
17 changed files with 393 additions and 39 deletions

View File

@ -228,6 +228,9 @@ PeerScenarioClient::PeerScenarioClient(
pcf_deps.network_state_predictor_factory = nullptr;
pc_factory_ = CreateModularPeerConnectionFactory(std::move(pcf_deps));
PeerConnectionFactoryInterface::Options pc_options;
pc_options.disable_encryption = config.disable_encryption;
pc_factory_->SetOptions(pc_options);
PeerConnectionDependencies pc_deps(observer_.get());
pc_deps.allocator =
@ -285,14 +288,17 @@ void PeerScenarioClient::AddVideoReceiveSink(
}
void PeerScenarioClient::CreateAndSetSdp(
std::function<void(SessionDescriptionInterface*)> munge_offer,
std::function<void(std::string)> offer_handler) {
RTC_DCHECK_RUN_ON(signaling_thread_);
peer_connection_->CreateOffer(
SdpCreateObserver([=](SessionDescriptionInterface* offer) {
RTC_DCHECK_RUN_ON(signaling_thread_);
if (munge_offer) {
munge_offer(offer);
}
std::string sdp_offer;
offer->ToString(&sdp_offer);
RTC_LOG(LS_INFO) << sdp_offer;
RTC_CHECK(offer->ToString(&sdp_offer));
peer_connection_->SetLocalDescription(
SdpSetObserver(
[sdp_offer, offer_handler]() { offer_handler(sdp_offer); }),

View File

@ -89,6 +89,7 @@ class PeerScenarioClient {
{0, EmulatedEndpointConfig()}};
CallbackHandlers handlers;
PeerConnectionInterface::RTCConfiguration rtc_config;
bool disable_encryption = false;
Config() { rtc_config.sdp_semantics = SdpSemantics::kUnifiedPlan; }
};
@ -136,9 +137,13 @@ class PeerScenarioClient {
CallbackHandlers* handlers() { return &handlers_; }
// Note that there's no provision for munging SDP as that is deprecated
// behavior.
void CreateAndSetSdp(std::function<void(std::string)> offer_handler);
// The |munge_offer| function can be used to munge the SDP, i.e. modify a
// local description afer creating it but before setting it. Note that this is
// legacy behavior. It's added here only to be able to have test coverage for
// scenarios even if they are not spec compliant.
void CreateAndSetSdp(
std::function<void(SessionDescriptionInterface*)> munge_offer,
std::function<void(std::string)> offer_handler);
void SetSdpOfferAndGetAnswer(std::string remote_offer,
std::function<void(std::string)> answer_handler);
void SetSdpAnswer(

View File

@ -58,9 +58,10 @@ void StartSdpNegotiation(
PeerScenarioClient* callee,
TrafficRoute* send_route,
TrafficRoute* ret_route,
std::function<void(SessionDescriptionInterface* offer)> munge_offer,
std::function<void(SessionDescriptionInterface*)> modify_offer,
std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
caller->CreateAndSetSdp([=](std::string sdp_offer) {
caller->CreateAndSetSdp(munge_offer, [=](std::string sdp_offer) {
if (modify_offer) {
auto offer = CreateSessionDescription(SdpType::kOffer, sdp_offer);
modify_offer(offer.get());
@ -92,15 +93,22 @@ void SignalingRoute::StartIceSignaling() {
}
void SignalingRoute::NegotiateSdp(
std::function<void(SessionDescriptionInterface*)> munge_offer,
std::function<void(SessionDescriptionInterface*)> modify_offer,
std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
StartSdpNegotiation(caller_, callee_, send_route_, ret_route_, modify_offer,
exchange_finished);
StartSdpNegotiation(caller_, callee_, send_route_, ret_route_, munge_offer,
modify_offer, exchange_finished);
}
void SignalingRoute::NegotiateSdp(
std::function<void(SessionDescriptionInterface*)> modify_offer,
std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
NegotiateSdp({}, modify_offer, exchange_finished);
}
void SignalingRoute::NegotiateSdp(
std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
NegotiateSdp({}, exchange_finished);
NegotiateSdp({}, {}, exchange_finished);
}
} // namespace test

View File

@ -30,7 +30,19 @@ class SignalingRoute {
void StartIceSignaling();
// The |modify_offer| callback is used to modify an offer after the local
// description has been set. This is legal (but odd) behavior.
// The |munge_offer| callback is used to modify an offer between its creation
// and set local description. This behavior is forbidden according to the spec
// but available here in order to allow test coverage on corner cases.
// The |exchange_finished| callback is called with the answer produced after
// SDP negotations has completed.
// TODO(srte): Handle lossy links.
void NegotiateSdp(
std::function<void(SessionDescriptionInterface* offer)> munge_offer,
std::function<void(SessionDescriptionInterface* offer)> modify_offer,
std::function<void(const SessionDescriptionInterface& answer)>
exchange_finished);
void NegotiateSdp(
std::function<void(SessionDescriptionInterface* offer)> modify_offer,
std::function<void(const SessionDescriptionInterface& answer)>

View File

@ -14,12 +14,16 @@ if (rtc_include_tests) {
sources = [
"peer_scenario_quality_test.cc",
"remote_estimate_test.cc",
"unsignaled_stream_test.cc",
]
deps = [
"..:peer_scenario",
"../../:field_trial",
"../../:rtp_test_utils",
"../../:test_support",
"../../../media:rtc_media_base",
"../../../modules/rtp_rtcp:rtp_rtcp",
"../../../modules/rtp_rtcp:rtp_rtcp_format",
"../../../pc:rtc_pc_base",
]
}

View File

@ -0,0 +1,157 @@
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "media/base/stream_params.h"
#include "modules/rtp_rtcp/source/byte_io.h"
#include "pc/media_session.h"
#include "pc/session_description.h"
#include "test/field_trial.h"
#include "test/peer_scenario/peer_scenario.h"
#include "test/rtp_header_parser.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace test {
namespace {
class FrameObserver : public rtc::VideoSinkInterface<VideoFrame> {
public:
FrameObserver() : frame_observed_(false) {}
void OnFrame(const VideoFrame&) override { frame_observed_ = true; }
std::atomic<bool> frame_observed_;
};
uint32_t get_ssrc(SessionDescriptionInterface* offer, size_t track_index) {
EXPECT_LT(track_index, offer->description()->contents().size());
return offer->description()
->contents()[track_index]
.media_description()
->streams()[0]
.ssrcs[0];
}
void set_ssrc(SessionDescriptionInterface* offer, size_t index, uint32_t ssrc) {
EXPECT_LT(index, offer->description()->contents().size());
cricket::StreamParams& new_stream_params = offer->description()
->contents()[index]
.media_description()
->mutable_streams()[0];
new_stream_params.ssrcs[0] = ssrc;
new_stream_params.ssrc_groups[0].ssrcs[0] = ssrc;
}
} // namespace
TEST(UnsignaledStreamTest, ReplacesUnsignaledStreamOnCompletedSignaling) {
// This test covers a scenario that might occur if a remote client starts
// sending media packets before negotiation has completed. These packets will
// trigger an unsignalled default stream to be created, and connects that to
// a default video sink.
// In some edge cases using unified plan, the default stream is create in a
// different transceiver to where the media SSRC will actually be used.
// This test verifies that the default stream is removed properly, and that
// packets are demuxed and video frames reach the desired sink.
// Defined before PeerScenario so it gets destructed after, to avoid use after
// free.
PeerScenario s(*test_info_);
PeerScenarioClient::Config config = PeerScenarioClient::Config();
// Disable encryption so that we can inject a fake early media packet without
// triggering srtp failures.
config.disable_encryption = true;
auto* caller = s.CreateClient(config);
auto* callee = s.CreateClient(config);
auto send_node = s.net()->NodeBuilder().Build().node;
auto ret_node = s.net()->NodeBuilder().Build().node;
s.net()->CreateRoute(caller->endpoint(), {send_node}, callee->endpoint());
s.net()->CreateRoute(callee->endpoint(), {ret_node}, caller->endpoint());
auto signaling = s.ConnectSignaling(caller, callee, {send_node}, {ret_node});
PeerScenarioClient::VideoSendTrackConfig video_conf;
video_conf.generator.squares_video->framerate = 15;
auto first_track = caller->CreateVideo("VIDEO", video_conf);
FrameObserver first_sink;
callee->AddVideoReceiveSink(first_track.track->id(), &first_sink);
signaling.StartIceSignaling();
std::atomic<bool> offer_exchange_done(false);
std::atomic<bool> got_unsignaled_packet(false);
// We will capture the media ssrc of the first added stream, and preemptively
// inject a new media packet using a different ssrc.
// This will create "default stream" for the second ssrc and connected it to
// the default video sink (not set in this test).
uint32_t first_ssrc = 0;
uint32_t second_ssrc = 0;
signaling.NegotiateSdp(
/* munge_sdp = */ {},
/* modify_sdp = */
[&](SessionDescriptionInterface* offer) {
first_ssrc = get_ssrc(offer, 0);
second_ssrc = first_ssrc + 1;
send_node->router()->SetWatcher([&](const EmulatedIpPacket& packet) {
if (packet.size() > 1 && packet.cdata()[0] >> 6 == 2 &&
!RtpHeaderParser::IsRtcp(packet.data.cdata(),
packet.data.size())) {
if (ByteReader<uint32_t>::ReadBigEndian(&(packet.cdata()[8])) ==
first_ssrc &&
!got_unsignaled_packet) {
rtc::CopyOnWriteBuffer updated_buffer = packet.data;
ByteWriter<uint32_t>::WriteBigEndian(&updated_buffer.data()[8],
second_ssrc);
EmulatedIpPacket updated_packet(
packet.from, packet.to, updated_buffer, packet.arrival_time);
send_node->OnPacketReceived(std::move(updated_packet));
got_unsignaled_packet = true;
}
}
});
},
[&](const SessionDescriptionInterface& answer) {
EXPECT_EQ(answer.description()->contents().size(), 1u);
offer_exchange_done = true;
});
EXPECT_TRUE(s.WaitAndProcess(&offer_exchange_done));
EXPECT_TRUE(s.WaitAndProcess(&got_unsignaled_packet));
EXPECT_TRUE(s.WaitAndProcess(&first_sink.frame_observed_));
auto second_track = caller->CreateVideo("VIDEO2", video_conf);
FrameObserver second_sink;
callee->AddVideoReceiveSink(second_track.track->id(), &second_sink);
// Create a second video stream, munge the sdp to force it to use our fake
// early media ssrc.
offer_exchange_done = false;
signaling.NegotiateSdp(
/* munge_sdp = */
[&](SessionDescriptionInterface* offer) {
set_ssrc(offer, 1, second_ssrc);
},
/* modify_sdp = */ {},
[&](const SessionDescriptionInterface& answer) {
EXPECT_EQ(answer.description()->contents().size(), 2u);
offer_exchange_done = true;
});
EXPECT_TRUE(s.WaitAndProcess(&offer_exchange_done));
EXPECT_TRUE(s.WaitAndProcess(&second_sink.frame_observed_));
}
} // namespace test
} // namespace webrtc