Add support for sending RTP two-byte header extensions.

Automatic detection if one-byte header or two-byte header should be used based
on extension ID and extension length.

Bug: webrtc:7990
Change-Id: I9fc848ecc59458d1ca97bace0e57ea04d3d0ced6
Reviewed-on: https://webrtc-review.googlesource.com/c/103422
Commit-Queue: Johannes Kron <kron@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Seth Hampson <shampson@webrtc.org>
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25004}
This commit is contained in:
Johannes Kron
2018-10-05 10:00:46 +02:00
committed by Commit Bot
parent 7880be1799
commit 78cdde3df6
9 changed files with 217 additions and 23 deletions

View File

@ -142,7 +142,9 @@ const char RtpExtension::kEncryptHeaderExtensionsUri[] =
constexpr int RtpExtension::kMinId;
constexpr int RtpExtension::kMaxId;
constexpr int RtpExtension::kMaxValueSize;
constexpr int RtpExtension::kOneByteHeaderExtensionMaxId;
constexpr int RtpExtension::kOneByteHeaderExtensionMaxValueSize;
bool RtpExtension::IsSupportedForAudio(const std::string& uri) {
return uri == webrtc::RtpExtension::kAudioLevelUri ||

View File

@ -311,7 +311,9 @@ struct RtpExtension {
// header extensions, per RFC8285 Section 4.2-4.3.
static constexpr int kMinId = 1;
static constexpr int kMaxId = 255;
static constexpr int kMaxValueSize = 255;
static constexpr int kOneByteHeaderExtensionMaxId = 14;
static constexpr int kOneByteHeaderExtensionMaxValueSize = 16;
std::string uri;
int id = 0;

View File

@ -53,10 +53,18 @@ class RtpHeaderExtensionMap {
}
int32_t Deregister(RTPExtensionType type);
bool IsMixedOneTwoByteHeaderSupported() const {
return mixed_one_two_byte_header_supported_;
}
void SetMixedOneTwoByteHeaderSupported(bool supported) {
mixed_one_two_byte_header_supported_ = supported;
}
private:
bool Register(int id, RTPExtensionType type, const char* uri);
uint8_t ids_[kRtpExtensionNumberOfExtensions];
bool mixed_one_two_byte_header_supported_;
};
} // namespace webrtc

View File

@ -56,7 +56,8 @@ static_assert(arraysize(kExtensions) ==
constexpr RTPExtensionType RtpHeaderExtensionMap::kInvalidType;
constexpr int RtpHeaderExtensionMap::kInvalidId;
RtpHeaderExtensionMap::RtpHeaderExtensionMap() {
RtpHeaderExtensionMap::RtpHeaderExtensionMap()
: mixed_one_two_byte_header_supported_(false) {
for (auto& id : ids_)
id = kInvalidId;
}

View File

@ -18,8 +18,6 @@ int RtpHeaderExtensionSize(rtc::ArrayView<const RtpExtensionSize> extensions,
const RtpHeaderExtensionMap& registered_extensions) {
// RFC3550 Section 5.3.1
static constexpr int kExtensionBlockHeaderSize = 4;
// RFC8285 Section 4.2-4.3.
static constexpr int kOneByteHeaderExtensionMaxValueSize = 16;
int values_size = 0;
int num_extensions = 0;
@ -31,7 +29,8 @@ int RtpHeaderExtensionSize(rtc::ArrayView<const RtpExtensionSize> extensions,
// All extensions should use same size header. Check if the |extension|
// forces to switch to two byte header that allows larger id and value size.
if (id > RtpExtension::kOneByteHeaderExtensionMaxId ||
extension.value_size > kOneByteHeaderExtensionMaxValueSize) {
extension.value_size >
RtpExtension::kOneByteHeaderExtensionMaxValueSize) {
each_extension_header_size = 2;
}
values_size += extension.value_size;

View File

@ -462,9 +462,11 @@ bool BaseRtpStringExtension::Parse(rtc::ArrayView<const uint8_t> data,
bool BaseRtpStringExtension::Write(rtc::ArrayView<uint8_t> data,
const std::string& str) {
if (str.size() > StringRtpHeaderExtension::kMaxSize) {
return false;
}
RTC_DCHECK_EQ(data.size(), str.size());
RTC_DCHECK_GE(str.size(), 1);
RTC_DCHECK_LE(str.size(), StringRtpHeaderExtension::kMaxSize);
memcpy(data.data(), str.data(), str.size());
return true;
}

View File

@ -178,10 +178,9 @@ void RtpPacket::SetCsrcs(rtc::ArrayView<const uint32_t> csrcs) {
rtc::ArrayView<uint8_t> RtpPacket::AllocateRawExtension(int id, size_t length) {
RTC_DCHECK_GE(id, RtpExtension::kMinId);
RTC_DCHECK_LE(id, RtpExtension::kOneByteHeaderExtensionMaxId);
RTC_DCHECK_LE(id, RtpExtension::kMaxId);
RTC_DCHECK_GE(length, 1);
RTC_DCHECK_LE(length, 16);
RTC_DCHECK_LE(length, RtpExtension::kMaxValueSize);
const ExtensionInfo* extension_entry = FindExtensionInfo(id);
if (extension_entry != nullptr) {
// Extension already reserved. Check if same length is used.
@ -205,10 +204,51 @@ rtc::ArrayView<uint8_t> RtpPacket::AllocateRawExtension(int id, size_t length) {
return nullptr;
}
size_t num_csrc = data()[0] & 0x0F;
size_t extensions_offset = kFixedHeaderSize + (num_csrc * 4) + 4;
const size_t num_csrc = data()[0] & 0x0F;
const size_t extensions_offset = kFixedHeaderSize + (num_csrc * 4) + 4;
// Determine if two-byte header is required for the extension based on id and
// length. Please note that a length of 0 also requires two-byte header
// extension. See RFC8285 Section 4.2-4.3.
const bool two_byte_header_required =
id > RtpExtension::kOneByteHeaderExtensionMaxId ||
length > RtpExtension::kOneByteHeaderExtensionMaxValueSize || length == 0;
RTC_CHECK(!two_byte_header_required ||
extensions_.IsMixedOneTwoByteHeaderSupported());
uint16_t profile_id;
if (extensions_size_ > 0) {
profile_id =
ByteReader<uint16_t>::ReadBigEndian(data() + extensions_offset - 4);
if (profile_id == kOneByteExtensionProfileId && two_byte_header_required) {
// Is buffer size big enough to fit promotion and new data field?
// The header extension will grow with one byte per already allocated
// extension + the size of the extension that is about to be allocated.
size_t expected_new_extensions_size =
extensions_size_ + extension_entries_.size() +
kTwoByteExtensionHeaderLength + length;
if (extensions_offset + expected_new_extensions_size > capacity()) {
RTC_LOG(LS_ERROR)
<< "Extension cannot be registered: Not enough space left in "
"buffer to change to two-byte header extension and add new "
"extension.";
return nullptr;
}
// Promote already written data to two-byte header format.
PromoteToTwoByteHeaderExtension();
profile_id = kTwoByteExtensionProfileId;
}
} else {
// Profile specific ID, set to OneByteExtensionHeader unless
// TwoByteExtensionHeader is required.
profile_id = two_byte_header_required ? kTwoByteExtensionProfileId
: kOneByteExtensionProfileId;
}
const size_t extension_header_size = profile_id == kOneByteExtensionProfileId
? kOneByteExtensionHeaderLength
: kTwoByteExtensionHeaderLength;
size_t new_extensions_size =
extensions_size_ + kOneByteExtensionHeaderLength + length;
extensions_size_ + extension_header_size + length;
if (extensions_offset + new_extensions_size > capacity()) {
RTC_LOG(LS_ERROR)
<< "Extension cannot be registered: Not enough space left in buffer.";
@ -219,22 +259,76 @@ rtc::ArrayView<uint8_t> RtpPacket::AllocateRawExtension(int id, size_t length) {
if (extensions_size_ == 0) {
RTC_DCHECK_EQ(payload_offset_, kFixedHeaderSize + (num_csrc * 4));
WriteAt(0, data()[0] | 0x10); // Set extension bit.
// Profile specific ID always set to OneByteExtensionHeader.
ByteWriter<uint16_t>::WriteBigEndian(WriteAt(extensions_offset - 4),
kOneByteExtensionProfileId);
profile_id);
}
if (profile_id == kOneByteExtensionProfileId) {
uint8_t one_byte_header = rtc::dchecked_cast<uint8_t>(id) << 4;
one_byte_header |= rtc::dchecked_cast<uint8_t>(length - 1);
WriteAt(extensions_offset + extensions_size_, one_byte_header);
} else {
// TwoByteHeaderExtension.
uint8_t extension_id = rtc::dchecked_cast<uint8_t>(id);
WriteAt(extensions_offset + extensions_size_, extension_id);
uint8_t extension_length = rtc::dchecked_cast<uint8_t>(length);
WriteAt(extensions_offset + extensions_size_ + 1, extension_length);
}
const uint16_t extension_info_offset = rtc::dchecked_cast<uint16_t>(
extensions_offset + extensions_size_ + kOneByteExtensionHeaderLength);
extensions_offset + extensions_size_ + extension_header_size);
const uint8_t extension_info_length = rtc::dchecked_cast<uint8_t>(length);
extension_entries_.emplace_back(id, extension_info_length,
extension_info_offset);
extensions_size_ = new_extensions_size;
uint16_t extensions_size_padded =
SetExtensionLengthMaybeAddZeroPadding(extensions_offset);
payload_offset_ = extensions_offset + extensions_size_padded;
buffer_.SetSize(payload_offset_);
return rtc::MakeArrayView(WriteAt(extension_info_offset),
extension_info_length);
}
void RtpPacket::PromoteToTwoByteHeaderExtension() {
size_t num_csrc = data()[0] & 0x0F;
size_t extensions_offset = kFixedHeaderSize + (num_csrc * 4) + 4;
RTC_CHECK_GT(extension_entries_.size(), 0);
RTC_CHECK_EQ(payload_size_, 0);
RTC_CHECK_EQ(kOneByteExtensionProfileId, ByteReader<uint16_t>::ReadBigEndian(
data() + extensions_offset - 4));
// Rewrite data.
// Each extension adds one to the offset. The write-read delta for the last
// extension is therefore the same as the number of extension entries.
size_t write_read_delta = extension_entries_.size();
for (auto extension_entry = extension_entries_.rbegin();
extension_entry != extension_entries_.rend(); ++extension_entry) {
size_t read_index = extension_entry->offset;
size_t write_index = read_index + write_read_delta;
// Update offset.
extension_entry->offset = rtc::dchecked_cast<uint16_t>(write_index);
// Copy data. Use memmove since read/write regions may overlap.
memmove(WriteAt(write_index), data() + read_index, extension_entry->length);
// Rewrite id and length.
WriteAt(--write_index, extension_entry->length);
WriteAt(--write_index, extension_entry->id);
--write_read_delta;
}
// Update profile header, extensions length, and zero padding.
ByteWriter<uint16_t>::WriteBigEndian(WriteAt(extensions_offset - 4),
kTwoByteExtensionProfileId);
extensions_size_ += extension_entries_.size();
uint16_t extensions_size_padded =
SetExtensionLengthMaybeAddZeroPadding(extensions_offset);
payload_offset_ = extensions_offset + extensions_size_padded;
buffer_.SetSize(payload_offset_);
}
uint16_t RtpPacket::SetExtensionLengthMaybeAddZeroPadding(
size_t extensions_offset) {
// Update header length field.
uint16_t extensions_words = rtc::dchecked_cast<uint16_t>(
(extensions_size_ + 3) / 4); // Wrap up to 32bit.
@ -244,10 +338,7 @@ rtc::ArrayView<uint8_t> RtpPacket::AllocateRawExtension(int id, size_t length) {
size_t extension_padding_size = 4 * extensions_words - extensions_size_;
memset(WriteAt(extensions_offset + extensions_size_), 0,
extension_padding_size);
payload_offset_ = extensions_offset + 4 * extensions_words;
buffer_.SetSize(payload_offset_);
return rtc::MakeArrayView(WriteAt(extension_info_offset),
extension_info_length);
return 4 * extensions_words;
}
uint8_t* RtpPacket::AllocatePayload(size_t size_bytes) {
@ -463,11 +554,22 @@ rtc::ArrayView<const uint8_t> RtpPacket::FindExtension(
rtc::ArrayView<uint8_t> RtpPacket::AllocateExtension(ExtensionType type,
size_t length) {
// TODO(webrtc:7990): Add support for empty extensions (length==0).
if (length == 0 || length > RtpExtension::kMaxValueSize ||
(!extensions_.IsMixedOneTwoByteHeaderSupported() &&
length > RtpExtension::kOneByteHeaderExtensionMaxValueSize)) {
return nullptr;
}
uint8_t id = extensions_.GetId(type);
if (id == ExtensionManager::kInvalidId) {
// Extension not registered.
return nullptr;
}
if (!extensions_.IsMixedOneTwoByteHeaderSupported() &&
id > RtpExtension::kOneByteHeaderExtensionMaxId) {
return nullptr;
}
return AllocateRawExtension(id, length);
}

View File

@ -143,6 +143,12 @@ class RtpPacket {
// Returns empty arrayview on failure.
rtc::ArrayView<uint8_t> AllocateRawExtension(int id, size_t length);
// Promotes existing one-byte header extensions to two-byte header extensions
// by rewriting the data and updates the corresponding extension offsets.
void PromoteToTwoByteHeaderExtension();
uint16_t SetExtensionLengthMaybeAddZeroPadding(size_t extensions_offset);
// Find or allocate an extension |type|. Returns view of size |length|
// to write raw extension to or an empty view on failure.
rtc::ArrayView<uint8_t> AllocateExtension(ExtensionType type, size_t length);
@ -187,8 +193,6 @@ rtc::ArrayView<const uint8_t> RtpPacket::GetRawExtension() const {
template <typename Extension, typename... Values>
bool RtpPacket::SetExtension(Values... values) {
const size_t value_size = Extension::ValueSize(values...);
if (value_size == 0 || value_size > 16)
return false;
auto buffer = AllocateExtension(Extension::kId, value_size);
if (buffer.empty())
return false;

View File

@ -64,6 +64,27 @@ constexpr uint8_t kPacketWithTOAndAL[] = {
0x12, 0x00, 0x56, 0xce,
0x90, 0x80|kAudioLevel, 0x00, 0x00};
constexpr uint8_t kPacketWithTwoByteExtensionIdLast[] = {
0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte,
0x65, 0x43, 0x12, 0x78,
0x12, 0x34, 0x56, 0x78,
0x10, 0x00, 0x00, 0x04,
0x01, 0x03, 0x00, 0x56,
0xce, 0x09, 0x01, 0x80|kAudioLevel,
kTwoByteExtensionId, 0x03, 0x00, 0x30, // => 0x00 0x30 0x22
0x22, 0x00, 0x00, 0x00}; // => Playout delay.min_ms = 3*10
// => Playout delay.max_ms = 34*10
constexpr uint8_t kPacketWithTwoByteExtensionIdFirst[] = {
0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte,
0x65, 0x43, 0x12, 0x78,
0x12, 0x34, 0x56, 0x78,
0x10, 0x00, 0x00, 0x04,
kTwoByteExtensionId, 0x03, 0x00, 0x30, // => 0x00 0x30 0x22
0x22, 0x01, 0x03, 0x00, // => Playout delay.min_ms = 3*10
0x56, 0xce, 0x09, 0x01, // => Playout delay.max_ms = 34*10
0x80|kAudioLevel, 0x00, 0x00, 0x00};
constexpr uint8_t kPacketWithTOAndALInvalidPadding[] = {
0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte,
0x65, 0x43, 0x12, 0x78,
@ -203,6 +224,51 @@ TEST(RtpPacketTest, CreateWith2Extensions) {
ElementsAreArray(packet.data(), packet.size()));
}
TEST(RtpPacketTest, CreateWithTwoByteHeaderExtensionFirst) {
RtpPacketToSend::ExtensionManager extensions;
extensions.SetMixedOneTwoByteHeaderSupported(true);
extensions.Register(kRtpExtensionTransmissionTimeOffset,
kTransmissionOffsetExtensionId);
extensions.Register(kRtpExtensionAudioLevel, kAudioLevelExtensionId);
extensions.Register(kRtpExtensionPlayoutDelay, kTwoByteExtensionId);
RtpPacketToSend packet(&extensions);
packet.SetPayloadType(kPayloadType);
packet.SetSequenceNumber(kSeqNum);
packet.SetTimestamp(kTimestamp);
packet.SetSsrc(kSsrc);
// Set extension that requires two-byte header.
PlayoutDelay playoutDelay = {30, 340};
ASSERT_TRUE(packet.SetExtension<PlayoutDelayLimits>(playoutDelay));
packet.SetExtension<TransmissionOffset>(kTimeOffset);
packet.SetExtension<AudioLevel>(kVoiceActive, kAudioLevel);
EXPECT_THAT(kPacketWithTwoByteExtensionIdFirst,
ElementsAreArray(packet.data(), packet.size()));
}
TEST(RtpPacketTest, CreateWithTwoByteHeaderExtensionLast) {
// This test will trigger RtpPacket::PromoteToTwoByteHeaderExtension().
RtpPacketToSend::ExtensionManager extensions;
extensions.SetMixedOneTwoByteHeaderSupported(true);
extensions.Register(kRtpExtensionTransmissionTimeOffset,
kTransmissionOffsetExtensionId);
extensions.Register(kRtpExtensionAudioLevel, kAudioLevelExtensionId);
extensions.Register(kRtpExtensionPlayoutDelay, kTwoByteExtensionId);
RtpPacketToSend packet(&extensions);
packet.SetPayloadType(kPayloadType);
packet.SetSequenceNumber(kSeqNum);
packet.SetTimestamp(kTimestamp);
packet.SetSsrc(kSsrc);
packet.SetExtension<TransmissionOffset>(kTimeOffset);
packet.SetExtension<AudioLevel>(kVoiceActive, kAudioLevel);
EXPECT_THAT(kPacketWithTOAndAL,
ElementsAreArray(packet.data(), packet.size()));
// Set extension that requires two-byte header.
PlayoutDelay playoutDelay = {30, 340};
ASSERT_TRUE(packet.SetExtension<PlayoutDelayLimits>(playoutDelay));
EXPECT_THAT(kPacketWithTwoByteExtensionIdLast,
ElementsAreArray(packet.data(), packet.size()));
}
TEST(RtpPacketTest, CreateWithDynamicSizedExtensions) {
RtpPacketToSend::ExtensionManager extensions;
extensions.Register<RtpStreamId>(kRtpStreamIdExtensionId);
@ -247,6 +313,14 @@ TEST(RtpPacketTest, TryToCreateWithLongMid) {
EXPECT_FALSE(packet.SetExtension<RtpMid>(kLongMid));
}
TEST(RtpPacketTest, TryToCreateTwoByteHeaderNotSupported) {
RtpPacketToSend::ExtensionManager extensions;
extensions.Register(kRtpExtensionAudioLevel, kTwoByteExtensionId);
RtpPacketToSend packet(&extensions);
// Set extension that requires two-byte header.
EXPECT_FALSE(packet.SetExtension<AudioLevel>(kVoiceActive, kAudioLevel));
}
TEST(RtpPacketTest, CreateWithMaxSizeHeaderExtension) {
const std::string kValue = "123456789abcdef";
RtpPacket::ExtensionManager extensions;