rtx-time implementation
provides an implementation of the rtx-time parameter from
https://tools.ietf.org/html/rfc4588#section-8
that determines the maximum time a receiver waits for a frame
before sending a PLI.
BUG=webrtc:12420
Change-Id: Iff20d92c806989cd4d56fe330d105b3dd127ed24
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/201400
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33627}
diff --git a/media/base/media_constants.cc b/media/base/media_constants.cc
index d16196a..2ac3825 100644
--- a/media/base/media_constants.cc
+++ b/media/base/media_constants.cc
@@ -24,7 +24,6 @@
const float kLowSystemCpuThreshold = 0.65f;
const float kProcessCpuThreshold = 0.10f;
-const char kRtxCodecName[] = "rtx";
const char kRedCodecName[] = "red";
const char kUlpfecCodecName[] = "ulpfec";
const char kMultiplexCodecName[] = "multiplex";
@@ -36,7 +35,11 @@
// draft-ietf-payload-flexible-fec-scheme-02.txt
const char kFlexfecFmtpRepairWindow[] = "repair-window";
+// RFC 4588 RTP Retransmission Payload Format
+const char kRtxCodecName[] = "rtx";
+const char kCodecParamRtxTime[] = "rtx-time";
const char kCodecParamAssociatedPayloadType[] = "apt";
+
const char kCodecParamAssociatedCodecName[] = "acn";
const char kOpusCodecName[] = "opus";
diff --git a/media/base/media_constants.h b/media/base/media_constants.h
index 90fb424..16b97ca 100644
--- a/media/base/media_constants.h
+++ b/media/base/media_constants.h
@@ -32,7 +32,6 @@
extern const float kLowSystemCpuThreshold;
extern const float kProcessCpuThreshold;
-extern const char kRtxCodecName[];
extern const char kRedCodecName[];
extern const char kUlpfecCodecName[];
extern const char kFlexfecCodecName[];
@@ -40,8 +39,10 @@
extern const char kFlexfecFmtpRepairWindow[];
-// Codec parameters
+extern const char kRtxCodecName[];
+extern const char kCodecParamRtxTime[];
extern const char kCodecParamAssociatedPayloadType[];
+
extern const char kCodecParamAssociatedCodecName[];
extern const char kOpusCodecName[];
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index c260de4..057fdf6 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -1038,7 +1038,7 @@
if (changed_params.send_codec || changed_params.rtcp_mode) {
// Update receive feedback parameters from new codec or RTCP mode.
RTC_LOG(LS_INFO)
- << "SetFeedbackOptions on all the receive streams because the send "
+ << "SetFeedbackParameters on all the receive streams because the send "
"codec or RTCP mode has changed.";
for (auto& kv : receive_streams_) {
RTC_DCHECK(kv.second != nullptr);
@@ -1046,7 +1046,8 @@
HasLntf(send_codec_->codec), HasNack(send_codec_->codec),
HasTransportCc(send_codec_->codec),
send_params_.rtcp.reduced_size ? webrtc::RtcpMode::kReducedSize
- : webrtc::RtcpMode::kCompound);
+ : webrtc::RtcpMode::kCompound,
+ send_codec_->rtx_time);
}
}
return true;
@@ -1523,6 +1524,12 @@
? webrtc::RtcpMode::kReducedSize
: webrtc::RtcpMode::kCompound;
+ // rtx-time (RFC 4588) is a declarative attribute similar to rtcp-rsize and
+ // determined by the sender / send codec.
+ if (send_codec_ && send_codec_->rtx_time != -1) {
+ config->rtp.nack.rtp_history_ms = send_codec_->rtx_time;
+ }
+
config->rtp.transport_cc =
send_codec_ ? HasTransportCc(send_codec_->codec) : false;
@@ -2864,6 +2871,11 @@
config_.rtp.lntf.enabled = HasLntf(codec.codec);
config_.rtp.nack.rtp_history_ms = HasNack(codec.codec) ? kNackHistoryMs : 0;
+ // The rtx-time parameter can be used to override the hardcoded default for
+ // the NACK buffer length.
+ if (codec.rtx_time != -1 && config_.rtp.nack.rtp_history_ms != 0) {
+ config_.rtp.nack.rtp_history_ms = codec.rtx_time;
+ }
config_.rtp.rtcp_xr.receiver_reference_time_report = HasRrtr(codec.codec);
if (codec.ulpfec.red_rtx_payload_type != -1) {
config_.rtp
@@ -2897,8 +2909,10 @@
bool lntf_enabled,
bool nack_enabled,
bool transport_cc_enabled,
- webrtc::RtcpMode rtcp_mode) {
- int nack_history_ms = nack_enabled ? kNackHistoryMs : 0;
+ webrtc::RtcpMode rtcp_mode,
+ int rtx_time) {
+ int nack_history_ms =
+ nack_enabled ? rtx_time != -1 ? rtx_time : kNackHistoryMs : 0;
if (config_.rtp.lntf.enabled == lntf_enabled &&
config_.rtp.nack.rtp_history_ms == nack_history_ms &&
config_.rtp.transport_cc == transport_cc_enabled &&
@@ -2907,7 +2921,8 @@
<< "Ignoring call to SetFeedbackParameters because parameters are "
"unchanged; lntf="
<< lntf_enabled << ", nack=" << nack_enabled
- << ", transport_cc=" << transport_cc_enabled;
+ << ", transport_cc=" << transport_cc_enabled
+ << ", rtx_time=" << rtx_time;
return;
}
config_.rtp.lntf.enabled = lntf_enabled;
@@ -3177,20 +3192,21 @@
}
WebRtcVideoChannel::VideoCodecSettings::VideoCodecSettings()
- : flexfec_payload_type(-1), rtx_payload_type(-1) {}
+ : flexfec_payload_type(-1), rtx_payload_type(-1), rtx_time(-1) {}
bool WebRtcVideoChannel::VideoCodecSettings::operator==(
const WebRtcVideoChannel::VideoCodecSettings& other) const {
return codec == other.codec && ulpfec == other.ulpfec &&
flexfec_payload_type == other.flexfec_payload_type &&
- rtx_payload_type == other.rtx_payload_type;
+ rtx_payload_type == other.rtx_payload_type &&
+ rtx_time == other.rtx_time;
}
bool WebRtcVideoChannel::VideoCodecSettings::EqualsDisregardingFlexfec(
const WebRtcVideoChannel::VideoCodecSettings& a,
const WebRtcVideoChannel::VideoCodecSettings& b) {
return a.codec == b.codec && a.ulpfec == b.ulpfec &&
- a.rtx_payload_type == b.rtx_payload_type;
+ a.rtx_payload_type == b.rtx_payload_type && a.rtx_time == b.rtx_time;
}
bool WebRtcVideoChannel::VideoCodecSettings::operator!=(
@@ -3208,6 +3224,7 @@
std::map<int, VideoCodec::CodecType> payload_codec_type;
// |rtx_mapping| maps video payload type to rtx payload type.
std::map<int, int> rtx_mapping;
+ std::map<int, int> rtx_time_mapping;
webrtc::UlpfecConfig ulpfec_config;
absl::optional<int> flexfec_payload_type;
@@ -3269,6 +3286,10 @@
<< in_codec.ToString();
return {};
}
+ int rtx_time;
+ if (in_codec.GetParam(kCodecParamRtxTime, &rtx_time) && rtx_time > 0) {
+ rtx_time_mapping[associated_payload_type] = rtx_time;
+ }
rtx_mapping[associated_payload_type] = payload_type;
break;
}
@@ -3318,6 +3339,16 @@
if (it != rtx_mapping.end()) {
const int rtx_payload_type = it->second;
codec_settings.rtx_payload_type = rtx_payload_type;
+
+ auto rtx_time_it = rtx_time_mapping.find(payload_type);
+ if (rtx_time_it != rtx_time_mapping.end()) {
+ const int rtx_time = rtx_time_it->second;
+ if (rtx_time < kNackHistoryMs) {
+ codec_settings.rtx_time = rtx_time;
+ } else {
+ codec_settings.rtx_time = kNackHistoryMs;
+ }
+ }
}
}
diff --git a/media/engine/webrtc_video_engine.h b/media/engine/webrtc_video_engine.h
index f278897..fe3ad6d 100644
--- a/media/engine/webrtc_video_engine.h
+++ b/media/engine/webrtc_video_engine.h
@@ -273,6 +273,7 @@
webrtc::UlpfecConfig ulpfec;
int flexfec_payload_type; // -1 if absent.
int rtx_payload_type; // -1 if absent.
+ int rtx_time; // -1 if absent.
};
struct ChangedSendParameters {
@@ -455,7 +456,8 @@
void SetFeedbackParameters(bool lntf_enabled,
bool nack_enabled,
bool transport_cc_enabled,
- webrtc::RtcpMode rtcp_mode);
+ webrtc::RtcpMode rtcp_mode,
+ int rtx_time);
void SetRecvParameters(const ChangedRecvParameters& recv_params);
void OnFrame(const webrtc::VideoFrame& frame) override;
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index a4176ba..fc945dd 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -4934,6 +4934,76 @@
EXPECT_EQ(kRtxSsrcs1[0], config_after.rtp.rtx_ssrc);
}
+TEST_F(WebRtcVideoChannelTest, SetRecvCodecsRtxWithRtxTime) {
+ const int kUnusedPayloadType1 = 126;
+ const int kUnusedPayloadType2 = 127;
+ EXPECT_FALSE(FindCodecById(engine_.recv_codecs(), kUnusedPayloadType1));
+ EXPECT_FALSE(FindCodecById(engine_.recv_codecs(), kUnusedPayloadType2));
+
+ // SSRCs for RTX.
+ cricket::StreamParams params =
+ cricket::StreamParams::CreateLegacy(kSsrcs1[0]);
+ params.AddFidSsrc(kSsrcs1[0], kRtxSsrcs1[0]);
+ AddRecvStream(params);
+
+ // Payload type for RTX.
+ cricket::VideoRecvParameters parameters;
+ parameters.codecs.push_back(GetEngineCodec("VP8"));
+ cricket::VideoCodec rtx_codec(kUnusedPayloadType1, "rtx");
+ rtx_codec.SetParam("apt", GetEngineCodec("VP8").id);
+ parameters.codecs.push_back(rtx_codec);
+ EXPECT_TRUE(channel_->SetRecvParameters(parameters));
+ ASSERT_EQ(1U, fake_call_->GetVideoReceiveStreams().size());
+ const webrtc::VideoReceiveStream::Config& config =
+ fake_call_->GetVideoReceiveStreams()[0]->GetConfig();
+
+ const int kRtxTime = 343;
+ // Assert that the default value is different from the ones we test
+ // and store the default value.
+ EXPECT_NE(config.rtp.nack.rtp_history_ms, kRtxTime);
+ int default_history_ms = config.rtp.nack.rtp_history_ms;
+
+ // Set rtx-time.
+ parameters.codecs[1].SetParam(kCodecParamRtxTime, kRtxTime);
+ EXPECT_TRUE(channel_->SetRecvParameters(parameters));
+ EXPECT_EQ(fake_call_->GetVideoReceiveStreams()[0]
+ ->GetConfig()
+ .rtp.nack.rtp_history_ms,
+ kRtxTime);
+
+ // Negative values are ignored so the default value applies.
+ parameters.codecs[1].SetParam(kCodecParamRtxTime, -1);
+ EXPECT_TRUE(channel_->SetRecvParameters(parameters));
+ EXPECT_NE(fake_call_->GetVideoReceiveStreams()[0]
+ ->GetConfig()
+ .rtp.nack.rtp_history_ms,
+ -1);
+ EXPECT_EQ(fake_call_->GetVideoReceiveStreams()[0]
+ ->GetConfig()
+ .rtp.nack.rtp_history_ms,
+ default_history_ms);
+
+ // 0 is ignored so the default applies.
+ parameters.codecs[1].SetParam(kCodecParamRtxTime, 0);
+ EXPECT_TRUE(channel_->SetRecvParameters(parameters));
+ EXPECT_NE(fake_call_->GetVideoReceiveStreams()[0]
+ ->GetConfig()
+ .rtp.nack.rtp_history_ms,
+ 0);
+ EXPECT_EQ(fake_call_->GetVideoReceiveStreams()[0]
+ ->GetConfig()
+ .rtp.nack.rtp_history_ms,
+ default_history_ms);
+
+ // Values larger than the default are clamped to the default.
+ parameters.codecs[1].SetParam(kCodecParamRtxTime, default_history_ms + 100);
+ EXPECT_TRUE(channel_->SetRecvParameters(parameters));
+ EXPECT_EQ(fake_call_->GetVideoReceiveStreams()[0]
+ ->GetConfig()
+ .rtp.nack.rtp_history_ms,
+ default_history_ms);
+}
+
TEST_F(WebRtcVideoChannelTest, SetRecvCodecsDifferentPayloadType) {
cricket::VideoRecvParameters parameters;
parameters.codecs.push_back(GetEngineCodec("VP8"));
diff --git a/pc/media_session.cc b/pc/media_session.cc
index caf2458..6374f17 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -793,6 +793,12 @@
// FindMatchingCodec shouldn't return something with no apt value.
RTC_DCHECK(apt_it != theirs.params.end());
negotiated.SetParam(kCodecParamAssociatedPayloadType, apt_it->second);
+
+ // We support parsing the declarative rtx-time parameter.
+ const auto rtx_time_it = theirs.params.find(kCodecParamRtxTime);
+ if (rtx_time_it != theirs.params.end()) {
+ negotiated.SetParam(kCodecParamRtxTime, rtx_time_it->second);
+ }
}
if (absl::EqualsIgnoreCase(ours.name, kH264CodecName)) {
webrtc::H264::GenerateProfileLevelIdForAnswer(
diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc
index 8df76d5..6664820 100644
--- a/video/rtp_video_stream_receiver2.cc
+++ b/video/rtp_video_stream_receiver2.cc
@@ -114,6 +114,7 @@
if (config.rtp.nack.rtp_history_ms == 0)
return nullptr;
+ // TODO(bugs.webrtc.org/12420): pass rtp_history_ms to the nack module.
return std::make_unique<NackModule2>(current_queue, clock, nack_sender,
keyframe_request_sender);
}
diff --git a/video/video_receive_stream2.cc b/video/video_receive_stream2.cc
index 073fd93..2e35302 100644
--- a/video/video_receive_stream2.cc
+++ b/video/video_receive_stream2.cc
@@ -186,6 +186,23 @@
} // namespace
+int DetermineMaxWaitForFrame(const VideoReceiveStream::Config& config,
+ bool is_keyframe) {
+ // A (arbitrary) conversion factor between the remotely signalled NACK buffer
+ // time (if not present defaults to 1000ms) and the maximum time we wait for a
+ // remote frame. Chosen to not change existing defaults when using not
+ // rtx-time.
+ const int conversion_factor = 3;
+
+ if (config.rtp.nack.rtp_history_ms > 0 &&
+ conversion_factor * config.rtp.nack.rtp_history_ms < kMaxWaitForFrameMs) {
+ return is_keyframe ? config.rtp.nack.rtp_history_ms
+ : conversion_factor * config.rtp.nack.rtp_history_ms;
+ }
+ return is_keyframe ? VideoReceiveStream2::kMaxWaitForKeyFrameMs
+ : kMaxWaitForFrameMs;
+}
+
VideoReceiveStream2::VideoReceiveStream2(
TaskQueueFactory* task_queue_factory,
TaskQueueBase* current_queue,
@@ -225,8 +242,8 @@
config_.frame_decryptor,
config_.frame_transformer),
rtp_stream_sync_(current_queue, this),
- max_wait_for_keyframe_ms_(kMaxWaitForKeyFrameMs),
- max_wait_for_frame_ms_(kMaxWaitForFrameMs),
+ max_wait_for_keyframe_ms_(DetermineMaxWaitForFrame(config, true)),
+ max_wait_for_frame_ms_(DetermineMaxWaitForFrame(config, false)),
low_latency_renderer_enabled_("enabled", true),
low_latency_renderer_include_predecode_buffer_("include_predecode_buffer",
true),
diff --git a/video/video_receive_stream2_unittest.cc b/video/video_receive_stream2_unittest.cc
index aa9cee3..f0916c1 100644
--- a/video/video_receive_stream2_unittest.cc
+++ b/video/video_receive_stream2_unittest.cc
@@ -482,7 +482,8 @@
video_receive_stream_->Stop();
}
-class VideoReceiveStream2TestWithSimulatedClock : public ::testing::Test {
+class VideoReceiveStream2TestWithSimulatedClock
+ : public ::testing::TestWithParam<int> {
public:
class FakeDecoder2 : public test::FakeDecoder {
public:
@@ -509,6 +510,7 @@
VideoReceiveStream::Config config(transport);
config.rtp.remote_ssrc = 1111;
config.rtp.local_ssrc = 2222;
+ config.rtp.nack.rtp_history_ms = GetParam(); // rtx-time.
config.renderer = renderer;
config.decoder_factory = decoder_factory;
VideoReceiveStream::Decoder fake_decoder;
@@ -566,10 +568,9 @@
std::unique_ptr<rtc::Event> event_;
};
-TEST_F(VideoReceiveStream2TestWithSimulatedClock,
+TEST_P(VideoReceiveStream2TestWithSimulatedClock,
RequestsKeyFramesUntilKeyFrameReceived) {
- auto tick = TimeDelta::Millis(
- internal::VideoReceiveStream2::kMaxWaitForKeyFrameMs / 2);
+ auto tick = TimeDelta::Millis(GetParam() / 2);
EXPECT_CALL(mock_transport_, SendRtcp).Times(1).WillOnce(Invoke([this]() {
loop_.Quit();
return 0;
@@ -581,7 +582,8 @@
loop_.Run();
testing::Mock::VerifyAndClearExpectations(&mock_transport_);
- // T+200ms: still no key frame received, expect key frame request sent again.
+ // T+keyframetimeout: still no key frame received, expect key frame request
+ // sent again.
EXPECT_CALL(mock_transport_, SendRtcp).Times(1).WillOnce(Invoke([this]() {
loop_.Quit();
return 0;
@@ -591,8 +593,8 @@
loop_.Run();
testing::Mock::VerifyAndClearExpectations(&mock_transport_);
- // T+200ms: now send a key frame - we should not observe new key frame
- // requests after this.
+ // T+keyframetimeout: now send a key frame - we should not observe new key
+ // frame requests after this.
EXPECT_CALL(mock_transport_, SendRtcp).Times(0);
PassEncodedFrameAndWait(MakeFrame(VideoFrameType::kVideoFrameKey, 3));
time_controller_.AdvanceTime(2 * tick);
@@ -601,6 +603,12 @@
loop_.Run();
}
+INSTANTIATE_TEST_SUITE_P(
+ RtxTime,
+ VideoReceiveStream2TestWithSimulatedClock,
+ ::testing::Values(internal::VideoReceiveStream2::kMaxWaitForKeyFrameMs,
+ 50 /*ms*/));
+
class VideoReceiveStream2TestWithLazyDecoderCreation : public ::testing::Test {
public:
VideoReceiveStream2TestWithLazyDecoderCreation()