Add read support of RtpStreamId/RepairedRtpStreamId header extensions.

BUG=webrtc:7433

Review-Url: https://codereview.webrtc.org/2805023002
Cr-Commit-Position: refs/heads/master@{#17759}
This commit is contained in:
danilchap 2017-04-19 02:59:48 -07:00 committed by Commit bot
parent 6f27633f47
commit ef8d773d26
12 changed files with 244 additions and 22 deletions

View File

@ -10,8 +10,9 @@
#include "webrtc/common_types.h"
#include <limits>
#include <string.h>
#include <limits>
#include <type_traits>
#include "webrtc/base/checks.h"
#include "webrtc/base/stringutils.h"
@ -20,6 +21,20 @@ namespace webrtc {
StreamDataCounters::StreamDataCounters() : first_packet_time_ms(-1) {}
constexpr size_t StreamId::kMaxSize;
void StreamId::Set(const char* data, size_t size) {
// If |data| contains \0, the stream id size might become less than |size|.
RTC_DCHECK_LE(size, kMaxSize);
memcpy(value_, data, size);
if (size < kMaxSize)
value_[size] = 0;
}
// StreamId is used as member of RTPHeader that is sometimes copied with memcpy
// and thus assume trivial destructibility.
static_assert(std::is_trivially_destructible<StreamId>::value, "");
RTPHeaderExtension::RTPHeaderExtension()
: hasTransmissionTimeOffset(false),
transmissionTimeOffset(0),

View File

@ -20,6 +20,7 @@
#include "webrtc/api/video/video_content_type.h"
#include "webrtc/api/video/video_rotation.h"
#include "webrtc/base/array_view.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/optional.h"
#include "webrtc/typedefs.h"
@ -695,6 +696,42 @@ struct PlayoutDelay {
int max_ms;
};
// Class to represent RtpStreamId which is a string.
// Unlike std::string, it can be copied with memcpy and cleared with memset.
// Empty value represent unset RtpStreamId.
class StreamId {
public:
// Stream id is limited to 16 bytes because it is the maximum length
// that can be encoded with one-byte header extensions.
static constexpr size_t kMaxSize = 16;
StreamId() { value_[0] = 0; }
explicit StreamId(rtc::ArrayView<const char> value) {
Set(value.data(), value.size());
}
StreamId(const StreamId&) = default;
StreamId& operator=(const StreamId&) = default;
bool empty() const { return value_[0] == 0; }
const char* data() const { return value_; }
size_t size() const { return strnlen(value_, kMaxSize); }
void Set(rtc::ArrayView<const uint8_t> value) {
Set(reinterpret_cast<const char*>(value.data()), value.size());
}
void Set(const char* data, size_t size);
friend bool operator==(const StreamId& lhs, const StreamId& rhs) {
return strncmp(lhs.value_, rhs.value_, kMaxSize) == 0;
}
friend bool operator!=(const StreamId& lhs, const StreamId& rhs) {
return !(lhs == rhs);
}
private:
char value_[kMaxSize];
};
struct RTPHeaderExtension {
RTPHeaderExtension();
@ -723,6 +760,12 @@ struct RTPHeaderExtension {
VideoContentType videoContentType;
PlayoutDelay playout_delay = {-1, -1};
// For identification of a stream when ssrc is not signaled. See
// https://tools.ietf.org/html/draft-ietf-avtext-rid-09
// TODO(danilchap): Update url from draft to release version.
StreamId stream_id;
StreamId repaired_stream_id;
};
struct RTPHeader {

View File

@ -77,6 +77,8 @@ enum RTPExtensionType {
kRtpExtensionTransportSequenceNumber,
kRtpExtensionPlayoutDelay,
kRtpExtensionVideoContentType,
kRtpExtensionRtpStreamId,
kRtpExtensionRepairedRtpStreamId,
kRtpExtensionNumberOfExtensions // Must be the last entity in the enum.
};

View File

@ -40,6 +40,8 @@ constexpr ExtensionInfo kExtensions[] = {
CreateExtensionInfo<TransportSequenceNumber>(),
CreateExtensionInfo<PlayoutDelayLimits>(),
CreateExtensionInfo<VideoContentTypeExtension>(),
CreateExtensionInfo<RtpStreamId>(),
CreateExtensionInfo<RepairedRtpStreamId>(),
};
// Because of kRtpExtensionNone, NumberOfExtension is 1 bigger than the actual

View File

@ -244,4 +244,44 @@ bool VideoContentTypeExtension::Write(uint8_t* data,
return true;
}
// RtpStreamId.
constexpr RTPExtensionType RtpStreamId::kId;
constexpr uint8_t RtpStreamId::kValueSizeBytes;
constexpr const char* RtpStreamId::kUri;
bool RtpStreamId::Parse(rtc::ArrayView<const uint8_t> data, StreamId* rsid) {
if (data.empty() || data[0] == 0) // Valid rsid can't be empty.
return false;
rsid->Set(data);
RTC_DCHECK(!rsid->empty());
return true;
}
bool RtpStreamId::Parse(rtc::ArrayView<const uint8_t> data, std::string* rsid) {
if (data.empty() || data[0] == 0) // Valid rsid can't be empty.
return false;
const char* str = reinterpret_cast<const char*>(data.data());
// If there is a \0 character in the middle of the |data|, treat it as end of
// the string. Well-formed rsid shouldn't contain it.
rsid->assign(str, strnlen(str, data.size()));
RTC_DCHECK(!rsid->empty());
return true;
}
// RepairedRtpStreamId.
constexpr RTPExtensionType RepairedRtpStreamId::kId;
constexpr uint8_t RepairedRtpStreamId::kValueSizeBytes;
constexpr const char* RepairedRtpStreamId::kUri;
// RtpStreamId and RepairedRtpStreamId use the same format to store rsid.
bool RepairedRtpStreamId::Parse(rtc::ArrayView<const uint8_t> data,
StreamId* rsid) {
return RtpStreamId::Parse(data, rsid);
}
bool RepairedRtpStreamId::Parse(rtc::ArrayView<const uint8_t> data,
std::string* rsid) {
return RtpStreamId::Parse(data, rsid);
}
} // namespace webrtc

View File

@ -11,6 +11,7 @@
#define WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_HEADER_EXTENSIONS_H_
#include <stdint.h>
#include <string>
#include "webrtc/api/video/video_content_type.h"
#include "webrtc/api/video/video_rotation.h"
@ -111,5 +112,31 @@ class VideoContentTypeExtension {
static bool Write(uint8_t* data, VideoContentType content_type);
};
class RtpStreamId {
public:
static constexpr RTPExtensionType kId = kRtpExtensionRtpStreamId;
// TODO(danilchap): Implement write support of dynamic size extension that
// allows to remove the ValueSize constant.
static constexpr uint8_t kValueSizeBytes = 1;
static constexpr const char* kUri =
"urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id";
static bool Parse(rtc::ArrayView<const uint8_t> data, StreamId* rid);
static bool Parse(rtc::ArrayView<const uint8_t> data, std::string* rid);
};
class RepairedRtpStreamId {
public:
static constexpr RTPExtensionType kId = kRtpExtensionRepairedRtpStreamId;
// TODO(danilchap): Implement write support of dynamic size extension that
// allows to remove the ValueSize constant.
static constexpr uint8_t kValueSizeBytes = 1;
static constexpr const char* kUri =
"urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id";
static bool Parse(rtc::ArrayView<const uint8_t> data, StreamId* rid);
static bool Parse(rtc::ArrayView<const uint8_t> data, std::string* rid);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_RTP_RTCP_SOURCE_RTP_HEADER_EXTENSIONS_H_

View File

@ -172,6 +172,8 @@ void Packet::GetHeader(RTPHeader* header) const {
header->extension.hasVideoContentType =
GetExtension<VideoContentTypeExtension>(
&header->extension.videoContentType);
GetExtension<RtpStreamId>(&header->extension.stream_id);
GetExtension<RepairedRtpStreamId>(&header->extension.repaired_stream_id);
}
size_t Packet::headers_size() const {

View File

@ -364,6 +364,45 @@ TEST(RtpPacketTest, ParseWithoutExtensionManager) {
EXPECT_EQ(time_offset, kTimeOffset);
}
TEST(RtpPacketTest, ParseDynamicSizeExtension) {
// clang-format off
const uint8_t kPacket1[] = {
0x90, kPayloadType, 0x00, kSeqNum,
0x65, 0x43, 0x12, 0x78, // Timestamp.
0x12, 0x34, 0x56, 0x78, // Ssrc.
0xbe, 0xde, 0x00, 0x02, // Extensions block of size 2x32bit words.
0x21, 'H', 'D', // Extension with id = 2, size = (1+1).
0x12, 'r', 't', 'x', // Extension with id = 1, size = (2+1).
0x00}; // Extension padding.
const uint8_t kPacket2[] = {
0x90, kPayloadType, 0x00, kSeqNum,
0x65, 0x43, 0x12, 0x78, // Timestamp.
0x12, 0x34, 0x56, 0x79, // Ssrc.
0xbe, 0xde, 0x00, 0x01, // Extensions block of size 1x32bit words.
0x11, 'H', 'D', // Extension with id = 1, size = (1+1).
0x00}; // Extension padding.
// clang-format on
RtpPacketReceived::ExtensionManager extensions;
extensions.Register<RtpStreamId>(1);
extensions.Register<RepairedRtpStreamId>(2);
RtpPacketReceived packet(&extensions);
ASSERT_TRUE(packet.Parse(kPacket1, sizeof(kPacket1)));
std::string rsid;
EXPECT_TRUE(packet.GetExtension<RtpStreamId>(&rsid));
EXPECT_EQ(rsid, "rtx");
std::string repaired_rsid;
EXPECT_TRUE(packet.GetExtension<RepairedRtpStreamId>(&repaired_rsid));
EXPECT_EQ(repaired_rsid, "HD");
// Parse another packet with RtpStreamId extension of different size.
ASSERT_TRUE(packet.Parse(kPacket2, sizeof(kPacket2)));
EXPECT_TRUE(packet.GetExtension<RtpStreamId>(&rsid));
EXPECT_EQ(rsid, "HD");
EXPECT_FALSE(packet.GetExtension<RepairedRtpStreamId>(&repaired_rsid));
}
TEST(RtpPacketTest, RawExtensionFunctionsAcceptZeroIdAndReturnFalse) {
RtpPacketReceived::ExtensionManager extensions;
RtpPacketReceived packet(&extensions);

View File

@ -469,6 +469,15 @@ void RtpHeaderParser::ParseOneByteExtensionHeader(
}
break;
}
case kRtpExtensionRtpStreamId: {
header->extension.stream_id.Set(rtc::MakeArrayView(ptr, len + 1));
break;
}
case kRtpExtensionRepairedRtpStreamId: {
header->extension.repaired_stream_id.Set(
rtc::MakeArrayView(ptr, len + 1));
break;
}
case kRtpExtensionNone:
case kRtpExtensionNumberOfExtensions: {
RTC_NOTREACHED() << "Invalid extension type: " << type;

View File

@ -148,21 +148,23 @@ TEST(RtpHeaderParser, ParseWithOverSizedExtension) {
EXPECT_EQ(sizeof(kPacket), header.headerLength);
}
TEST(RtpHeaderParser, ParseAll6Extensions) {
TEST(RtpHeaderParser, ParseAll8Extensions) {
const uint8_t kAudioLevel = 0x5a;
// clang-format off
const uint8_t kPacket[] = {
0x90, kPayloadType, 0x00, kSeqNum,
0x65, 0x43, 0x12, 0x78, // kTimestamp.
0x12, 0x34, 0x56, 0x78, // kSsrc.
0xbe, 0xde, 0x00, 0x05, // Extension of size 5x32bit word.
0xbe, 0xde, 0x00, 0x08, // Extension of size 8x32bit words.
0x40, 0x80|kAudioLevel, // AudioLevel.
0x22, 0x01, 0x56, 0xce, // TransmissionOffset.
0x62, 0x12, 0x34, 0x56, // AbsoluteSendTime.
0x81, 0xce, 0xab, // TransportSequenceNumber.
0xa0, 0x03, // VideoRotation.
0xb2, 0x12, 0x48, 0x76, // PlayoutDelayLimits.
0x00, // Padding to 32bit boundary.
0xc2, 'r', 't', 'x', // RtpStreamId
0xd5, 's', 't', 'r', 'e', 'a', 'm', // RepairedRtpStreamId
0x00, 0x00, // Padding to 32bit boundary.
};
// clang-format on
ASSERT_EQ(sizeof(kPacket) % 4, 0u);
@ -174,6 +176,8 @@ TEST(RtpHeaderParser, ParseAll6Extensions) {
extensions.Register<TransportSequenceNumber>(8);
extensions.Register<VideoOrientation>(0xa);
extensions.Register<PlayoutDelayLimits>(0xb);
extensions.Register<RtpStreamId>(0xc);
extensions.Register<RepairedRtpStreamId>(0xd);
RtpUtility::RtpHeaderParser parser(kPacket, sizeof(kPacket));
RTPHeader header;
@ -199,6 +203,33 @@ TEST(RtpHeaderParser, ParseAll6Extensions) {
header.extension.playout_delay.min_ms);
EXPECT_EQ(0x876 * PlayoutDelayLimits::kGranularityMs,
header.extension.playout_delay.max_ms);
EXPECT_EQ(header.extension.stream_id, StreamId("rtx"));
EXPECT_EQ(header.extension.repaired_stream_id, StreamId("stream"));
}
TEST(RtpHeaderParser, ParseMalformedRsidExtensions) {
// clang-format off
const uint8_t kPacket[] = {
0x90, kPayloadType, 0x00, kSeqNum,
0x65, 0x43, 0x12, 0x78, // kTimestamp.
0x12, 0x34, 0x56, 0x78, // kSsrc.
0xbe, 0xde, 0x00, 0x03, // Extension of size 3x32bit words.
0xc2, '\0', 't', 'x', // empty RtpStreamId
0xd5, 's', 't', 'r', '\0', 'a', 'm', // RepairedRtpStreamId
0x00, // Padding to 32bit boundary.
};
// clang-format on
ASSERT_EQ(sizeof(kPacket) % 4, 0u);
RtpHeaderExtensionMap extensions;
extensions.Register<RtpStreamId>(0xc);
extensions.Register<RepairedRtpStreamId>(0xd);
RtpUtility::RtpHeaderParser parser(kPacket, sizeof(kPacket));
RTPHeader header;
EXPECT_TRUE(parser.Parse(&header, &extensions));
EXPECT_TRUE(header.extension.stream_id.empty());
EXPECT_EQ(header.extension.repaired_stream_id, StreamId("str"));
}
TEST(RtpHeaderParser, ParseWithCsrcsExtensionAndPadding) {

View File

@ -15,30 +15,31 @@
#include "webrtc/modules/rtp_rtcp/source/rtp_utility.h"
namespace webrtc {
// We decide which header extensions to register by reading one byte
// We decide which header extensions to register by reading two bytes
// from the beginning of |data| and interpreting it as a bitmask over
// the RTPExtensionType enum. This assert ensures one byte is enough.
static_assert(kRtpExtensionNumberOfExtensions <= 8,
// the RTPExtensionType enum. This assert ensures two bytes are enough.
static_assert(kRtpExtensionNumberOfExtensions <= 16,
"Insufficient bits read to configure all header extensions. Add "
"an extra byte and update the switches.");
void FuzzOneInput(const uint8_t* data, size_t size) {
if (size <= 1)
if (size <= 2)
return;
// Don't use the configuration byte as part of the packet.
std::bitset<8> extensionMask(data[0]);
data++;
size--;
std::bitset<16> extensionMask(*reinterpret_cast<const uint16_t*>(data));
data += 2;
size -= 2;
RtpPacketReceived::ExtensionManager extensions;
// Skip i = 0 since it maps to ExtensionNone and extension id = 0 is invalid.
for (int i = 0; i < kRtpExtensionNumberOfExtensions; i++) {
RTPExtensionType extension_type = static_cast<RTPExtensionType>(i);
if (extensionMask[i] && extension_type != kRtpExtensionNone) {
// Extensions are registered with an ID, which you signal to the
// peer so they know what to expect. This code only cares about
// parsing so the value of the ID isn't relevant; we use i.
extensions.Register(extension_type, i);
extensions.RegisterByType(i, extension_type);
}
}

View File

@ -14,30 +14,31 @@
namespace webrtc {
// We decide which header extensions to register by reading one byte
// We decide which header extensions to register by reading two bytes
// from the beginning of |data| and interpreting it as a bitmask over
// the RTPExtensionType enum. This assert ensures one byte is enough.
static_assert(kRtpExtensionNumberOfExtensions <= 8,
// the RTPExtensionType enum. This assert ensures two bytes are enough.
static_assert(kRtpExtensionNumberOfExtensions <= 16,
"Insufficient bits read to configure all header extensions. Add "
"an extra byte and update the switches.");
void FuzzOneInput(const uint8_t* data, size_t size) {
if (size <= 1)
if (size <= 2)
return;
// Don't use the configuration byte as part of the packet.
std::bitset<8> extensionMask(data[0]);
data++;
size--;
// Don't use the configuration bytes as part of the packet.
std::bitset<16> extensionMask(*reinterpret_cast<const uint16_t*>(data));
data += 2;
size -= 2;
RtpPacketReceived::ExtensionManager extensions;
for (int i = 0; i < kRtpExtensionNumberOfExtensions; i++) {
// Skip i = 0 since it maps to ExtensionNone and extension id = 0 is invalid.
for (int i = 1; i < kRtpExtensionNumberOfExtensions; i++) {
RTPExtensionType extension_type = static_cast<RTPExtensionType>(i);
if (extensionMask[i] && extension_type != kRtpExtensionNone) {
// Extensions are registered with an ID, which you signal to the
// peer so they know what to expect. This code only cares about
// parsing so the value of the ID isn't relevant; we use i.
extensions.Register(extension_type, i);
extensions.RegisterByType(i, extension_type);
}
}
@ -89,6 +90,16 @@ void FuzzOneInput(const uint8_t* data, size_t size) {
VideoContentType content_type;
packet.GetExtension<VideoContentTypeExtension>(&content_type);
break;
case kRtpExtensionRtpStreamId: {
std::string rsid;
packet.GetExtension<RtpStreamId>(&rsid);
break;
}
case kRtpExtensionRepairedRtpStreamId: {
std::string rsid;
packet.GetExtension<RepairedRtpStreamId>(&rsid);
break;
}
}
}
}