red: handle opus dtx 400ms timestamp gap
by not encoding redundancy. The timestamp gap of 400ms means a
rtp timestamp difference of 19200 which would overflow the 14 bit
RED timestamp difference field.
To test in Chrome, go to
https://webrtc.github.io/samples/src/content/peerconnection/audio/
set `useDtx = true` in the console and be very quiet.
BUG=webrtc:13182,webrtc:11640
Change-Id: I1cedc7d846ac7ae821bb7cb5c0f37a17511ac727
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/231940
Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org>
Commit-Queue: Henrik Lundin <henrik.lundin@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35005}
diff --git a/modules/audio_coding/codecs/red/audio_encoder_copy_red.cc b/modules/audio_coding/codecs/red/audio_encoder_copy_red.cc
index 0e9e100..9643c7b 100644
--- a/modules/audio_coding/codecs/red/audio_encoder_copy_red.cc
+++ b/modules/audio_coding/codecs/red/audio_encoder_copy_red.cc
@@ -24,6 +24,8 @@
static constexpr const int kRedMaxPacketSize =
1 << 10; // RED packets must be less than 1024 bytes to fit the 10 bit
// block length.
+static constexpr const size_t kRedMaxTimestampDelta =
+ 1 << 14; // RED packets can encode a timestamp delta of 14 bits.
static constexpr const size_t kAudioMaxRtpPacketLen =
1200; // The typical MTU is 1200 bytes.
@@ -100,7 +102,7 @@
RTC_CHECK(info.redundant.empty()) << "Cannot use nested redundant encoders.";
RTC_DCHECK_EQ(primary_encoded_.size(), info.encoded_bytes);
- if (info.encoded_bytes == 0 || info.encoded_bytes > kRedMaxPacketSize) {
+ if (info.encoded_bytes == 0 || info.encoded_bytes >= kRedMaxPacketSize) {
return info;
}
RTC_DCHECK_GT(max_packet_length_, info.encoded_bytes);
@@ -110,7 +112,9 @@
auto it = redundant_encodings_.begin();
// Determine how much redundancy we can fit into our packet by
- // iterating forward.
+ // iterating forward. This is determined both by the length as well
+ // as the timestamp difference. The latter can occur with opus DTX which
+ // has timestamp gaps of 400ms which exceeds REDs timestamp delta field size.
for (; it != redundant_encodings_.end(); it++) {
if (bytes_available < kRedHeaderLength + it->first.encoded_bytes) {
break;
@@ -118,6 +122,9 @@
if (it->first.encoded_bytes == 0) {
break;
}
+ if (rtp_timestamp - it->first.encoded_timestamp >= kRedMaxTimestampDelta) {
+ break;
+ }
bytes_available -= kRedHeaderLength + it->first.encoded_bytes;
header_length_bytes += kRedHeaderLength;
}
diff --git a/modules/audio_coding/codecs/red/audio_encoder_copy_red_unittest.cc b/modules/audio_coding/codecs/red/audio_encoder_copy_red_unittest.cc
index 168ac3a..0eeac01 100644
--- a/modules/audio_coding/codecs/red/audio_encoder_copy_red_unittest.cc
+++ b/modules/audio_coding/codecs/red/audio_encoder_copy_red_unittest.cc
@@ -583,6 +583,29 @@
EXPECT_EQ(encoded_.size(), 5u + 500u + 400u);
}
+TEST_F(AudioEncoderCopyRedTest, LargeTimestampGap) {
+ const int primary_payload_type = red_payload_type_ + 1;
+ AudioEncoder::EncodedInfo info;
+ info.encoded_bytes = 100;
+ info.encoded_timestamp = timestamp_;
+ info.payload_type = primary_payload_type;
+
+ EXPECT_CALL(*mock_encoder_, EncodeImpl(_, _, _))
+ .WillOnce(Invoke(MockAudioEncoder::FakeEncoding(info)));
+ Encode();
+ // Update timestamp to simulate a 400ms gap like the one
+ // opus DTX causes.
+ timestamp_ += 19200;
+ info.encoded_timestamp = timestamp_; // update timestamp.
+ info.encoded_bytes = 200;
+ EXPECT_CALL(*mock_encoder_, EncodeImpl(_, _, _))
+ .WillOnce(Invoke(MockAudioEncoder::FakeEncoding(info)));
+ Encode();
+
+ // The old packet will be dropped.
+ EXPECT_EQ(encoded_.size(), 1u + 200u);
+}
+
#if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
// This test fixture tests various error conditions that makes the