diff --git a/CMakeLists.txt b/CMakeLists.txt index ca0f12e52..678ee56d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ set(LIBDATACHANNEL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/channel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/configuration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/datachannel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/dependencydescriptor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/description.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/mediahandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/global.cpp @@ -93,6 +94,7 @@ set(LIBDATACHANNEL_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/channel.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/configuration.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/datachannel.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/dependencydescriptor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/description.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediahandler.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpreceivingsession.hpp diff --git a/include/rtc/dependencydescriptor.hpp b/include/rtc/dependencydescriptor.hpp new file mode 100644 index 000000000..16a86d451 --- /dev/null +++ b/include/rtc/dependencydescriptor.hpp @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2024 Shigemasa Watanabe (Wandbox) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef RTC_DEPENDENCY_DESCRIPTOR_H +#define RTC_DEPENDENCY_DESCRIPTOR_H + +#include +#include +#include +#include + +namespace rtc { + +struct BitWriter { + static BitWriter fromSizeBits(std::byte *buf, size_t offsetBits, size_t sizeBits); + static BitWriter fromNull(); + + size_t getWrittenBits() const; + + bool write(uint64_t v, size_t bits); + // Write non-symmetric unsigned encoded integer + // ref: https://aomediacodec.github.io/av1-rtp-spec/#a82-syntax + bool writeNonSymmetric(uint64_t v, uint64_t n); + +private: + size_t writePartialByte(uint8_t *p, size_t offset, uint64_t v, size_t bits); + +private: + std::byte *mBuf = nullptr; + size_t mInitialOffset = 0; + size_t mOffset = 0; + size_t mSize = 0; +}; + +enum class DecodeTargetIndication { + NotPresent = 0, + Discardable = 1, + Switch = 2, + Required = 3, +}; + +struct RenderResolution { + int width = 0; + int height = 0; +}; + +struct FrameDependencyTemplate { + int spatialId = 0; + int temporalId = 0; + std::vector decodeTargetIndications; + std::vector frameDiffs; + std::vector chainDiffs; +}; + +struct FrameDependencyStructure { + int templateIdOffset = 0; + int decodeTargetCount = 0; + int chainCount = 0; + std::vector decodeTargetProtectedBy; + std::vector resolutions; + std::vector templates; +}; + +struct DependencyDescriptor { + bool startOfFrame = true; + bool endOfFrame = true; + int frameNumber = 0; + FrameDependencyTemplate dependencyTemplate; + std::optional resolution; + std::optional activeDecodeTargetsBitmask; + bool structureAttached; +}; + +// Write dependency descriptor to RTP Header Extension +// Dependency descriptor specification is here: +// https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension +class DependencyDescriptorWriter { +public: + DependencyDescriptorWriter(const FrameDependencyStructure &structure, + std::bitset<32> activeChains, + const DependencyDescriptor &descriptor); + size_t getSizeBits() const; + void writeTo(std::byte *buf, size_t sizeBits) const; + +private: + void doWriteTo(BitWriter &writer) const; + void writeBits(BitWriter &writer, uint64_t v, size_t bits) const; + void writeNonSymmetric(BitWriter &writer, uint64_t v, uint64_t n) const; + +private: + const FrameDependencyStructure &mStructure; + std::bitset<32> mActiveChains; + const DependencyDescriptor &mDescriptor; +}; + +} // namespace rtc + +#endif diff --git a/include/rtc/rtc.hpp b/include/rtc/rtc.hpp index dc7f62eac..99cc1e3e6 100644 --- a/include/rtc/rtc.hpp +++ b/include/rtc/rtc.hpp @@ -29,6 +29,7 @@ // Media #include "av1rtppacketizer.hpp" +#include "dependencydescriptor.hpp" #include "h264rtppacketizer.hpp" #include "h264rtpdepacketizer.hpp" #include "h265rtppacketizer.hpp" diff --git a/include/rtc/rtp.hpp b/include/rtc/rtp.hpp index 503a61ff5..5b4946b75 100644 --- a/include/rtc/rtp.hpp +++ b/include/rtc/rtp.hpp @@ -38,8 +38,10 @@ struct RTC_CPP_EXPORT RtpExtensionHeader { void setHeaderLength(uint16_t headerLength); void clearBody(); - void writeCurrentVideoOrientation(size_t offset, uint8_t id, uint8_t value); - void writeOneByteHeader(size_t offset, uint8_t id, const byte *value, size_t size); + int writeCurrentVideoOrientation(bool twoByteHeader, size_t offset, uint8_t id, uint8_t value); + int writeOneByteHeader(size_t offset, uint8_t id, const byte *value, size_t size); + int writeTwoByteHeader(size_t offset, uint8_t id, const byte *value, size_t size); + int writeHeader(bool twoByteHeader, size_t offset, uint8_t id, const byte *value, size_t size); }; struct RTC_CPP_EXPORT RtpHeader { diff --git a/include/rtc/rtppacketizationconfig.hpp b/include/rtc/rtppacketizationconfig.hpp index 0e6dcada2..dce48f342 100644 --- a/include/rtc/rtppacketizationconfig.hpp +++ b/include/rtc/rtppacketizationconfig.hpp @@ -11,6 +11,7 @@ #if RTC_ENABLE_MEDIA +#include "dependencydescriptor.hpp" #include "rtp.hpp" namespace rtc { @@ -61,6 +62,15 @@ class RTC_CPP_EXPORT RtpPacketizationConfig { uint8_t ridId = 0; optional rid; + // Dependency Descriptor Extension Header + uint8_t dependencyDescriptorId = 0; + struct DependencyDescriptorContext { + DependencyDescriptor descriptor; + std::bitset<32> activeChains; + FrameDependencyStructure structure; + }; + optional dependencyDescriptorContext; + /// Construct RTP configuration used in packetization process /// @param ssrc SSRC of source /// @param cname CNAME of source diff --git a/src/av1rtppacketizer.cpp b/src/av1rtppacketizer.cpp index b7cfc55e8..5968d7123 100644 --- a/src/av1rtppacketizer.cpp +++ b/src/av1rtppacketizer.cpp @@ -188,8 +188,7 @@ std::vector AV1RtpPacketizer::packetizeObu(binary_ptr message, AV1RtpPacketizer::AV1RtpPacketizer(AV1RtpPacketizer::Packetization packetization, shared_ptr rtpConfig, uint16_t maxFragmentSize) - : RtpPacketizer(rtpConfig), maxFragmentSize(maxFragmentSize), - packetization(packetization) {} + : RtpPacketizer(rtpConfig), maxFragmentSize(maxFragmentSize), packetization(packetization) {} void AV1RtpPacketizer::outgoing(message_vector &messages, [[maybe_unused]] const message_callback &send) { @@ -211,10 +210,15 @@ void AV1RtpPacketizer::outgoing(message_vector &messages, if (fragments.size() == 0) continue; - for (size_t i = 0; i < fragments.size() - 1; i++) - result.push_back(packetize(fragments[i], false)); - - result.push_back(packetize(fragments[fragments.size() - 1], true)); + for (size_t i = 0; i < fragments.size(); i++) { + if (rtpConfig->dependencyDescriptorContext.has_value()) { + auto &ctx = *rtpConfig->dependencyDescriptorContext; + ctx.descriptor.startOfFrame = i == 0; + ctx.descriptor.endOfFrame = i == fragments.size() - 1; + } + bool mark = i == fragments.size() - 1; + result.push_back(packetize(fragments[i], mark)); + } } messages.swap(result); diff --git a/src/dependencydescriptor.cpp b/src/dependencydescriptor.cpp new file mode 100644 index 000000000..a630fe926 --- /dev/null +++ b/src/dependencydescriptor.cpp @@ -0,0 +1,406 @@ +/** + * Copyright (c) 2024 Shigemasa Watanabe (Wandbox) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include "dependencydescriptor.hpp" + +#include +#include + +namespace rtc { + +BitWriter BitWriter::fromSizeBits(std::byte *buf, size_t offsetBits, size_t sizeBits) { + BitWriter writer; + writer.mBuf = buf; + writer.mInitialOffset = offsetBits; + writer.mOffset = offsetBits; + writer.mSize = sizeBits; + return writer; +} +BitWriter BitWriter::fromNull() { + BitWriter writer; + writer.mSize = SIZE_MAX; + return writer; +} + +size_t BitWriter::getWrittenBits() const { return mOffset - mInitialOffset; } + +bool BitWriter::write(uint64_t v, size_t bits) { + if (mOffset + bits > mSize) { + return false; + } + uint8_t *p = mBuf == nullptr ? nullptr : reinterpret_cast(mBuf + mOffset / 8); + // First, write up to the 8-bit boundary + size_t written_bits = writePartialByte(p, mOffset % 8, v, bits); + + if (p != nullptr) { + p++; + } + bits -= written_bits; + mOffset += written_bits; + + if (bits == 0) { + return true; + } + + // Write 8 bits at a time + while (bits >= 8) { + if (p != nullptr) { + *p = (v >> (bits - 8)) & 0xff; + p++; + } + bits -= 8; + mOffset += 8; + } + + // Write the remaining bits + written_bits = writePartialByte(p, 0, v, bits); + bits -= written_bits; + mOffset += written_bits; + + assert(bits == 0); + + return true; +} + +bool BitWriter::writeNonSymmetric(uint64_t v, uint64_t n) { + if (n == 1) { + return true; + } + size_t w = 0; + uint64_t x = n; + while (x != 0) { + x = x >> 1; + w++; + } + uint64_t m = (1ULL << w) - n; + if (v < m) { + return write(v, w - 1); + } else { + return write(v + m, w); + } +} + +size_t BitWriter::writePartialByte(uint8_t *p, size_t offset, uint64_t v, size_t bits) { + // How many bits are remaining + size_t remaining_bits = 8 - offset; + // Number of bits to write + size_t need_write_bits = std::min(remaining_bits, bits); + // Number of remaining bits + size_t shift = remaining_bits - need_write_bits; + // The relationship between each values are as follows + // 0bxxxxxxxx + // ^ - offset == 1 + // ^-----^ - remaining_bits == 7 + // ^---^ - need_write_bits == 5 + // ^^ - shift == 2 + assert(offset + remaining_bits == 8); + assert(remaining_bits == need_write_bits + shift); + + // For writing 4 bits from the 3rd bit of 0bxxxxxxxx with 0byyyy, it becomes + // (0bxxxxxxxx & 0b11100001) | ((0byyyy >> (4 - 4)) << 1) + // For writing 2 bits from the 6th bit of 0bxxxxxxxx with 0byyyyy, it becomes + // (0bxxxxxxxx & 0b11111100) | (((0byyyyy >> (5 - 2)) << 0) + + // Creating a mask + // For need_write_bits == 4, shift == 1 + // 1 << 4 == 0b00010000 + // 0b00010000 - 1 == 0b00001111 + // 0b00001111 << 1 == 0b00011110 + // ~0b00011110 == 0b11100001 + uint8_t mask = ~(((1 << need_write_bits) - 1) << shift); + + uint8_t vv = v >> (bits - need_write_bits); + + if (p != nullptr) { + *p = (*p & mask) | (vv << shift); + } + + return need_write_bits; +} + +using TemplateIterator = std::vector::const_iterator; + +struct TemplateMatch { + size_t templatePosition; + bool needCustomDtis; + bool needCustomFdiffs; + bool needCustomChains; + // Size in bits to store frame-specific details, i.e. + // excluding mandatory fields and template dependency structure. + int extraSizeBits; +}; + +static TemplateMatch calculate_match(TemplateIterator frameTemplate, + const FrameDependencyStructure &structure, + std::bitset<32> activeChains, + const DependencyDescriptor &descriptor) { + TemplateMatch result; + result.templatePosition = frameTemplate - structure.templates.begin(); + result.needCustomFdiffs = descriptor.dependencyTemplate.frameDiffs != frameTemplate->frameDiffs; + result.needCustomDtis = descriptor.dependencyTemplate.decodeTargetIndications != + frameTemplate->decodeTargetIndications; + result.needCustomChains = false; + for (int i = 0; i < structure.chainCount; ++i) { + if (activeChains[i] && + descriptor.dependencyTemplate.chainDiffs[i] != frameTemplate->chainDiffs[i]) { + result.needCustomChains = true; + break; + } + } + + result.extraSizeBits = 0; + if (result.needCustomFdiffs) { + result.extraSizeBits += 2 * (1 + descriptor.dependencyTemplate.frameDiffs.size()); + for (int fdiff : descriptor.dependencyTemplate.frameDiffs) { + if (fdiff <= (1 << 4)) { + result.extraSizeBits += 4; + } else if (fdiff <= (1 << 8)) { + result.extraSizeBits += 8; + } else { + result.extraSizeBits += 12; + } + } + } + if (result.needCustomDtis) { + result.extraSizeBits += 2 * descriptor.dependencyTemplate.decodeTargetIndications.size(); + } + if (result.needCustomChains) { + result.extraSizeBits += 8 * structure.chainCount; + } + return result; +} + +static bool find_best_template(const FrameDependencyStructure &structure, + std::bitset<32> activeChains, const DependencyDescriptor &descriptor, + TemplateMatch *best) { + auto &templates = structure.templates; + // Find range of templates with matching spatial/temporal id. + auto sameLayer = [&](const FrameDependencyTemplate &frameTemplate) { + return descriptor.dependencyTemplate.spatialId == frameTemplate.spatialId && + descriptor.dependencyTemplate.temporalId == frameTemplate.temporalId; + }; + auto first = std::find_if(templates.begin(), templates.end(), sameLayer); + if (first == templates.end()) { + return false; + } + auto last = std::find_if_not(first, templates.end(), sameLayer); + + *best = calculate_match(first, structure, activeChains, descriptor); + // Search if there any better template than the first one. + for (auto next = std::next(first); next != last; ++next) { + auto match = calculate_match(next, structure, activeChains, descriptor); + if (match.extraSizeBits < best->extraSizeBits) { + *best = match; + } + } + return true; +} + +static const uint32_t MaxTemplates = 64; + +DependencyDescriptorWriter::DependencyDescriptorWriter(const FrameDependencyStructure &structure, + std::bitset<32> activeChains, + const DependencyDescriptor &descriptor) + : mStructure(structure), mActiveChains(activeChains), mDescriptor(descriptor) {} + +size_t DependencyDescriptorWriter::getSizeBits() const { + auto writer = rtc::BitWriter::fromNull(); + doWriteTo(writer); + return writer.getWrittenBits(); +} + +void DependencyDescriptorWriter::writeTo(std::byte *buf, size_t sizeBits) const { + auto writer = BitWriter::fromSizeBits(buf, 0, (sizeBits + 7) / 8 * 8); + doWriteTo(writer); + // Pad up to the byte boundary + if (auto bits = (writer.getWrittenBits() % 8); bits != 0) { + writer.write(0, 8 - bits); + } +} + +void DependencyDescriptorWriter::doWriteTo(BitWriter &w) const { + TemplateMatch bestTemplate; + if (!find_best_template(mStructure, mActiveChains, mDescriptor, &bestTemplate)) { + throw std::logic_error("No matching template found"); + } + + // mandatory_descriptor_fields() + writeBits(w, mDescriptor.startOfFrame ? 1 : 0, 1); + writeBits(w, mDescriptor.endOfFrame ? 1 : 0, 1); + uint32_t templateId = + (bestTemplate.templatePosition + mStructure.templateIdOffset) % MaxTemplates; + writeBits(w, templateId, 6); + writeBits(w, mDescriptor.frameNumber, 16); + + bool hasExtendedFields = bestTemplate.extraSizeBits > 0 || + (mDescriptor.startOfFrame && mDescriptor.structureAttached) || + mDescriptor.activeDecodeTargetsBitmask != std::nullopt; + if (hasExtendedFields) { + // extended_descriptor_fields() + bool templateDependencyStructurePresentFlag = mDescriptor.structureAttached; + writeBits(w, templateDependencyStructurePresentFlag ? 1 : 0, 1); + bool activeDecodeTargetsPresentFlag = std::invoke([&]() { + if (!mDescriptor.activeDecodeTargetsBitmask) + return false; + const uint64_t allDecodeTargetsBitmask = (1ULL << mStructure.decodeTargetCount) - 1; + if (mDescriptor.structureAttached && + mDescriptor.activeDecodeTargetsBitmask == allDecodeTargetsBitmask) + return false; + return true; + }); + writeBits(w, activeDecodeTargetsPresentFlag ? 1 : 0, 1); + writeBits(w, bestTemplate.needCustomDtis ? 1 : 0, 1); + writeBits(w, bestTemplate.needCustomFdiffs ? 1 : 0, 1); + writeBits(w, bestTemplate.needCustomChains ? 1 : 0, 1); + if (templateDependencyStructurePresentFlag) { + // template_dependency_structure() + writeBits(w, mStructure.templateIdOffset, 6); + writeBits(w, mStructure.decodeTargetCount - 1, 5); + + // template_layers() + const auto &templates = mStructure.templates; + assert(!templates.empty()); + assert(templates.size() < MaxTemplates); + assert(templates[0].spatialId == 0); + assert(templates[0].temporalId == 0); + for (size_t i = 1; i < templates.size(); ++i) { + auto &prev = templates[i - 1]; + auto &next = templates[i]; + + uint32_t nextLayerIdc; + if (next.spatialId == prev.spatialId && next.temporalId == prev.temporalId) { + // same layer + nextLayerIdc = 0; + } else if (next.spatialId == prev.spatialId && + next.temporalId == prev.temporalId + 1) { + // next temporal + nextLayerIdc = 1; + } else if (next.spatialId == prev.spatialId + 1 && next.temporalId == 0) { + // new spatial + nextLayerIdc = 2; + } else { + throw std::logic_error("Invalid layer"); + } + writeBits(w, nextLayerIdc, 2); + } + // no more layers + writeBits(w, 3, 2); + + // template_dtis() + for (const FrameDependencyTemplate &frameTemplate : mStructure.templates) { + assert(frameTemplate.decodeTargetIndications.size() == + static_cast(mStructure.decodeTargetCount)); + for (DecodeTargetIndication dti : frameTemplate.decodeTargetIndications) { + writeBits(w, static_cast(dti), 2); + } + } + + // template_fdiffs() + for (const FrameDependencyTemplate &frameTemplate : mStructure.templates) { + for (int fdiff : frameTemplate.frameDiffs) { + assert(fdiff - 1 >= 0); + assert(fdiff - 1 < (1 << 4)); + writeBits(w, (1u << 4) | (fdiff - 1), 1 + 4); + } + // No more diffs for current template. + writeBits(w, 0, 1); + } + + // template_chains() + assert(mStructure.chainCount >= 0); + assert(mStructure.chainCount <= mStructure.decodeTargetCount); + writeNonSymmetric(w, mStructure.chainCount, mStructure.decodeTargetCount + 1); + if (mStructure.chainCount != 0) { + assert(mStructure.decodeTargetProtectedBy.size() == + static_cast(mStructure.decodeTargetCount)); + for (int protectedBy : mStructure.decodeTargetProtectedBy) { + assert(protectedBy >= 0); + assert(protectedBy < mStructure.chainCount); + writeNonSymmetric(w, protectedBy, mStructure.chainCount); + } + for (const auto &frameTemplate : mStructure.templates) { + assert(frameTemplate.chainDiffs.size() == + static_cast(mStructure.chainCount)); + for (int chain_diff : frameTemplate.chainDiffs) { + assert(chain_diff >= 0); + assert(chain_diff < (1 << 4)); + writeBits(w, chain_diff, 4); + } + } + } + + bool hasResolutions = !mStructure.resolutions.empty(); + writeBits(w, hasResolutions ? 1 : 0, 1); + if (hasResolutions) { + // render_resolutions() + assert(mStructure.resolutions.size() == + static_cast(mStructure.templates.back().spatialId) + 1); + for (const RenderResolution &resolution : mStructure.resolutions) { + assert(resolution.width > 0); + assert(resolution.width <= (1 << 16)); + assert(resolution.height > 0); + assert(resolution.height <= (1 << 16)); + + writeBits(w, resolution.width - 1, 16); + writeBits(w, resolution.height - 1, 16); + } + } + } + if (activeDecodeTargetsPresentFlag) { + writeBits(w, *mDescriptor.activeDecodeTargetsBitmask, mStructure.decodeTargetCount); + } + } + + // frame_dependency_definition() + if (bestTemplate.needCustomDtis) { + // frame_dtis() + assert(mDescriptor.dependencyTemplate.decodeTargetIndications.size() == + static_cast(mStructure.decodeTargetCount)); + for (DecodeTargetIndication dti : mDescriptor.dependencyTemplate.decodeTargetIndications) { + writeBits(w, static_cast(dti), 2); + } + } + if (bestTemplate.needCustomFdiffs) { + // frame_fdiffs() + for (int fdiff : mDescriptor.dependencyTemplate.frameDiffs) { + assert(fdiff > 0); + assert(fdiff <= (1 << 12)); + if (fdiff <= (1 << 4)) { + writeBits(w, (1u << 4) | (fdiff - 1), 2 + 4); + } else if (fdiff <= (1 << 8)) { + writeBits(w, (2u << 8) | (fdiff - 1), 2 + 8); + } else { // fdiff <= (1 << 12) + writeBits(w, (3u << 12) | (fdiff - 1), 2 + 12); + } + } + // No more diffs. + writeBits(w, 0, 2); + } + if (bestTemplate.needCustomChains) { + // frame_chains() + for (int i = 0; i < mStructure.chainCount; ++i) { + int chainDiff = mActiveChains[i] ? mDescriptor.dependencyTemplate.chainDiffs[i] : 0; + assert(chainDiff >= 0); + assert(chainDiff < (1 << 8)); + writeBits(w, chainDiff, 8); + } + } +} +void DependencyDescriptorWriter::writeBits(BitWriter &writer, uint64_t v, size_t bits) const { + if (!writer.write(v, bits)) { + throw std::logic_error("Failed to write bits"); + } +} +void DependencyDescriptorWriter::writeNonSymmetric(BitWriter &writer, uint64_t v, + uint64_t n) const { + if (!writer.writeNonSymmetric(v, n)) { + throw std::logic_error("Failed to write non-symmetric value"); + } +} + +} // namespace rtc \ No newline at end of file diff --git a/src/rtp.cpp b/src/rtp.cpp index 4038111e3..2467e1e66 100644 --- a/src/rtp.cpp +++ b/src/rtp.cpp @@ -130,22 +130,46 @@ void RtpExtensionHeader::setHeaderLength(uint16_t headerLength) { void RtpExtensionHeader::clearBody() { std::memset(getBody(), 0, getSize()); } -void RtpExtensionHeader::writeOneByteHeader(size_t offset, uint8_t id, const byte *value, - size_t size) { +int RtpExtensionHeader::writeOneByteHeader(size_t offset, uint8_t id, const byte *value, + size_t size) { if ((id == 0) || (id > 14) || (size == 0) || (size > 16) || ((offset + 1 + size) > getSize())) - return; + return 0; auto buf = getBody() + offset; buf[0] = id << 4; if (size != 1) { buf[0] |= (uint8_t(size) - 1); } std::memcpy(buf + 1, value, size); + return 1 + size; +} + +int RtpExtensionHeader::writeTwoByteHeader(size_t offset, uint8_t id, const byte *value, + size_t size) { + if ((id == 0) || (size > 255) || ((offset + 2 + size) > getSize())) + return 0; + auto buf = getBody() + offset; + buf[0] = id; + buf[1] = uint8_t(size); + std::memcpy(buf + 2, value, size); + return 2 + size; } -void RtpExtensionHeader::writeCurrentVideoOrientation(size_t offset, const uint8_t id, - uint8_t value) { +int RtpExtensionHeader::writeCurrentVideoOrientation(bool twoByteHeader, size_t offset, + const uint8_t id, uint8_t value) { auto v = std::byte{value}; - writeOneByteHeader(offset, id, &v, 1); + if (twoByteHeader) { + return writeTwoByteHeader(offset, id, &v, 1); + } else { + return writeOneByteHeader(offset, id, &v, 1); + } +} +int RtpExtensionHeader::writeHeader(bool twoByteHeader, size_t offset, uint8_t id, + const byte *value, size_t size) { + if (twoByteHeader) { + return writeTwoByteHeader(offset, id, value, size); + } else { + return writeOneByteHeader(offset, id, value, size); + } } SSRC RtcpReportBlock::getSSRC() const { return ntohl(_ssrc); } @@ -169,9 +193,9 @@ void RtcpReportBlock::preparePacket(SSRC in_ssrc, [[maybe_unused]] unsigned int void RtcpReportBlock::setSSRC(SSRC in_ssrc) { _ssrc = htonl(in_ssrc); } -void RtcpReportBlock::setPacketsLost(uint8_t fractionLost, - unsigned int packetsLostCount) { - _fractionLostAndPacketsLost = htonl((uint32_t(fractionLost) << 24) | (packetsLostCount & 0xFFFFFF)); +void RtcpReportBlock::setPacketsLost(uint8_t fractionLost, unsigned int packetsLostCount) { + _fractionLostAndPacketsLost = + htonl((uint32_t(fractionLost) << 24) | (packetsLostCount & 0xFFFFFF)); } uint8_t RtcpReportBlock::getFractionLost() const { @@ -188,7 +212,9 @@ uint16_t RtcpReportBlock::seqNoCycles() const { return ntohs(_seqNoCycles); } uint16_t RtcpReportBlock::highestSeqNo() const { return ntohs(_highestSeqNo); } -uint32_t RtcpReportBlock::extendedHighestSeqNo() const { return (seqNoCycles() << 16) | highestSeqNo(); } +uint32_t RtcpReportBlock::extendedHighestSeqNo() const { + return (seqNoCycles() << 16) | highestSeqNo(); +} uint32_t RtcpReportBlock::jitter() const { return ntohl(_jitter); } diff --git a/src/rtppacketizer.cpp b/src/rtppacketizer.cpp index ba7048ac5..7e586fe1c 100644 --- a/src/rtppacketizer.cpp +++ b/src/rtppacketizer.cpp @@ -21,21 +21,43 @@ RtpPacketizer::~RtpPacketizer() {} message_ptr RtpPacketizer::packetize(shared_ptr payload, bool mark) { size_t rtpExtHeaderSize = 0; - - const bool setVideoRotation = (rtpConfig->videoOrientationId != 0) && - (rtpConfig->videoOrientationId < - 15) && // needs fixing if longer extension headers are supported - mark && - (rtpConfig->videoOrientation != 0); + bool twoByteHeader = false; + + const bool setVideoRotation = + (rtpConfig->videoOrientationId != 0) && mark && (rtpConfig->videoOrientation != 0); + + // Determine if a two-byte header is necessary + // Check for dependency descriptor extension + if (rtpConfig->dependencyDescriptorContext.has_value()) { + auto &ctx = *rtpConfig->dependencyDescriptorContext; + DependencyDescriptorWriter writer(ctx.structure, ctx.activeChains, ctx.descriptor); + auto sizeBytes = (writer.getSizeBits() + 7) / 8; + if (sizeBytes > 16 || rtpConfig->dependencyDescriptorId > 14) { + twoByteHeader = true; + } + } + // Check for other extensions + if ((setVideoRotation && rtpConfig->videoOrientationId > 14) || + (rtpConfig->mid.has_value() && rtpConfig->midId > 14) || + (rtpConfig->rid.has_value() && rtpConfig->ridId > 14)) { + twoByteHeader = true; + } + size_t headerSize = twoByteHeader ? 2 : 1; if (setVideoRotation) - rtpExtHeaderSize += 2; + rtpExtHeaderSize += headerSize + 1; if (rtpConfig->mid.has_value()) - rtpExtHeaderSize += (1 + rtpConfig->mid->length()); + rtpExtHeaderSize += headerSize + rtpConfig->mid->length(); if (rtpConfig->rid.has_value()) - rtpExtHeaderSize += (1 + rtpConfig->rid->length()); + rtpExtHeaderSize += headerSize + rtpConfig->rid->length(); + + if (rtpConfig->dependencyDescriptorContext.has_value()) { + auto &ctx = *rtpConfig->dependencyDescriptorContext; + DependencyDescriptorWriter writer(ctx.structure, ctx.activeChains, ctx.descriptor); + rtpExtHeaderSize += headerSize + (writer.getSizeBits() + 7) / 8; + } if (rtpExtHeaderSize != 0) rtpExtHeaderSize += 4; @@ -57,7 +79,7 @@ message_ptr RtpPacketizer::packetize(shared_ptr payload, bool mark) { rtp->setExtension(true); auto extHeader = rtp->getExtensionHeader(); - extHeader->setProfileSpecificId(0xbede); + extHeader->setProfileSpecificId(twoByteHeader ? 0x1000 : 0xbede); auto headerLength = static_cast(rtpExtHeaderSize / 4) - 1; @@ -66,24 +88,33 @@ message_ptr RtpPacketizer::packetize(shared_ptr payload, bool mark) { size_t offset = 0; if (setVideoRotation) { - extHeader->writeCurrentVideoOrientation(offset, rtpConfig->videoOrientationId, - rtpConfig->videoOrientation); - offset += 2; + offset += extHeader->writeCurrentVideoOrientation( + twoByteHeader, offset, rtpConfig->videoOrientationId, rtpConfig->videoOrientation); } if (rtpConfig->mid.has_value()) { - extHeader->writeOneByteHeader( - offset, rtpConfig->midId, - reinterpret_cast(rtpConfig->mid->c_str()), - rtpConfig->mid->length()); - offset += (1 + rtpConfig->mid->length()); + offset += + extHeader->writeHeader(twoByteHeader, offset, rtpConfig->midId, + reinterpret_cast(rtpConfig->mid->c_str()), + rtpConfig->mid->length()); } if (rtpConfig->rid.has_value()) { - extHeader->writeOneByteHeader( - offset, rtpConfig->ridId, - reinterpret_cast(rtpConfig->rid->c_str()), - rtpConfig->rid->length()); + offset += + extHeader->writeHeader(twoByteHeader, offset, rtpConfig->ridId, + reinterpret_cast(rtpConfig->rid->c_str()), + rtpConfig->rid->length()); + } + + if (rtpConfig->dependencyDescriptorContext.has_value()) { + auto &ctx = *rtpConfig->dependencyDescriptorContext; + DependencyDescriptorWriter writer(ctx.structure, ctx.activeChains, ctx.descriptor); + auto sizeBits = writer.getSizeBits(); + size_t sizeBytes = (sizeBits + 7) / 8; + std::unique_ptr buf(new std::byte[sizeBytes]); + writer.writeTo(buf.get(), sizeBits); + offset += extHeader->writeHeader( + twoByteHeader, offset, rtpConfig->dependencyDescriptorId, buf.get(), sizeBytes); } }