WebRtcVideoChannel creates default stream with dummy SSRC on received RTX packet.
This ensure transport feedback is sent for RTX packets that are received before media payload packets.
Bug: webrtc:14795, webrtc:14817
Change-Id: I6a2579b87c8863e003decb2b2559ef51a852cadb
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/291119
Commit-Queue: Per Kjellander <perkj@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#39174}
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index dfd8d66..c6ef603 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -13,6 +13,7 @@
#include <stdio.h>
#include <algorithm>
+#include <cstdint>
#include <set>
#include <string>
#include <utility>
@@ -20,6 +21,7 @@
#include "absl/algorithm/container.h"
#include "absl/functional/bind_front.h"
#include "absl/strings/match.h"
+#include "absl/types/optional.h"
#include "api/media_stream_interface.h"
#include "api/video/video_codec_constants.h"
#include "api/video/video_codec_type.h"
@@ -583,58 +585,6 @@
return nullptr;
}
-DefaultUnsignalledSsrcHandler::DefaultUnsignalledSsrcHandler()
- : default_sink_(nullptr) {}
-
-UnsignalledSsrcHandler::Action DefaultUnsignalledSsrcHandler::OnUnsignalledSsrc(
- WebRtcVideoChannel* channel,
- uint32_t ssrc,
- absl::optional<uint32_t> rtx_ssrc) {
- absl::optional<uint32_t> default_recv_ssrc = channel->GetUnsignaledSsrc();
-
- if (default_recv_ssrc) {
- RTC_LOG(LS_INFO) << "Destroying old default receive stream for SSRC="
- << ssrc << ".";
- channel->RemoveRecvStream(*default_recv_ssrc);
- }
-
- StreamParams sp = channel->unsignaled_stream_params();
- sp.ssrcs.push_back(ssrc);
- if (rtx_ssrc) {
- sp.AddFidSsrc(ssrc, *rtx_ssrc);
- }
- RTC_LOG(LS_INFO) << "Creating default receive stream for SSRC=" << ssrc
- << ".";
- if (!channel->AddRecvStream(sp, /*default_stream=*/true)) {
- RTC_LOG(LS_WARNING) << "Could not create default receive stream.";
- }
-
- // SSRC 0 returns default_recv_base_minimum_delay_ms.
- const int unsignaled_ssrc = 0;
- int default_recv_base_minimum_delay_ms =
- channel->GetBaseMinimumPlayoutDelayMs(unsignaled_ssrc).value_or(0);
- // Set base minimum delay if it was set before for the default receive stream.
- channel->SetBaseMinimumPlayoutDelayMs(ssrc,
- default_recv_base_minimum_delay_ms);
- channel->SetSink(ssrc, default_sink_);
- return kDeliverPacket;
-}
-
-rtc::VideoSinkInterface<webrtc::VideoFrame>*
-DefaultUnsignalledSsrcHandler::GetDefaultSink() const {
- return default_sink_;
-}
-
-void DefaultUnsignalledSsrcHandler::SetDefaultSink(
- WebRtcVideoChannel* channel,
- rtc::VideoSinkInterface<webrtc::VideoFrame>* sink) {
- default_sink_ = sink;
- absl::optional<uint32_t> default_recv_ssrc = channel->GetUnsignaledSsrc();
- if (default_recv_ssrc) {
- channel->SetSink(*default_recv_ssrc, default_sink_);
- }
-}
-
WebRtcVideoEngine::WebRtcVideoEngine(
std::unique_ptr<webrtc::VideoEncoderFactory> video_encoder_factory,
std::unique_ptr<webrtc::VideoDecoderFactory> video_decoder_factory,
@@ -724,7 +674,7 @@
: VideoMediaChannel(call->network_thread(), config.enable_dscp),
worker_thread_(call->worker_thread()),
call_(call),
- unsignalled_ssrc_handler_(&default_unsignalled_ssrc_handler_),
+ default_sink_(nullptr),
video_config_(config.video),
encoder_factory_(encoder_factory),
decoder_factory_(decoder_factory),
@@ -1151,7 +1101,7 @@
const {
RTC_DCHECK_RUN_ON(&thread_checker_);
webrtc::RtpParameters rtp_params;
- if (!default_unsignalled_ssrc_handler_.GetDefaultSink()) {
+ if (!default_sink_) {
// Getting parameters on a default, unsignaled video receive stream but
// because we've not configured to receive such a stream, `encodings` is
// empty.
@@ -1648,7 +1598,7 @@
rtc::VideoSinkInterface<webrtc::VideoFrame>* sink) {
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_LOG(LS_INFO) << "SetDefaultSink: " << (sink ? "(ptr)" : "nullptr");
- default_unsignalled_ssrc_handler_.SetDefaultSink(this, sink);
+ default_sink_ = sink;
}
bool WebRtcVideoChannel::GetSendStats(VideoMediaSendInfo* info) {
@@ -1809,35 +1759,6 @@
return false;
}
- absl::optional<uint32_t> rtx_ssrc;
- uint32_t ssrc = packet.Ssrc();
- // See if this payload_type is registered as one that usually gets its
- // own SSRC (RTX) or at least is safe to drop either way (FEC). If it
- // is, and it wasn't handled above by DeliverPacket, that means we don't
- // know what stream it associates with, and we shouldn't ever create an
- // implicit channel for these.
- for (auto& codec : recv_codecs_) {
- if (packet.PayloadType() == codec.ulpfec.red_rtx_payload_type ||
- packet.PayloadType() == codec.ulpfec.ulpfec_payload_type) {
- return false;
- }
- if (packet.PayloadType() == codec.rtx_payload_type) {
- // As we don't support receiving simulcast there can only be one RTX
- // stream, which will be associated with unsignaled media stream.
- // It is not possible to update the ssrcs of a receive stream, so we
- // recreate it insead if found.
- auto default_ssrc = GetUnsignaledSsrc();
- if (!default_ssrc) {
- return false;
- }
- rtx_ssrc = ssrc;
- ssrc = *default_ssrc;
- // Allow recreating the receive stream even if the RTX packet is
- // received just after the media packet.
- last_unsignalled_ssrc_creation_time_ms_.reset();
- break;
- }
- }
if (packet.PayloadType() == recv_flexfec_payload_type_) {
return false;
}
@@ -1849,33 +1770,102 @@
if (demuxer_criteria_id_ != demuxer_criteria_completed_id_) {
return false;
}
- // Ignore unknown ssrcs if we recently created an unsignalled receive
- // stream since this shouldn't happen frequently. Getting into a state
- // of creating decoders on every packet eats up processing time (e.g.
- // https://crbug.com/1069603) and this cooldown prevents that.
- if (last_unsignalled_ssrc_creation_time_ms_.has_value()) {
- int64_t now_ms = rtc::TimeMillis();
- if (now_ms - last_unsignalled_ssrc_creation_time_ms_.value() <
- kUnsignaledSsrcCooldownMs) {
- // We've already created an unsignalled ssrc stream within the last
- // 0.5 s, ignore with a warning.
- RTC_LOG(LS_WARNING)
- << "Another unsignalled ssrc packet arrived shortly after the "
- << "creation of an unsignalled ssrc stream. Dropping packet.";
+
+ // See if this payload_type is registered as one that usually gets its
+ // own SSRC (RTX) or at least is safe to drop either way (FEC). If it
+ // is, and it wasn't handled above by DeliverPacket, that means we don't
+ // know what stream it associates with, and we shouldn't ever create an
+ // implicit channel for these.
+ bool is_rtx_payload = false;
+ for (auto& codec : recv_codecs_) {
+ if (packet.PayloadType() == codec.ulpfec.red_rtx_payload_type ||
+ packet.PayloadType() == codec.ulpfec.ulpfec_payload_type) {
return false;
}
- }
- // Let the unsignalled ssrc handler decide whether to drop or deliver.
- switch (unsignalled_ssrc_handler_->OnUnsignalledSsrc(this, ssrc, rtx_ssrc)) {
- case UnsignalledSsrcHandler::kDropPacket:
- return false;
- case UnsignalledSsrcHandler::kDeliverPacket:
+
+ if (packet.PayloadType() == codec.rtx_payload_type) {
+ is_rtx_payload = true;
break;
+ }
}
+
+ if (is_rtx_payload) {
+ // As we don't support receiving simulcast there can only be one RTX
+ // stream, which will be associated with unsignaled media stream.
+ absl::optional<uint32_t> current_default_ssrc = GetUnsignaledSsrc();
+ if (current_default_ssrc) {
+ // TODO(bug.webrtc.org/14817): Consider associating the existing default
+ // stream with this RTX stream instead of recreating.
+ ReCreateDefaulReceiveStream(/*ssrc =*/*current_default_ssrc,
+ packet.Ssrc());
+ } else {
+ // Received unsignaled RTX packet before a media packet. Create a default
+ // stream with a "random" SSRC and the RTX SSRC from the packet. The
+ // stream will be recreated on the first media packet, unless we are
+ // extremely lucky and used the right media SSRC.
+ ReCreateDefaulReceiveStream(/*ssrc =*/14795, /*rtx_ssrc=*/packet.Ssrc());
+ }
+ return true;
+ } else {
+ // Ignore unknown ssrcs if we recently created an unsignalled receive
+ // stream since this shouldn't happen frequently. Getting into a state
+ // of creating decoders on every packet eats up processing time (e.g.
+ // https://crbug.com/1069603) and this cooldown prevents that.
+ if (last_unsignalled_ssrc_creation_time_ms_.has_value()) {
+ int64_t now_ms = rtc::TimeMillis();
+ if (now_ms - last_unsignalled_ssrc_creation_time_ms_.value() <
+ kUnsignaledSsrcCooldownMs) {
+ // We've already created an unsignalled ssrc stream within the last
+ // 0.5 s, ignore with a warning.
+ RTC_LOG(LS_WARNING)
+ << "Another unsignalled ssrc packet arrived shortly after the "
+ << "creation of an unsignalled ssrc stream. Dropping packet.";
+ return false;
+ }
+ }
+ }
+
+ // TODO(bug.webrtc.org/14817): Consider creating a default stream with a fake
+ // RTX ssrc that can be updated when the real SSRC is known if rtx has been
+ // negotiated.
+ ReCreateDefaulReceiveStream(packet.Ssrc(), absl::nullopt);
last_unsignalled_ssrc_creation_time_ms_ = rtc::TimeMillis();
return true;
}
+void WebRtcVideoChannel::ReCreateDefaulReceiveStream(
+ uint32_t ssrc,
+ absl::optional<uint32_t> rtx_ssrc) {
+ RTC_DCHECK_RUN_ON(&thread_checker_);
+
+ absl::optional<uint32_t> default_recv_ssrc = GetUnsignaledSsrc();
+ if (default_recv_ssrc) {
+ RTC_LOG(LS_INFO) << "Destroying old default receive stream for SSRC="
+ << ssrc << ".";
+ RemoveRecvStream(*default_recv_ssrc);
+ }
+
+ StreamParams sp = unsignaled_stream_params();
+ sp.ssrcs.push_back(ssrc);
+ if (rtx_ssrc) {
+ sp.AddFidSsrc(ssrc, *rtx_ssrc);
+ }
+ RTC_LOG(LS_INFO) << "Creating default receive stream for SSRC=" << ssrc
+ << ".";
+ if (!AddRecvStream(sp, /*default_stream=*/true)) {
+ RTC_LOG(LS_WARNING) << "Could not create default receive stream.";
+ }
+
+ // SSRC 0 returns default_recv_base_minimum_delay_ms.
+ const int unsignaled_ssrc = 0;
+ int default_recv_base_minimum_delay_ms =
+ GetBaseMinimumPlayoutDelayMs(unsignaled_ssrc).value_or(0);
+ // Set base minimum delay if it was set before for the default receive
+ // stream.
+ SetBaseMinimumPlayoutDelayMs(ssrc, default_recv_base_minimum_delay_ms);
+ SetSink(ssrc, default_sink_);
+}
+
void WebRtcVideoChannel::OnPacketSent(const rtc::SentPacket& sent_packet) {
RTC_DCHECK_RUN_ON(&network_thread_checker_);
// TODO(tommi): We shouldn't need to go through call_ to deliver this
diff --git a/media/engine/webrtc_video_engine.h b/media/engine/webrtc_video_engine.h
index 43d53f9..0242bdd 100644
--- a/media/engine/webrtc_video_engine.h
+++ b/media/engine/webrtc_video_engine.h
@@ -60,36 +60,6 @@
MergeInfoAboutOutboundRtpSubstreamsForTesting(
const std::map<uint32_t, webrtc::VideoSendStream::StreamStats>& substreams);
-class UnsignalledSsrcHandler {
- public:
- enum Action {
- kDropPacket,
- kDeliverPacket,
- };
- virtual Action OnUnsignalledSsrc(WebRtcVideoChannel* channel,
- uint32_t ssrc,
- absl::optional<uint32_t> rtx_ssrc) = 0;
- virtual ~UnsignalledSsrcHandler() = default;
-};
-
-// TODO(pbos): Remove, use external handlers only.
-class DefaultUnsignalledSsrcHandler : public UnsignalledSsrcHandler {
- public:
- DefaultUnsignalledSsrcHandler();
- Action OnUnsignalledSsrc(WebRtcVideoChannel* channel,
- uint32_t ssrc,
- absl::optional<uint32_t> rtx_ssrc) override;
-
- rtc::VideoSinkInterface<webrtc::VideoFrame>* GetDefaultSink() const;
- void SetDefaultSink(WebRtcVideoChannel* channel,
- rtc::VideoSinkInterface<webrtc::VideoFrame>* sink);
-
- virtual ~DefaultUnsignalledSsrcHandler() = default;
-
- private:
- rtc::VideoSinkInterface<webrtc::VideoFrame>* default_sink_;
-};
-
// WebRtcVideoEngine is used for the new native WebRTC Video API (webrtc:1667).
class WebRtcVideoEngine : public VideoEngineInterface {
public:
@@ -321,6 +291,8 @@
bool MaybeCreateDefaultReceiveStream(
const webrtc::RtpPacketReceived& parsed_packet)
RTC_EXCLUSIVE_LOCKS_REQUIRED(thread_checker_);
+ void ReCreateDefaulReceiveStream(uint32_t ssrc,
+ absl::optional<uint32_t> rtx_ssrc);
void ConfigureReceiverRtp(
webrtc::VideoReceiveStreamInterface::Config* config,
webrtc::FlexfecReceiveStream::Config* flexfec_config,
@@ -600,9 +572,7 @@
bool sending_ RTC_GUARDED_BY(thread_checker_);
webrtc::Call* const call_;
- DefaultUnsignalledSsrcHandler default_unsignalled_ssrc_handler_
- RTC_GUARDED_BY(thread_checker_);
- UnsignalledSsrcHandler* const unsignalled_ssrc_handler_
+ rtc::VideoSinkInterface<webrtc::VideoFrame>* default_sink_
RTC_GUARDED_BY(thread_checker_);
// Delay for unsignaled streams, which may be set before the stream exists.
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index 1e8f11e..713cfb0 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -53,6 +53,8 @@
#include "media/engine/fake_webrtc_call.h"
#include "media/engine/fake_webrtc_video_engine.h"
#include "media/engine/webrtc_voice_engine.h"
+#include "modules/rtp_rtcp/include/rtp_header_extension_map.h"
+#include "modules/rtp_rtcp/source/rtcp_packet.h"
#include "modules/rtp_rtcp/source/rtp_packet.h"
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
#include "modules/video_coding/svc/scalability_mode_util.h"
@@ -66,6 +68,7 @@
#include "test/fake_decoder.h"
#include "test/frame_forwarder.h"
#include "test/gmock.h"
+#include "test/rtcp_packet_parser.h"
#include "test/scoped_key_value_config.h"
#include "test/time_controller/simulated_time_controller.h"
#include "video/config/simulcast.h"
@@ -85,12 +88,14 @@
using ::testing::SizeIs;
using ::testing::StrNe;
using ::testing::Values;
+using ::testing::WithArg;
using ::webrtc::BitrateConstraints;
using ::webrtc::kDefaultScalabilityModeStr;
using ::webrtc::RtpExtension;
using ::webrtc::RtpPacket;
using ::webrtc::RtpPacketReceived;
using ::webrtc::ScalabilityMode;
+using ::webrtc::test::RtcpPacketParser;
namespace {
static const int kDefaultQpMax = 56;
@@ -246,6 +251,24 @@
(override));
};
+class MockNetworkInterface : public cricket::MediaChannelNetworkInterface {
+ public:
+ MOCK_METHOD(bool,
+ SendPacket,
+ (rtc::CopyOnWriteBuffer * packet,
+ const rtc::PacketOptions& options),
+ (override));
+ MOCK_METHOD(bool,
+ SendRtcp,
+ (rtc::CopyOnWriteBuffer * packet,
+ const rtc::PacketOptions& options),
+ (override));
+ MOCK_METHOD(int,
+ SetOption,
+ (SocketType type, rtc::Socket::Option opt, int option),
+ (override));
+};
+
std::vector<webrtc::Resolution> GetStreamResolutions(
const std::vector<webrtc::VideoStream>& streams) {
std::vector<webrtc::Resolution> res;
@@ -832,6 +855,51 @@
}
}
+TEST_F(WebRtcVideoEngineTest, SendsFeedbackAfterUnsignaledRtxPacket) {
+ // Setup a channel with VP8, RTX and transport sequence number header
+ // extension. Receive stream is not explicitly configured.
+ AddSupportedVideoCodecType("VP8");
+ std::vector<VideoCodec> supported_codecs =
+ engine_.recv_codecs(/*include_rtx=*/true);
+ ASSERT_EQ(supported_codecs[1].name, "rtx");
+ int rtx_payload_type = supported_codecs[1].id;
+ MockNetworkInterface network;
+ RtcpPacketParser rtcp_parser;
+ ON_CALL(network, SendRtcp)
+ .WillByDefault(testing::DoAll(
+ WithArg<0>([&](rtc::CopyOnWriteBuffer* packet) {
+ ASSERT_TRUE(rtcp_parser.Parse(packet->cdata(), packet->size()));
+ }),
+ Return(true)));
+ std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
+ call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
+ video_bitrate_allocator_factory_.get()));
+ cricket::VideoRecvParameters parameters;
+ parameters.codecs = supported_codecs;
+ const int kTransportSeqExtensionId = 1;
+ parameters.extensions.push_back(RtpExtension(
+ RtpExtension::kTransportSequenceNumberUri, kTransportSeqExtensionId));
+ ASSERT_TRUE(channel->SetRecvParameters(parameters));
+ channel->SetInterface(&network);
+ channel->AsVideoReceiveChannel()->OnReadyToSend(true);
+
+ // Inject a RTX packet.
+ webrtc::RtpHeaderExtensionMap extension_map(parameters.extensions);
+ webrtc::RtpPacketReceived packet(&extension_map);
+ packet.SetMarker(true);
+ packet.SetPayloadType(rtx_payload_type);
+ packet.SetSsrc(999);
+ packet.SetExtension<webrtc::TransportSequenceNumber>(7);
+ uint8_t* buf_ptr = packet.AllocatePayload(11);
+ memset(buf_ptr, 0, 11); // Pass MSAN (don't care about bytes 1-9)
+ channel->AsVideoReceiveChannel()->OnPacketReceived(packet);
+
+ // Expect that feedback is sent after a while.
+ time_controller_.AdvanceTime(webrtc::TimeDelta::Seconds(1));
+ EXPECT_GT(rtcp_parser.transport_feedback()->num_packets(), 0);
+
+ channel->SetInterface(nullptr);
+}
TEST_F(WebRtcVideoEngineTest, UsesSimulcastAdapterForVp8Factories) {
AddSupportedVideoCodecType("VP8");
@@ -7178,12 +7246,12 @@
true /* expect_created_receive_stream */);
}
-TEST_F(WebRtcVideoChannelTest, RtxPacketDoesntCreateUnsignalledStream) {
+TEST_F(WebRtcVideoChannelTest, RtxPacketCreateUnsignalledStream) {
AssignDefaultAptRtxTypes();
const cricket::VideoCodec vp8 = GetEngineCodec("VP8");
const int rtx_vp8_payload_type = default_apt_rtx_types_[vp8.id];
TestReceiveUnsignaledSsrcPacket(rtx_vp8_payload_type,
- false /* expect_created_receive_stream */);
+ true /* expect_created_receive_stream */);
}
TEST_F(WebRtcVideoChannelTest, UlpfecPacketDoesntCreateUnsignalledStream) {
@@ -7237,6 +7305,37 @@
<< "Receive stream should have correct rtx ssrc";
}
+TEST_F(WebRtcVideoChannelTest,
+ MediaPacketAfterRtxImmediatelyRecreatesUnsignalledStream) {
+ AssignDefaultAptRtxTypes();
+ const cricket::VideoCodec vp8 = GetEngineCodec("VP8");
+ const int payload_type = vp8.id;
+ const int rtx_vp8_payload_type = default_apt_rtx_types_[vp8.id];
+ const uint32_t ssrc = kIncomingUnsignalledSsrc;
+ const uint32_t rtx_ssrc = ssrc + 1;
+
+ // Send rtx packet.
+ RtpPacketReceived rtx_packet;
+ rtx_packet.SetPayloadType(rtx_vp8_payload_type);
+ rtx_packet.SetSsrc(rtx_ssrc);
+ receive_channel_->OnPacketReceived(rtx_packet);
+ rtc::Thread::Current()->ProcessMessages(0);
+ EXPECT_EQ(1u, fake_call_->GetVideoReceiveStreams().size());
+
+ // Send media packet.
+ RtpPacketReceived packet;
+ packet.SetPayloadType(payload_type);
+ packet.SetSsrc(ssrc);
+ ReceivePacketAndAdvanceTime(packet);
+ EXPECT_EQ(1u, fake_call_->GetVideoReceiveStreams().size());
+
+ // Check receive stream has been recreated with correct ssrcs.
+ auto recv_stream = fake_call_->GetVideoReceiveStreams().front();
+ auto& config = recv_stream->GetConfig();
+ EXPECT_EQ(config.rtp.remote_ssrc, ssrc)
+ << "Receive stream should have correct media ssrc";
+}
+
// Test that receiving any unsignalled SSRC works even if it changes.
// The first unsignalled SSRC received will create a default receive stream.
// Any different unsignalled SSRC received will replace the default.