#define MS_CLASS "RTC::RTCP::FeedbackRtpTransport"
#include "RTC/RTCP/FeedbackRtpTransport.hpp"
#include "Logger.hpp"
#include "Utils.hpp"
#include "RTC/SeqManager.hpp"
#include <limits>
#include <sstream>
inline static int32_t parseReferenceTime(uint8_t* buffer)
{
int32_t referenceTime;
uint32_t unsignedVal = Utils::Byte::Get3Bytes(buffer, 0);
const auto msb = static_cast<uint8_t>(unsignedVal >> ((3 - 1) * 8));
if ((msb & 0x80) != 0)
{
static uint32_t usedBitMask = (1 << ((3 % sizeof(int32_t)) * 8)) - 1;
unsignedVal = ~usedBitMask | unsignedVal;
}
static uint32_t unsignedHighestBitMask = static_cast<uint32_t>(1) << ((sizeof(uint32_t) * 8) - 1);
static int32_t signedHighestBitMask = std::numeric_limits<int32_t>::min();
if ((unsignedVal & unsignedHighestBitMask) != 0)
{
referenceTime = static_cast<int32_t>(unsignedVal & ~unsignedHighestBitMask);
referenceTime |= signedHighestBitMask;
}
else
{
referenceTime = static_cast<int32_t>(unsignedVal);
}
return referenceTime;
}
namespace RTC
{
namespace RTCP
{
size_t FeedbackRtpTransportPacket::fixedHeaderSize{ 8u };
uint16_t FeedbackRtpTransportPacket::maxMissingPackets{ (1 << 13) - 1 };
uint16_t FeedbackRtpTransportPacket::maxPacketStatusCount{ (1 << 16) - 1 };
int16_t FeedbackRtpTransportPacket::maxPacketDelta{ 0x7FFF };
std::map<FeedbackRtpTransportPacket::Status, std::string> FeedbackRtpTransportPacket::status2String =
{
{ FeedbackRtpTransportPacket::Status::NotReceived, "NR" },
{ FeedbackRtpTransportPacket::Status::SmallDelta, "SD" },
{ FeedbackRtpTransportPacket::Status::LargeDelta, "LD" }
};
FeedbackRtpTransportPacket* FeedbackRtpTransportPacket::Parse(const uint8_t* data, size_t len)
{
MS_TRACE();
if (len < sizeof(CommonHeader) + sizeof(FeedbackPacket::Header) + FeedbackRtpTransportPacket::fixedHeaderSize)
{
MS_WARN_TAG(rtcp, "not enough space for Feedback packet, discarded");
return nullptr;
}
auto* commonHeader = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));
std::unique_ptr<FeedbackRtpTransportPacket> packet(
new FeedbackRtpTransportPacket(commonHeader, len));
if (!packet->IsCorrect())
return nullptr;
return packet.release();
}
FeedbackRtpTransportPacket::FeedbackRtpTransportPacket(CommonHeader* commonHeader, size_t availableLen)
: FeedbackRtpPacket(commonHeader)
{
MS_TRACE();
size_t len = static_cast<size_t>(ntohs(commonHeader->length) + 1) * 4;
if (len > availableLen)
{
MS_WARN_TAG(rtcp, "packet announced length exceeds the available buffer length, discarded");
this->isCorrect = false;
return;
}
auto* data = reinterpret_cast<uint8_t*>(commonHeader) + sizeof(CommonHeader) +
sizeof(FeedbackPacket::Header);
this->baseSequenceNumber = Utils::Byte::Get2Bytes(data, 0);
this->packetStatusCount = Utils::Byte::Get2Bytes(data, 2);
this->referenceTime = parseReferenceTime(data + 4);
this->feedbackPacketCount = Utils::Byte::Get1Byte(data, 7);
this->size = len;
uint8_t* contentData = data + FeedbackRtpTransportPacket::fixedHeaderSize;
size_t contentLen = len - sizeof(CommonHeader) - sizeof(FeedbackPacket::Header) -
FeedbackRtpTransportPacket::fixedHeaderSize;
size_t offset{ 0u };
uint16_t count{ 0u };
uint16_t receivedPacketStatusCount{ 0u };
while (count < this->packetStatusCount && contentLen > offset)
{
if (contentLen - offset < 2u)
{
MS_WARN_TAG(rtcp, "not enough space for chunk");
this->isCorrect = false;
return;
}
auto* chunk =
Chunk::Parse(contentData + offset, contentLen - offset, this->packetStatusCount - count);
if (!chunk)
{
MS_WARN_TAG(rtcp, "invalid chunk");
this->isCorrect = false;
return;
}
this->chunks.push_back(chunk);
this->deltasAndChunksSize += 2u;
offset += 2u;
count += chunk->GetCount();
receivedPacketStatusCount += chunk->GetReceivedStatusCount();
}
if (count != this->packetStatusCount)
{
MS_WARN_TAG(rtcp, "provided packet status count does not match with content");
this->isCorrect = false;
return;
}
auto chunksIt = this->chunks.begin();
while (chunksIt != this->chunks.end() && contentLen > offset)
{
size_t deltasOffset{ 0u };
auto* chunk = *chunksIt;
if (!chunk->AddDeltas(contentData + offset, contentLen - offset, this->deltas, deltasOffset))
{
MS_WARN_TAG(rtcp, "not enough space for deltas");
this->isCorrect = false;
return;
}
offset += deltasOffset;
this->deltasAndChunksSize += deltasOffset;
++chunksIt;
}
if (this->deltas.size() != receivedPacketStatusCount)
{
MS_WARN_TAG(rtcp, "received deltas does not match received status count");
this->isCorrect = false;
return;
}
}
FeedbackRtpTransportPacket::~FeedbackRtpTransportPacket()
{
MS_TRACE();
for (auto* chunk : this->chunks)
{
delete chunk;
}
this->chunks.clear();
}
void FeedbackRtpTransportPacket::Dump() const
{
MS_TRACE();
MS_DUMP("<FeedbackRtpTransportPacket>");
MS_DUMP(" base sequence : %" PRIu16, this->baseSequenceNumber);
MS_DUMP(" packet status count : %" PRIu16, this->packetStatusCount);
MS_DUMP(" reference time : %" PRIi32, this->referenceTime);
MS_DUMP(" feedback packet count : %" PRIu8, this->feedbackPacketCount);
MS_DUMP(" size : %zu", GetSize());
for (auto* chunk : this->chunks)
{
chunk->Dump();
}
MS_DUMP(" <Deltas>");
for (auto delta : this->deltas)
{
MS_DUMP(" %" PRIi16 " ms", static_cast<int16_t>(delta / 4));
}
MS_DUMP(" </Deltas>");
auto packetResults = GetPacketResults();
MS_DUMP(" <PacketResults>");
for (auto& packetResult : packetResults)
{
if (packetResult.received)
{
MS_DUMP(
" seq:%" PRIu16 ", received:yes, receivedAtMs:%" PRIi64,
packetResult.sequenceNumber,
packetResult.receivedAtMs);
}
else
{
MS_DUMP(" seq:%" PRIu16 ", received:no", packetResult.sequenceNumber);
}
}
MS_DUMP(" </PacketResults>");
MS_DUMP("</FeedbackRtpTransportPacket>");
}
size_t FeedbackRtpTransportPacket::Serialize(uint8_t* buffer)
{
MS_TRACE();
AddPendingChunks();
size_t offset = FeedbackPacket::Serialize(buffer);
Utils::Byte::Set2Bytes(buffer, offset, this->baseSequenceNumber);
offset += 2;
Utils::Byte::Set2Bytes(buffer, offset, this->packetStatusCount);
offset += 2;
Utils::Byte::Set3Bytes(buffer, offset, static_cast<uint32_t>(this->referenceTime));
offset += 3;
Utils::Byte::Set1Byte(buffer, offset, this->feedbackPacketCount);
offset += 1;
for (auto* chunk : this->chunks)
{
offset += chunk->Serialize(buffer + offset);
}
for (auto delta : this->deltas)
{
if (delta >= 0 && delta <= 255)
{
Utils::Byte::Set1Byte(buffer, offset, delta);
offset += 1u;
}
else
{
Utils::Byte::Set2Bytes(buffer, offset, delta);
offset += 2u;
}
}
size_t padding = (-offset) & 3;
for (size_t i{ 0u }; i < padding; ++i)
{
buffer[offset + i] = 0u;
}
offset += padding;
return offset;
}
FeedbackRtpTransportPacket::AddPacketResult FeedbackRtpTransportPacket::AddPacket(
uint16_t sequenceNumber, uint64_t timestamp, size_t maxRtcpPacketLen)
{
MS_TRACE();
MS_ASSERT(!IsFull(), "packet is full");
if (this->latestTimestamp == 0u)
{
this->baseSequenceNumber = sequenceNumber + 1;
this->referenceTime = static_cast<int32_t>((timestamp & 0x1FFFFFC0) / 64);
this->latestSequenceNumber = sequenceNumber;
this->latestTimestamp = (timestamp >> 6) * 64;
return AddPacketResult::SUCCESS;
}
if (!RTC::SeqManager<uint16_t>::IsSeqHigherThan(sequenceNumber, this->latestSequenceNumber))
{
return AddPacketResult::SUCCESS;
}
{
auto missingPackets = sequenceNumber - (this->latestSequenceNumber + 1);
if (missingPackets > FeedbackRtpTransportPacket::maxMissingPackets)
{
MS_WARN_DEV("RTP missing packet number exceeded");
return AddPacketResult::FATAL;
}
}
int64_t delta64 = (timestamp - this->latestTimestamp) * 4;
if (
delta64 > FeedbackRtpTransportPacket::maxPacketDelta ||
delta64 < -1 * static_cast<int64_t>(FeedbackRtpTransportPacket::maxPacketDelta)
)
{
MS_WARN_DEV(
"RTP packet delta exceeded [latestTimestamp:%" PRIu64 ", timestamp:%" PRIu64 "]",
this->latestTimestamp,
timestamp);
return AddPacketResult::FATAL;
}
auto delta = static_cast<int16_t>(delta64);
{
size_t size = FeedbackRtpPacket::GetSize();
size += FeedbackRtpTransportPacket::fixedHeaderSize;
size += this->deltasAndChunksSize;
size += 2u;
size += 2u;
size += (-size) & 3;
if (size > maxRtcpPacketLen)
{
MS_WARN_DEV("maximum packet size exceeded");
return AddPacketResult::MAX_SIZE_EXCEEDED;
}
}
FillChunk(this->latestSequenceNumber, sequenceNumber, delta);
this->latestSequenceNumber = sequenceNumber;
this->latestTimestamp = timestamp;
return AddPacketResult::SUCCESS;
}
void FeedbackRtpTransportPacket::Finish()
{
MS_TRACE();
AddPendingChunks();
}
std::vector<struct FeedbackRtpTransportPacket::PacketResult> FeedbackRtpTransportPacket::GetPacketResults() const
{
MS_TRACE();
std::vector<struct PacketResult> packetResults;
uint16_t currentSequenceNumber = this->baseSequenceNumber - 1;
for (auto* chunk : this->chunks)
{
chunk->FillResults(packetResults, currentSequenceNumber);
}
size_t deltaIdx{ 0u };
int64_t currentReceivedAtMs = static_cast<int64_t>(this->referenceTime * 64);
for (size_t idx{ 0u }; idx < packetResults.size(); ++idx)
{
auto& packetResult = packetResults[idx];
if (!packetResult.received)
continue;
currentReceivedAtMs += this->deltas.at(deltaIdx) / 4;
packetResult.delta = this->deltas.at(deltaIdx);
packetResult.receivedAtMs = currentReceivedAtMs;
deltaIdx++;
}
return packetResults;
}
uint8_t FeedbackRtpTransportPacket::GetPacketFractionLost() const
{
MS_TRACE();
uint16_t expected = this->packetStatusCount;
uint16_t lost{ 0u };
if (expected == 0u)
return 0u;
for (auto* chunk : this->chunks)
{
lost += chunk->GetCount() - chunk->GetReceivedStatusCount();
}
if (lost == expected)
return 255u;
return (lost << 8) / expected;
}
void FeedbackRtpTransportPacket::FillChunk(
uint16_t previousSequenceNumber, uint16_t sequenceNumber, int16_t delta)
{
MS_TRACE();
auto missingPackets = static_cast<uint16_t>(sequenceNumber - (previousSequenceNumber + 1));
if (missingPackets > 0)
{
if (this->context.statuses.size() >= 7 && this->context.allSameStatus)
{
CreateRunLengthChunk(this->context.currentStatus, this->context.statuses.size());
this->context.statuses.clear();
this->context.currentStatus = Status::None;
}
this->context.currentStatus = Status::NotReceived;
size_t representedPackets{ 0u };
for (uint8_t i{ 0u }; i < missingPackets && this->context.statuses.size() < 7; ++i)
{
this->context.statuses.emplace_back(Status::NotReceived);
representedPackets++;
}
if (this->context.statuses.size() == 7)
{
CreateTwoBitVectorChunk(this->context.statuses);
this->context.statuses.clear();
this->context.currentStatus = Status::None;
}
missingPackets -= representedPackets;
if (missingPackets != 0)
{
CreateRunLengthChunk(Status::NotReceived, missingPackets);
this->context.statuses.clear();
this->context.currentStatus = Status::None;
}
}
Status status;
if (delta >= 0 && delta <= 255)
status = Status::SmallDelta;
else
status = Status::LargeDelta;
if (
this->context.statuses.size() >= 7 &&
this->context.allSameStatus &&
status != this->context.currentStatus
)
{
CreateRunLengthChunk(this->context.currentStatus, this->context.statuses.size());
this->context.statuses.clear();
}
this->context.statuses.emplace_back(status);
this->deltas.push_back(delta);
this->deltasAndChunksSize += (status == Status::SmallDelta) ? 1u : 2u;
if (
this->context.currentStatus == Status::None ||
(this->context.allSameStatus && this->context.currentStatus == status)
)
{
this->context.allSameStatus = true;
}
else
{
this->context.allSameStatus = false;
}
this->context.currentStatus = status;
if (this->context.statuses.size() < 7)
{
return;
}
else if (this->context.statuses.size() == 7 && !this->context.allSameStatus)
{
this->context.currentStatus = Status::None;
CreateTwoBitVectorChunk(this->context.statuses);
this->context.statuses.clear();
}
}
void FeedbackRtpTransportPacket::CreateRunLengthChunk(Status status, uint16_t count)
{
auto* chunk = new RunLengthChunk(status, count);
this->chunks.push_back(chunk);
this->packetStatusCount += count;
this->deltasAndChunksSize += 2u;
}
void FeedbackRtpTransportPacket::CreateOneBitVectorChunk(std::vector<Status>& statuses)
{
auto* chunk = new OneBitVectorChunk(statuses);
this->chunks.push_back(chunk);
this->packetStatusCount += static_cast<uint16_t>(statuses.size());
this->deltasAndChunksSize += 2u;
}
void FeedbackRtpTransportPacket::CreateTwoBitVectorChunk(std::vector<Status>& statuses)
{
auto* chunk = new TwoBitVectorChunk(statuses);
this->chunks.push_back(chunk);
this->packetStatusCount += static_cast<uint16_t>(statuses.size());
this->deltasAndChunksSize += 2u;
}
void FeedbackRtpTransportPacket::AddPendingChunks()
{
if (this->context.statuses.empty())
return;
if (this->context.allSameStatus)
{
CreateRunLengthChunk(this->context.currentStatus, this->context.statuses.size());
}
else
{
MS_ASSERT(this->context.statuses.size() < 7, "already 7 status packets present");
CreateTwoBitVectorChunk(this->context.statuses);
}
this->context.statuses.clear();
}
FeedbackRtpTransportPacket::Chunk* FeedbackRtpTransportPacket::Chunk::Parse(
const uint8_t* data, size_t len, uint16_t count)
{
MS_TRACE();
if (len < 2u)
{
MS_WARN_TAG(rtcp, "not enough space for FeedbackRtpTransportPacket chunk, discarded");
return nullptr;
}
auto bytes = Utils::Byte::Get2Bytes(data, 0);
uint8_t chunkType = (bytes >> 15) & 0x01;
if (chunkType == 0)
{
auto* chunk = new RunLengthChunk(bytes);
switch (chunk->GetStatus())
{
case Status::NotReceived:
case Status::SmallDelta:
case Status::LargeDelta:
{
return chunk;
}
default:
{
MS_WARN_DEV("invalid status for a run length chunk");
delete chunk;
return nullptr;
}
}
}
else
{
uint8_t symbolSize = data[0] & 0x40;
if (symbolSize == 0)
return new OneBitVectorChunk(bytes, count);
else
return new TwoBitVectorChunk(bytes, count);
}
return nullptr;
}
FeedbackRtpTransportPacket::RunLengthChunk::RunLengthChunk(uint16_t buffer)
{
MS_TRACE();
this->status = static_cast<Status>((buffer >> 13) & 0x03);
this->count = buffer & 0x1FFF;
}
bool FeedbackRtpTransportPacket::RunLengthChunk::AddDeltas(
const uint8_t* data, size_t len, std::vector<int16_t>& deltas, size_t& offset)
{
MS_TRACE();
if (this->status == Status::NotReceived)
{
return true;
}
else if (this->status == Status::SmallDelta)
{
if (len < this->count * 1u)
{
MS_WARN_TAG(rtcp, "not enough space for small deltas");
return false;
}
for (size_t i{ 0 }; i < this->count; ++i)
{
auto delta = static_cast<int16_t>(Utils::Byte::Get1Byte(data, offset));
deltas.push_back(delta);
offset += 1u;
}
}
else if (this->status == Status::LargeDelta)
{
if (len < this->count * 2u)
{
MS_WARN_TAG(rtcp, "not enough space for large deltas");
return false;
}
for (size_t i{ 0 }; i < this->count; ++i)
{
auto delta = static_cast<int16_t>(Utils::Byte::Get2Bytes(data, offset));
deltas.push_back(delta);
offset += 2u;
}
}
return true;
}
void FeedbackRtpTransportPacket::RunLengthChunk::Dump() const
{
MS_TRACE();
MS_DUMP(" <RunLengthChunk>");
MS_DUMP(" status : %s", FeedbackRtpTransportPacket::status2String[this->status].c_str());
MS_DUMP(" count : %" PRIu16, this->count);
MS_DUMP(" </RunLengthChunk>");
}
uint16_t FeedbackRtpTransportPacket::RunLengthChunk::GetReceivedStatusCount() const
{
MS_TRACE();
if (this->status == Status::SmallDelta || this->status == Status::LargeDelta)
return this->count;
else
return 0u;
}
void FeedbackRtpTransportPacket::RunLengthChunk::FillResults(
std::vector<struct FeedbackRtpTransportPacket::PacketResult>& packetResults,
uint16_t& currentSequenceNumber) const
{
MS_TRACE();
bool received = (this->status == Status::SmallDelta || this->status == Status::LargeDelta);
for (uint16_t count{ 1u }; count <= this->count; ++count)
{
packetResults.emplace_back(++currentSequenceNumber, received);
}
}
size_t FeedbackRtpTransportPacket::RunLengthChunk::Serialize(uint8_t* buffer)
{
MS_TRACE();
uint16_t bytes{ 0x0000 };
bytes |= this->status << 13;
bytes |= this->count & 0x1FFF;
Utils::Byte::Set2Bytes(buffer, 0, bytes);
return 2u;
}
FeedbackRtpTransportPacket::OneBitVectorChunk::OneBitVectorChunk(uint16_t buffer, uint16_t count)
{
MS_TRACE();
MS_ASSERT(buffer & 0x8000, "invalid one bit vector chunk");
for (uint8_t i{ 0u }; i < 14 && count > 0; ++i, --count)
{
auto status = static_cast<Status>((buffer >> (14 - 1 - i)) & 0x01);
this->statuses.emplace_back(status);
}
}
bool FeedbackRtpTransportPacket::OneBitVectorChunk::AddDeltas(
const uint8_t* data, size_t len, std::vector<int16_t>& deltas, size_t& offset)
{
MS_TRACE();
for (auto status : this->statuses)
{
if (status == Status::NotReceived)
{
continue;
}
else if (status == Status::SmallDelta)
{
if (len < 1u)
{
MS_WARN_TAG(rtcp, "not enough space for small delta");
return false;
}
auto delta = static_cast<int16_t>(Utils::Byte::Get1Byte(data, offset));
deltas.push_back(delta);
offset += 1u;
len -= 1u;
continue;
}
else
{
MS_WARN_TAG(rtcp, "invalid status for one bit vector chunk");
return false;
}
}
return true;
}
void FeedbackRtpTransportPacket::OneBitVectorChunk::Dump() const
{
MS_TRACE();
std::ostringstream out;
for (auto status : this->statuses)
{
out << "|" << FeedbackRtpTransportPacket::status2String[status];
}
for (size_t i{ this->statuses.size() }; i < 14; ++i)
{
out << "|--";
}
out << "|";
MS_DUMP(" <OneBitVectorChunk>");
MS_DUMP(" %s", out.str().c_str());
MS_DUMP(" </OneBitVectorChunk>");
}
uint16_t FeedbackRtpTransportPacket::OneBitVectorChunk::GetReceivedStatusCount() const
{
MS_TRACE();
uint16_t count{ 0u };
for (auto status : this->statuses)
{
if (status == Status::SmallDelta || status == Status::LargeDelta)
count++;
}
return count;
}
void FeedbackRtpTransportPacket::OneBitVectorChunk::FillResults(
std::vector<struct FeedbackRtpTransportPacket::PacketResult>& packetResults,
uint16_t& currentSequenceNumber) const
{
MS_TRACE();
for (auto status : this->statuses)
{
bool received = (status == Status::SmallDelta || status == Status::LargeDelta);
packetResults.emplace_back(++currentSequenceNumber, received);
}
}
size_t FeedbackRtpTransportPacket::OneBitVectorChunk::Serialize(uint8_t* buffer)
{
MS_TRACE();
MS_ASSERT(this->statuses.size() <= 14, "packet info size must be 14 or less");
uint16_t bytes{ 0x8000 };
uint8_t i{ 13u };
for (auto status : this->statuses)
{
bytes |= status << i;
i -= 1;
}
Utils::Byte::Set2Bytes(buffer, 0, bytes);
return 2u;
}
FeedbackRtpTransportPacket::TwoBitVectorChunk::TwoBitVectorChunk(uint16_t buffer, uint16_t count)
{
MS_TRACE();
MS_ASSERT(buffer & 0xC000, "invalid two bit vector chunk");
for (uint8_t i{ 0u }; i < 7 && count > 0; ++i, --count)
{
auto status = static_cast<Status>((buffer >> 2 * (7 - 1 - i)) & 0x03);
this->statuses.emplace_back(status);
}
}
bool FeedbackRtpTransportPacket::TwoBitVectorChunk::AddDeltas(
const uint8_t* data, size_t len, std::vector<int16_t>& deltas, size_t& offset)
{
MS_TRACE();
for (auto status : this->statuses)
{
if (status == Status::NotReceived)
{
continue;
}
else if (status == Status::SmallDelta)
{
if (len < 1u)
{
MS_WARN_TAG(rtcp, "not enough space for small delta");
return false;
}
auto delta = static_cast<int16_t>(Utils::Byte::Get1Byte(data, offset));
deltas.push_back(delta);
offset += 1u;
len -= 1u;
continue;
}
else if (status == Status::LargeDelta)
{
if (len < 2u)
{
MS_WARN_TAG(rtcp, "not enough space for large delta");
return false;
}
auto delta = static_cast<int16_t>(Utils::Byte::Get2Bytes(data, offset));
deltas.push_back(delta);
offset += 2u;
len -= 2u;
continue;
}
}
return true;
}
void FeedbackRtpTransportPacket::TwoBitVectorChunk::Dump() const
{
MS_TRACE();
std::ostringstream out;
for (auto status : this->statuses)
{
out << "|" << FeedbackRtpTransportPacket::status2String[status];
}
for (size_t i{ this->statuses.size() }; i < 7; ++i)
{
out << "|--";
}
out << "|";
MS_DUMP(" <TwoBitVectorChunk>");
MS_DUMP(" %s", out.str().c_str());
MS_DUMP(" </TwoBitVectorChunk>");
}
uint16_t FeedbackRtpTransportPacket::TwoBitVectorChunk::GetReceivedStatusCount() const
{
MS_TRACE();
uint16_t count{ 0u };
for (auto status : this->statuses)
{
if (status == Status::SmallDelta || status == Status::LargeDelta)
count++;
}
return count;
}
void FeedbackRtpTransportPacket::TwoBitVectorChunk::FillResults(
std::vector<struct FeedbackRtpTransportPacket::PacketResult>& packetResults,
uint16_t& currentSequenceNumber) const
{
MS_TRACE();
for (auto status : this->statuses)
{
bool received = (status == Status::SmallDelta || status == Status::LargeDelta);
packetResults.emplace_back(++currentSequenceNumber, received);
}
}
size_t FeedbackRtpTransportPacket::TwoBitVectorChunk::Serialize(uint8_t* buffer)
{
MS_TRACE();
MS_ASSERT(this->statuses.size() <= 7, "packet info size must be 7 or less");
uint16_t bytes{ 0x8000 };
uint8_t i{ 12u };
bytes |= 0x01 << 14;
for (auto status : this->statuses)
{
bytes |= status << i;
i -= 2;
}
Utils::Byte::Set2Bytes(buffer, 0, bytes);
return 2u;
}
} }