From 603cc3a31e832565829a578b05036bd9def54796 Mon Sep 17 00:00:00 2001 From: Philipp Hancke Date: Thu, 11 Jun 2020 10:57:58 +0200 Subject: [PATCH] red: modify the encoder to send RFC 2198 modifies the RED encoder to send the actual RFC 2198 format described in https://tools.ietf.org/html/rfc2198 Decoding is handled in neteq, see red_payload_splitter.h BUG=webrtc:11640 Change-Id: Ib3005882a3ceee49d2b05c43357f552432a984ac Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/176371 Commit-Queue: Harald Alvestrand Reviewed-by: Henrik Lundin Reviewed-by: Harald Alvestrand Cr-Commit-Position: refs/heads/master@{#31560} --- AUTHORS | 2 + .../codecs/red/audio_encoder_copy_red.cc | 75 +++++++++++++------ .../red/audio_encoder_copy_red_unittest.cc | 49 ++++++++++-- 3 files changed, 96 insertions(+), 30 deletions(-) diff --git a/AUTHORS b/AUTHORS index 90b6cb7d4d..188503e7f4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -92,6 +92,8 @@ Raman Budny Stephan Hartmann &yet LLC <*@andyet.com> +8x8 Inc. <*@sip-communicator.org> +8x8 Inc. <*@8x8.com> Agora IO <*@agora.io> ARM Holdings <*@arm.com> BroadSoft Inc. <*@broadsoft.com> 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 e75806af10..8d028c9b9a 100644 --- a/modules/audio_coding/codecs/red/audio_encoder_copy_red.cc +++ b/modules/audio_coding/codecs/red/audio_encoder_copy_red.cc @@ -15,6 +15,7 @@ #include #include +#include "rtc_base/byte_order.h" #include "rtc_base/checks.h" namespace webrtc { @@ -59,32 +60,62 @@ AudioEncoder::EncodedInfo AudioEncoderCopyRed::EncodeImpl( uint32_t rtp_timestamp, rtc::ArrayView audio, rtc::Buffer* encoded) { - const size_t primary_offset = encoded->size(); + // Allocate room for RFC 2198 header if there is redundant data. + // Otherwise this will send the primary payload type without + // wrapping in RED. + const size_t header_length_bytes = secondary_info_.encoded_bytes > 0 ? 5 : 0; + size_t secondary_length_bytes = 0; + + if (secondary_info_.encoded_bytes > 0) { + encoded->SetSize(header_length_bytes); + encoded->AppendData(secondary_encoded_); + secondary_length_bytes = secondary_info_.encoded_bytes; + } EncodedInfo info = speech_encoder_->Encode(rtp_timestamp, audio, encoded); - RTC_CHECK(info.redundant.empty()) << "Cannot use nested redundant encoders."; - RTC_DCHECK_EQ(encoded->size() - primary_offset, info.encoded_bytes); - - if (info.encoded_bytes > 0) { - // |info| will be implicitly cast to an EncodedInfoLeaf struct, effectively - // discarding the (empty) vector of redundant information. This is - // intentional. - info.redundant.push_back(info); - RTC_DCHECK_EQ(info.redundant.size(), 1); - if (secondary_info_.encoded_bytes > 0) { - encoded->AppendData(secondary_encoded_); - info.redundant.push_back(secondary_info_); - RTC_DCHECK_EQ(info.redundant.size(), 2); - } - // Save primary to secondary. - secondary_encoded_.SetData(encoded->data() + primary_offset, - info.encoded_bytes); - secondary_info_ = info; - RTC_DCHECK_EQ(info.speech, info.redundant[0].speech); + if (info.encoded_bytes == 0) { + encoded->Clear(); + return info; } + + // Actually construct the RFC 2198 header. + if (secondary_info_.encoded_bytes > 0) { + const uint32_t timestamp_delta = + info.encoded_timestamp - secondary_info_.encoded_timestamp; + + encoded->data()[0] = secondary_info_.payload_type | 0x80; + RTC_DCHECK_LT(secondary_info_.encoded_bytes, 1 << 10); + rtc::SetBE16(static_cast(encoded->data()) + 1, + (timestamp_delta << 2) | (secondary_info_.encoded_bytes >> 8)); + encoded->data()[3] = secondary_info_.encoded_bytes & 0xff; + encoded->data()[4] = info.payload_type; + } + + RTC_CHECK(info.redundant.empty()) << "Cannot use nested redundant encoders."; + RTC_DCHECK_EQ(encoded->size() - header_length_bytes - secondary_length_bytes, + info.encoded_bytes); + + // |info| will be implicitly cast to an EncodedInfoLeaf struct, effectively + // discarding the (empty) vector of redundant information. This is + // intentional. + info.redundant.push_back(info); + RTC_DCHECK_EQ(info.redundant.size(), 1); + if (secondary_info_.encoded_bytes > 0) { + info.redundant.push_back(secondary_info_); + RTC_DCHECK_EQ(info.redundant.size(), 2); + } + // Save primary to secondary. + secondary_encoded_.SetData( + &encoded->data()[header_length_bytes + secondary_info_.encoded_bytes], + info.encoded_bytes); + secondary_info_ = info; + RTC_DCHECK_EQ(info.speech, info.redundant[0].speech); + // Update main EncodedInfo. - info.payload_type = red_payload_type_; - info.encoded_bytes = 0; + if (header_length_bytes > 0) { + info.payload_type = red_payload_type_; + } + info.encoded_bytes = header_length_bytes; for (std::vector::const_iterator it = info.redundant.begin(); it != info.redundant.end(); ++it) { info.encoded_bytes += it->encoded_bytes; 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 e20515a165..720acb4f87 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 @@ -139,6 +139,7 @@ TEST_F(AudioEncoderCopyRedTest, CheckImmediateEncode) { // new data, even if the RED codec is loaded with a secondary encoding. TEST_F(AudioEncoderCopyRedTest, CheckNoOutput) { static const size_t kEncodedSize = 17; + static const size_t kHeaderLenBytes = 5; { InSequence s; EXPECT_CALL(*mock_encoder_, EncodeImpl(_, _, _)) @@ -160,7 +161,7 @@ TEST_F(AudioEncoderCopyRedTest, CheckNoOutput) { // Final call to the speech encoder will produce output. Encode(); - EXPECT_EQ(2 * kEncodedSize, encoded_info_.encoded_bytes); + EXPECT_EQ(2 * kEncodedSize + kHeaderLenBytes, encoded_info_.encoded_bytes); ASSERT_EQ(2u, encoded_info_.redundant.size()); } @@ -187,7 +188,7 @@ TEST_F(AudioEncoderCopyRedTest, CheckPayloadSizes) { ASSERT_EQ(2u, encoded_info_.redundant.size()); EXPECT_EQ(i, encoded_info_.redundant[0].encoded_bytes); EXPECT_EQ(i - 1, encoded_info_.redundant[1].encoded_bytes); - EXPECT_EQ(i + i - 1, encoded_info_.encoded_bytes); + EXPECT_EQ(5 + i + i - 1, encoded_info_.encoded_bytes); } } @@ -224,6 +225,7 @@ TEST_F(AudioEncoderCopyRedTest, CheckPayloads) { // Let the mock encoder write payloads with increasing values. The first // payload will have values 0, 1, 2, ..., kPayloadLenBytes - 1. static const size_t kPayloadLenBytes = 5; + static const size_t kHeaderLenBytes = 5; uint8_t payload[kPayloadLenBytes]; for (uint8_t i = 0; i < kPayloadLenBytes; ++i) { payload[i] = i; @@ -239,7 +241,7 @@ TEST_F(AudioEncoderCopyRedTest, CheckPayloads) { EXPECT_EQ(i, encoded_.data()[i]); } - for (int j = 0; j < 5; ++j) { + for (int j = 0; j < 1; ++j) { // Increment all values of the payload by 10. for (size_t i = 0; i < kPayloadLenBytes; ++i) payload[i] += 10; @@ -249,16 +251,17 @@ TEST_F(AudioEncoderCopyRedTest, CheckPayloads) { EXPECT_EQ(kPayloadLenBytes, encoded_info_.redundant[0].encoded_bytes); EXPECT_EQ(kPayloadLenBytes, encoded_info_.redundant[1].encoded_bytes); for (size_t i = 0; i < kPayloadLenBytes; ++i) { - // Check primary payload. - EXPECT_EQ((j + 1) * 10 + i, encoded_.data()[i]); // Check secondary payload. - EXPECT_EQ(j * 10 + i, encoded_.data()[i + kPayloadLenBytes]); + EXPECT_EQ(j * 10 + i, encoded_.data()[kHeaderLenBytes + i]); + + // Check primary payload. + EXPECT_EQ((j + 1) * 10 + i, + encoded_.data()[kHeaderLenBytes + i + kPayloadLenBytes]); } } } // Checks correct propagation of payload type. -// Checks that the correct timestamps are returned. TEST_F(AudioEncoderCopyRedTest, CheckPayloadType) { const int primary_payload_type = red_payload_type_ + 1; AudioEncoder::EncodedInfo info; @@ -272,7 +275,7 @@ TEST_F(AudioEncoderCopyRedTest, CheckPayloadType) { Encode(); ASSERT_EQ(1u, encoded_info_.redundant.size()); EXPECT_EQ(primary_payload_type, encoded_info_.redundant[0].payload_type); - EXPECT_EQ(red_payload_type_, encoded_info_.payload_type); + EXPECT_EQ(primary_payload_type, encoded_info_.payload_type); const int secondary_payload_type = red_payload_type_ + 2; info.payload_type = secondary_payload_type; @@ -286,6 +289,36 @@ TEST_F(AudioEncoderCopyRedTest, CheckPayloadType) { EXPECT_EQ(red_payload_type_, encoded_info_.payload_type); } +TEST_F(AudioEncoderCopyRedTest, CheckRFC2198Header) { + const int primary_payload_type = red_payload_type_ + 1; + AudioEncoder::EncodedInfo info; + info.encoded_bytes = 10; + info.encoded_timestamp = timestamp_; + info.payload_type = primary_payload_type; + + EXPECT_CALL(*mock_encoder_, EncodeImpl(_, _, _)) + .WillOnce(Invoke(MockAudioEncoder::FakeEncoding(info))); + Encode(); + info.encoded_timestamp = timestamp_; // update timestamp. + EXPECT_CALL(*mock_encoder_, EncodeImpl(_, _, _)) + .WillOnce(Invoke(MockAudioEncoder::FakeEncoding(info))); + Encode(); // Second call will produce a redundant encoding. + + EXPECT_EQ(encoded_.size(), + 5u + 2 * 10u); // header size + two encoded payloads. + EXPECT_EQ(encoded_[0], primary_payload_type | 0x80); + + uint32_t timestamp_delta = encoded_info_.encoded_timestamp - + encoded_info_.redundant[1].encoded_timestamp; + // Timestamp delta is encoded as a 14 bit value. + EXPECT_EQ(encoded_[1], timestamp_delta >> 6); + EXPECT_EQ(static_cast(encoded_[2] >> 2), timestamp_delta & 0x3f); + // Redundant length is encoded as 10 bit value. + EXPECT_EQ(encoded_[2] & 0x3u, encoded_info_.redundant[1].encoded_bytes >> 8); + EXPECT_EQ(encoded_[3], encoded_info_.redundant[1].encoded_bytes & 0xff); + EXPECT_EQ(encoded_[4], primary_payload_type); +} + #if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) // This test fixture tests various error conditions that makes the