diff --git a/api/rtp_parameters.h b/api/rtp_parameters.h index 7fd4952eb6..ab7e52f3ff 100644 --- a/api/rtp_parameters.h +++ b/api/rtp_parameters.h @@ -50,6 +50,7 @@ enum class FecMechanism { // Used in RtcpFeedback struct. enum class RtcpFeedbackType { CCM, + LNTF, // "goog-lntf" NACK, REMB, // "goog-remb" TRANSPORT_CC, diff --git a/call/rtp_config.cc b/call/rtp_config.cc index f11d69c22c..7840308ecd 100644 --- a/call/rtp_config.cc +++ b/call/rtp_config.cc @@ -17,6 +17,10 @@ namespace webrtc { +std::string LntfConfig::ToString() const { + return enabled ? "{enabled: true}" : "{enabled: false}"; +} + std::string NackConfig::ToString() const { char buf[1024]; rtc::SimpleStringBuilder ss(buf); @@ -72,6 +76,7 @@ std::string RtpConfig::ToString() const { } ss << ']'; + ss << ", lntf: " << lntf.ToString(); ss << ", nack: {rtp_history_ms: " << nack.rtp_history_ms << '}'; ss << ", ulpfec: " << ulpfec.ToString(); ss << ", payload_name: " << payload_name; diff --git a/call/rtp_config.h b/call/rtp_config.h index adffc89d12..105d28425f 100644 --- a/call/rtp_config.h +++ b/call/rtp_config.h @@ -26,6 +26,14 @@ struct RtpPayloadState { uint8_t tl0_pic_idx = 0; int64_t shared_frame_id = 0; }; + +// Settings for LNTF (LossNotification). Still highly experimental. +struct LntfConfig { + std::string ToString() const; + + bool enabled{false}; // TODO(bugs.webrtc.org/10662): Consume this flag. +}; + // Settings for NACK, see RFC 4585 for details. struct NackConfig { NackConfig() : rtp_history_ms(0) {} @@ -104,6 +112,9 @@ struct RtpConfig { // frame descriptor RTP header extension). bool raw_payload = false; + // See LntfConfig for description. + LntfConfig lntf; + // See NackConfig for description. NackConfig nack; diff --git a/call/video_receive_stream.cc b/call/video_receive_stream.cc index e825956754..2112647261 100644 --- a/call/video_receive_stream.cc +++ b/call/video_receive_stream.cc @@ -116,6 +116,7 @@ std::string VideoReceiveStream::Config::Rtp::ToString() const { ss << '}'; ss << ", remb: " << (remb ? "on" : "off"); ss << ", transport_cc: " << (transport_cc ? "on" : "off"); + ss << ", lntf: {enabled: " << (lntf.enabled ? "true" : "false") << '}'; ss << ", nack: {rtp_history_ms: " << nack.rtp_history_ms << '}'; ss << ", ulpfec_payload_type: " << ulpfec_payload_type; ss << ", red_type: " << red_payload_type; diff --git a/call/video_receive_stream.h b/call/video_receive_stream.h index 5a819f9bcc..63fc5b7b07 100644 --- a/call/video_receive_stream.h +++ b/call/video_receive_stream.h @@ -179,6 +179,9 @@ class VideoReceiveStream { // See draft-holmer-rmcat-transport-wide-cc-extensions for details. bool transport_cc = false; + // See LntfConfig for description. + LntfConfig lntf; + // See NackConfig for description. NackConfig nack; diff --git a/media/base/codec.cc b/media/base/codec.cc index 4380514957..5b1bc9d972 100644 --- a/media/base/codec.cc +++ b/media/base/codec.cc @@ -353,6 +353,11 @@ std::string RtpDataCodec::ToString() const { return sb.str(); } +bool HasLntf(const Codec& codec) { + return codec.HasFeedbackParam( + FeedbackParam(kRtcpFbParamLntf, kParamValueEmpty)); +} + bool HasNack(const Codec& codec) { return codec.HasFeedbackParam( FeedbackParam(kRtcpFbParamNack, kParamValueEmpty)); diff --git a/media/base/codec.h b/media/base/codec.h index bbb147d4a2..b1a1f07238 100644 --- a/media/base/codec.h +++ b/media/base/codec.h @@ -220,6 +220,7 @@ const Codec* FindCodecById(const std::vector& codecs, int payload_type) { return nullptr; } +bool HasLntf(const Codec& codec); bool HasNack(const Codec& codec); bool HasRemb(const Codec& codec); bool HasRrtr(const Codec& codec); diff --git a/media/base/media_constants.cc b/media/base/media_constants.cc index 6078d4817b..3b4d2f27f8 100644 --- a/media/base/media_constants.cc +++ b/media/base/media_constants.cc @@ -77,6 +77,7 @@ const int kPreferredSPropStereo = 0; const int kPreferredStereo = 0; const int kPreferredUseInbandFec = 0; +const char kRtcpFbParamLntf[] = "goog-lntf"; const char kRtcpFbParamNack[] = "nack"; const char kRtcpFbNackParamPli[] = "pli"; const char kRtcpFbParamRemb[] = "goog-remb"; diff --git a/media/base/media_constants.h b/media/base/media_constants.h index 0f940f40fb..a796474dc3 100644 --- a/media/base/media_constants.h +++ b/media/base/media_constants.h @@ -91,6 +91,8 @@ extern const int kPreferredSPropStereo; extern const int kPreferredStereo; extern const int kPreferredUseInbandFec; +// rtcp-fb message in its first experimental stages. Documentation pending. +extern const char kRtcpFbParamLntf[]; // rtcp-fb messages according to RFC 4585 extern const char kRtcpFbParamNack[]; extern const char kRtcpFbNackParamPli[]; diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc index f95ab952c5..2e7e86e13b 100644 --- a/media/engine/webrtc_video_engine.cc +++ b/media/engine/webrtc_video_engine.cc @@ -72,6 +72,10 @@ void AddDefaultFeedbackParams(VideoCodec* codec) { codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamCcm, kRtcpFbCcmParamFir)); codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamNack, kParamValueEmpty)); codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamNack, kRtcpFbNackParamPli)); + if (codec->name == kVp8CodecName && + webrtc::field_trial::IsEnabled("WebRTC-RtcpLossNotification")) { + codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamLntf, kParamValueEmpty)); + } } // This function will assign dynamic payload types (in the range [96, 127]) to @@ -766,8 +770,8 @@ bool WebRtcVideoChannel::SetSendParameters(const VideoSendParameters& params) { for (auto& kv : receive_streams_) { RTC_DCHECK(kv.second != nullptr); kv.second->SetFeedbackParameters( - HasNack(send_codec_->codec), HasRemb(send_codec_->codec), - HasTransportCc(send_codec_->codec), + HasLntf(send_codec_->codec), HasNack(send_codec_->codec), + HasRemb(send_codec_->codec), HasTransportCc(send_codec_->codec), params.rtcp.reduced_size ? webrtc::RtcpMode::kReducedSize : webrtc::RtcpMode::kCompound); } @@ -1889,6 +1893,8 @@ void WebRtcVideoChannel::WebRtcVideoSendStream::SetCodec( } } + parameters_.config.rtp.lntf.enabled = HasLntf(codec_settings.codec); + parameters_.config.rtp.nack.rtp_history_ms = HasNack(codec_settings.codec) ? kNackHistoryMs : 0; @@ -2470,6 +2476,7 @@ void WebRtcVideoChannel::WebRtcVideoReceiveStream::ConfigureCodecs( config_.rtp.ulpfec_payload_type = codec.ulpfec.ulpfec_payload_type; config_.rtp.red_payload_type = codec.ulpfec.red_payload_type; + config_.rtp.lntf.enabled = HasLntf(codec.codec); config_.rtp.nack.rtp_history_ms = HasNack(codec.codec) ? kNackHistoryMs : 0; config_.rtp.rtcp_xr.receiver_reference_time_report = HasRrtr(codec.codec); if (codec.ulpfec.red_rtx_payload_type != -1) { @@ -2507,23 +2514,27 @@ void WebRtcVideoChannel::WebRtcVideoReceiveStream::SetLocalSsrc( } void WebRtcVideoChannel::WebRtcVideoReceiveStream::SetFeedbackParameters( + bool lntf_enabled, bool nack_enabled, bool remb_enabled, bool transport_cc_enabled, webrtc::RtcpMode rtcp_mode) { int nack_history_ms = nack_enabled ? kNackHistoryMs : 0; - if (config_.rtp.nack.rtp_history_ms == nack_history_ms && + if (config_.rtp.lntf.enabled == lntf_enabled && + config_.rtp.nack.rtp_history_ms == nack_history_ms && config_.rtp.remb == remb_enabled && config_.rtp.transport_cc == transport_cc_enabled && config_.rtp.rtcp_mode == rtcp_mode) { RTC_LOG(LS_INFO) << "Ignoring call to SetFeedbackParameters because parameters are " - "unchanged; nack=" - << nack_enabled << ", remb=" << remb_enabled + "unchanged; lntf=" + << lntf_enabled << ", nack=" << nack_enabled + << ", remb=" << remb_enabled << ", transport_cc=" << transport_cc_enabled; return; } config_.rtp.remb = remb_enabled; + config_.rtp.lntf.enabled = lntf_enabled; config_.rtp.nack.rtp_history_ms = nack_history_ms; config_.rtp.transport_cc = transport_cc_enabled; config_.rtp.rtcp_mode = rtcp_mode; @@ -2743,6 +2754,7 @@ WebRtcVideoChannel::WebRtcVideoReceiveStream::GetVideoReceiverInfo( info.firs_sent = stats.rtcp_packet_type_counts.fir_packets; info.plis_sent = stats.rtcp_packet_type_counts.pli_packets; info.nacks_sent = stats.rtcp_packet_type_counts.nack_packets; + // TODO(bugs.webrtc.org/10662): Add stats for LNTF. info.timing_frame_info = stats.timing_frame_info; diff --git a/media/engine/webrtc_video_engine.h b/media/engine/webrtc_video_engine.h index ccec8cc1d4..971cab52af 100644 --- a/media/engine/webrtc_video_engine.h +++ b/media/engine/webrtc_video_engine.h @@ -400,7 +400,8 @@ class WebRtcVideoChannel : public VideoMediaChannel, public webrtc::Transport { void SetLocalSsrc(uint32_t local_ssrc); // TODO(deadbeef): Move these feedback parameters into the recv parameters. - void SetFeedbackParameters(bool nack_enabled, + void SetFeedbackParameters(bool lntf_enabled, + bool nack_enabled, bool remb_enabled, bool transport_cc_enabled, webrtc::RtcpMode rtcp_mode); diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc index dd22598ca7..39dc3b422c 100644 --- a/media/engine/webrtc_video_engine_unittest.cc +++ b/media/engine/webrtc_video_engine_unittest.cc @@ -91,7 +91,11 @@ cricket::VideoCodec RemoveFeedbackParams(cricket::VideoCodec&& codec) { return std::move(codec); } -void VerifyCodecHasDefaultFeedbackParams(const cricket::VideoCodec& codec) { +void VerifyCodecHasDefaultFeedbackParams(const cricket::VideoCodec& codec, + bool lntf_expected) { + EXPECT_EQ(lntf_expected, + codec.HasFeedbackParam(cricket::FeedbackParam( + cricket::kRtcpFbParamLntf, cricket::kParamValueEmpty))); EXPECT_TRUE(codec.HasFeedbackParam(cricket::FeedbackParam( cricket::kRtcpFbParamNack, cricket::kParamValueEmpty))); EXPECT_TRUE(codec.HasFeedbackParam(cricket::FeedbackParam( @@ -1104,7 +1108,8 @@ TEST(WebRtcVideoEngineNewVideoCodecFactoryTest, Vp8) { cricket::kCodecParamAssociatedPayloadType, &associated_payload_type)); EXPECT_EQ(engine_codecs.at(0).id, associated_payload_type); // Verify default parameters has been added to the VP8 codec. - VerifyCodecHasDefaultFeedbackParams(engine_codecs.at(0)); + VerifyCodecHasDefaultFeedbackParams(engine_codecs.at(0), + /*lntf_expected=*/false); // Mock encoder creation. |engine| take ownership of the encoder. webrtc::VideoEncoderFactory::CodecInfo codec_info; @@ -2262,6 +2267,26 @@ class WebRtcVideoChannelTest : public WebRtcVideoEngineTest { EXPECT_EQ(ext_uri, recv_stream->GetConfig().rtp.extensions[0].uri); } + void TestLossNotificationState(bool expect_lntf_enabled) { + AssignDefaultCodec(); + VerifyCodecHasDefaultFeedbackParams(default_codec_, expect_lntf_enabled); + + cricket::VideoSendParameters parameters; + parameters.codecs = engine_.codecs(); + EXPECT_TRUE(channel_->SetSendParameters(parameters)); + EXPECT_TRUE(channel_->SetSend(true)); + + // Send side. + FakeVideoSendStream* send_stream = + AddSendStream(cricket::StreamParams::CreateLegacy(1)); + EXPECT_EQ(send_stream->GetConfig().rtp.lntf.enabled, expect_lntf_enabled); + + // Receiver side. + FakeVideoReceiveStream* recv_stream = + AddRecvStream(cricket::StreamParams::CreateLegacy(1)); + EXPECT_EQ(recv_stream->GetConfig().rtp.lntf.enabled, expect_lntf_enabled); + } + void TestExtensionFilter(const std::vector& extensions, const std::string& expected_extension) { cricket::VideoSendParameters parameters = send_parameters_; @@ -2734,9 +2759,64 @@ TEST_F(WebRtcVideoChannelTest, TransportCcCanBeEnabledAndDisabled) { EXPECT_TRUE(stream->GetConfig().rtp.transport_cc); } +TEST_F(WebRtcVideoChannelTest, LossNotificationIsDisabledByDefault) { + TestLossNotificationState(false); +} + +TEST_F(WebRtcVideoChannelTest, LossNotificationIsEnabledByFieldTrial) { + RTC_DCHECK(!override_field_trials_); + override_field_trials_ = absl::make_unique( + "WebRTC-RtcpLossNotification/Enabled/"); + SetUp(); + TestLossNotificationState(true); +} + +TEST_F(WebRtcVideoChannelTest, LossNotificationCanBeEnabledAndDisabled) { + RTC_DCHECK(!override_field_trials_); + override_field_trials_ = absl::make_unique( + "WebRTC-RtcpLossNotification/Enabled/"); + SetUp(); + + AssignDefaultCodec(); + VerifyCodecHasDefaultFeedbackParams(default_codec_, true); + + { + cricket::VideoSendParameters parameters; + parameters.codecs = engine_.codecs(); + EXPECT_TRUE(channel_->SetSendParameters(parameters)); + EXPECT_TRUE(channel_->SetSend(true)); + } + + // Start with LNTF enabled. + FakeVideoSendStream* send_stream = + AddSendStream(cricket::StreamParams::CreateLegacy(1)); + ASSERT_TRUE(send_stream->GetConfig().rtp.lntf.enabled); + FakeVideoReceiveStream* recv_stream = + AddRecvStream(cricket::StreamParams::CreateLegacy(1)); + ASSERT_TRUE(recv_stream->GetConfig().rtp.lntf.enabled); + + // Verify that LNTF is turned off when send(!) codecs without LNTF are set. + cricket::VideoSendParameters parameters; + parameters.codecs.push_back(RemoveFeedbackParams(GetEngineCodec("VP8"))); + EXPECT_TRUE(parameters.codecs[0].feedback_params.params().empty()); + EXPECT_TRUE(channel_->SetSendParameters(parameters)); + recv_stream = fake_call_->GetVideoReceiveStreams()[0]; + EXPECT_FALSE(recv_stream->GetConfig().rtp.lntf.enabled); + send_stream = fake_call_->GetVideoSendStreams()[0]; + EXPECT_FALSE(send_stream->GetConfig().rtp.lntf.enabled); + + // Setting the default codecs again, including VP8, turns LNTF back on. + parameters.codecs = engine_.codecs(); + EXPECT_TRUE(channel_->SetSendParameters(parameters)); + recv_stream = fake_call_->GetVideoReceiveStreams()[0]; + EXPECT_TRUE(recv_stream->GetConfig().rtp.lntf.enabled); + send_stream = fake_call_->GetVideoSendStreams()[0]; + EXPECT_TRUE(send_stream->GetConfig().rtp.lntf.enabled); +} + TEST_F(WebRtcVideoChannelTest, NackIsEnabledByDefault) { AssignDefaultCodec(); - VerifyCodecHasDefaultFeedbackParams(default_codec_); + VerifyCodecHasDefaultFeedbackParams(default_codec_, false); cricket::VideoSendParameters parameters; parameters.codecs = engine_.codecs(); diff --git a/modules/rtp_rtcp/source/rtp_sender_video.cc b/modules/rtp_rtcp/source/rtp_sender_video.cc index 8d12ff4cd1..cd388b49c0 100644 --- a/modules/rtp_rtcp/source/rtp_sender_video.cc +++ b/modules/rtp_rtcp/source/rtp_sender_video.cc @@ -195,8 +195,9 @@ RTPSenderVideo::RTPSenderVideo(Clock* clock, last_rotation_(kVideoRotation_0), transmit_color_space_next_frame_(false), playout_delay_oracle_(playout_delay_oracle), - // TODO(eladalon): Choose whether to instantiate rtp_sequence_number_map_ - // according to the negotiation of the RTCP message. + // TODO(bugs.webrtc.org/10662): Choose whether to instantiate + // |rtp_sequence_number_map_| according to the negotiation of the + // LNTF (loss notification) rtcp-fb message. rtp_sequence_number_map_( field_trials.Lookup("WebRTC-RtcpLossNotification").find("Enabled") != std::string::npos diff --git a/pc/rtp_parameters_conversion.cc b/pc/rtp_parameters_conversion.cc index cf9e6e1252..b7fb69175c 100644 --- a/pc/rtp_parameters_conversion.cc +++ b/pc/rtp_parameters_conversion.cc @@ -39,6 +39,13 @@ RTCErrorOr ToCricketFeedbackParam( } return cricket::FeedbackParam(cricket::kRtcpFbParamCcm, cricket::kRtcpFbCcmParamFir); + case RtcpFeedbackType::LNTF: + if (feedback.message_type) { + LOG_AND_RETURN_ERROR( + RTCErrorType::INVALID_PARAMETER, + "Didn't expect message type in LNTF RtcpFeedback."); + } + return cricket::FeedbackParam(cricket::kRtcpFbParamLntf); case RtcpFeedbackType::NACK: if (!feedback.message_type) { LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, @@ -253,6 +260,14 @@ absl::optional ToRtcpFeedback( << cricket_feedback.param(); return absl::nullopt; } + } else if (cricket_feedback.id() == cricket::kRtcpFbParamLntf) { + if (cricket_feedback.param().empty()) { + return RtcpFeedback(RtcpFeedbackType::LNTF); + } else { + RTC_LOG(LS_WARNING) << "Unsupported parameter for LNTF RTCP feedback: " + << cricket_feedback.param(); + return absl::nullopt; + } } else if (cricket_feedback.id() == cricket::kRtcpFbParamNack) { if (cricket_feedback.param().empty()) { return RtcpFeedback(RtcpFeedbackType::NACK, diff --git a/pc/rtp_parameters_conversion_unittest.cc b/pc/rtp_parameters_conversion_unittest.cc index 9874ed328b..83a2893af3 100644 --- a/pc/rtp_parameters_conversion_unittest.cc +++ b/pc/rtp_parameters_conversion_unittest.cc @@ -22,14 +22,21 @@ TEST(RtpParametersConversionTest, ToCricketFeedbackParam) { auto result = ToCricketFeedbackParam( {RtcpFeedbackType::CCM, RtcpFeedbackMessageType::FIR}); EXPECT_EQ(cricket::FeedbackParam("ccm", "fir"), result.value()); + + result = ToCricketFeedbackParam(RtcpFeedback(RtcpFeedbackType::LNTF)); + EXPECT_EQ(cricket::FeedbackParam("goog-lntf"), result.value()); + result = ToCricketFeedbackParam( {RtcpFeedbackType::NACK, RtcpFeedbackMessageType::GENERIC_NACK}); EXPECT_EQ(cricket::FeedbackParam("nack"), result.value()); + result = ToCricketFeedbackParam( {RtcpFeedbackType::NACK, RtcpFeedbackMessageType::PLI}); EXPECT_EQ(cricket::FeedbackParam("nack", "pli"), result.value()); + result = ToCricketFeedbackParam(RtcpFeedback(RtcpFeedbackType::REMB)); EXPECT_EQ(cricket::FeedbackParam("goog-remb"), result.value()); + result = ToCricketFeedbackParam(RtcpFeedback(RtcpFeedbackType::TRANSPORT_CC)); EXPECT_EQ(cricket::FeedbackParam("transport-cc"), result.value()); } @@ -38,19 +45,29 @@ TEST(RtpParametersConversionTest, ToCricketFeedbackParamErrors) { // CCM with missing or invalid message type. auto result = ToCricketFeedbackParam(RtcpFeedback(RtcpFeedbackType::CCM)); EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + result = ToCricketFeedbackParam( {RtcpFeedbackType::CCM, RtcpFeedbackMessageType::PLI}); EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + + // LNTF with message type (should be left empty). + result = ToCricketFeedbackParam( + {RtcpFeedbackType::LNTF, RtcpFeedbackMessageType::GENERIC_NACK}); + EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + // NACK with missing or invalid message type. result = ToCricketFeedbackParam(RtcpFeedback(RtcpFeedbackType::NACK)); EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + result = ToCricketFeedbackParam( {RtcpFeedbackType::NACK, RtcpFeedbackMessageType::FIR}); EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + // REMB with message type (should be left empty). result = ToCricketFeedbackParam( {RtcpFeedbackType::REMB, RtcpFeedbackMessageType::GENERIC_NACK}); EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, result.error().type()); + // TRANSPORT_CC with message type (should be left empty). result = ToCricketFeedbackParam( {RtcpFeedbackType::TRANSPORT_CC, RtcpFeedbackMessageType::FIR}); @@ -88,6 +105,7 @@ TEST(RtpParametersConversionTest, ToVideoCodec) { codec.clock_rate.emplace(90000); codec.parameters["foo"] = "bar"; codec.parameters["PING"] = "PONG"; + codec.rtcp_feedback.emplace_back(RtcpFeedbackType::LNTF); codec.rtcp_feedback.emplace_back(RtcpFeedbackType::TRANSPORT_CC); codec.rtcp_feedback.emplace_back(RtcpFeedbackType::NACK, RtcpFeedbackMessageType::PLI); @@ -100,7 +118,9 @@ TEST(RtpParametersConversionTest, ToVideoCodec) { ASSERT_EQ(2u, result.value().params.size()); EXPECT_EQ("bar", result.value().params["foo"]); EXPECT_EQ("PONG", result.value().params["PING"]); - EXPECT_EQ(2u, result.value().feedback_params.params().size()); + EXPECT_EQ(3u, result.value().feedback_params.params().size()); + EXPECT_TRUE( + result.value().feedback_params.Has(cricket::FeedbackParam("goog-lntf"))); EXPECT_TRUE(result.value().feedback_params.Has( cricket::FeedbackParam("transport-cc"))); EXPECT_TRUE(result.value().feedback_params.Has( @@ -382,15 +402,22 @@ TEST(RtpParametersConversionTest, ToRtcpFeedback) { absl::optional result = ToRtcpFeedback({"ccm", "fir"}); EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::CCM, RtcpFeedbackMessageType::FIR), *result); + + result = ToRtcpFeedback(cricket::FeedbackParam("goog-lntf")); + EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::LNTF), *result); + result = ToRtcpFeedback(cricket::FeedbackParam("nack")); EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::NACK, RtcpFeedbackMessageType::GENERIC_NACK), *result); + result = ToRtcpFeedback({"nack", "pli"}); EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::NACK, RtcpFeedbackMessageType::PLI), *result); + result = ToRtcpFeedback(cricket::FeedbackParam("goog-remb")); EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::REMB), *result); + result = ToRtcpFeedback(cricket::FeedbackParam("transport-cc")); EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::TRANSPORT_CC), *result); } @@ -399,17 +426,26 @@ TEST(RtpParametersConversionTest, ToRtcpFeedbackErrors) { // CCM with missing or invalid message type. absl::optional result = ToRtcpFeedback({"ccm", "pli"}); EXPECT_FALSE(result); + result = ToRtcpFeedback(cricket::FeedbackParam("ccm")); EXPECT_FALSE(result); + + // LNTF with message type (should be left empty). + result = ToRtcpFeedback({"goog-lntf", "pli"}); + EXPECT_FALSE(result); + // NACK with missing or invalid message type. result = ToRtcpFeedback({"nack", "fir"}); EXPECT_FALSE(result); + // REMB with message type (should be left empty). result = ToRtcpFeedback({"goog-remb", "pli"}); EXPECT_FALSE(result); + // TRANSPORT_CC with message type (should be left empty). result = ToRtcpFeedback({"transport-cc", "fir"}); EXPECT_FALSE(result); + // Unknown message type. result = ToRtcpFeedback(cricket::FeedbackParam("foo")); EXPECT_FALSE(result); @@ -445,6 +481,7 @@ TEST(RtpParametersConversionTest, ToVideoRtpCodecCapability) { cricket_codec.params["foo"] = "bar"; cricket_codec.params["ANOTHER"] = "param"; cricket_codec.feedback_params.Add(cricket::FeedbackParam("transport-cc")); + cricket_codec.feedback_params.Add(cricket::FeedbackParam("goog-lntf")); cricket_codec.feedback_params.Add({"nack", "pli"}); RtpCodecCapability codec = ToRtpCodecCapability(cricket_codec); @@ -455,11 +492,12 @@ TEST(RtpParametersConversionTest, ToVideoRtpCodecCapability) { ASSERT_EQ(2u, codec.parameters.size()); EXPECT_EQ("bar", codec.parameters["foo"]); EXPECT_EQ("param", codec.parameters["ANOTHER"]); - EXPECT_EQ(2u, codec.rtcp_feedback.size()); + EXPECT_EQ(3u, codec.rtcp_feedback.size()); EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::TRANSPORT_CC), codec.rtcp_feedback[0]); + EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::LNTF), codec.rtcp_feedback[1]); EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::NACK, RtcpFeedbackMessageType::PLI), - codec.rtcp_feedback[1]); + codec.rtcp_feedback[2]); } TEST(RtpParametersConversionTest, ToRtpEncodingsWithEmptyStreamParamsVec) { @@ -519,6 +557,7 @@ TEST(RtpParametersConversionTest, ToVideoRtpCodecParameters) { cricket_codec.params["foo"] = "bar"; cricket_codec.params["ANOTHER"] = "param"; cricket_codec.feedback_params.Add(cricket::FeedbackParam("transport-cc")); + cricket_codec.feedback_params.Add(cricket::FeedbackParam("goog-lntf")); cricket_codec.feedback_params.Add({"nack", "pli"}); RtpCodecParameters codec = ToRtpCodecParameters(cricket_codec); @@ -529,11 +568,12 @@ TEST(RtpParametersConversionTest, ToVideoRtpCodecParameters) { ASSERT_EQ(2u, codec.parameters.size()); EXPECT_EQ("bar", codec.parameters["foo"]); EXPECT_EQ("param", codec.parameters["ANOTHER"]); - EXPECT_EQ(2u, codec.rtcp_feedback.size()); + EXPECT_EQ(3u, codec.rtcp_feedback.size()); EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::TRANSPORT_CC), codec.rtcp_feedback[0]); + EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::LNTF), codec.rtcp_feedback[1]); EXPECT_EQ(RtcpFeedback(RtcpFeedbackType::NACK, RtcpFeedbackMessageType::PLI), - codec.rtcp_feedback[1]); + codec.rtcp_feedback[2]); } // An unknown feedback param should just be ignored. diff --git a/pc/webrtc_sdp_unittest.cc b/pc/webrtc_sdp_unittest.cc index a3a87492a5..9a99b6dcc7 100644 --- a/pc/webrtc_sdp_unittest.cc +++ b/pc/webrtc_sdp_unittest.cc @@ -1993,6 +1993,7 @@ class WebRtcSdpTest : public ::testing::Test { std::string sdp_video = "m=video 3457 RTP/SAVPF 101\r\n" "a=rtpmap:101 VP8/90000\r\n" + "a=rtcp-fb:101 goog-lntf\r\n" "a=rtcp-fb:101 nack\r\n" "a=rtcp-fb:101 nack pli\r\n" "a=rtcp-fb:101 goog-remb\r\n"; @@ -2022,6 +2023,8 @@ class WebRtcSdpTest : public ::testing::Test { EXPECT_STREQ(webrtc::JsepSessionDescription::kDefaultVideoCodecName, vp8.name.c_str()); EXPECT_EQ(101, vp8.id); + EXPECT_TRUE(vp8.HasFeedbackParam(cricket::FeedbackParam( + cricket::kRtcpFbParamLntf, cricket::kParamValueEmpty))); EXPECT_TRUE(vp8.HasFeedbackParam(cricket::FeedbackParam( cricket::kRtcpFbParamNack, cricket::kParamValueEmpty))); EXPECT_TRUE(vp8.HasFeedbackParam(cricket::FeedbackParam( diff --git a/sdk/objc/unittests/RTCSessionDescriptionTest.mm b/sdk/objc/unittests/RTCSessionDescriptionTest.mm index 8a04a42589..0807eedf3a 100644 --- a/sdk/objc/unittests/RTCSessionDescriptionTest.mm +++ b/sdk/objc/unittests/RTCSessionDescriptionTest.mm @@ -60,71 +60,72 @@ } - (NSString *)sdp { - return @"v=0\r\n" - "o=- 5319989746393411314 2 IN IP4 127.0.0.1\r\n" - "s=-\r\n" - "t=0 0\r\n" - "a=group:BUNDLE audio video\r\n" - "a=msid-semantic: WMS ARDAMS\r\n" - "m=audio 9 UDP/TLS/RTP/SAVPF 111 103 9 0 8 126\r\n" - "c=IN IP4 0.0.0.0\r\n" - "a=rtcp:9 IN IP4 0.0.0.0\r\n" - "a=ice-ufrag:f3o+0HG7l9nwIWFY\r\n" - "a=ice-pwd:VDctmJNCptR2TB7+meDpw7w5\r\n" - "a=fingerprint:sha-256 A9:D5:8D:A8:69:22:39:60:92:AD:94:1A:22:2D:5E:" - "A5:4A:A9:18:C2:35:5D:46:5E:59:BD:1C:AF:38:9F:E6:E1\r\n" - "a=setup:active\r\n" - "a=mid:audio\r\n" - "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" - "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/" - "abs-send-time\r\n" - "a=sendrecv\r\n" - "a=rtcp-mux\r\n" - "a=rtpmap:111 opus/48000/2\r\n" - "a=fmtp:111 minptime=10;useinbandfec=1\r\n" - "a=rtpmap:103 ISAC/16000\r\n" - "a=rtpmap:9 G722/8000\r\n" - "a=rtpmap:0 PCMU/8000\r\n" - "a=rtpmap:8 PCMA/8000\r\n" - "a=rtpmap:126 telephone-event/8000\r\n" - "a=maxptime:60\r\n" - "a=ssrc:1504474588 cname:V+FdIC5AJpxLhdYQ\r\n" - "a=ssrc:1504474588 msid:ARDAMS ARDAMSa0\r\n" - "a=ssrc:1504474588 mslabel:ARDAMS\r\n" - "a=ssrc:1504474588 label:ARDAMSa0\r\n" - "m=video 9 UDP/TLS/RTP/SAVPF 100 116 117 96\r\n" - "c=IN IP4 0.0.0.0\r\n" - "a=rtcp:9 IN IP4 0.0.0.0\r\n" - "a=ice-ufrag:f3o+0HG7l9nwIWFY\r\n" - "a=ice-pwd:VDctmJNCptR2TB7+meDpw7w5\r\n" - "a=fingerprint:sha-256 A9:D5:8D:A8:69:22:39:60:92:AD:94:1A:22:2D:5E:" - "A5:4A:A9:18:C2:35:5D:46:5E:59:BD:1C:AF:38:9F:E6:E1\r\n" - "a=setup:active\r\n" - "a=mid:video\r\n" - "a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n" - "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/" - "abs-send-time\r\n" - "a=extmap:4 urn:3gpp:video-orientation\r\n" - "a=sendrecv\r\n" - "a=rtcp-mux\r\n" - "a=rtpmap:100 VP8/90000\r\n" - "a=rtcp-fb:100 ccm fir\r\n" - "a=rtcp-fb:100 nack\r\n" - "a=rtcp-fb:100 nack pli\r\n" - "a=rtcp-fb:100 goog-remb\r\n" - "a=rtpmap:116 red/90000\r\n" - "a=rtpmap:117 ulpfec/90000\r\n" - "a=rtpmap:96 rtx/90000\r\n" - "a=fmtp:96 apt=100\r\n" - "a=ssrc-group:FID 498297514 1644357692\r\n" - "a=ssrc:498297514 cname:V+FdIC5AJpxLhdYQ\r\n" - "a=ssrc:498297514 msid:ARDAMS ARDAMSv0\r\n" - "a=ssrc:498297514 mslabel:ARDAMS\r\n" - "a=ssrc:498297514 label:ARDAMSv0\r\n" - "a=ssrc:1644357692 cname:V+FdIC5AJpxLhdYQ\r\n" - "a=ssrc:1644357692 msid:ARDAMS ARDAMSv0\r\n" - "a=ssrc:1644357692 mslabel:ARDAMS\r\n" - "a=ssrc:1644357692 label:ARDAMSv0\r\n"; + return @"v=0\r\n" + "o=- 5319989746393411314 2 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "t=0 0\r\n" + "a=group:BUNDLE audio video\r\n" + "a=msid-semantic: WMS ARDAMS\r\n" + "m=audio 9 UDP/TLS/RTP/SAVPF 111 103 9 0 8 126\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=rtcp:9 IN IP4 0.0.0.0\r\n" + "a=ice-ufrag:f3o+0HG7l9nwIWFY\r\n" + "a=ice-pwd:VDctmJNCptR2TB7+meDpw7w5\r\n" + "a=fingerprint:sha-256 A9:D5:8D:A8:69:22:39:60:92:AD:94:1A:22:2D:5E:" + "A5:4A:A9:18:C2:35:5D:46:5E:59:BD:1C:AF:38:9F:E6:E1\r\n" + "a=setup:active\r\n" + "a=mid:audio\r\n" + "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" + "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/" + "abs-send-time\r\n" + "a=sendrecv\r\n" + "a=rtcp-mux\r\n" + "a=rtpmap:111 opus/48000/2\r\n" + "a=fmtp:111 minptime=10;useinbandfec=1\r\n" + "a=rtpmap:103 ISAC/16000\r\n" + "a=rtpmap:9 G722/8000\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:126 telephone-event/8000\r\n" + "a=maxptime:60\r\n" + "a=ssrc:1504474588 cname:V+FdIC5AJpxLhdYQ\r\n" + "a=ssrc:1504474588 msid:ARDAMS ARDAMSa0\r\n" + "a=ssrc:1504474588 mslabel:ARDAMS\r\n" + "a=ssrc:1504474588 label:ARDAMSa0\r\n" + "m=video 9 UDP/TLS/RTP/SAVPF 100 116 117 96\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=rtcp:9 IN IP4 0.0.0.0\r\n" + "a=ice-ufrag:f3o+0HG7l9nwIWFY\r\n" + "a=ice-pwd:VDctmJNCptR2TB7+meDpw7w5\r\n" + "a=fingerprint:sha-256 A9:D5:8D:A8:69:22:39:60:92:AD:94:1A:22:2D:5E:" + "A5:4A:A9:18:C2:35:5D:46:5E:59:BD:1C:AF:38:9F:E6:E1\r\n" + "a=setup:active\r\n" + "a=mid:video\r\n" + "a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n" + "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/" + "abs-send-time\r\n" + "a=extmap:4 urn:3gpp:video-orientation\r\n" + "a=sendrecv\r\n" + "a=rtcp-mux\r\n" + "a=rtpmap:100 VP8/90000\r\n" + "a=rtcp-fb:100 ccm fir\r\n" + "a=rtcp-fb:100 goog-lntf\r\n" + "a=rtcp-fb:100 nack\r\n" + "a=rtcp-fb:100 nack pli\r\n" + "a=rtcp-fb:100 goog-remb\r\n" + "a=rtpmap:116 red/90000\r\n" + "a=rtpmap:117 ulpfec/90000\r\n" + "a=rtpmap:96 rtx/90000\r\n" + "a=fmtp:96 apt=100\r\n" + "a=ssrc-group:FID 498297514 1644357692\r\n" + "a=ssrc:498297514 cname:V+FdIC5AJpxLhdYQ\r\n" + "a=ssrc:498297514 msid:ARDAMS ARDAMSv0\r\n" + "a=ssrc:498297514 mslabel:ARDAMS\r\n" + "a=ssrc:498297514 label:ARDAMSv0\r\n" + "a=ssrc:1644357692 cname:V+FdIC5AJpxLhdYQ\r\n" + "a=ssrc:1644357692 msid:ARDAMS ARDAMSv0\r\n" + "a=ssrc:1644357692 mslabel:ARDAMS\r\n" + "a=ssrc:1644357692 label:ARDAMSv0\r\n"; } @end diff --git a/test/call_config_utils.cc b/test/call_config_utils.cc index af9ca11b99..155fad1c24 100644 --- a/test/call_config_utils.cc +++ b/test/call_config_utils.cc @@ -45,6 +45,7 @@ VideoReceiveStream::Config ParseVideoReceiveStreamJsonConfig( : RtcpMode::kReducedSize; receive_config.rtp.remb = json["rtp"]["remb"].asBool(); receive_config.rtp.transport_cc = json["rtp"]["transport_cc"].asBool(); + receive_config.rtp.lntf.enabled = json["rtp"]["lntf"]["enabled"].asInt64(); receive_config.rtp.nack.rtp_history_ms = json["rtp"]["nack"]["rtp_history_ms"].asInt64(); receive_config.rtp.ulpfec_payload_type = @@ -94,6 +95,7 @@ Json::Value GenerateVideoReceiveStreamJsonConfig( : "RtcpMode::kReducedSize"; rtp_json["remb"] = config.rtp.remb; rtp_json["transport_cc"] = config.rtp.transport_cc; + rtp_json["lntf"]["enabled"] = config.rtp.lntf.enabled; rtp_json["nack"]["rtp_history_ms"] = config.rtp.nack.rtp_history_ms; rtp_json["ulpfec_payload_type"] = config.rtp.ulpfec_payload_type; rtp_json["red_payload_type"] = config.rtp.red_payload_type; diff --git a/test/call_config_utils_unittest.cc b/test/call_config_utils_unittest.cc index dbefc9b79d..bb834394f6 100644 --- a/test/call_config_utils_unittest.cc +++ b/test/call_config_utils_unittest.cc @@ -31,6 +31,7 @@ TEST(CallConfigUtils, MarshalUnmarshalProcessSameObject) { recv_config.rtp.rtcp_mode = RtcpMode::kCompound; recv_config.rtp.remb = false; recv_config.rtp.transport_cc = false; + recv_config.rtp.lntf.enabled = false; recv_config.rtp.nack.rtp_history_ms = 150; recv_config.rtp.red_payload_type = 50; recv_config.rtp.rtx_ssrc = 1000; @@ -54,6 +55,7 @@ TEST(CallConfigUtils, MarshalUnmarshalProcessSameObject) { EXPECT_EQ(recv_config.rtp.rtcp_mode, unmarshaled_config.rtp.rtcp_mode); EXPECT_EQ(recv_config.rtp.remb, unmarshaled_config.rtp.remb); EXPECT_EQ(recv_config.rtp.transport_cc, unmarshaled_config.rtp.transport_cc); + EXPECT_EQ(recv_config.rtp.lntf.enabled, unmarshaled_config.rtp.lntf.enabled); EXPECT_EQ(recv_config.rtp.nack.rtp_history_ms, unmarshaled_config.rtp.nack.rtp_history_ms); EXPECT_EQ(recv_config.rtp.red_payload_type, diff --git a/test/fuzzers/configs/replay_packet_fuzzer/h264_fec_config.json b/test/fuzzers/configs/replay_packet_fuzzer/h264_fec_config.json index d9b2576d1b..59b5db9446 100644 --- a/test/fuzzers/configs/replay_packet_fuzzer/h264_fec_config.json +++ b/test/fuzzers/configs/replay_packet_fuzzer/h264_fec_config.json @@ -112,6 +112,9 @@ } ], "local_ssrc" : 1, + "lntf" : { + "enabled": false, + }, "nack" : { "rtp_history_ms" : 1000 }, diff --git a/test/fuzzers/configs/replay_packet_fuzzer/h264_non_interleaved_config.json b/test/fuzzers/configs/replay_packet_fuzzer/h264_non_interleaved_config.json index 3dfa7a6b37..9cb5bd767b 100644 --- a/test/fuzzers/configs/replay_packet_fuzzer/h264_non_interleaved_config.json +++ b/test/fuzzers/configs/replay_packet_fuzzer/h264_non_interleaved_config.json @@ -36,6 +36,9 @@ "rtp" : { "extensions" : [], "local_ssrc" : 1, + "lntf" : { + "enabled": false, + }, "nack" : { "rtp_history_ms" : 1000 }, diff --git a/test/fuzzers/configs/replay_packet_fuzzer/h264_single_nal_config.json b/test/fuzzers/configs/replay_packet_fuzzer/h264_single_nal_config.json index f9fa4cf4eb..f3ae6bbafc 100644 --- a/test/fuzzers/configs/replay_packet_fuzzer/h264_single_nal_config.json +++ b/test/fuzzers/configs/replay_packet_fuzzer/h264_single_nal_config.json @@ -36,6 +36,9 @@ "rtp" : { "extensions" : [], "local_ssrc" : 1, + "lntf" : { + "enabled": false, + }, "nack" : { "rtp_history_ms" : 1000 }, diff --git a/test/fuzzers/configs/replay_packet_fuzzer/vp8_config.json b/test/fuzzers/configs/replay_packet_fuzzer/vp8_config.json index 2330659fae..0a5eef8b09 100644 --- a/test/fuzzers/configs/replay_packet_fuzzer/vp8_config.json +++ b/test/fuzzers/configs/replay_packet_fuzzer/vp8_config.json @@ -10,6 +10,9 @@ "rtp" : { "extensions" : [], "local_ssrc" : 7331, + "lntf" : { + "enabled": false, + }, "nack" : { "rtp_history_ms" : 1000 }, diff --git a/test/fuzzers/configs/replay_packet_fuzzer/vp8_fec_config.json b/test/fuzzers/configs/replay_packet_fuzzer/vp8_fec_config.json index 24e13b53d5..3d2b66301d 100644 --- a/test/fuzzers/configs/replay_packet_fuzzer/vp8_fec_config.json +++ b/test/fuzzers/configs/replay_packet_fuzzer/vp8_fec_config.json @@ -47,6 +47,9 @@ } ], "local_ssrc" : 1, + "lntf" : { + "enabled": false, + }, "nack" : { "rtp_history_ms" : 1000 }, diff --git a/test/fuzzers/configs/replay_packet_fuzzer/vp9_config.json b/test/fuzzers/configs/replay_packet_fuzzer/vp9_config.json index cda637f9af..a9c88a2402 100644 --- a/test/fuzzers/configs/replay_packet_fuzzer/vp9_config.json +++ b/test/fuzzers/configs/replay_packet_fuzzer/vp9_config.json @@ -10,6 +10,9 @@ "rtp" : { "extensions" : [], "local_ssrc" : 7331, + "lntf" : { + "enabled": false, + }, "nack" : { "rtp_history_ms" : 1000 }, diff --git a/test/fuzzers/configs/replay_packet_fuzzer/vp9_fec_config.json b/test/fuzzers/configs/replay_packet_fuzzer/vp9_fec_config.json index d82b026eb6..d089aa8fec 100644 --- a/test/fuzzers/configs/replay_packet_fuzzer/vp9_fec_config.json +++ b/test/fuzzers/configs/replay_packet_fuzzer/vp9_fec_config.json @@ -52,6 +52,9 @@ } ], "local_ssrc" : 1, + "lntf" : { + "enabled": false, + }, "nack" : { "rtp_history_ms" : 1000 }, diff --git a/test/fuzzers/vp8_replay_fuzzer.cc b/test/fuzzers/vp8_replay_fuzzer.cc index ff073b1eca..7f8299b5f7 100644 --- a/test/fuzzers/vp8_replay_fuzzer.cc +++ b/test/fuzzers/vp8_replay_fuzzer.cc @@ -31,6 +31,7 @@ void FuzzOneInput(const uint8_t* data, size_t size) { vp8_config.rtp.transport_cc = true; vp8_config.rtp.remb = true; vp8_config.rtp.nack.rtp_history_ms = 1000; + vp8_config.rtp.lntf.enabled = true; std::vector replay_configs; replay_configs.push_back(std::move(vp8_config)); diff --git a/video/end_to_end_tests/config_tests.cc b/video/end_to_end_tests/config_tests.cc index c5f6e3fd65..7d9221a5f2 100644 --- a/video/end_to_end_tests/config_tests.cc +++ b/video/end_to_end_tests/config_tests.cc @@ -52,6 +52,8 @@ void VerifyEmptyFlexfecConfig(const RtpConfig::Flexfec& config) { TEST_F(ConfigEndToEndTest, VerifyDefaultSendConfigParameters) { VideoSendStream::Config default_send_config(nullptr); + EXPECT_FALSE(default_send_config.rtp.lntf.enabled) + << "Enabling LNTF require rtcp-fb: goog-lntf negotiation."; EXPECT_EQ(0, default_send_config.rtp.nack.rtp_history_ms) << "Enabling NACK require rtcp-fb: nack negotiation."; EXPECT_TRUE(default_send_config.rtp.rtx.ssrcs.empty()) @@ -74,6 +76,8 @@ TEST_F(ConfigEndToEndTest, VerifyDefaultVideoReceiveConfigParameters) { VideoReceiveStream::Config default_receive_config(nullptr); EXPECT_EQ(RtcpMode::kCompound, default_receive_config.rtp.rtcp_mode) << "Reduced-size RTCP require rtcp-rsize to be negotiated."; + EXPECT_FALSE(default_receive_config.rtp.lntf.enabled) + << "Enabling LNTF require rtcp-fb: goog-lntf negotiation."; EXPECT_FALSE(default_receive_config.rtp.remb) << "REMB require rtcp-fb: goog-remb to be negotiated."; EXPECT_FALSE( diff --git a/video/rtp_video_stream_receiver.cc b/video/rtp_video_stream_receiver.cc index 0f38f19424..e5e15c3dd3 100644 --- a/video/rtp_video_stream_receiver.cc +++ b/video/rtp_video_stream_receiver.cc @@ -158,7 +158,8 @@ RtpVideoStreamReceiver::RtpVideoStreamReceiver( process_thread_->RegisterModule(rtp_rtcp_.get(), RTC_FROM_HERE); - if (webrtc::field_trial::IsEnabled("WebRTC-RtcpLossNotification")) { + // TODO(bugs.webrtc.org/10662): NACK and LNTF shouldn't be mutually exclusive. + if (config_.rtp.lntf.enabled) { loss_notification_controller_ = absl::make_unique(this, this); } else if (config_.rtp.nack.rtp_history_ms != 0) { @@ -395,6 +396,7 @@ void RtpVideoStreamReceiver::SendLossNotification( uint16_t last_decoded_seq_num, uint16_t last_received_seq_num, bool decodability_flag) { + RTC_DCHECK(config_.rtp.lntf.enabled); rtp_rtcp_->SendLossNotification(last_decoded_seq_num, last_received_seq_num, decodability_flag); }