2
0
Эх сурвалжийг харах

Add VP8 packetizer and depacketizer

Paul-Louis Ageneau 2 сар өмнө
parent
commit
041c6dffa6

+ 4 - 0
CMakeLists.txt

@@ -94,6 +94,8 @@ set(LIBDATACHANNEL_SOURCES
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h265rtpdepacketizer.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h265nalunit.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/av1rtppacketizer.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/src/vp8rtppacketizer.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/src/vp8rtpdepacketizer.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpnackresponder.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/rtp.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/capi.cpp
@@ -135,6 +137,8 @@ set(LIBDATACHANNEL_HEADERS
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265rtpdepacketizer.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265nalunit.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/av1rtppacketizer.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/vp8rtppacketizer.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/vp8rtpdepacketizer.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpnackresponder.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/utils.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/plihandler.hpp

+ 1 - 0
include/rtc/rtc.h

@@ -388,6 +388,7 @@ RTC_C_EXPORT int rtcSetMediaInterceptorCallback(int id, rtcInterceptorCallbackFu
 RTC_C_EXPORT int rtcSetH264Packetizer(int tr, const rtcPacketizerInit *init);
 RTC_C_EXPORT int rtcSetH265Packetizer(int tr, const rtcPacketizerInit *init);
 RTC_C_EXPORT int rtcSetAV1Packetizer(int tr, const rtcPacketizerInit *init);
+RTC_C_EXPORT int rtcSetVP8Packetizer(int tr, const rtcPacketizerInit *init);
 RTC_C_EXPORT int rtcSetOpusPacketizer(int tr, const rtcPacketizerInit *init);
 RTC_C_EXPORT int rtcSetAACPacketizer(int tr, const rtcPacketizerInit *init);
 RTC_C_EXPORT int rtcSetPCMUPacketizer(int tr, const rtcPacketizerInit *init);

+ 5 - 3
include/rtc/rtc.hpp

@@ -14,9 +14,9 @@
 #include "global.hpp"
 //
 #include "datachannel.hpp"
+#include "iceudpmuxlistener.hpp"
 #include "peerconnection.hpp"
 #include "track.hpp"
-#include "iceudpmuxlistener.hpp"
 
 #if RTC_ENABLE_WEBSOCKET
 
@@ -31,10 +31,14 @@
 // Media
 #include "av1rtppacketizer.hpp"
 #include "dependencydescriptor.hpp"
+#include "rtppacketizer.hpp"
+#include "rtpdepacketizer.hpp"
 #include "h264rtppacketizer.hpp"
 #include "h264rtpdepacketizer.hpp"
 #include "h265rtppacketizer.hpp"
 #include "h265rtpdepacketizer.hpp"
+#include "vp8rtppacketizer.hpp"
+#include "vp8rtpdepacketizer.hpp"
 #include "mediahandler.hpp"
 #include "plihandler.hpp"
 #include "rembhandler.hpp"
@@ -42,7 +46,5 @@
 #include "rtcpnackresponder.hpp"
 #include "rtcpreceivingsession.hpp"
 #include "rtcpsrreporter.hpp"
-#include "rtppacketizer.hpp"
-#include "rtpdepacketizer.hpp"
 
 #endif // RTC_ENABLE_MEDIA

+ 34 - 0
include/rtc/vp8rtpdepacketizer.hpp

@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2026 Paul-Louis Ageneau
+ *
+ * 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_VP8_RTP_DEPACKETIZER_H
+#define RTC_VP8_RTP_DEPACKETIZER_H
+
+#if RTC_ENABLE_MEDIA
+
+#include "common.hpp"
+#include "message.hpp"
+#include "rtpdepacketizer.hpp"
+
+namespace rtc {
+
+/// RTP depacketization for VP8
+class RTC_CPP_EXPORT VP8RtpDepacketizer final : public VideoRtpDepacketizer {
+public:
+	VP8RtpDepacketizer();
+	~VP8RtpDepacketizer();
+
+private:
+	message_ptr reassemble(message_buffer &buffer) override;
+};
+
+} // namespace rtc
+
+#endif // RTC_ENABLE_MEDIA
+
+#endif /* RTC_VP8_RTP_DEPACKETIZER_H */

+ 42 - 0
include/rtc/vp8rtppacketizer.hpp

@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2026 Paul-Louis Ageneau
+ *
+ * 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_VP8_RTP_PACKETIZER_H
+#define RTC_VP8_RTP_PACKETIZER_H
+
+#if RTC_ENABLE_MEDIA
+
+#include "rtppacketizer.hpp"
+
+namespace rtc {
+
+/// RTP packetization for VP8
+class RTC_CPP_EXPORT VP8RtpPacketizer final : public RtpPacketizer {
+public:
+	inline static const uint32_t ClockRate = VideoClockRate;
+	[[deprecated("Use ClockRate")]] inline static const uint32_t defaultClockRate = ClockRate;
+
+	/// Constructs VP8 payload packetizer with given RTP configuration.
+	/// @note RTP configuration is used in packetization process which may change some configuration
+	/// properties such as sequence number.
+	/// @param rtpConfig RTP configuration
+	/// @param maxFragmentSize maximum size of one packet payload
+	VP8RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig,
+	                 size_t maxFragmentSize = DefaultMaxFragmentSize);
+
+private:
+	std::vector<binary> fragment(binary data) override;
+
+	const size_t mMaxFragmentSize;
+};
+
+} // namespace rtc
+
+#endif /* RTC_ENABLE_MEDIA */
+
+#endif /* RTC_VP8_RTP_PACKETIZER_H */

+ 29 - 12
src/capi.cpp

@@ -935,7 +935,8 @@ int rtcCreateDataChannelEx(int pc, const char *label, const rtcDataChannelInit *
 			dci.reliability.unordered = reliability->unordered;
 			if (reliability->unreliable) {
 				if (reliability->maxPacketLifeTime > 0)
-					dci.reliability.maxPacketLifeTime.emplace(milliseconds(reliability->maxPacketLifeTime));
+					dci.reliability.maxPacketLifeTime.emplace(
+					    milliseconds(reliability->maxPacketLifeTime));
 				else
 					dci.reliability.maxRetransmits.emplace(reliability->maxRetransmits);
 			}
@@ -999,9 +1000,10 @@ int rtcGetDataChannelReliability(int dc, rtcReliability *reliability) {
 		Reliability dcr = dataChannel->reliability();
 		std::memset(reliability, 0, sizeof(*reliability));
 		reliability->unordered = dcr.unordered;
-		if(dcr.maxPacketLifeTime) {
+		if (dcr.maxPacketLifeTime) {
 			reliability->unreliable = true;
-			reliability->maxPacketLifeTime = static_cast<unsigned int>(dcr.maxPacketLifeTime->count());
+			reliability->maxPacketLifeTime =
+			    static_cast<unsigned int>(dcr.maxPacketLifeTime->count());
 		} else if (dcr.maxRetransmits) {
 			reliability->unreliable = true;
 			reliability->maxRetransmits = *dcr.maxRetransmits;
@@ -1256,8 +1258,8 @@ int rtcSetH264Packetizer(int tr, const rtcPacketizerInit *init) {
 		emplaceRtpConfig(rtpConfig, tr);
 		// create packetizer
 		auto nalSeparator = init ? init->nalSeparator : RTC_NAL_SEPARATOR_LENGTH;
-		auto maxFragmentSize = init && init->maxFragmentSize ? init->maxFragmentSize
-		                                                     : RTC_DEFAULT_MAX_FRAGMENT_SIZE;
+		auto maxFragmentSize =
+		    init && init->maxFragmentSize ? init->maxFragmentSize : RTC_DEFAULT_MAX_FRAGMENT_SIZE;
 		auto packetizer = std::make_shared<H264RtpPacketizer>(
 		    static_cast<rtc::NalUnit::Separator>(nalSeparator), rtpConfig, maxFragmentSize);
 		track->setMediaHandler(packetizer);
@@ -1273,8 +1275,8 @@ int rtcSetH265Packetizer(int tr, const rtcPacketizerInit *init) {
 		emplaceRtpConfig(rtpConfig, tr);
 		// create packetizer
 		auto nalSeparator = init ? init->nalSeparator : RTC_NAL_SEPARATOR_LENGTH;
-		auto maxFragmentSize = init && init->maxFragmentSize ? init->maxFragmentSize
-		                                                     : RTC_DEFAULT_MAX_FRAGMENT_SIZE;
+		auto maxFragmentSize =
+		    init && init->maxFragmentSize ? init->maxFragmentSize : RTC_DEFAULT_MAX_FRAGMENT_SIZE;
 		auto packetizer = std::make_shared<H265RtpPacketizer>(
 		    static_cast<rtc::NalUnit::Separator>(nalSeparator), rtpConfig, maxFragmentSize);
 		track->setMediaHandler(packetizer);
@@ -1289,8 +1291,8 @@ int rtcSetAV1Packetizer(int tr, const rtcPacketizerInit *init) {
 		auto rtpConfig = createRtpPacketizationConfig(init);
 		emplaceRtpConfig(rtpConfig, tr);
 		// create packetizer
-		auto maxFragmentSize = init && init->maxFragmentSize ? init->maxFragmentSize
-		                                                     : RTC_DEFAULT_MAX_FRAGMENT_SIZE;
+		auto maxFragmentSize =
+		    init && init->maxFragmentSize ? init->maxFragmentSize : RTC_DEFAULT_MAX_FRAGMENT_SIZE;
 		auto packetization = init->obuPacketization == RTC_OBU_PACKETIZED_TEMPORAL_UNIT
 		                         ? AV1RtpPacketizer::Packetization::TemporalUnit
 		                         : AV1RtpPacketizer::Packetization::Obu;
@@ -1301,6 +1303,21 @@ int rtcSetAV1Packetizer(int tr, const rtcPacketizerInit *init) {
 	});
 }
 
+int rtcSetVP8Packetizer(int tr, const rtcPacketizerInit *init) {
+	return wrap([&] {
+		auto track = getTrack(tr);
+		// create RTP configuration
+		auto rtpConfig = createRtpPacketizationConfig(init);
+		emplaceRtpConfig(rtpConfig, tr);
+		// create packetizer
+		auto maxFragmentSize =
+		    init && init->maxFragmentSize ? init->maxFragmentSize : RTC_DEFAULT_MAX_FRAGMENT_SIZE;
+		auto packetizer = std::make_shared<VP8RtpPacketizer>(rtpConfig, maxFragmentSize);
+		track->setMediaHandler(packetizer);
+		return RTC_ERR_SUCCESS;
+	});
+}
+
 int rtcSetOpusPacketizer(int tr, const rtcPacketizerInit *init) {
 	return wrap([&] {
 		auto track = getTrack(tr);
@@ -1592,7 +1609,7 @@ int rtcCreateWebSocketEx(const char *url, const rtcWsConfiguration *config) {
 		else if (config->maxOutstandingPings < 0)
 			c.maxOutstandingPings = 0; // setting to 0 disables, not setting keeps default
 
-		if(config->maxMessageSize > 0)
+		if (config->maxMessageSize > 0)
 			c.maxMessageSize = size_t(config->maxMessageSize);
 
 		auto webSocket = std::make_shared<WebSocket>(std::move(c));
@@ -1632,7 +1649,7 @@ int rtcGetWebSocketPath(int ws, char *buffer, int size) {
 }
 
 int rtcCreateWebSocketServer(const rtcWsServerConfiguration *config,
-                                          rtcWebSocketClientCallbackFunc cb) {
+                             rtcWebSocketClientCallbackFunc cb) {
 	return wrap([&] {
 		if (!config)
 			throw std::invalid_argument("Unexpected null pointer for config");
@@ -1650,7 +1667,7 @@ int rtcCreateWebSocketServer(const rtcWsServerConfiguration *config,
 		c.keyPemPass = config->keyPemPass ? make_optional(string(config->keyPemPass)) : nullopt;
 		c.bindAddress = config->bindAddress ? make_optional(string(config->bindAddress)) : nullopt;
 
-		if(config->maxMessageSize > 0)
+		if (config->maxMessageSize > 0)
 			c.maxMessageSize = size_t(config->maxMessageSize);
 
 		auto webSocketServer = std::make_shared<WebSocketServer>(std::move(c));

+ 137 - 0
src/vp8rtpdepacketizer.cpp

@@ -0,0 +1,137 @@
+/**
+ * Copyright (c) 2026 Paul-Louis Ageneau
+ *
+ * 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/.
+ */
+
+#if RTC_ENABLE_MEDIA
+
+#include "vp8rtpdepacketizer.hpp"
+#include "rtp.hpp"
+
+namespace rtc {
+
+VP8RtpDepacketizer::VP8RtpDepacketizer() {}
+
+VP8RtpDepacketizer::~VP8RtpDepacketizer() {}
+
+message_ptr VP8RtpDepacketizer::reassemble(message_buffer &buffer) {
+    /*
+	 * VP8 payload descriptor
+	 *
+     *     0 1 2 3 4 5 6 7
+     *    +-+-+-+-+-+-+-+-+
+     *    |X|R|N|S|R| PID | (REQUIRED)
+     *    +-+-+-+-+-+-+-+-+
+     * X: |I|L|T|K| RSV   | (OPTIONAL)
+     *    +-+-+-+-+-+-+-+-+
+     * I: |M| PictureID   | (OPTIONAL)
+     *    +-+-+-+-+-+-+-+-+
+ 	 *
+	 * X: Extended control bits present
+	 * R: Reserved (always 0)
+	 * N: Non-reference frame
+	 * S: Start of VP8 partition (1 for first fragment, 0 otherwise)
+	 * PID: Partition index (assumed 0)
+	 * I: PictureID present
+	 * M: PictureID 15-byte extension flag
+	 */
+
+	// First byte
+    const uint8_t X = 0b10000000;
+    //const uint8_t N = 0b00100000;
+    //const uint8_t S = 0b00010000;
+
+	// Extension byte
+	const uint8_t I = 0b10000000;
+    const uint8_t L = 0b01000000;
+    //const uint8_t T = 0b00100000;
+    const uint8_t K = 0b00010000;
+
+	// PictureID byte
+	const uint8_t M = 0b10000000;
+
+	if (buffer.empty())
+		return nullptr;
+
+	auto first = *buffer.begin();
+	auto firstRtpHeader = reinterpret_cast<const RtpHeader *>(first->data());
+	uint8_t payloadType = firstRtpHeader->payloadType();
+	uint32_t timestamp = firstRtpHeader->timestamp();
+	uint16_t nextSeqNumber = firstRtpHeader->seqNumber();
+
+	binary frame;
+	for (const auto &packet : buffer) {
+		auto rtpHeader = reinterpret_cast<const rtc::RtpHeader *>(packet->data());
+		if (rtpHeader->seqNumber() < nextSeqNumber) {
+			// Skip
+			continue;
+		}
+
+		nextSeqNumber = rtpHeader->seqNumber() + 1;
+
+		auto rtpHeaderSize = rtpHeader->getSize() + rtpHeader->getExtensionHeaderSize();
+		auto paddingSize = 0;
+		if (rtpHeader->padding())
+			paddingSize = std::to_integer<uint8_t>(packet->back());
+
+		if (packet->size() <= rtpHeaderSize + paddingSize)
+			continue; // Empty payload
+
+		const std::byte *payloadData = packet->data() + rtpHeaderSize;
+		size_t payloadLen = packet->size() - rtpHeaderSize - paddingSize;
+
+		// VP8 Payload Descriptor (RFC 7741)
+		if (payloadLen < 1)
+			continue;
+
+		size_t descriptorSize = 1;
+		uint8_t firstByte = std::to_integer<uint8_t>(payloadData[0]);
+
+		if (firstByte & X) {
+			if (payloadLen < descriptorSize + 1)
+				continue;
+
+			uint8_t extensionByte = std::to_integer<uint8_t>(payloadData[descriptorSize]);
+			descriptorSize++;
+
+			if (extensionByte & I) {
+				if (payloadLen < descriptorSize + 1)
+					continue;
+				uint8_t pictureIdByte = std::to_integer<uint8_t>(payloadData[descriptorSize]);
+				descriptorSize++;
+				if (pictureIdByte & M) { // M bit, 16-bit PictureID
+					if (payloadLen < descriptorSize + 1)
+						continue;
+					descriptorSize++;
+				}
+			}
+
+			if (extensionByte & L) {
+				if (payloadLen < descriptorSize + 1)
+					continue;
+				descriptorSize++;
+			}
+
+			if ((extensionByte & L) || (extensionByte & K)) {
+				if (payloadLen < descriptorSize + 1)
+					continue;
+				descriptorSize++;
+			}
+		}
+
+		if (payloadLen <= descriptorSize)
+			continue;
+
+		frame.insert(frame.end(), packet->begin() + rtpHeaderSize + descriptorSize,
+		             packet->end() - paddingSize);
+	}
+
+	return make_message(std::move(frame), createFrameInfo(timestamp, payloadType));
+}
+
+} // namespace rtc
+
+#endif // RTC_ENABLE_MEDIA

+ 91 - 0
src/vp8rtppacketizer.cpp

@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) 2026 Paul-Louis Ageneau
+ *
+ * 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/.
+ */
+
+#if RTC_ENABLE_MEDIA
+
+#include "vp8rtppacketizer.hpp"
+
+#include <cstring>
+
+namespace rtc {
+
+VP8RtpPacketizer::VP8RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig,
+                                   size_t maxFragmentSize)
+    : RtpPacketizer(std::move(rtpConfig)), mMaxFragmentSize(maxFragmentSize) {}
+
+std::vector<binary> VP8RtpPacketizer::fragment(binary data) {
+    /*
+	 * VP8 payload descriptor
+	 *
+     *     0 1 2 3 4 5 6 7
+     *    +-+-+-+-+-+-+-+-+
+     *    |X|R|N|S|R| PID | (REQUIRED)
+     *    +-+-+-+-+-+-+-+-+
+     * X: |I|L|T|K| RSV   | (OPTIONAL)
+     *    +-+-+-+-+-+-+-+-+
+     * I: |M| PictureID   | (OPTIONAL)
+     *    +-+-+-+-+-+-+-+-+
+ 	 *
+	 * X: Extended control bits present
+	 * R: Reserved (always 0)
+	 * N: Non-reference frame
+	 * S: Start of VP8 partition (1 for first fragment, 0 otherwise)
+	 * PID: Partition index (assumed 0)
+	 * I: PictureID present
+	 * M: PictureID 15-byte extension flag
+	 */
+
+    const uint8_t N = 0b00100000;
+    const uint8_t S = 0b00010000;
+
+	if (data.empty())
+		return {};
+
+	const size_t descriptorSize = 1;
+	if (mMaxFragmentSize <= descriptorSize)
+		return {};
+
+    bool isKeyframe = (std::to_integer<uint8_t>(data[0]) & 0b00000001) == 0;
+
+	std::vector<binary> payloads;
+	size_t index = 0;
+	size_t remaining = data.size();
+	bool firstFragment = true;
+
+	while (remaining > 0) {
+		size_t payloadSize = std::min(mMaxFragmentSize - descriptorSize, remaining);
+
+		binary payload(descriptorSize + payloadSize);
+
+		// Set 1-byte Payload Descriptor
+		uint8_t descriptor = 0;
+		if (!isKeyframe) {
+			descriptor |= N;
+        }
+		if (firstFragment) {
+			descriptor |= S;
+			firstFragment = false;
+		}
+
+		payload[0] = std::byte(descriptor);
+
+		// Copy data
+		std::memcpy(payload.data() + descriptorSize, data.data() + index, payloadSize);
+
+		payloads.push_back(std::move(payload));
+
+		index += payloadSize;
+		remaining -= payloadSize;
+	}
+
+	return payloads;
+}
+
+} // namespace rtc
+
+#endif /* RTC_ENABLE_MEDIA */