diff --git a/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.cc b/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.cc index dbaa36b15c..1587bc34cf 100644 --- a/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.cc +++ b/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.cc @@ -10,10 +10,13 @@ #include "modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h" -#include +#include +#include +#include "absl/algorithm/container.h" #include "api/video/video_layers_allocation.h" -#include "rtc_base/bit_buffer.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/checks.h" namespace webrtc { @@ -22,202 +25,348 @@ constexpr const char RtpVideoLayersAllocationExtension::kUri[]; namespace { -// Counts the number of bits used in the binary representation of val. -size_t CountBits(uint64_t val) { - size_t bit_count = 0; - while (val != 0) { - bit_count++; - val >>= 1; +constexpr int kMaxNumRtpStreams = 4; + +// TODO(bugs.webrtc.org/12000): share Leb128 functions with av1 packetizer. +// Returns minimum number of bytes required to store `value`. +int Leb128Size(uint32_t value) { + int size = 0; + while (value >= 0x80) { + ++size; + value >>= 7; } - return bit_count; + return size + 1; } -// Counts the number of bits used if `val`is encoded using unsigned exponential -// Golomb encoding. -// TODO(bugs.webrtc.org/12000): Move to bit_buffer.cc if Golomb encoding is used -// in the final version. -size_t SizeExponentialGolomb(uint32_t val) { - if (val == std::numeric_limits::max()) { - return 0; +// Returns number of bytes consumed. +int WriteLeb128(uint32_t value, uint8_t* buffer) { + int size = 0; + while (value >= 0x80) { + buffer[size] = 0x80 | (value & 0x7F); + ++size; + value >>= 7; } - uint64_t val_to_encode = static_cast(val) + 1; - return CountBits(val_to_encode) * 2 - 1; + buffer[size] = value; + ++size; + return size; +} + +// Reads leb128 encoded value and advance read_at by number of bytes consumed. +// Sets read_at to nullptr on error. +uint64_t ReadLeb128(const uint8_t*& read_at, const uint8_t* end) { + uint64_t value = 0; + int fill_bits = 0; + while (read_at != end && fill_bits < 64 - 7) { + uint8_t leb128_byte = *read_at; + value |= uint64_t{leb128_byte & 0x7Fu} << fill_bits; + ++read_at; + fill_bits += 7; + if ((leb128_byte & 0x80) == 0) { + return value; + } + } + // Failed to find terminator leb128 byte. + read_at = nullptr; + return 0; +} + +bool AllocationIsValid(const VideoLayersAllocation& allocation) { + // Since all multivalue fields are stored in (rtp_stream_id, spatial_id) order + // assume `allocation.active_spatial_layers` is already sorted. It is simpler + // to assemble it in the sorted way than to resort during serialization. + if (!absl::c_is_sorted( + allocation.active_spatial_layers, + [](const VideoLayersAllocation::SpatialLayer& lhs, + const VideoLayersAllocation::SpatialLayer& rhs) { + return std::make_tuple(lhs.rtp_stream_index, lhs.spatial_id) < + std::make_tuple(rhs.rtp_stream_index, rhs.spatial_id); + })) { + return false; + } + + int max_rtp_stream_idx = 0; + for (const auto& spatial_layer : allocation.active_spatial_layers) { + if (spatial_layer.rtp_stream_index < 0 || + spatial_layer.rtp_stream_index >= 4) { + return false; + } + if (spatial_layer.spatial_id < 0 || spatial_layer.spatial_id >= 4) { + return false; + } + if (spatial_layer.target_bitrate_per_temporal_layer.empty() || + spatial_layer.target_bitrate_per_temporal_layer.size() > 4) { + return false; + } + if (max_rtp_stream_idx < spatial_layer.rtp_stream_index) { + max_rtp_stream_idx = spatial_layer.rtp_stream_index; + } + if (allocation.resolution_and_frame_rate_is_valid) { + // TODO(danilchap): Add check width and height are no more than 0x10000 + // when width and height become larger type and thus would support maximum + // resolution. + if (spatial_layer.width <= 0) { + return false; + } + if (spatial_layer.height <= 0) { + return false; + } + if (spatial_layer.frame_rate_fps < 0 || + spatial_layer.frame_rate_fps > 255) { + return false; + } + } + } + if (allocation.rtp_stream_index < 0 || + allocation.rtp_stream_index > max_rtp_stream_idx) { + return false; + } + return true; +} + +struct SpatialLayersBitmasks { + int max_rtp_stream_id = 0; + uint8_t spatial_layer_bitmask[kMaxNumRtpStreams] = {}; + bool bitmasks_are_the_same = true; +}; + +SpatialLayersBitmasks SpatialLayersBitmasksPerRtpStream( + const VideoLayersAllocation& allocation) { + RTC_DCHECK(AllocationIsValid(allocation)); + SpatialLayersBitmasks result; + for (const auto& layer : allocation.active_spatial_layers) { + result.spatial_layer_bitmask[layer.rtp_stream_index] |= + (1u << layer.spatial_id); + if (result.max_rtp_stream_id < layer.rtp_stream_index) { + result.max_rtp_stream_id = layer.rtp_stream_index; + } + } + for (int i = 1; i <= result.max_rtp_stream_id; ++i) { + if (result.spatial_layer_bitmask[i] != result.spatial_layer_bitmask[0]) { + result.bitmasks_are_the_same = false; + break; + } + } + return result; } } // namespace -// TODO(bugs.webrtc.org/12000): Review and revise the content and encoding of -// this extension. This is an experimental first version. - -// 0 1 2 -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | NS|RSID|T|X|Res| Bit encoded data... -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// NS: Number of spatial layers/simulcast streams - 1. 2 bits, thus allowing -// passing number of layers/streams up-to 4. -// RSID: RTP stream id this allocation is sent on, numbered from 0. 2 bits. -// T: indicates if all spatial layers have the same amount of temporal layers. -// X: indicates if resolution and frame rate per spatial layer is present. -// Res: 2 bits reserved for future use. -// Bit encoded data: consists of following fields written in order: -// 1) T=1: Nt - 2-bit value of number of temporal layers - 1 -// T=0: NS 2-bit values of numbers of temporal layers - 1 for all spatial -// layers from lower to higher. -// 2) Bitrates: -// One value for each spatial x temporal layer. -// Format: RSID (2-bit) SID(2-bit),folowed by bitrate for all temporal -// layers for the RSID,SID tuple. All bitrates are in kbps. All bitrates are -// total required bitrate to receive the corresponding layer, i.e. in -// simulcast mode they include only corresponding spatial layer, in full-svc -// all lower spatial layers are included. All lower temporal layers are also -// included. All bitrates are written using unsigned Exponential Golomb -// encoding. -// 3) [only if X bit is set]. Encoded width, 16-bit, height, 16-bit, -// max frame rate 8-bit per spatial layer in order from lower to higher. +// +-+-+-+-+-+-+-+-+ +// |RID| NS| sl_bm | +// +-+-+-+-+-+-+-+-+ +// Spatial layer bitmask |sl0_bm |sl1_bm | +// up to 2 bytes |---------------| +// when sl_bm == 0 |sl2_bm |sl3_bm | +// +-+-+-+-+-+-+-+-+ +// Number of temporal |#tl|#tl|#tl|#tl| +// layers per spatial layer :---------------: +// up to 4 bytes | ... | +// +-+-+-+-+-+-+-+-+ +// Target bitrate in kpbs | | +// per temporal layer : ... : +// leb128 encoded | | +// +-+-+-+-+-+-+-+-+ +// Resolution and framerate | | +// 5 bytes per spatial layer + width-1 for + +// (optional) | rid=0, sid=0 | +// +---------------+ +// | | +// + height-1 for + +// | rid=0, sid=0 | +// +---------------+ +// | max framerate | +// +-+-+-+-+-+-+-+-+ +// : ... : +// +-+-+-+-+-+-+-+-+ +// +// RID: RTP stream index this allocation is sent on, numbered from 0. 2 bits. +// NS: Number of RTP streams - 1. 2 bits, thus allowing up-to 4 RTP streams. +// sl_bm: BitMask of the active Spatial Layers when same for all RTP streams or +// 0 otherwise. 4 bits thus allows up to 4 spatial layers per RTP streams. +// slX_bm: BitMask of the active Spatial Layers for RTP stream with index=X. +// byte-aligned. When NS < 2, takes ones byte, otherwise uses two bytes. +// #tl: 2-bit value of number of temporal layers-1, thus allowing up-to 4 +// temporal layer per spatial layer. One per spatial layer per RTP stream. +// values are stored in (RTP stream id, spatial id) ascending order. +// zero-padded to byte alignment. +// Target bitrate in kbps. Values are stored using leb128 encoding. +// one value per temporal layer. values are stored in +// (RTP stream id, spatial id, temporal id) ascending order. +// All bitrates are total required bitrate to receive the corresponding +// layer, i.e. in simulcast mode they include only corresponding spatial +// layer, in full-svc all lower spatial layers are included. All lower +// temporal layers are also included. +// Resolution and framerate. +// Optional. Presense is infered from the rtp header extension size. +// Encoded (width - 1), 16-bit, (height - 1), 16-bit, max frame rate 8-bit +// per spatial layer per RTP stream. +// Values are stored in (RTP stream id, spatial id) ascending order. bool RtpVideoLayersAllocationExtension::Write( rtc::ArrayView data, const VideoLayersAllocation& allocation) { - RTC_DCHECK_LT(allocation.rtp_stream_index, - VideoLayersAllocation::kMaxSpatialIds); - RTC_DCHECK_GE(data.size(), ValueSize(allocation)); - rtc::BitBufferWriter writer(data.data(), data.size()); - - // NS: - if (allocation.active_spatial_layers.empty()) + if (allocation.active_spatial_layers.empty()) { return false; - writer.WriteBits(allocation.active_spatial_layers.size() - 1, 2); - - // RSID: - writer.WriteBits(allocation.rtp_stream_index, 2); - - // T: - bool num_tls_is_the_same = true; - size_t first_layers_number_of_temporal_layers = - allocation.active_spatial_layers.front() - .target_bitrate_per_temporal_layer.size(); - for (const auto& spatial_layer : allocation.active_spatial_layers) { - if (first_layers_number_of_temporal_layers != - spatial_layer.target_bitrate_per_temporal_layer.size()) { - num_tls_is_the_same = false; - break; - } } - writer.WriteBits(num_tls_is_the_same ? 1 : 0, 1); - // X: - writer.WriteBits(allocation.resolution_and_frame_rate_is_valid ? 1 : 0, 1); + RTC_DCHECK(AllocationIsValid(allocation)); + RTC_DCHECK_GE(data.size(), ValueSize(allocation)); - // RESERVED: - writer.WriteBits(/*val=*/0, /*bit_count=*/2); - - if (num_tls_is_the_same) { - writer.WriteBits(first_layers_number_of_temporal_layers - 1, 2); + SpatialLayersBitmasks slb = SpatialLayersBitmasksPerRtpStream(allocation); + uint8_t* write_at = data.data(); + // First half of the header byte. + *write_at = (allocation.rtp_stream_index << 6); + // number of rtp stream - 1 is the same as the maximum rtp_stream_id. + *write_at |= slb.max_rtp_stream_id << 4; + if (slb.bitmasks_are_the_same) { + // Second half of the header byte. + *write_at |= slb.spatial_layer_bitmask[0]; } else { - for (const auto& spatial_layer : allocation.active_spatial_layers) { - writer.WriteBits( - spatial_layer.target_bitrate_per_temporal_layer.size() - 1, 2); + // spatial layer bitmasks when they are different for different RTP streams. + *++write_at = + (slb.spatial_layer_bitmask[0] << 4) | slb.spatial_layer_bitmask[1]; + if (slb.max_rtp_stream_id >= 2) { + *++write_at = + (slb.spatial_layer_bitmask[2] << 4) | slb.spatial_layer_bitmask[3]; } } + ++write_at; + { // Number of temporal layers. + int bit_offset = 8; + *write_at = 0; + for (const auto& layer : allocation.active_spatial_layers) { + if (bit_offset == 0) { + bit_offset = 6; + *++write_at = 0; + } else { + bit_offset -= 2; + } + *write_at |= + ((layer.target_bitrate_per_temporal_layer.size() - 1) << bit_offset); + } + ++write_at; + } + + // Target bitrates. for (const auto& spatial_layer : allocation.active_spatial_layers) { - writer.WriteBits(spatial_layer.rtp_stream_index, 2); - writer.WriteBits(spatial_layer.spatial_id, 2); for (const DataRate& bitrate : spatial_layer.target_bitrate_per_temporal_layer) { - writer.WriteExponentialGolomb(bitrate.kbps()); + write_at += WriteLeb128(bitrate.kbps(), write_at); } } if (allocation.resolution_and_frame_rate_is_valid) { for (const auto& spatial_layer : allocation.active_spatial_layers) { - writer.WriteUInt16(spatial_layer.width); - writer.WriteUInt16(spatial_layer.height); - writer.WriteUInt8(spatial_layer.frame_rate_fps); + ByteWriter::WriteBigEndian(write_at, spatial_layer.width - 1); + write_at += 2; + ByteWriter::WriteBigEndian(write_at, spatial_layer.height - 1); + write_at += 2; + *write_at = spatial_layer.frame_rate_fps; + ++write_at; } } + RTC_DCHECK_EQ(write_at - data.data(), ValueSize(allocation)); return true; } bool RtpVideoLayersAllocationExtension::Parse( rtc::ArrayView data, VideoLayersAllocation* allocation) { - if (data.size() == 0) - return false; - rtc::BitBuffer reader(data.data(), data.size()); - if (!allocation) + if (data.empty() || allocation == nullptr) { return false; + } + const uint8_t* read_at = data.data(); + const uint8_t* const end = data.data() + data.size(); + allocation->active_spatial_layers.clear(); + // Header byte. + allocation->rtp_stream_index = *read_at >> 6; + int num_rtp_streams = 1 + ((*read_at >> 4) & 0b11); + uint8_t spatial_layers_bitmasks[kMaxNumRtpStreams]; + spatial_layers_bitmasks[0] = *read_at & 0b1111; - uint32_t val; - // NS: - if (!reader.ReadBits(&val, 2)) - return false; - int active_spatial_layers = val + 1; - - // RSID: - if (!reader.ReadBits(&val, 2)) - return false; - allocation->rtp_stream_index = val; - - // T: - if (!reader.ReadBits(&val, 1)) - return false; - bool num_tls_is_constant = (val == 1); - - // X: - if (!reader.ReadBits(&val, 1)) - return false; - allocation->resolution_and_frame_rate_is_valid = (val == 1); - - // RESERVED: - if (!reader.ReadBits(&val, 2)) - return false; - - int number_of_temporal_layers[VideoLayersAllocation::kMaxSpatialIds]; - if (num_tls_is_constant) { - if (!reader.ReadBits(&val, 2)) - return false; - for (int sl_idx = 0; sl_idx < active_spatial_layers; ++sl_idx) { - number_of_temporal_layers[sl_idx] = val + 1; + if (spatial_layers_bitmasks[0] != 0) { + for (int i = 1; i < num_rtp_streams; ++i) { + spatial_layers_bitmasks[i] = spatial_layers_bitmasks[0]; } } else { - for (int sl_idx = 0; sl_idx < active_spatial_layers; ++sl_idx) { - if (!reader.ReadBits(&val, 2)) + // Spatial layer bitmasks when they are different for different RTP streams. + if (++read_at == end) { + return false; + } + spatial_layers_bitmasks[0] = *read_at >> 4; + spatial_layers_bitmasks[1] = *read_at & 0b1111; + if (num_rtp_streams > 2) { + if (++read_at == end) { return false; - number_of_temporal_layers[sl_idx] = val + 1; - if (number_of_temporal_layers[sl_idx] > - VideoLayersAllocation::kMaxTemporalIds) + } + spatial_layers_bitmasks[2] = *read_at >> 4; + spatial_layers_bitmasks[3] = *read_at & 0b1111; + } + } + if (++read_at == end) { + return false; + } + + // Read number of temporal layers, + // Create `allocation->active_spatial_layers` while iterating though it. + int bit_offset = 8; + for (int stream_idx = 0; stream_idx < num_rtp_streams; ++stream_idx) { + for (int sid = 0; sid < VideoLayersAllocation::kMaxSpatialIds; ++sid) { + if ((spatial_layers_bitmasks[stream_idx] & (1 << sid)) == 0) { + continue; + } + + if (bit_offset == 0) { + bit_offset = 6; + if (++read_at == end) { + return false; + } + } else { + bit_offset -= 2; + } + int num_temporal_layers = 1 + ((*read_at >> bit_offset) & 0b11); + allocation->active_spatial_layers.emplace_back(); + auto& layer = allocation->active_spatial_layers.back(); + layer.rtp_stream_index = stream_idx; + layer.spatial_id = sid; + layer.target_bitrate_per_temporal_layer.resize(num_temporal_layers, + DataRate::Zero()); + } + } + if (++read_at == end) { + return false; + } + + // Target bitrates. + for (auto& layer : allocation->active_spatial_layers) { + for (DataRate& rate : layer.target_bitrate_per_temporal_layer) { + rate = DataRate::KilobitsPerSec(ReadLeb128(read_at, end)); + if (read_at == nullptr) { return false; + } } } - for (int sl_idx = 0; sl_idx < active_spatial_layers; ++sl_idx) { - allocation->active_spatial_layers.emplace_back(); - auto& spatial_layer = allocation->active_spatial_layers.back(); - auto& temporal_layers = spatial_layer.target_bitrate_per_temporal_layer; - if (!reader.ReadBits(&val, 2)) - return false; - spatial_layer.rtp_stream_index = val; - if (!reader.ReadBits(&val, 2)) - return false; - spatial_layer.spatial_id = val; - for (int tl_idx = 0; tl_idx < number_of_temporal_layers[sl_idx]; ++tl_idx) { - reader.ReadExponentialGolomb(&val); - temporal_layers.push_back(DataRate::KilobitsPerSec(val)); - } + if (read_at == end) { + allocation->resolution_and_frame_rate_is_valid = false; + return true; } - if (allocation->resolution_and_frame_rate_is_valid) { - for (auto& spatial_layer : allocation->active_spatial_layers) { - if (!reader.ReadUInt16(&spatial_layer.width)) - return false; - if (!reader.ReadUInt16(&spatial_layer.height)) - return false; - if (!reader.ReadUInt8(&spatial_layer.frame_rate_fps)) - return false; - } + if (read_at + 5 * allocation->active_spatial_layers.size() != end) { + // data is left, but it size is not what can be used for resolutions and + // framerates. + return false; + } + allocation->resolution_and_frame_rate_is_valid = true; + for (auto& layer : allocation->active_spatial_layers) { + layer.width = 1 + ByteReader::ReadBigEndian(read_at); + read_at += 2; + layer.height = 1 + ByteReader::ReadBigEndian(read_at); + read_at += 2; + layer.frame_rate_fps = *read_at; + ++read_at; } return true; } @@ -227,34 +376,26 @@ size_t RtpVideoLayersAllocationExtension::ValueSize( if (allocation.active_spatial_layers.empty()) { return 0; } - size_t size_in_bits = 8; // Fixed first byte.ยจ - bool num_tls_is_the_same = true; - size_t first_layers_number_of_temporal_layers = - allocation.active_spatial_layers.front() - .target_bitrate_per_temporal_layer.size(); - for (const auto& spatial_layer : allocation.active_spatial_layers) { - if (first_layers_number_of_temporal_layers != - spatial_layer.target_bitrate_per_temporal_layer.size()) { - num_tls_is_the_same = false; - } - size_in_bits += 4; // RSID, SID tuple. - for (const auto& bitrate : - spatial_layer.target_bitrate_per_temporal_layer) { - size_in_bits += SizeExponentialGolomb(bitrate.kbps()); + size_t result = 1; // header + SpatialLayersBitmasks slb = SpatialLayersBitmasksPerRtpStream(allocation); + if (!slb.bitmasks_are_the_same) { + ++result; + if (slb.max_rtp_stream_id >= 2) { + ++result; } } - if (num_tls_is_the_same) { - size_in_bits += 2; - } else { - for (const auto& spatial_layer : allocation.active_spatial_layers) { - size_in_bits += - 2 * spatial_layer.target_bitrate_per_temporal_layer.size(); + // 2 bits per active spatial layer, rounded up to full byte, i.e. + // 0.25 byte per active spatial layer. + result += (allocation.active_spatial_layers.size() + 3) / 4; + for (const auto& spatial_layer : allocation.active_spatial_layers) { + for (DataRate value : spatial_layer.target_bitrate_per_temporal_layer) { + result += Leb128Size(value.kbps()); } } if (allocation.resolution_and_frame_rate_is_valid) { - size_in_bits += allocation.active_spatial_layers.size() * 5 * 8; + result += 5 * allocation.active_spatial_layers.size(); } - return (size_in_bits + 7) / 8; + return result; } } // namespace webrtc diff --git a/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension_unittest.cc b/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension_unittest.cc index e51c6372f6..c8363ae257 100644 --- a/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension_unittest.cc +++ b/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension_unittest.cc @@ -11,6 +11,7 @@ #include "modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h" #include "api/video/video_layers_allocation.h" +#include "rtc_base/bit_buffer.h" #include "rtc_base/buffer.h" #include "test/gmock.h" @@ -61,6 +62,96 @@ TEST(RtpVideoLayersAllocationExtension, EXPECT_EQ(written_allocation, parsed_allocation); } +TEST(RtpVideoLayersAllocationExtension, + CanWriteAndParseAllocationWithDifferentNumerOfSpatialLayers) { + VideoLayersAllocation written_allocation; + written_allocation.rtp_stream_index = 1; + written_allocation.active_spatial_layers = { + {/*rtp_stream_index*/ 0, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(50)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + {/*rtp_stream_index*/ 1, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(100)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + {/*rtp_stream_index*/ 1, + /*spatial_id*/ 1, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(200)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + }; + rtc::Buffer buffer( + RtpVideoLayersAllocationExtension::ValueSize(written_allocation)); + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Write(buffer, written_allocation)); + VideoLayersAllocation parsed_allocation; + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation)); + EXPECT_EQ(written_allocation, parsed_allocation); +} + +TEST(RtpVideoLayersAllocationExtension, + CanWriteAndParseAllocationWithSkippedLowerSpatialLayer) { + VideoLayersAllocation written_allocation; + written_allocation.rtp_stream_index = 1; + written_allocation.active_spatial_layers = { + {/*rtp_stream_index*/ 0, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(50)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + {/*rtp_stream_index*/ 1, + /*spatial_id*/ 1, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(200)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + }; + rtc::Buffer buffer( + RtpVideoLayersAllocationExtension::ValueSize(written_allocation)); + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Write(buffer, written_allocation)); + VideoLayersAllocation parsed_allocation; + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation)); + EXPECT_EQ(written_allocation, parsed_allocation); +} + +TEST(RtpVideoLayersAllocationExtension, + CanWriteAndParseAllocationWithSkippedRtpStreamIds) { + VideoLayersAllocation written_allocation; + written_allocation.rtp_stream_index = 2; + written_allocation.active_spatial_layers = { + {/*rtp_stream_index*/ 0, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(50)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + {/*rtp_stream_index*/ 2, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(200)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + }; + rtc::Buffer buffer( + RtpVideoLayersAllocationExtension::ValueSize(written_allocation)); + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Write(buffer, written_allocation)); + VideoLayersAllocation parsed_allocation; + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation)); + EXPECT_EQ(written_allocation, parsed_allocation); +} + TEST(RtpVideoLayersAllocationExtension, CanWriteAndParseAllocationWithDifferentNumerOfTemporalLayers) { VideoLayersAllocation written_allocation; @@ -110,7 +201,7 @@ TEST(RtpVideoLayersAllocationExtension, /*frame_rate_fps*/ 8, }, { - /*rtp_stream_index*/ 0, + /*rtp_stream_index*/ 1, /*spatial_id*/ 1, /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(100), DataRate::KilobitsPerSec(200)},