diff --git a/src/common/sequence_number_compare.h b/src/common/sequence_number_compare.h new file mode 100644 index 0000000..596666f --- /dev/null +++ b/src/common/sequence_number_compare.h @@ -0,0 +1,44 @@ +#include +#include +#include + +template +inline bool IsNewer(U value, U prev_value) { + static_assert(!std::numeric_limits::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::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(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; +} \ No newline at end of file diff --git a/src/common/sequence_number_unwrapper.h b/src/common/sequence_number_unwrapper.h deleted file mode 100644 index a63617f..0000000 --- a/src/common/sequence_number_unwrapper.h +++ /dev/null @@ -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 - -#include -#include - -// A sequence number unwrapper where the first unwrapped value equals the -// first value being unwrapped. -template -class SeqNumUnwrapper { - static_assert( - std::is_unsigned::value && - std::numeric_limits::max() < std::numeric_limits::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::max()} + 1 : M; - int64_t result = ForwardDiff(last_value, new_value); - if (!AheadOrAt(new_value, last_value)) { - result -= kBackwardAdjustment; - } - return result; - } - - int64_t last_unwrapped_ = 0; - std::unique_ptr last_value_; -}; - -using RtpTimestampUnwrapper = SeqNumUnwrapper; -using RtpSeqNumUnwrapper = SeqNumUnwrapper; - -#endif \ No newline at end of file diff --git a/src/rtcp/rtcp_packet.cpp b/src/rtcp/rtcp_packet.cpp new file mode 100644 index 0000000..37d5e56 --- /dev/null +++ b/src/rtcp/rtcp_packet.cpp @@ -0,0 +1,66 @@ +#include "rtcp_packet.h" + +#include "log.h" + +#define IP_PACKET_SIZE 1500 // we assume ethernet + +bool RtcpPacket::OnBufferFull(std::vector& 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(count_or_format); + buffer[*pos + 1] = packet_type; + buffer[*pos + 2] = (length >> 8) & 0xff; + buffer[*pos + 3] = length & 0xff; + *pos += kHeaderLength; +} diff --git a/src/rtcp/rtcp_packet.h b/src/rtcp/rtcp_packet.h new file mode 100644 index 0000000..e1341b6 --- /dev/null +++ b/src/rtcp/rtcp_packet.h @@ -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 +#include + +#include +#include + +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 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& 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& 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& buffer); + + bool OnBufferFull(std::vector& packet, + PacketReadyCallback callback) const; + // Size of the rtcp packet as written in header. + size_t HeaderLength() const; + + private: + uint32_t sender_ssrc_ = 0; +}; + +#endif \ No newline at end of file diff --git a/src/rtcp/rtcp_tcc.cpp b/src/rtcp/rtcp_tcc.cpp deleted file mode 100644 index 03ae599..0000000 --- a/src/rtcp/rtcp_tcc.cpp +++ /dev/null @@ -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 &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_; -} \ No newline at end of file diff --git a/src/rtcp/rtcp_tcc.h b/src/rtcp/rtcp_tcc.h deleted file mode 100644 index 268613d..0000000 --- a/src/rtcp/rtcp_tcc.h +++ /dev/null @@ -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 - -#include "rtcp_header.h" -#include "rtcp_typedef.h" - -class RtcpTcc { - public: - RtcpTcc(); - ~RtcpTcc(); - - public: - void SetReportBlock(RtcpReportBlock &rtcp_report_block); - void SetReportBlock(std::vector &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 reports_; - - // Entire RTCP buffer - uint8_t *buffer_ = nullptr; - size_t size_ = 0; -}; - -#endif \ No newline at end of file diff --git a/src/rtcp/transport_feedback.cpp b/src/rtcp/transport_feedback.cpp new file mode 100644 index 0000000..6ad0e24 --- /dev/null +++ b/src/rtcp/transport_feedback.cpp @@ -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(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& 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::WriteBigEndian(&packet[position], base_seq_no_); + position += 2; + + ByteWriter::WriteBigEndian(&packet[position], pkt_stat_cnt_); + position += 2; + + ByteWriter::WriteBigEndian(&packet[position], base_time_ticks_); + position += 3; + + packet[(position)++] = feedback_seq_; + + for (uint16_t chunk : encoded_chunks_) { + ByteWriter::WriteBigEndian(&packet[position], chunk); + position += 2; + } + if (!last_chunk_.Empty()) { + uint16_t chunk = last_chunk_.EncodeLast(); + ByteWriter::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::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; +} \ No newline at end of file diff --git a/src/rtcp/transport_feedback.h b/src/rtcp/transport_feedback.h new file mode 100644 index 0000000..7ffef8c --- /dev/null +++ b/src/rtcp/transport_feedback.h @@ -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 +#include + +#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* 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 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& 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 received_packets_; + std::vector all_packets_; + // All but last encoded packet chunks. + std::vector encoded_chunks_; + LastChunk last_chunk_; + size_t size_bytes_; +}; + +#endif \ No newline at end of file diff --git a/xmake.lua b/xmake.lua index 315113d..b266e93 100644 --- a/xmake.lua +++ b/xmake.lua @@ -78,7 +78,7 @@ target("statistics") target("rtcp") set_kind("object") - add_deps("log") + add_deps("log", "common") add_files("src/rtcp/*.cpp") add_includedirs("src/rtcp", {public = true})