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:
parent
6f27633f47
commit
ef8d773d26
@ -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),
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -77,6 +77,8 @@ enum RTPExtensionType {
|
||||
kRtpExtensionTransportSequenceNumber,
|
||||
kRtpExtensionPlayoutDelay,
|
||||
kRtpExtensionVideoContentType,
|
||||
kRtpExtensionRtpStreamId,
|
||||
kRtpExtensionRepairedRtpStreamId,
|
||||
kRtpExtensionNumberOfExtensions // Must be the last entity in the enum.
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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_
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user