diff options
author | Philipp Hancke <fippo@sip-communicator.org> | 2020-07-03 12:01:09 +0200 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2020-07-03 13:53:28 +0000 |
commit | 41525d0cc0fccc57321d4600e7d11521358da439 (patch) | |
tree | 4174a7e1a940bd6ec807594a5dda5629102435ca /modules/audio_coding | |
parent | a018919bf6959eccf5fb89865d4f9f910ed3fa3b (diff) | |
download | webrtc-41525d0cc0fccc57321d4600e7d11521358da439.tar.gz |
red: implement RED with distance 2
Extends the RED implementation to support a distance of two, i.e. two
packets redundancy.
BUG=webrtc:11640
Change-Id: I5113a97a4e3d45d836d7952a0c19c5381069c158
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/178565
Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org>
Commit-Queue: Henrik Lundin <henrik.lundin@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31625}
Diffstat (limited to 'modules/audio_coding')
3 files changed, 104 insertions, 33 deletions
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 8d028c9b9a..2bfd2c44df 100644 --- a/modules/audio_coding/codecs/red/audio_encoder_copy_red.cc +++ b/modules/audio_coding/codecs/red/audio_encoder_copy_red.cc @@ -19,6 +19,7 @@ #include "rtc_base/checks.h" namespace webrtc { +static const int kRedMaxPacketSize = 1 << 10; AudioEncoderCopyRed::Config::Config() = default; AudioEncoderCopyRed::Config::Config(Config&&) = default; @@ -56,70 +57,101 @@ int AudioEncoderCopyRed::GetTargetBitrate() const { return speech_encoder_->GetTargetBitrate(); } +size_t AudioEncoderCopyRed::CalculateHeaderLength() const { + size_t header_size = 1; + if (secondary_info_.encoded_bytes > 0) { + header_size += 4; + } + if (tertiary_info_.encoded_bytes > 0) { + header_size += 4; + } + return header_size > 1 ? header_size : 0; +} + AudioEncoder::EncodedInfo AudioEncoderCopyRed::EncodeImpl( uint32_t rtp_timestamp, rtc::ArrayView<const int16_t> audio, rtc::Buffer* encoded) { + rtc::Buffer primary_encoded; + EncodedInfo info = + speech_encoder_->Encode(rtp_timestamp, audio, &primary_encoded); + RTC_CHECK(info.redundant.empty()) << "Cannot use nested redundant encoders."; + RTC_DCHECK_EQ(primary_encoded.size(), info.encoded_bytes); + + if (info.encoded_bytes == 0) { + return info; + } + // 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; + const size_t header_length_bytes = CalculateHeaderLength(); + encoded->SetSize(header_length_bytes); - 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); + size_t header_offset = 0; + if (tertiary_info_.encoded_bytes > 0 && + tertiary_info_.encoded_bytes < kRedMaxPacketSize) { + encoded->AppendData(tertiary_encoded_); - if (info.encoded_bytes == 0) { - encoded->Clear(); - return info; + const uint32_t timestamp_delta = + info.encoded_timestamp - tertiary_info_.encoded_timestamp; + + encoded->data()[header_offset] = tertiary_info_.payload_type | 0x80; + rtc::SetBE16(static_cast<uint8_t*>(encoded->data()) + header_offset + 1, + (timestamp_delta << 2) | (tertiary_info_.encoded_bytes >> 8)); + encoded->data()[header_offset + 3] = tertiary_info_.encoded_bytes & 0xff; + header_offset += 4; } - // Actually construct the RFC 2198 header. - if (secondary_info_.encoded_bytes > 0) { + if (secondary_info_.encoded_bytes > 0 && + secondary_info_.encoded_bytes < kRedMaxPacketSize) { + encoded->AppendData(secondary_encoded_); + 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<uint8_t*>(encoded->data()) + 1, + encoded->data()[header_offset] = secondary_info_.payload_type | 0x80; + rtc::SetBE16(static_cast<uint8_t*>(encoded->data()) + header_offset + 1, (timestamp_delta << 2) | (secondary_info_.encoded_bytes >> 8)); - encoded->data()[3] = secondary_info_.encoded_bytes & 0xff; - encoded->data()[4] = info.payload_type; + encoded->data()[header_offset + 3] = secondary_info_.encoded_bytes & 0xff; + header_offset += 4; } - RTC_CHECK(info.redundant.empty()) << "Cannot use nested redundant encoders."; - RTC_DCHECK_EQ(encoded->size() - header_length_bytes - secondary_length_bytes, - info.encoded_bytes); + encoded->AppendData(primary_encoded); + if (header_length_bytes > 0) { + RTC_DCHECK_EQ(header_offset, header_length_bytes - 1); + encoded->data()[header_offset] = info.payload_type; + } // |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); + RTC_DCHECK_EQ(info.speech, info.redundant[0].speech); if (secondary_info_.encoded_bytes > 0) { info.redundant.push_back(secondary_info_); RTC_DCHECK_EQ(info.redundant.size(), 2); } + if (tertiary_info_.encoded_bytes > 0) { + info.redundant.push_back(tertiary_info_); + RTC_DCHECK_EQ(info.redundant.size(), + 2 + (secondary_info_.encoded_bytes > 0 ? 1 : 0)); + } + + // Save secondary to tertiary. + tertiary_encoded_.SetData(secondary_encoded_); + tertiary_info_ = secondary_info_; + // Save primary to secondary. - secondary_encoded_.SetData( - &encoded->data()[header_length_bytes + secondary_info_.encoded_bytes], - info.encoded_bytes); + secondary_encoded_.SetData(primary_encoded); secondary_info_ = info; - RTC_DCHECK_EQ(info.speech, info.redundant[0].speech); // Update main EncodedInfo. if (header_length_bytes > 0) { info.payload_type = red_payload_type_; } - info.encoded_bytes = header_length_bytes; - for (std::vector<EncodedInfoLeaf>::const_iterator it = info.redundant.begin(); - it != info.redundant.end(); ++it) { - info.encoded_bytes += it->encoded_bytes; - } + info.encoded_bytes = encoded->size(); return info; } diff --git a/modules/audio_coding/codecs/red/audio_encoder_copy_red.h b/modules/audio_coding/codecs/red/audio_encoder_copy_red.h index c6e829eeb6..4d7fc404f1 100644 --- a/modules/audio_coding/codecs/red/audio_encoder_copy_red.h +++ b/modules/audio_coding/codecs/red/audio_encoder_copy_red.h @@ -71,10 +71,13 @@ class AudioEncoderCopyRed final : public AudioEncoder { rtc::Buffer* encoded) override; private: + size_t CalculateHeaderLength() const; std::unique_ptr<AudioEncoder> speech_encoder_; int red_payload_type_; rtc::Buffer secondary_encoded_; EncodedInfoLeaf secondary_info_; + rtc::Buffer tertiary_encoded_; + EncodedInfoLeaf tertiary_info_; RTC_DISALLOW_COPY_AND_ASSIGN(AudioEncoderCopyRed); }; 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 720acb4f87..fbc0b8aa38 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 @@ -183,12 +183,19 @@ TEST_F(AudioEncoderCopyRedTest, CheckPayloadSizes) { EXPECT_EQ(1u, encoded_info_.redundant.size()); EXPECT_EQ(1u, encoded_info_.encoded_bytes); - for (size_t i = 2; i <= kNumPackets; ++i) { + // Second call is also special since it does not include a ternary + // payload. + Encode(); + EXPECT_EQ(2u, encoded_info_.redundant.size()); + EXPECT_EQ(8u, encoded_info_.encoded_bytes); + + for (size_t i = 3; i <= kNumPackets; ++i) { Encode(); - ASSERT_EQ(2u, encoded_info_.redundant.size()); + ASSERT_EQ(3u, 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(5 + i + i - 1, encoded_info_.encoded_bytes); + EXPECT_EQ(i - 2, encoded_info_.redundant[2].encoded_bytes); + EXPECT_EQ(9 + i + (i - 1) + (i - 2), encoded_info_.encoded_bytes); } } @@ -317,6 +324,35 @@ TEST_F(AudioEncoderCopyRedTest, CheckRFC2198Header) { 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); + + EXPECT_CALL(*mock_encoder_, EncodeImpl(_, _, _)) + .WillOnce(Invoke(MockAudioEncoder::FakeEncoding(info))); + Encode(); // Third call will produce a redundant encoding with double + // redundancy. + + EXPECT_EQ(encoded_.size(), + 9u + 3 * 10u); // header size + two encoded payloads. + EXPECT_EQ(encoded_[0], primary_payload_type | 0x80); + + timestamp_delta = encoded_info_.encoded_timestamp - + encoded_info_.redundant[2].encoded_timestamp; + // Timestamp delta is encoded as a 14 bit value. + EXPECT_EQ(encoded_[1], timestamp_delta >> 6); + EXPECT_EQ(static_cast<uint8_t>(encoded_[2] >> 2), timestamp_delta & 0x3f); + // Redundant length is encoded as 10 bit value. + EXPECT_EQ(encoded_[2] & 0x3u, encoded_info_.redundant[2].encoded_bytes >> 8); + EXPECT_EQ(encoded_[3], encoded_info_.redundant[2].encoded_bytes & 0xff); + + EXPECT_EQ(encoded_[4], primary_payload_type | 0x80); + timestamp_delta = encoded_info_.encoded_timestamp - + encoded_info_.redundant[1].encoded_timestamp; + // Timestamp delta is encoded as a 14 bit value. + EXPECT_EQ(encoded_[5], timestamp_delta >> 6); + EXPECT_EQ(static_cast<uint8_t>(encoded_[6] >> 2), timestamp_delta & 0x3f); + // Redundant length is encoded as 10 bit value. + EXPECT_EQ(encoded_[6] & 0x3u, encoded_info_.redundant[2].encoded_bytes >> 8); + EXPECT_EQ(encoded_[7], encoded_info_.redundant[2].encoded_bytes & 0xff); + EXPECT_EQ(encoded_[8], primary_payload_type); } #if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) |