[fix] fix transport feedback module

This commit is contained in:
dijunkun
2024-12-12 16:37:11 +08:00
parent bacf62c6b8
commit c6d4b172fc
5 changed files with 474 additions and 43 deletions

394
src/rtcp/byte_io.h Normal file
View File

@@ -0,0 +1,394 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_RTP_RTCP_SOURCE_BYTE_IO_H_
#define MODULES_RTP_RTCP_SOURCE_BYTE_IO_H_
// This file contains classes for reading and writing integer types from/to
// byte array representations. Signed/unsigned, partial (whole byte) sizes,
// and big/little endian byte order is all supported.
//
// Usage examples:
//
// uint8_t* buffer = ...;
//
// // Read an unsigned 4 byte integer in big endian format
// uint32_t val = ByteReader<uint32_t>::ReadBigEndian(buffer);
//
// // Read a signed 24-bit (3 byte) integer in little endian format
// int32_t val = ByteReader<int32_t, 3>::ReadLittle(buffer);
//
// // Write an unsigned 8 byte integer in little endian format
// ByteWriter<uint64_t>::WriteLittleEndian(buffer, val);
//
// Write an unsigned 40-bit (5 byte) integer in big endian format
// ByteWriter<uint64_t, 5>::WriteBigEndian(buffer, val);
//
// These classes are implemented as recursive templetizations, intended to make
// it easy for the compiler to completely inline the reading/writing.
#include <stdint.h>
#include <limits>
// According to ISO C standard ISO/IEC 9899, section 6.2.6.2 (2), the three
// representations of signed integers allowed are two's complement, one's
// complement and sign/magnitude. We can detect which is used by looking at
// the two last bits of -1, which will be 11 in two's complement, 10 in one's
// complement and 01 in sign/magnitude.
// TODO(sprang): In the unlikely event that we actually need to support a
// platform that doesn't use two's complement, implement conversion to/from
// wire format.
// Assume the if any one signed integer type is two's complement, then all
// other will be too.
static_assert(
(-1 & 0x03) == 0x03,
"Only two's complement representation of signed integers supported.");
// Plain const char* won't work for static_assert, use #define instead.
#define kSizeErrorMsg "Byte size must be less than or equal to data type size."
// Utility class for getting the unsigned equivalent of a signed type.
template <typename T>
struct UnsignedOf;
// Class for reading integers from a sequence of bytes.
// T = type of integer, B = bytes to read, is_signed = true if signed integer.
// If is_signed is true and B < sizeof(T), sign extension might be needed.
template <typename T, unsigned int B = sizeof(T),
bool is_signed = std::numeric_limits<T>::is_signed>
class ByteReader;
// Specialization of ByteReader for unsigned types.
template <typename T, unsigned int B>
class ByteReader<T, B, false> {
public:
static T ReadBigEndian(const uint8_t* data) {
static_assert(B <= sizeof(T), kSizeErrorMsg);
return InternalReadBigEndian(data);
}
static T ReadLittleEndian(const uint8_t* data) {
static_assert(B <= sizeof(T), kSizeErrorMsg);
return InternalReadLittleEndian(data);
}
private:
static T InternalReadBigEndian(const uint8_t* data) {
T val(0);
for (unsigned int i = 0; i < B; ++i)
val |= static_cast<T>(data[i]) << ((B - 1 - i) * 8);
return val;
}
static T InternalReadLittleEndian(const uint8_t* data) {
T val(0);
for (unsigned int i = 0; i < B; ++i)
val |= static_cast<T>(data[i]) << (i * 8);
return val;
}
};
// Specialization of ByteReader for signed types.
template <typename T, unsigned int B>
class ByteReader<T, B, true> {
public:
typedef typename UnsignedOf<T>::Type U;
static T ReadBigEndian(const uint8_t* data) {
U unsigned_val = ByteReader<T, B, false>::ReadBigEndian(data);
if (B < sizeof(T)) unsigned_val = SignExtend(unsigned_val);
return ReinterpretAsSigned(unsigned_val);
}
static T ReadLittleEndian(const uint8_t* data) {
U unsigned_val = ByteReader<T, B, false>::ReadLittleEndian(data);
if (B < sizeof(T)) unsigned_val = SignExtend(unsigned_val);
return ReinterpretAsSigned(unsigned_val);
}
private:
// As a hack to avoid implementation-specific or undefined behavior when
// bit-shifting or casting signed integers, read as a signed equivalent
// instead and convert to signed. This is safe since we have asserted that
// two's complement for is used.
static T ReinterpretAsSigned(U unsigned_val) {
// An unsigned value with only the highest order bit set (ex 0x80).
const U kUnsignedHighestBitMask = static_cast<U>(1)
<< ((sizeof(U) * 8) - 1);
// A signed value with only the highest bit set. Since this is two's
// complement form, we can use the min value from std::numeric_limits.
const T kSignedHighestBitMask = std::numeric_limits<T>::min();
T val;
if ((unsigned_val & kUnsignedHighestBitMask) != 0) {
// Casting is only safe when unsigned value can be represented in the
// signed target type, so mask out highest bit and mask it back manually.
val = static_cast<T>(unsigned_val & ~kUnsignedHighestBitMask);
val |= kSignedHighestBitMask;
} else {
val = static_cast<T>(unsigned_val);
}
return val;
}
// If number of bytes is less than native data type (eg 24 bit, in int32_t),
// and the most significant bit of the actual data is set, we must sign
// extend the remaining byte(s) with ones so that the correct negative
// number is retained.
// Ex: 0x810A0B -> 0xFF810A0B, but 0x710A0B -> 0x00710A0B
static U SignExtend(const U val) {
const uint8_t kMsb = static_cast<uint8_t>(val >> ((B - 1) * 8));
if ((kMsb & 0x80) != 0) {
// Create a mask where all bits used by the B bytes are set to one,
// for instance 0x00FFFFFF for B = 3. Bit-wise invert that mask (to
// (0xFF000000 in the example above) and add it to the input value.
// The "B % sizeof(T)" is a workaround to undefined values warnings for
// B == sizeof(T), in which case this code won't be called anyway.
const U kUsedBitsMask = (1 << ((B % sizeof(T)) * 8)) - 1;
return ~kUsedBitsMask | val;
}
return val;
}
};
// Class for writing integers to a sequence of bytes
// T = type of integer, B = bytes to write
template <typename T, unsigned int B = sizeof(T),
bool is_signed = std::numeric_limits<T>::is_signed>
class ByteWriter;
// Specialization of ByteWriter for unsigned types.
template <typename T, unsigned int B>
class ByteWriter<T, B, false> {
public:
static void WriteBigEndian(uint8_t* data, T val) {
static_assert(B <= sizeof(T), kSizeErrorMsg);
for (unsigned int i = 0; i < B; ++i) {
data[i] = val >> ((B - 1 - i) * 8);
}
}
static void WriteLittleEndian(uint8_t* data, T val) {
static_assert(B <= sizeof(T), kSizeErrorMsg);
for (unsigned int i = 0; i < B; ++i) {
data[i] = val >> (i * 8);
}
}
};
// Specialization of ByteWriter for signed types.
template <typename T, unsigned int B>
class ByteWriter<T, B, true> {
public:
typedef typename UnsignedOf<T>::Type U;
static void WriteBigEndian(uint8_t* data, T val) {
ByteWriter<U, B, false>::WriteBigEndian(data, ReinterpretAsUnsigned(val));
}
static void WriteLittleEndian(uint8_t* data, T val) {
ByteWriter<U, B, false>::WriteLittleEndian(data,
ReinterpretAsUnsigned(val));
}
private:
static U ReinterpretAsUnsigned(T val) {
// According to ISO C standard ISO/IEC 9899, section 6.3.1.3 (1, 2) a
// conversion from signed to unsigned keeps the value if the new type can
// represent it, and otherwise adds one more than the max value of T until
// the value is in range. For two's complement, this fortunately means
// that the bit-wise value will be intact. Thus, since we have asserted that
// two's complement form is actually used, a simple cast is sufficient.
return static_cast<U>(val);
}
};
// ----- Below follows specializations of UnsignedOf utility class -----
template <>
struct UnsignedOf<int8_t> {
typedef uint8_t Type;
};
template <>
struct UnsignedOf<int16_t> {
typedef uint16_t Type;
};
template <>
struct UnsignedOf<int32_t> {
typedef uint32_t Type;
};
template <>
struct UnsignedOf<int64_t> {
typedef uint64_t Type;
};
// ----- Below follows specializations for unsigned, B in { 1, 2, 4, 8 } -----
// TODO(sprang): Check if these actually help or if generic cases will be
// unrolled to and optimized to similar performance.
// Specializations for single bytes
template <typename T>
class ByteReader<T, 1, false> {
public:
static T ReadBigEndian(const uint8_t* data) {
static_assert(sizeof(T) == 1, kSizeErrorMsg);
return data[0];
}
static T ReadLittleEndian(const uint8_t* data) {
static_assert(sizeof(T) == 1, kSizeErrorMsg);
return data[0];
}
};
template <typename T>
class ByteWriter<T, 1, false> {
public:
static void WriteBigEndian(uint8_t* data, T val) {
static_assert(sizeof(T) == 1, kSizeErrorMsg);
data[0] = val;
}
static void WriteLittleEndian(uint8_t* data, T val) {
static_assert(sizeof(T) == 1, kSizeErrorMsg);
data[0] = val;
}
};
// Specializations for two byte words
template <typename T>
class ByteReader<T, 2, false> {
public:
static T ReadBigEndian(const uint8_t* data) {
static_assert(sizeof(T) >= 2, kSizeErrorMsg);
return (data[0] << 8) | data[1];
}
static T ReadLittleEndian(const uint8_t* data) {
static_assert(sizeof(T) >= 2, kSizeErrorMsg);
return data[0] | (data[1] << 8);
}
};
template <typename T>
class ByteWriter<T, 2, false> {
public:
static void WriteBigEndian(uint8_t* data, T val) {
static_assert(sizeof(T) >= 2, kSizeErrorMsg);
data[0] = val >> 8;
data[1] = val;
}
static void WriteLittleEndian(uint8_t* data, T val) {
static_assert(sizeof(T) >= 2, kSizeErrorMsg);
data[0] = val;
data[1] = val >> 8;
}
};
// Specializations for four byte words.
template <typename T>
class ByteReader<T, 4, false> {
public:
static T ReadBigEndian(const uint8_t* data) {
static_assert(sizeof(T) >= 4, kSizeErrorMsg);
return (Get(data, 0) << 24) | (Get(data, 1) << 16) | (Get(data, 2) << 8) |
Get(data, 3);
}
static T ReadLittleEndian(const uint8_t* data) {
static_assert(sizeof(T) >= 4, kSizeErrorMsg);
return Get(data, 0) | (Get(data, 1) << 8) | (Get(data, 2) << 16) |
(Get(data, 3) << 24);
}
private:
inline static T Get(const uint8_t* data, unsigned int index) {
return static_cast<T>(data[index]);
}
};
// Specializations for four byte words.
template <typename T>
class ByteWriter<T, 4, false> {
public:
static void WriteBigEndian(uint8_t* data, T val) {
static_assert(sizeof(T) >= 4, kSizeErrorMsg);
data[0] = val >> 24;
data[1] = val >> 16;
data[2] = val >> 8;
data[3] = val;
}
static void WriteLittleEndian(uint8_t* data, T val) {
static_assert(sizeof(T) >= 4, kSizeErrorMsg);
data[0] = val;
data[1] = val >> 8;
data[2] = val >> 16;
data[3] = val >> 24;
}
};
// Specializations for eight byte words.
template <typename T>
class ByteReader<T, 8, false> {
public:
static T ReadBigEndian(const uint8_t* data) {
static_assert(sizeof(T) >= 8, kSizeErrorMsg);
return (Get(data, 0) << 56) | (Get(data, 1) << 48) | (Get(data, 2) << 40) |
(Get(data, 3) << 32) | (Get(data, 4) << 24) | (Get(data, 5) << 16) |
(Get(data, 6) << 8) | Get(data, 7);
}
static T ReadLittleEndian(const uint8_t* data) {
static_assert(sizeof(T) >= 8, kSizeErrorMsg);
return Get(data, 0) | (Get(data, 1) << 8) | (Get(data, 2) << 16) |
(Get(data, 3) << 24) | (Get(data, 4) << 32) | (Get(data, 5) << 40) |
(Get(data, 6) << 48) | (Get(data, 7) << 56);
}
private:
inline static T Get(const uint8_t* data, unsigned int index) {
return static_cast<T>(data[index]);
}
};
template <typename T>
class ByteWriter<T, 8, false> {
public:
static void WriteBigEndian(uint8_t* data, T val) {
static_assert(sizeof(T) >= 8, kSizeErrorMsg);
data[0] = val >> 56;
data[1] = val >> 48;
data[2] = val >> 40;
data[3] = val >> 32;
data[4] = val >> 24;
data[5] = val >> 16;
data[6] = val >> 8;
data[7] = val;
}
static void WriteLittleEndian(uint8_t* data, T val) {
static_assert(sizeof(T) >= 8, kSizeErrorMsg);
data[0] = val;
data[1] = val >> 8;
data[2] = val >> 16;
data[3] = val >> 24;
data[4] = val >> 32;
data[5] = val >> 40;
data[6] = val >> 48;
data[7] = val >> 56;
}
};
#endif // MODULES_RTP_RTCP_SOURCE_BYTE_IO_H_

View File

@@ -4,12 +4,12 @@
#define IP_PACKET_SIZE 1500 // we assume ethernet #define IP_PACKET_SIZE 1500 // we assume ethernet
bool RtcpPacket::OnBufferFull(std::vector<uint8_t>& packet, bool RtcpPacket::OnBufferFull(uint8_t* packet, size_t* index,
PacketReadyCallback callback) const { PacketReadyCallback callback) const {
if (packet.empty()) { if (*index == 0) {
return false; return false;
} }
callback(packet); callback(packet, *index);
return true; return true;
} }

View File

@@ -19,7 +19,8 @@ class RtcpPacket {
// not contain all data in this RtcpPacket; if a packet cannot fit in // not contain all data in this RtcpPacket; if a packet cannot fit in
// max_length bytes, it will be fragmented and multiple calls to this // max_length bytes, it will be fragmented and multiple calls to this
// callback will be made. // callback will be made.
using PacketReadyCallback = std::function<void(std::vector<uint8_t> packet)>; using PacketReadyCallback =
std::function<void(const uint8_t* packet, size_t size)>;
virtual ~RtcpPacket() = default; virtual ~RtcpPacket() = default;
@@ -32,7 +33,7 @@ class RtcpPacket {
// Creates packet in the given buffer at the given position. // Creates packet in the given buffer at the given position.
// Calls PacketReadyCallback::OnPacketReady if remaining buffer is too small // Calls PacketReadyCallback::OnPacketReady if remaining buffer is too small
// and assume buffer can be reused after OnPacketReady returns. // and assume buffer can be reused after OnPacketReady returns.
virtual bool Create(std::vector<uint8_t>& packet, size_t max_length, virtual bool Create(uint8_t* packet, size_t* position, size_t max_length,
PacketReadyCallback callback) const = 0; PacketReadyCallback callback) const = 0;
protected: protected:
@@ -42,14 +43,14 @@ class RtcpPacket {
static void CreateHeader(size_t count_or_format, uint8_t packet_type, static void CreateHeader(size_t count_or_format, uint8_t packet_type,
size_t block_length, // Payload size in 32bit words. size_t block_length, // Payload size in 32bit words.
std::vector<uint8_t>& buffer); uint8_t* buffer, size_t* pos);
static void CreateHeader(size_t count_or_format, uint8_t packet_type, static void CreateHeader(size_t count_or_format, uint8_t packet_type,
size_t block_length, // Payload size in 32bit words. size_t block_length, // Payload size in 32bit words.
bool padding, // True if there are padding bytes. bool padding, // True if there are padding bytes.
std::vector<uint8_t>& buffer); uint8_t* buffer, size_t* pos);
bool OnBufferFull(std::vector<uint8_t>& packet, bool OnBufferFull(uint8_t* packet, size_t* index,
PacketReadyCallback callback) const; PacketReadyCallback callback) const;
// Size of the rtcp packet as written in header. // Size of the rtcp packet as written in header.
size_t HeaderLength() const; size_t HeaderLength() const;

View File

@@ -1,5 +1,6 @@
#include "transport_feedback.h" #include "transport_feedback.h"
#include "byte_io.h"
#include "log.h" #include "log.h"
#include "sequence_number_compare.h" #include "sequence_number_compare.h"
@@ -135,62 +136,63 @@ void TransportFeedback::Clear() {
} }
// Serialize packet. // Serialize packet.
bool TransportFeedback::Create(std::vector<uint8_t>& packet, size_t max_length, bool TransportFeedback::Create(uint8_t* packet, size_t* position,
size_t max_length,
PacketReadyCallback callback) const { PacketReadyCallback callback) const {
if (pkt_stat_cnt_ == 0) return false; if (pkt_stat_cnt_ == 0) return false;
size_t position = packet.size(); while (*position + BlockLength() > max_length) {
while (position + BlockLength() > max_length) { if (!OnBufferFull(packet, position, callback)) return false;
if (!OnBufferFull(packet, callback)) return false;
} }
const size_t position_end = position + BlockLength(); const size_t position_end = *position + BlockLength();
const size_t padding_length = PaddingLength(); const size_t padding_length = PaddingLength();
bool has_padding = padding_length > 0; bool has_padding = padding_length > 0;
CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), has_padding, CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), has_padding,
packet); packet, position);
CreateCommonFeedback(packet + position); CreateCommonFeedback(packet + *position);
position += kCommonFeedbackLength; *position += kCommonFeedbackLength;
ByteWriter<uint16_t>::WriteBigEndian(&packet[position], base_seq_no_); ByteWriter<uint16_t>::WriteBigEndian(&packet[*position], base_seq_no_);
position += 2; *position += 2;
ByteWriter<uint16_t>::WriteBigEndian(&packet[position], pkt_stat_cnt_); ByteWriter<uint16_t>::WriteBigEndian(&packet[*position], pkt_stat_cnt_);
position += 2; *position += 2;
ByteWriter<uint32_t, 3>::WriteBigEndian(&packet[position], base_time_ticks_); ByteWriter<uint32_t, 3>::WriteBigEndian(&packet[*position], ref_time_);
position += 3; *position += 3;
packet[(position)++] = feedback_seq_; packet[(*position)++] = feedback_pkt_cnt_;
for (uint16_t chunk : encoded_chunks_) { for (uint16_t chunk : encoded_chunks_) {
ByteWriter<uint16_t>::WriteBigEndian(&packet[position], chunk); ByteWriter<uint16_t>::WriteBigEndian(&packet[*position], chunk);
position += 2; *position += 2;
} }
if (!last_chunk_.Empty()) { if (!last_chunk_.Empty()) {
uint16_t chunk = last_chunk_.EncodeLast(); uint16_t chunk = last_chunk_.EncodeLast();
ByteWriter<uint16_t>::WriteBigEndian(&packet[position], chunk); ByteWriter<uint16_t>::WriteBigEndian(&packet[*position], chunk);
position += 2; *position += 2;
} }
if (include_timestamps_) {
for (const auto& received_packet : received_packets_) { for (const auto& received_packet : received_packets_) {
int16_t delta = received_packet.delta_ticks(); int16_t delta = received_packet.delta_ticks();
if (delta >= 0 && delta <= 0xFF) { if (delta >= 0 && delta <= 0xFF) {
packet[(position)++] = delta; packet[(*position)++] = delta;
} else { } else {
ByteWriter<int16_t>::WriteBigEndian(&packet[position], delta); ByteWriter<int16_t>::WriteBigEndian(&packet[*position], delta);
position += 2; *position += 2;
}
} }
} }
if (padding_length > 0) { if (padding_length > 0) {
for (size_t i = 0; i < padding_length - 1; ++i) { for (size_t i = 0; i < padding_length - 1; ++i) {
packet[(position)++] = 0; packet[(*position)++] = 0;
} }
packet[(position)++] = padding_length; packet[(*position)++] = padding_length;
}
if (*position != position_end) {
LOG_FATAL("padding_length is too small");
} }
RTC_DCHECK_EQ(position, position_end);
return true; return true;
} }
@@ -213,17 +215,36 @@ void TransportFeedback::SetFeedbackPacketCount(uint8_t feedback_packet_count) {
int64_t TransportFeedback::BaseTime() const { int64_t TransportFeedback::BaseTime() const {
// Add an extra kTimeWrapPeriod to allow add received packets arrived earlier // Add an extra kTimeWrapPeriod to allow add received packets arrived earlier
// than the first added packet (and thus allow to record negative deltas) // than the first added packet (and thus allow to record negative deltas)
// even when base_time_ticks_ == 0. // even when ref_time_ == 0.
return 0 + kTimeWrapPeriod + int64_t{base_time_ticks_} * kBaseTimeTick; return 0 + kTimeWrapPeriod + int64_t{ref_time_} * kBaseTimeTick;
} }
int64_t TransportFeedback::GetBaseDelta(int64_t prev_timestamp) const { int64_t TransportFeedback::GetBaseDelta(int64_t prev_timestamp) const {
int64_t delta = BaseTime() - prev_timestamp; int64_t delta = BaseTime() - prev_timestamp;
// Compensate for wrap around. // Compensate for wrap around.
if (std::abs(delta - kTimeWrapPeriod) < std::abs(delta) { if (std::abs(delta - kTimeWrapPeriod) < std::abs(delta)) {
delta -= kTimeWrapPeriod; // Wrap backwards. delta -= kTimeWrapPeriod; // Wrap backwards.
} else if (std::abs(delta + kTimeWrapPeriod) < std::abs(delta)) { } else if (std::abs(delta + kTimeWrapPeriod) < std::abs(delta)) {
delta += kTimeWrapPeriod; // Wrap forwards. delta += kTimeWrapPeriod; // Wrap forwards.
} }
return delta; return delta;
} }
size_t TransportFeedback::BlockLength() const {
// Round size_bytes_ up to multiple of 32bits.
return (size_bytes_ + 3) & (~static_cast<size_t>(3));
}
size_t TransportFeedback::PaddingLength() const {
return BlockLength() - size_bytes_;
}
void TransportFeedback::ParseCommonFeedback(const uint8_t* payload) {
SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(&payload[0]));
SetMediaSsrc(ByteReader<uint32_t>::ReadBigEndian(&payload[4]));
}
void TransportFeedback::CreateCommonFeedback(uint8_t* payload) const {
ByteWriter<uint32_t>::WriteBigEndian(&payload[0], sender_ssrc());
ByteWriter<uint32_t>::WriteBigEndian(&payload[4], media_ssrc());
}

View File

@@ -131,16 +131,26 @@ class TransportFeedback : public RtcpPacket {
TransportFeedback(); TransportFeedback();
~TransportFeedback(); ~TransportFeedback();
public:
static constexpr uint8_t kPacketType = 205;
public: public:
bool AddReceivedPacket(uint16_t sequence_number, int64_t timestamp); bool AddReceivedPacket(uint16_t sequence_number, int64_t timestamp);
bool AddMissingPackets(size_t num_missing_packets); bool AddMissingPackets(size_t num_missing_packets);
bool AddDeltaSize(DeltaSize delta_size); bool AddDeltaSize(DeltaSize delta_size);
void Clear(); void Clear();
bool Create(std::vector<uint8_t>& packet, size_t max_length, bool Create(uint8_t* packet, size_t* position, size_t max_length,
PacketReadyCallback callback) const override; PacketReadyCallback callback) const override;
public: public:
void SetMediaSsrc(uint32_t ssrc) { media_ssrc_ = ssrc; }
uint32_t media_ssrc() const { return media_ssrc_; }
void ParseCommonFeedback(const uint8_t* payload);
void CreateCommonFeedback(uint8_t* payload) const;
public:
static constexpr size_t kCommonFeedbackLength = 8;
void SetBaseSequenceNumber(uint16_t base_sequence_number); void SetBaseSequenceNumber(uint16_t base_sequence_number);
void SetPacketStatusCount(uint16_t packet_status_count); void SetPacketStatusCount(uint16_t packet_status_count);
void SetReferenceTime(uint32_t reference_time); void SetReferenceTime(uint32_t reference_time);
@@ -148,6 +158,11 @@ class TransportFeedback : public RtcpPacket {
int64_t BaseTime() const; int64_t BaseTime() const;
int64_t GetBaseDelta(int64_t prev_timestamp) const; int64_t GetBaseDelta(int64_t prev_timestamp) const;
size_t BlockLength() const override;
size_t PaddingLength() const;
private:
uint32_t media_ssrc_ = 0;
private: private:
uint16_t base_seq_no_; uint16_t base_seq_no_;