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 <hta@webrtc.org>
Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31560}
This commit is contained in:
Philipp Hancke
2020-06-11 10:57:58 +02:00
committed by Commit Bot
parent 1b48532208
commit 603cc3a31e
3 changed files with 96 additions and 30 deletions

View File

@ -15,6 +15,7 @@
#include <utility>
#include <vector>
#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<const int16_t> 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<uint8_t*>(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<EncodedInfoLeaf>::const_iterator it = info.redundant.begin();
it != info.redundant.end(); ++it) {
info.encoded_bytes += it->encoded_bytes;

View File

@ -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<uint8_t>(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