[feat] add rtcp packet module

This commit is contained in:
dijunkun
2024-12-11 17:34:51 +08:00
parent b29f3de868
commit bacf62c6b8
9 changed files with 569 additions and 178 deletions

View File

@@ -0,0 +1,44 @@
#include <cstddef>
#include <cstdint>
#include <limits>
template <typename U>
inline bool IsNewer(U value, U prev_value) {
static_assert(!std::numeric_limits<U>::is_signed, "U must be unsigned");
// kBreakpoint is the half-way mark for the type U. For instance, for a
// uint16_t it will be 0x8000, and for a uint32_t, it will be 0x8000000.
constexpr U kBreakpoint = (std::numeric_limits<U>::max() >> 1) + 1;
// Distinguish between elements that are exactly kBreakpoint apart.
// If t1>t2 and |t1-t2| = kBreakpoint: IsNewer(t1,t2)=true,
// IsNewer(t2,t1)=false
// rather than having IsNewer(t1,t2) = IsNewer(t2,t1) = false.
if (value - prev_value == kBreakpoint) {
return value > prev_value;
}
return value != prev_value &&
static_cast<U>(value - prev_value) < kBreakpoint;
}
// NB: Doesn't fulfill strict weak ordering requirements.
// Mustn't be used as std::map Compare function.
inline bool IsNewerSequenceNumber(uint16_t sequence_number,
uint16_t prev_sequence_number) {
return IsNewer(sequence_number, prev_sequence_number);
}
// NB: Doesn't fulfill strict weak ordering requirements.
// Mustn't be used as std::map Compare function.
inline bool IsNewerTimestamp(uint32_t timestamp, uint32_t prev_timestamp) {
return IsNewer(timestamp, prev_timestamp);
}
inline uint16_t LatestSequenceNumber(uint16_t sequence_number1,
uint16_t sequence_number2) {
return IsNewerSequenceNumber(sequence_number1, sequence_number2)
? sequence_number1
: sequence_number2;
}
inline uint32_t LatestTimestamp(uint32_t timestamp1, uint32_t timestamp2) {
return IsNewerTimestamp(timestamp1, timestamp2) ? timestamp1 : timestamp2;
}

View File

@@ -1,70 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-08-21
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SEQUENCE_NUMBER_UNWRAPPER_H_
#define _SEQUENCE_NUMBER_UNWRAPPER_H_
#include <stdint.h>
#include <limits>
#include <type_traits>
// A sequence number unwrapper where the first unwrapped value equals the
// first value being unwrapped.
template <typename T, T M = 0>
class SeqNumUnwrapper {
static_assert(
std::is_unsigned<T>::value &&
std::numeric_limits<T>::max() < std::numeric_limits<int64_t>::max(),
"Type unwrapped must be an unsigned integer smaller than int64_t.");
public:
// Unwraps `value` and updates the internal state of the unwrapper.
int64_t Unwrap(T value) {
if (!last_value_) {
last_unwrapped_ = {value};
} else {
last_unwrapped_ += Delta(*last_value_, value);
}
last_value_ = value;
return last_unwrapped_;
}
// Returns the `value` without updating the internal state of the unwrapper.
int64_t PeekUnwrap(T value) const {
if (!last_value_) {
return value;
}
return last_unwrapped_ + Delta(*last_value_, value);
}
// Resets the unwrapper to its initial state. Unwrapped sequence numbers will
// being at 0 after resetting.
void Reset() {
last_unwrapped_ = 0;
last_value_.reset();
}
private:
static int64_t Delta(T last_value, T new_value) {
constexpr int64_t kBackwardAdjustment =
M == 0 ? int64_t{std::numeric_limits<T>::max()} + 1 : M;
int64_t result = ForwardDiff<T, M>(last_value, new_value);
if (!AheadOrAt<T, M>(new_value, last_value)) {
result -= kBackwardAdjustment;
}
return result;
}
int64_t last_unwrapped_ = 0;
std::unique_ptr<T> last_value_;
};
using RtpTimestampUnwrapper = SeqNumUnwrapper<uint32_t>;
using RtpSeqNumUnwrapper = SeqNumUnwrapper<uint16_t>;
#endif

66
src/rtcp/rtcp_packet.cpp Normal file
View File

@@ -0,0 +1,66 @@
#include "rtcp_packet.h"
#include "log.h"
#define IP_PACKET_SIZE 1500 // we assume ethernet
bool RtcpPacket::OnBufferFull(std::vector<uint8_t>& packet,
PacketReadyCallback callback) const {
if (packet.empty()) {
return false;
}
callback(packet);
return true;
}
size_t RtcpPacket::HeaderLength() const {
size_t length_in_bytes = BlockLength();
if (length_in_bytes <= 0) {
LOG_FATAL("length_in_bytes must be a positive value");
return -1;
}
if (length_in_bytes % 4 != 0) {
LOG_FATAL("Padding must be handled by each subclass, length_in_bytes [{}]",
length_in_bytes);
return -1;
}
// Length in 32-bit words without common header.
return (length_in_bytes - kHeaderLength) / 4;
}
// From RFC 3550, RTP: A Transport Protocol for Real-Time Applications.
//
// RTP header format.
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |V=2|P| RC/FMT | PT | length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
void RtcpPacket::CreateHeader(
size_t count_or_format, // Depends on packet type.
uint8_t packet_type, size_t length, uint8_t* buffer, size_t* pos) {
CreateHeader(count_or_format, packet_type, length, /*padding=*/false, buffer,
pos);
}
void RtcpPacket::CreateHeader(
size_t count_or_format, // Depends on packet type.
uint8_t packet_type, size_t length, bool padding, uint8_t* buffer,
size_t* pos) {
if (length >= 0xffffU) {
LOG_FATAL("length must be less than 0xffffU");
}
if (count_or_format >= 0x1f) {
LOG_FATAL("count_or_format must be less than 0x1f");
}
constexpr uint8_t kVersionBits = 2 << 6;
uint8_t padding_bit = padding ? 1 << 5 : 0;
buffer[*pos + 0] =
kVersionBits | padding_bit | static_cast<uint8_t>(count_or_format);
buffer[*pos + 1] = packet_type;
buffer[*pos + 2] = (length >> 8) & 0xff;
buffer[*pos + 3] = length & 0xff;
*pos += kHeaderLength;
}

61
src/rtcp/rtcp_packet.h Normal file
View File

@@ -0,0 +1,61 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-12-11
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _RTCP_PACKET_H_
#define _RTCP_PACKET_H_
#include <stddef.h>
#include <stdint.h>
#include <functional>
#include <vector>
class RtcpPacket {
public:
// Callback used to signal that an RTCP packet is ready. Note that this may
// 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
// callback will be made.
using PacketReadyCallback = std::function<void(std::vector<uint8_t> packet)>;
virtual ~RtcpPacket() = default;
void SetSenderSsrc(uint32_t ssrc) { sender_ssrc_ = ssrc; }
uint32_t sender_ssrc() const { return sender_ssrc_; }
// Size of this packet in bytes (including headers).
virtual size_t BlockLength() const = 0;
// Creates packet in the given buffer at the given position.
// Calls PacketReadyCallback::OnPacketReady if remaining buffer is too small
// and assume buffer can be reused after OnPacketReady returns.
virtual bool Create(std::vector<uint8_t>& packet, size_t max_length,
PacketReadyCallback callback) const = 0;
protected:
// Size of the rtcp common header.
static constexpr size_t kHeaderLength = 4;
RtcpPacket() {}
static void CreateHeader(size_t count_or_format, uint8_t packet_type,
size_t block_length, // Payload size in 32bit words.
std::vector<uint8_t>& buffer);
static void CreateHeader(size_t count_or_format, uint8_t packet_type,
size_t block_length, // Payload size in 32bit words.
bool padding, // True if there are padding bytes.
std::vector<uint8_t>& buffer);
bool OnBufferFull(std::vector<uint8_t>& packet,
PacketReadyCallback callback) const;
// Size of the rtcp packet as written in header.
size_t HeaderLength() const;
private:
uint32_t sender_ssrc_ = 0;
};
#endif

View File

@@ -1,30 +0,0 @@
#include "rtcp_tcc.h"
RtcpTcc::RtcpTcc() {
buffer_ = new uint8_t[DEFAULT_RR_SIZE];
size_ = DEFAULT_RR_SIZE;
}
RtcpTcc::~RtcpTcc() {
if (buffer_) {
delete buffer_;
buffer_ = nullptr;
}
size_ = 0;
}
void RtcpTcc::SetReportBlock(RtcpReportBlock &rtcp_report_block) {
reports_.push_back(rtcp_report_block);
}
void RtcpTcc::SetReportBlock(std::vector<RtcpReportBlock> &rtcp_report_blocks) {
reports_ = rtcp_report_blocks;
}
const uint8_t *RtcpTcc::Encode() {
rtcp_header_.Encode(DEFAULT_RTCP_VERSION, 0, DEFAULT_RR_BLOCK_NUM,
RTCP_TYPE::RR, DEFAULT_RR_SIZE, buffer_);
return buffer_;
}

View File

@@ -1,77 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-12-06
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _RTCP_TCC_H_
#define _RTCP_TCC_H_
// RR
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |V=2|P| FMT=15 | PT=205 | length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC of packet sender |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC of media source |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | base sequence number | packet status count |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | reference time | fb pkt. count |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | packet chunk | packet chunk |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// . .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | packet chunk | recv delta | recv delta |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// . .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | recv delta | recv delta | zero padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RTP transport sequence number
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | 0xBE | 0xDE | length=1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | L=1 |transport-wide sequence number | zero padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#include <vector>
#include "rtcp_header.h"
#include "rtcp_typedef.h"
class RtcpTcc {
public:
RtcpTcc();
~RtcpTcc();
public:
void SetReportBlock(RtcpReportBlock &rtcp_report_block);
void SetReportBlock(std::vector<RtcpReportBlock> &rtcp_report_blocks);
public:
const uint8_t *Encode();
size_t Decode();
// Entire RTP buffer
const uint8_t *Buffer() const { return buffer_; }
size_t Size() const { return size_; }
private:
RtcpHeader rtcp_header_;
std::vector<RtcpReportBlock> reports_;
// Entire RTCP buffer
uint8_t *buffer_ = nullptr;
size_t size_ = 0;
};
#endif

View File

@@ -0,0 +1,229 @@
#include "transport_feedback.h"
#include "log.h"
#include "sequence_number_compare.h"
// Header size:
// * 4 bytes Common RTCP Packet Header
// * 8 bytes Common Packet Format for RTCP Feedback Messages
// * 8 bytes FeedbackPacket header
constexpr size_t kTransportFeedbackHeaderSizeBytes = 4 + 8 + 8;
constexpr size_t kChunkSizeBytes = 2;
// TODO(sprang): Add support for dynamic max size for easier fragmentation,
// eg. set it to what's left in the buffer or IP_PACKET_SIZE.
// Size constraint imposed by RTCP common header: 16bit size field interpreted
// as number of four byte words minus the first header word.
constexpr size_t kMaxSizeBytes = (1 << 16) * 4;
// Payload size:
// * 8 bytes Common Packet Format for RTCP Feedback Messages
// * 8 bytes FeedbackPacket header.
// * 2 bytes for one chunk.
constexpr size_t kMinPayloadSizeBytes = 8 + 8 + 2;
constexpr int64_t kBaseTimeTick =
TransportFeedback::kDeltaTick * (1 << 8); // 64ms
constexpr int64_t kTimeWrapPeriod = kBaseTimeTick * (1 << 24); // 12.43days
TransportFeedback::TransportFeedback()
: base_seq_no_(0), pkt_stat_cnt_(0), ref_time_(0), feedback_pkt_cnt_(0) {}
TransportFeedback::~TransportFeedback() {}
bool TransportFeedback::AddReceivedPacket(uint16_t sequence_number,
int64_t timestamp) {
int16_t delta = 0;
// Convert to ticks and round.
if (last_ts_ > timestamp) {
timestamp += (last_ts_ - timestamp) + kTimeWrapPeriod;
}
int64_t delta_full = timestamp - last_ts_ % kTimeWrapPeriod;
if (delta_full > kTimeWrapPeriod / 2) {
delta_full -= kTimeWrapPeriod;
delta_full -= kDeltaTick / 2;
} else {
delta_full += kDeltaTick / 2;
}
delta_full /= kDeltaTick;
delta = static_cast<int16_t>(delta_full);
// If larger than 16bit signed, we can't represent it - need new fb packet.
if (delta != delta_full) {
LOG_WARN("Delta value too large ( >= 2^16 ticks )");
return false;
}
uint16_t next_seq_no = base_seq_no_ + pkt_stat_cnt_;
if (sequence_number != next_seq_no) {
uint16_t last_seq_no = next_seq_no - 1;
if (!IsNewerSequenceNumber(sequence_number, last_seq_no)) return false;
uint16_t num_missing_packets = sequence_number - next_seq_no;
if (!AddMissingPackets(num_missing_packets)) return false;
}
DeltaSize delta_size = (delta >= 0 && delta <= 0xff) ? 1 : 2;
if (!AddDeltaSize(delta_size)) return false;
received_packets_.emplace_back(sequence_number, delta);
last_ts_ += delta * kDeltaTick;
size_bytes_ += delta_size;
return true;
}
bool TransportFeedback::AddMissingPackets(size_t num_missing_packets) {
size_t new_num_seq_no = pkt_stat_cnt_ + num_missing_packets;
if (new_num_seq_no > kMaxReportedPackets) {
return false;
}
if (!last_chunk_.Empty()) {
while (num_missing_packets > 0 && last_chunk_.CanAdd(0)) {
last_chunk_.Add(0);
--num_missing_packets;
}
if (num_missing_packets == 0) {
pkt_stat_cnt_ = new_num_seq_no;
return true;
}
encoded_chunks_.push_back(last_chunk_.Emit());
}
size_t full_chunks = num_missing_packets / LastChunk::kMaxRunLengthCapacity;
size_t partial_chunk = num_missing_packets % LastChunk::kMaxRunLengthCapacity;
size_t num_chunks = full_chunks + (partial_chunk > 0 ? 1 : 0);
if (size_bytes_ + kChunkSizeBytes * num_chunks > kMaxSizeBytes) {
pkt_stat_cnt_ = (new_num_seq_no - num_missing_packets);
return false;
}
size_bytes_ += kChunkSizeBytes * num_chunks;
// T = 0, S = 0, run length = kMaxRunLengthCapacity, see EncodeRunLength().
encoded_chunks_.insert(encoded_chunks_.end(), full_chunks,
LastChunk::kMaxRunLengthCapacity);
last_chunk_.AddMissingPackets(partial_chunk);
pkt_stat_cnt_ = new_num_seq_no;
return true;
}
bool TransportFeedback::AddDeltaSize(DeltaSize delta_size) {
if (pkt_stat_cnt_ == kMaxReportedPackets) return false;
size_t add_chunk_size = last_chunk_.Empty() ? kChunkSizeBytes : 0;
if (size_bytes_ + delta_size + add_chunk_size > kMaxSizeBytes) return false;
if (last_chunk_.CanAdd(delta_size)) {
size_bytes_ += add_chunk_size;
last_chunk_.Add(delta_size);
++pkt_stat_cnt_;
return true;
}
if (size_bytes_ + delta_size + kChunkSizeBytes > kMaxSizeBytes) return false;
encoded_chunks_.push_back(last_chunk_.Emit());
size_bytes_ += kChunkSizeBytes;
last_chunk_.Add(delta_size);
++pkt_stat_cnt_;
return true;
}
void TransportFeedback::Clear() {
pkt_stat_cnt_ = 0;
last_ts_ = BaseTime();
received_packets_.clear();
all_packets_.clear();
encoded_chunks_.clear();
last_chunk_.Clear();
size_bytes_ = kTransportFeedbackHeaderSizeBytes;
}
// Serialize packet.
bool TransportFeedback::Create(std::vector<uint8_t>& packet, size_t max_length,
PacketReadyCallback callback) const {
if (pkt_stat_cnt_ == 0) return false;
size_t position = packet.size();
while (position + BlockLength() > max_length) {
if (!OnBufferFull(packet, callback)) return false;
}
const size_t position_end = position + BlockLength();
const size_t padding_length = PaddingLength();
bool has_padding = padding_length > 0;
CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), has_padding,
packet);
CreateCommonFeedback(packet + position);
position += kCommonFeedbackLength;
ByteWriter<uint16_t>::WriteBigEndian(&packet[position], base_seq_no_);
position += 2;
ByteWriter<uint16_t>::WriteBigEndian(&packet[position], pkt_stat_cnt_);
position += 2;
ByteWriter<uint32_t, 3>::WriteBigEndian(&packet[position], base_time_ticks_);
position += 3;
packet[(position)++] = feedback_seq_;
for (uint16_t chunk : encoded_chunks_) {
ByteWriter<uint16_t>::WriteBigEndian(&packet[position], chunk);
position += 2;
}
if (!last_chunk_.Empty()) {
uint16_t chunk = last_chunk_.EncodeLast();
ByteWriter<uint16_t>::WriteBigEndian(&packet[position], chunk);
position += 2;
}
if (include_timestamps_) {
for (const auto& received_packet : received_packets_) {
int16_t delta = received_packet.delta_ticks();
if (delta >= 0 && delta <= 0xFF) {
packet[(position)++] = delta;
} else {
ByteWriter<int16_t>::WriteBigEndian(&packet[position], delta);
position += 2;
}
}
}
if (padding_length > 0) {
for (size_t i = 0; i < padding_length - 1; ++i) {
packet[(position)++] = 0;
}
packet[(position)++] = padding_length;
}
RTC_DCHECK_EQ(position, position_end);
return true;
}
void TransportFeedback::SetBaseSequenceNumber(uint16_t base_sequence) {
base_seq_no_ = base_sequence;
}
void TransportFeedback::SetPacketStatusCount(uint16_t packet_status_count) {
pkt_stat_cnt_ = packet_status_count;
}
void TransportFeedback::SetReferenceTime(uint32_t reference_time) {
ref_time_ = reference_time;
}
void TransportFeedback::SetFeedbackPacketCount(uint8_t feedback_packet_count) {
feedback_pkt_cnt_ = feedback_packet_count;
}
int64_t TransportFeedback::BaseTime() const {
// Add an extra kTimeWrapPeriod to allow add received packets arrived earlier
// than the first added packet (and thus allow to record negative deltas)
// even when base_time_ticks_ == 0.
return 0 + kTimeWrapPeriod + int64_t{base_time_ticks_} * kBaseTimeTick;
}
int64_t TransportFeedback::GetBaseDelta(int64_t prev_timestamp) const {
int64_t delta = BaseTime() - prev_timestamp;
// Compensate for wrap around.
if (std::abs(delta - kTimeWrapPeriod) < std::abs(delta) {
delta -= kTimeWrapPeriod; // Wrap backwards.
} else if (std::abs(delta + kTimeWrapPeriod) < std::abs(delta)) {
delta += kTimeWrapPeriod; // Wrap forwards.
}
return delta;
}

View File

@@ -0,0 +1,168 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-12-11
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _TRANSPORT_FEEDBACK_H_
#define _TRANSPORT_FEEDBACK_H_
// RR
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |V=2|P| FMT=15 | PT=205 | length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC of packet sender |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC of media source |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | base sequence number | packet status count |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | reference time | fb pkt. count |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | packet chunk | packet chunk |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// . .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | packet chunk | recv delta | recv delta |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// . .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | recv delta | recv delta | zero padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// RTP transport sequence number
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | 0xBE | 0xDE | length=1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | L=1 |transport-wide sequence number | zero padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#include <array>
#include <vector>
#include "rtcp_header.h"
#include "rtcp_packet.h"
#include "rtcp_typedef.h"
class TransportFeedback : public RtcpPacket {
public:
class ReceivedPacket {
public:
ReceivedPacket(uint16_t sequence_number, int16_t delta_ticks)
: sequence_number_(sequence_number), delta_ticks_(delta_ticks) {}
ReceivedPacket(const ReceivedPacket&) = default;
ReceivedPacket& operator=(const ReceivedPacket&) = default;
uint16_t sequence_number() const { return sequence_number_; }
int16_t delta_ticks() const { return delta_ticks_; }
int64_t delta() const { return delta_ticks_ * kDeltaTick; }
private:
uint16_t sequence_number_;
int16_t delta_ticks_;
};
static constexpr uint8_t kFeedbackMessageType = 15;
static constexpr int64_t kDeltaTick = 250; // 0.25ms
// Maximum number of packets (including missing) TransportFeedback can report.
static constexpr size_t kMaxReportedPackets = 0xffff;
private:
// Size in bytes of a delta time in rtcp packet.
// Valid values are 0 (packet wasn't received), 1 or 2.
using DeltaSize = uint8_t;
// Keeps DeltaSizes that can be encoded into single chunk if it is last chunk.
class LastChunk {
public:
using DeltaSize = TransportFeedback::DeltaSize;
static constexpr size_t kMaxRunLengthCapacity = 0x1fff;
LastChunk();
bool Empty() const;
void Clear();
// Return if delta sizes still can be encoded into single chunk with added
// `delta_size`.
bool CanAdd(DeltaSize delta_size) const;
// Add `delta_size`, assumes `CanAdd(delta_size)`,
void Add(DeltaSize delta_size);
// Equivalent to calling Add(0) `num_missing` times. Assumes `Empty()`.
void AddMissingPackets(size_t num_missing);
// Encode chunk as large as possible removing encoded delta sizes.
// Assume CanAdd() == false for some valid delta_size.
uint16_t Emit();
// Encode all stored delta_sizes into single chunk, pad with 0s if needed.
uint16_t EncodeLast() const;
// Decode up to `max_size` delta sizes from `chunk`.
void Decode(uint16_t chunk, size_t max_size);
// Appends content of the Lastchunk to `deltas`.
void AppendTo(std::vector<DeltaSize>* deltas) const;
private:
static constexpr size_t kMaxOneBitCapacity = 14;
static constexpr size_t kMaxTwoBitCapacity = 7;
static constexpr size_t kMaxVectorCapacity = kMaxOneBitCapacity;
static constexpr DeltaSize kLarge = 2;
uint16_t EncodeOneBit() const;
void DecodeOneBit(uint16_t chunk, size_t max_size);
uint16_t EncodeTwoBit(size_t size) const;
void DecodeTwoBit(uint16_t chunk, size_t max_size);
uint16_t EncodeRunLength() const;
void DecodeRunLength(uint16_t chunk, size_t max_size);
std::array<DeltaSize, kMaxVectorCapacity> delta_sizes_;
size_t size_;
bool all_same_;
bool has_large_delta_;
};
public:
TransportFeedback();
~TransportFeedback();
public:
bool AddReceivedPacket(uint16_t sequence_number, int64_t timestamp);
bool AddMissingPackets(size_t num_missing_packets);
bool AddDeltaSize(DeltaSize delta_size);
void Clear();
bool Create(std::vector<uint8_t>& packet, size_t max_length,
PacketReadyCallback callback) const override;
public:
void SetBaseSequenceNumber(uint16_t base_sequence_number);
void SetPacketStatusCount(uint16_t packet_status_count);
void SetReferenceTime(uint32_t reference_time);
void SetFeedbackPacketCount(uint8_t feedback_packet_count);
int64_t BaseTime() const;
int64_t GetBaseDelta(int64_t prev_timestamp) const;
private:
uint16_t base_seq_no_;
uint16_t pkt_stat_cnt_;
uint32_t ref_time_;
uint8_t feedback_pkt_cnt_;
int64_t last_ts_;
std::vector<ReceivedPacket> received_packets_;
std::vector<ReceivedPacket> all_packets_;
// All but last encoded packet chunks.
std::vector<uint16_t> encoded_chunks_;
LastChunk last_chunk_;
size_t size_bytes_;
};
#endif

View File

@@ -78,7 +78,7 @@ target("statistics")
target("rtcp") target("rtcp")
set_kind("object") set_kind("object")
add_deps("log") add_deps("log", "common")
add_files("src/rtcp/*.cpp") add_files("src/rtcp/*.cpp")
add_includedirs("src/rtcp", {public = true}) add_includedirs("src/rtcp", {public = true})