mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-18 08:07:56 +01:00

This reverts commit05ea12e513
. Reason for revert: Speculative revert. Original change's description: > Revert "Removing MessageHandler dependency from Connection." > > This reverts commit3202e29f72
. > > Reason for revert: Introduced a crash in the task posted by Destroy() > > Original change's description: > > Removing MessageHandler dependency from Connection. > > > > Bug: webrtc:11988 > > Change-Id: Ic35bb5baeafbda7210012dceb0d6d5f5b3eb95c9 > > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/249941 > > Reviewed-by: Niels Moller <nisse@webrtc.org> > > Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org> > > Cr-Commit-Position: refs/heads/main@{#35890} > > No-Try: True > Bug: webrtc:11988 > Change-Id: Ie70ee145fde75b8cf76b02784176970e7a78e001 > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/252541 > Auto-Submit: Taylor Brandstetter <deadbeef@webrtc.org> > Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> > Owners-Override: Mirko Bonadei <mbonadei@webrtc.org> > Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org> > Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org> > Cr-Commit-Position: refs/heads/main@{#36078} No-Try: True Bug: webrtc:11988 Change-Id: Idfd42d016e81d4352839c33dcb4ea3b0dafea08b Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/252584 Owners-Override: Mirko Bonadei <mbonadei@webrtc.org> Bot-Commit: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com> Commit-Queue: Mirko Bonadei <mbonadei@webrtc.org> Cr-Commit-Position: refs/heads/main@{#36081}
270 lines
12 KiB
C++
270 lines
12 KiB
C++
/*
|
|
* 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 "modules/rtp_rtcp/source/rtp_util.h"
|
|
#include "pc/media_session.h"
|
|
#include "pc/session_description.h"
|
|
#include "test/field_trial.h"
|
|
#include "test/gmock.h"
|
|
#include "test/gtest.h"
|
|
#include "test/peer_scenario/peer_scenario.h"
|
|
|
|
namespace webrtc {
|
|
namespace test {
|
|
namespace {
|
|
|
|
enum class MidTestConfiguration {
|
|
// Legacy endpoint setup where PT demuxing is used.
|
|
kMidNotNegotiated,
|
|
// MID is negotiated but missing from packets. PT demuxing is disabled, so
|
|
// SSRCs have to be added to the SDP for WebRTC to forward packets correctly.
|
|
// Happens when client is spec compliant but the SFU isn't. Popular legacy.
|
|
kMidNegotiatedButMissingFromPackets,
|
|
// Fully spec-compliant: MID is present so we can safely drop packets with
|
|
// unknown MIDs.
|
|
kMidNegotiatedAndPresentInPackets,
|
|
};
|
|
|
|
// Gives the parameterized test a readable suffix.
|
|
std::string TestParametersMidTestConfigurationToString(
|
|
testing::TestParamInfo<MidTestConfiguration> info) {
|
|
switch (info.param) {
|
|
case MidTestConfiguration::kMidNotNegotiated:
|
|
return "MidNotNegotiated";
|
|
case MidTestConfiguration::kMidNegotiatedButMissingFromPackets:
|
|
return "MidNegotiatedButMissingFromPackets";
|
|
case MidTestConfiguration::kMidNegotiatedAndPresentInPackets:
|
|
return "MidNegotiatedAndPresentInPackets";
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
class UnsignaledStreamTest
|
|
: public ::testing::Test,
|
|
public ::testing::WithParamInterface<MidTestConfiguration> {};
|
|
|
|
TEST_P(UnsignaledStreamTest, ReplacesUnsignaledStreamOnCompletedSignaling) {
|
|
// This test covers a scenario that might occur if a remote client starts
|
|
// sending media packets before negotiation has completed. Depending on setup,
|
|
// these packets either get dropped or trigger an unsignalled default stream
|
|
// to be created, and connects that to a default video sink.
|
|
// In some edge cases using Unified Plan and PT demuxing, 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.
|
|
const MidTestConfiguration kMidTestConfiguration = GetParam();
|
|
|
|
// Defined before PeerScenario so it gets destructed after, to avoid use after
|
|
// free.
|
|
PeerScenario s(*::testing::UnitTest::GetInstance()->current_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. What happens depends on
|
|
// the test configuration.
|
|
//
|
|
// MidTestConfiguration::kMidNotNegotiated:
|
|
// - MID is not negotiated which means PT-based demuxing is enabled. Because
|
|
// the packets have no MID, the second ssrc packet gets forwarded to the
|
|
// first m= section. This will create a "default stream" for the second ssrc
|
|
// and connect it to the default video sink (not set in this test). The test
|
|
// verifies we can recover from this when we later get packets for the first
|
|
// ssrc.
|
|
//
|
|
// MidTestConfiguration::kMidNegotiatedButMissingFromPackets:
|
|
// - MID is negotiated wich means PT-based demuxing is disabled. Because we
|
|
// modify the packets not to contain the MID anyway (simulating a legacy SFU
|
|
// that does not negotiate properly) unknown SSRCs are dropped but do not
|
|
// otherwise cause any issues.
|
|
//
|
|
// MidTestConfiguration::kMidNegotiatedAndPresentInPackets:
|
|
// - MID is negotiated which means PT-based demuxing is enabled. In this case
|
|
// the packets have the MID so they either get forwarded or dropped
|
|
// depending on if the MID is known. The spec-compliant way is also the most
|
|
// straight-forward one.
|
|
|
|
uint32_t first_ssrc = 0;
|
|
uint32_t second_ssrc = 0;
|
|
absl::optional<int> mid_header_extension_id = absl::nullopt;
|
|
|
|
signaling.NegotiateSdp(
|
|
/* munge_sdp = */
|
|
[&](SessionDescriptionInterface* offer) {
|
|
// Obtain the MID header extension ID and if we want the
|
|
// MidTestConfiguration::kMidNotNegotiated setup then we remove the MID
|
|
// header extension through SDP munging (otherwise SDP is not modified).
|
|
for (cricket::ContentInfo& content_info :
|
|
offer->description()->contents()) {
|
|
std::vector<RtpExtension> header_extensions =
|
|
content_info.media_description()->rtp_header_extensions();
|
|
for (auto it = header_extensions.begin();
|
|
it != header_extensions.end(); ++it) {
|
|
if (it->uri == RtpExtension::kMidUri) {
|
|
// MID header extension found!
|
|
mid_header_extension_id = it->id;
|
|
if (kMidTestConfiguration ==
|
|
MidTestConfiguration::kMidNotNegotiated) {
|
|
// Munge away the extension.
|
|
header_extensions.erase(it);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
content_info.media_description()->set_rtp_header_extensions(
|
|
std::move(header_extensions));
|
|
}
|
|
ASSERT_TRUE(mid_header_extension_id.has_value());
|
|
},
|
|
/* modify_sdp = */
|
|
[&](SessionDescriptionInterface* offer) {
|
|
first_ssrc = get_ssrc(offer, 0);
|
|
second_ssrc = first_ssrc + 1;
|
|
|
|
send_node->router()->SetWatcher([&](const EmulatedIpPacket& packet) {
|
|
if (IsRtpPacket(packet.data) &&
|
|
ByteReader<uint32_t>::ReadBigEndian(&(packet.cdata()[8])) ==
|
|
first_ssrc &&
|
|
!got_unsignaled_packet) {
|
|
// Parse packet and modify the SSRC to simulate a second m=
|
|
// section that has not been negotiated yet.
|
|
std::vector<RtpExtension> extensions;
|
|
extensions.emplace_back(RtpExtension::kMidUri,
|
|
mid_header_extension_id.value());
|
|
RtpHeaderExtensionMap extensions_map(extensions);
|
|
RtpPacket parsed_packet;
|
|
parsed_packet.IdentifyExtensions(extensions_map);
|
|
ASSERT_TRUE(parsed_packet.Parse(packet.data));
|
|
parsed_packet.SetSsrc(second_ssrc);
|
|
// The MID extension is present if and only if it was negotiated.
|
|
// If present, we either want to remove it or modify it depending
|
|
// on setup.
|
|
switch (kMidTestConfiguration) {
|
|
case MidTestConfiguration::kMidNotNegotiated:
|
|
EXPECT_FALSE(parsed_packet.HasExtension<RtpMid>());
|
|
break;
|
|
case MidTestConfiguration::kMidNegotiatedButMissingFromPackets:
|
|
EXPECT_TRUE(parsed_packet.HasExtension<RtpMid>());
|
|
ASSERT_TRUE(parsed_packet.RemoveExtension(RtpMid::kId));
|
|
break;
|
|
case MidTestConfiguration::kMidNegotiatedAndPresentInPackets:
|
|
EXPECT_TRUE(parsed_packet.HasExtension<RtpMid>());
|
|
// The simulated second m= section would have a different MID.
|
|
// If we don't modify it here then `second_ssrc` would end up
|
|
// being mapped to the first m= section which would cause SSRC
|
|
// conflicts if we later add the same SSRC to a second m=
|
|
// section. Hidden assumption: first m= section does not use
|
|
// MID:1.
|
|
ASSERT_TRUE(parsed_packet.SetExtension<RtpMid>("1"));
|
|
break;
|
|
}
|
|
// Inject the modified packet.
|
|
rtc::CopyOnWriteBuffer updated_buffer = parsed_packet.Buffer();
|
|
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_));
|
|
caller->pc()->Close();
|
|
callee->pc()->Close();
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
All,
|
|
UnsignaledStreamTest,
|
|
::testing::Values(MidTestConfiguration::kMidNotNegotiated,
|
|
MidTestConfiguration::kMidNegotiatedButMissingFromPackets,
|
|
MidTestConfiguration::kMidNegotiatedAndPresentInPackets),
|
|
TestParametersMidTestConfigurationToString);
|
|
|
|
} // namespace test
|
|
} // namespace webrtc
|