Browse Source

Merge pull request #932 from ZitaLiao/dev/HEVC_support

Add H.265 packetizer and related codec selection
Paul-Louis Ageneau 1 year ago
parent
commit
a84ea79d09

+ 6 - 0
CMakeLists.txt

@@ -79,6 +79,9 @@ set(LIBDATACHANNEL_SOURCES
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h264rtppacketizer.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h264rtppacketizer.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/nalunit.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/nalunit.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h264packetizationhandler.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/h264packetizationhandler.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/src/h265rtppacketizer.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/src/h265nalunit.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/src/h265packetizationhandler.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/av1rtppacketizer.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/av1rtppacketizer.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/av1packetizationhandler.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/av1packetizationhandler.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/mediachainablehandler.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/src/mediachainablehandler.cpp
@@ -117,6 +120,9 @@ set(LIBDATACHANNEL_HEADERS
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264rtppacketizer.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264rtppacketizer.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/nalunit.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/nalunit.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264packetizationhandler.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264packetizationhandler.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265rtppacketizer.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265nalunit.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265packetizationhandler.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/av1rtppacketizer.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/av1rtppacketizer.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/av1packetizationhandler.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/av1packetizationhandler.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediachainablehandler.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/mediachainablehandler.hpp

+ 1 - 1
examples/streamer/main.cpp

@@ -210,7 +210,7 @@ shared_ptr<ClientTrackData> addVideo(const shared_ptr<PeerConnection> pc, const
     // create RTP configuration
     // create RTP configuration
     auto rtpConfig = make_shared<RtpPacketizationConfig>(ssrc, cname, payloadType, H264RtpPacketizer::defaultClockRate);
     auto rtpConfig = make_shared<RtpPacketizationConfig>(ssrc, cname, payloadType, H264RtpPacketizer::defaultClockRate);
     // create packetizer
     // create packetizer
-    auto packetizer = make_shared<H264RtpPacketizer>(H264RtpPacketizer::Separator::Length, rtpConfig);
+    auto packetizer = make_shared<H264RtpPacketizer>(NalUnit::Separator::Length, rtpConfig);
     // create H264 handler
     // create H264 handler
     auto h264Handler = make_shared<H264PacketizationHandler>(packetizer);
     auto h264Handler = make_shared<H264PacketizationHandler>(packetizer);
     // add RTCP SR handler
     // add RTCP SR handler

+ 1 - 0
include/rtc/description.hpp

@@ -248,6 +248,7 @@ public:
 		void addVideoCodec(int payloadType, string codec, optional<string> profile = std::nullopt);
 		void addVideoCodec(int payloadType, string codec, optional<string> profile = std::nullopt);
 
 
 		void addH264Codec(int payloadType, optional<string> profile = DEFAULT_H264_VIDEO_PROFILE);
 		void addH264Codec(int payloadType, optional<string> profile = DEFAULT_H264_VIDEO_PROFILE);
+		void addH265Codec(int payloadType, optional<string> profile = std::nullopt);
 		void addVP8Codec(int payloadType);
 		void addVP8Codec(int payloadType);
 		void addVP9Codec(int payloadType);
 		void addVP9Codec(int payloadType);
 		void addAV1Codec(int payloadType);
 		void addAV1Codec(int payloadType);

+ 3 - 9
include/rtc/h264rtppacketizer.hpp

@@ -23,19 +23,13 @@ class RTC_CPP_EXPORT H264RtpPacketizer final : public RtpPacketizer,
 	shared_ptr<NalUnits> splitMessage(binary_ptr message);
 	shared_ptr<NalUnits> splitMessage(binary_ptr message);
 	const uint16_t maximumFragmentSize;
 	const uint16_t maximumFragmentSize;
 
 
+using Separator=NalUnit::Separator;
+
 public:
 public:
 	/// Default clock rate for H264 in RTP
 	/// Default clock rate for H264 in RTP
 	inline static const uint32_t defaultClockRate = 90 * 1000;
 	inline static const uint32_t defaultClockRate = 90 * 1000;
 
 
-	/// NAL unit separator
-	enum class Separator {
-		Length = RTC_NAL_SEPARATOR_LENGTH, // first 4 bytes are NAL unit length
-		LongStartSequence = RTC_NAL_SEPARATOR_LONG_START_SEQUENCE,   // 0x00, 0x00, 0x00, 0x01
-		ShortStartSequence = RTC_NAL_SEPARATOR_SHORT_START_SEQUENCE, // 0x00, 0x00, 0x01
-		StartSequence = RTC_NAL_SEPARATOR_START_SEQUENCE, // LongStartSequence or ShortStartSequence
-	};
-
-	H264RtpPacketizer(H264RtpPacketizer::Separator separator,
+	H264RtpPacketizer(Separator separator,
 	                  shared_ptr<RtpPacketizationConfig> rtpConfig,
 	                  shared_ptr<RtpPacketizationConfig> rtpConfig,
 	                  uint16_t maximumFragmentSize = NalUnits::defaultMaximumFragmentSize);
 	                  uint16_t maximumFragmentSize = NalUnits::defaultMaximumFragmentSize);
 
 

+ 181 - 0
include/rtc/h265nalunit.hpp

@@ -0,0 +1,181 @@
+/**
+ * Copyright (c) 2023 Zita Liao (Dolby)
+ *
+ * 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_H265_NAL_UNIT_H
+#define RTC_H265_NAL_UNIT_H
+
+#if RTC_ENABLE_MEDIA
+
+#include "common.hpp"
+#include "nalunit.hpp"
+
+#include <cassert>
+
+namespace rtc {
+
+#pragma pack(push, 1)
+
+#define H265_FU_HEADER_SIZE 1
+/// Nalu header
+struct RTC_CPP_EXPORT H265NalUnitHeader {
+/*
+* nal_unit_header( ) { 
+* forbidden_zero_bit	f(1)
+* nal_unit_type			u(6)
+* nuh_layer_id			u(6)
+* nuh_temporal_id_plus1	u(3)
+}
+*/
+	uint8_t _first = 0; // high byte of header
+	uint8_t _second = 0; // low byte of header
+
+	bool forbiddenBit() const { return _first >> 7; }
+	uint8_t unitType() const { return (_first & 0b0111'1110) >> 1; }
+	uint8_t nuhLayerId() const { return ((_first & 0x1) << 5) | ((_second & 0b1111'1000) >> 3); }
+	uint8_t nuhTempIdPlus1() const { return _second & 0b111;}
+
+	void setForbiddenBit(bool isSet) { _first = (_first & 0x7F) | (isSet << 7); }
+	void setUnitType(uint8_t type) { _first = (_first & 0b1000'0001) | ((type & 0b11'1111) << 1); }
+	void setNuhLayerId(uint8_t nuhLayerId) { 
+			_first = (_first & 0b1111'1110) | ((nuhLayerId & 0b10'0000) >> 5);
+			_second = (_second & 0b0000'0111)	| ((nuhLayerId & 0b01'1111) << 3); }
+	void setNuhTempIdPlus1(uint8_t nuhTempIdPlus1) { _second = (_second & 0b1111'1000) | (nuhTempIdPlus1 & 0b111); }
+};
+
+/// Nalu fragment header
+struct RTC_CPP_EXPORT H265NalUnitFragmentHeader {
+	/*
+	* +---------------+
+	* |0|1|2|3|4|5|6|7|
+	* +-+-+-+-+-+-+-+-+
+	* |S|E|  FuType   |
+	* +---------------+
+	*/
+	uint8_t _first = 0;
+
+	bool isStart() const { return _first >> 7; }
+	bool isEnd() const { return (_first >> 6) & 0x01; }
+	uint8_t unitType() const { return _first & 0b11'1111; }
+
+	void setStart(bool isSet) { _first = (_first & 0x7F) | (isSet << 7); }
+	void setEnd(bool isSet) { _first = (_first & 0b1011'1111) | (isSet << 6); }
+	void setUnitType(uint8_t type) { _first = (_first & 0b1100'0000) | (type & 0b11'1111); }
+};
+
+#pragma pack(pop)
+
+/// Nal unit
+struct RTC_CPP_EXPORT H265NalUnit : NalUnit {
+	H265NalUnit(const H265NalUnit &unit) = default;
+	H265NalUnit(size_t size, bool includingHeader = true) : NalUnit(size, includingHeader, NalUnit::Type::H265) {}
+	H265NalUnit(binary &&data) : NalUnit(std::move(data)) {}
+	H265NalUnit() : NalUnit(NalUnit::Type::H265) {}
+
+	template <typename Iterator> H265NalUnit(Iterator begin_, Iterator end_) : NalUnit(begin_, end_) {}
+
+	bool forbiddenBit() const { return header()->forbiddenBit(); }
+	uint8_t unitType() const { return header()->unitType(); }
+	uint8_t nuhLayerId() const { return header()->nuhLayerId(); }
+	uint8_t nuhTempIdPlus1() const { return header()->nuhTempIdPlus1();}
+
+	binary payload() const {
+		assert(size() >= H265_NAL_HEADER_SIZE);
+		return {begin() + H265_NAL_HEADER_SIZE, end()};
+	}
+
+	void setForbiddenBit(bool isSet) { header()->setForbiddenBit(isSet); }
+	void setUnitType(uint8_t type) { header()->setUnitType(type); }
+	void setNuhLayerId(uint8_t nuhLayerId) { header()->setNuhLayerId(nuhLayerId); }
+	void setNuhTempIdPlus1(uint8_t nuhTempIdPlus1) { header()->setNuhTempIdPlus1(nuhTempIdPlus1); }
+
+	void setPayload(binary payload) {
+		assert(size() >= H265_NAL_HEADER_SIZE);
+		erase(begin() + H265_NAL_HEADER_SIZE, end());
+		insert(end(), payload.begin(), payload.end());
+	}
+
+protected:
+	const H265NalUnitHeader *header() const {
+		assert(size() >= H265_NAL_HEADER_SIZE);
+		return reinterpret_cast<const H265NalUnitHeader *>(data());
+	}
+
+	H265NalUnitHeader *header() {
+		assert(size() >= H265_NAL_HEADER_SIZE);
+		return reinterpret_cast<H265NalUnitHeader *>(data());
+	}
+};
+
+/// Nal unit fragment A
+struct RTC_CPP_EXPORT H265NalUnitFragment : H265NalUnit {
+	static std::vector<shared_ptr<H265NalUnitFragment>> fragmentsFrom(shared_ptr<H265NalUnit> nalu,
+	                                                               uint16_t maximumFragmentSize);
+
+	enum class FragmentType { Start, Middle, End };
+
+	H265NalUnitFragment(FragmentType type, bool forbiddenBit, uint8_t nuhLayerId,
+					 uint8_t nuhTempIdPlus1, uint8_t unitType, binary data);
+
+	uint8_t unitType() const { return fragmentHeader()->unitType(); }
+
+	binary payload() const {
+		assert(size() >= H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE);
+		return {begin() + H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE, end()};
+	}
+
+	FragmentType type() const {
+		if (fragmentHeader()->isStart()) {
+			return FragmentType::Start;
+		} else if (fragmentHeader()->isEnd()) {
+			return FragmentType::End;
+		} else {
+			return FragmentType::Middle;
+		}
+	}
+
+	void setUnitType(uint8_t type) { fragmentHeader()->setUnitType(type); }
+
+	void setPayload(binary payload) {
+		assert(size() >= H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE);
+		erase(begin() + H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE, end());
+		insert(end(), payload.begin(), payload.end());
+	}
+
+	void setFragmentType(FragmentType type);
+
+protected:
+	const uint8_t nal_type_fu = 49;
+
+	H265NalUnitHeader *fragmentIndicator() { return reinterpret_cast<H265NalUnitHeader *>(data()); }
+
+	const H265NalUnitHeader *fragmentIndicator() const {
+		return reinterpret_cast<const H265NalUnitHeader *>(data());
+	}
+
+	H265NalUnitFragmentHeader *fragmentHeader() {
+		return reinterpret_cast<H265NalUnitFragmentHeader *>(fragmentIndicator() + H265_NAL_HEADER_SIZE);
+	}
+
+	const H265NalUnitFragmentHeader *fragmentHeader() const {
+		return reinterpret_cast<const H265NalUnitFragmentHeader *>(fragmentIndicator() + H265_NAL_HEADER_SIZE);
+	}
+};
+
+class RTC_CPP_EXPORT H265NalUnits : public std::vector<shared_ptr<H265NalUnit>> {
+public:
+	static const uint16_t defaultMaximumFragmentSize =
+	    uint16_t(RTC_DEFAULT_MTU - 12 - 8 - 40); // SRTP/UDP/IPv6
+
+	std::vector<shared_ptr<binary>> generateFragments(uint16_t maximumFragmentSize);
+};
+
+} // namespace rtc
+
+#endif /* RTC_ENABLE_MEDIA */
+
+#endif /* RTC_NAL_UNIT_H */

+ 32 - 0
include/rtc/h265packetizationhandler.hpp

@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2023 Zita Liao (Dolby)
+ *
+ * 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_H265_PACKETIZATION_HANDLER_H
+#define RTC_H265_PACKETIZATION_HANDLER_H
+
+#if RTC_ENABLE_MEDIA
+
+#include "h265rtppacketizer.hpp"
+#include "mediachainablehandler.hpp"
+#include "h265nalunit.hpp"
+
+namespace rtc {
+
+/// Handler for H265 packetization
+class RTC_CPP_EXPORT H265PacketizationHandler final : public MediaChainableHandler {
+public:
+	/// Construct handler for H265 packetization.
+	/// @param packetizer RTP packetizer for h265
+	H265PacketizationHandler(shared_ptr<H265RtpPacketizer> packetizer);
+};
+
+} // namespace rtc
+
+#endif /* RTC_ENABLE_MEDIA */
+
+#endif /* RTC_H265_PACKETIZATION_HANDLER_H */

+ 53 - 0
include/rtc/h265rtppacketizer.hpp

@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2023 Zita Liao (Dolby)
+ *
+ * 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_H265_RTP_PACKETIZER_H
+#define RTC_H265_RTP_PACKETIZER_H
+
+#if RTC_ENABLE_MEDIA
+
+#include "mediahandlerrootelement.hpp"
+#include "h265nalunit.hpp"
+#include "rtppacketizer.hpp"
+
+namespace rtc {
+
+/// RTP packetization of h265 payload
+class RTC_CPP_EXPORT H265RtpPacketizer final : public RtpPacketizer,
+                                               public MediaHandlerRootElement {
+	shared_ptr<H265NalUnits> splitMessage(binary_ptr message);
+	const uint16_t maximumFragmentSize;
+
+public:
+	/// Default clock rate for H265 in RTP
+	inline static const uint32_t defaultClockRate = 90 * 1000;
+
+	H265RtpPacketizer(NalUnit::Separator separator,
+	                  shared_ptr<RtpPacketizationConfig> rtpConfig,
+	                  uint16_t maximumFragmentSize = H265NalUnits::defaultMaximumFragmentSize);
+
+	/// Constructs h265 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 maximumFragmentSize maximum size of one NALU fragment
+	H265RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig,
+	                  uint16_t maximumFragmentSize = H265NalUnits::defaultMaximumFragmentSize);
+
+	ChainedOutgoingProduct processOutgoingBinaryMessage(ChainedMessagesProduct messages,
+	                                                    message_ptr control) override;
+
+private:
+	const NalUnit::Separator separator;
+};
+
+} // namespace rtc
+
+#endif /* RTC_ENABLE_MEDIA */
+
+#endif /* RTC_H265_RTP_PACKETIZER_H */

+ 70 - 2
include/rtc/nalunit.hpp

@@ -49,12 +49,29 @@ struct RTC_CPP_EXPORT NalUnitFragmentHeader {
 
 
 #pragma pack(pop)
 #pragma pack(pop)
 
 
+typedef enum {
+	NUSM_noMatch,
+	NUSM_firstZero,
+	NUSM_secondZero,
+	NUSM_thirdZero,
+	NUSM_shortMatch,
+	NUSM_longMatch
+} NalUnitStartSequenceMatch;
+
+static const size_t H264_NAL_HEADER_SIZE = 1;
+static const size_t H265_NAL_HEADER_SIZE = 2;
 /// Nal unit
 /// Nal unit
 struct RTC_CPP_EXPORT NalUnit : binary {
 struct RTC_CPP_EXPORT NalUnit : binary {
+	typedef enum {
+		H264,
+		H265
+	} Type;
+
 	NalUnit(const NalUnit &unit) = default;
 	NalUnit(const NalUnit &unit) = default;
-	NalUnit(size_t size, bool includingHeader = true) : binary(size + (includingHeader ? 0 : 1)) {}
+	NalUnit(size_t size, bool includingHeader = true, Type type = H264)
+		: binary(size + (includingHeader ? 0 : (type == H264? H264_NAL_HEADER_SIZE: H265_NAL_HEADER_SIZE))) {}
 	NalUnit(binary &&data) : binary(std::move(data)) {}
 	NalUnit(binary &&data) : binary(std::move(data)) {}
-	NalUnit() : binary(1) {}
+	NalUnit(Type type = H264) : binary( type == H264? H264_NAL_HEADER_SIZE: H265_NAL_HEADER_SIZE) {}
 	template <typename Iterator> NalUnit(Iterator begin_, Iterator end_) : binary(begin_, end_) {}
 	template <typename Iterator> NalUnit(Iterator begin_, Iterator end_) : binary(begin_, end_) {}
 
 
 	bool forbiddenBit() const { return header()->forbiddenBit(); }
 	bool forbiddenBit() const { return header()->forbiddenBit(); }
@@ -76,6 +93,57 @@ struct RTC_CPP_EXPORT NalUnit : binary {
 		insert(end(), payload.begin(), payload.end());
 		insert(end(), payload.begin(), payload.end());
 	}
 	}
 
 
+	/// NAL unit separator
+	enum class Separator {
+		Length = RTC_NAL_SEPARATOR_LENGTH, // first 4 bytes are NAL unit length
+		LongStartSequence = RTC_NAL_SEPARATOR_LONG_START_SEQUENCE,   // 0x00, 0x00, 0x00, 0x01
+		ShortStartSequence = RTC_NAL_SEPARATOR_SHORT_START_SEQUENCE, // 0x00, 0x00, 0x01
+		StartSequence = RTC_NAL_SEPARATOR_START_SEQUENCE, // LongStartSequence or ShortStartSequence
+	};
+
+	static NalUnitStartSequenceMatch StartSequenceMatchSucc(NalUnitStartSequenceMatch match, std::byte _byte, Separator separator)
+	{
+		assert(separator != Separator::Length);
+		auto byte = (uint8_t)_byte;
+		auto detectShort = separator == Separator::ShortStartSequence ||
+		                   separator == Separator::StartSequence;
+		auto detectLong = separator == Separator::LongStartSequence ||
+		                  separator == Separator::StartSequence;
+		switch (match) {
+		case NUSM_noMatch:
+			if (byte == 0x00) {
+				return NUSM_firstZero;
+			}
+			break;
+		case NUSM_firstZero:
+			if (byte == 0x00) {
+				return NUSM_secondZero;
+			}
+			break;
+		case NUSM_secondZero:
+			if (byte == 0x00 && detectLong) {
+				return NUSM_thirdZero;
+			} else if (byte == 0x00 && detectShort) {
+				return NUSM_secondZero;
+			} else if (byte == 0x01 && detectShort) {
+				return NUSM_shortMatch;
+			}
+			break;
+		case NUSM_thirdZero:
+			if (byte == 0x00 && detectLong) {
+				return NUSM_thirdZero;
+			} else if (byte == 0x01 && detectLong) {
+				return NUSM_longMatch;
+			}
+			break;
+		case NUSM_shortMatch:
+			return NUSM_shortMatch;
+		case NUSM_longMatch:
+			return NUSM_longMatch;
+		}
+		return NUSM_noMatch;
+	}
+
 protected:
 protected:
 	const NalUnitHeader *header() const {
 	const NalUnitHeader *header() const {
 		assert(size() >= 1);
 		assert(size() >= 1);

+ 6 - 2
include/rtc/rtc.h

@@ -112,6 +112,7 @@ typedef enum {
 	RTC_CODEC_H264 = 0,
 	RTC_CODEC_H264 = 0,
 	RTC_CODEC_VP8 = 1,
 	RTC_CODEC_VP8 = 1,
 	RTC_CODEC_VP9 = 2,
 	RTC_CODEC_VP9 = 2,
+	RTC_CODEC_H265 = 3,
 
 
 	// audio
 	// audio
 	RTC_CODEC_OPUS = 128,
 	RTC_CODEC_OPUS = 128,
@@ -299,7 +300,7 @@ typedef enum {
 	RTC_OBU_PACKETIZED_TEMPORAL_UNIT = 1,
 	RTC_OBU_PACKETIZED_TEMPORAL_UNIT = 1,
 } rtcObuPacketization;
 } rtcObuPacketization;
 
 
-// Define how NAL units are separated in a H264 sample
+// Define how NAL units are separated in a H264/H265 sample
 typedef enum {
 typedef enum {
 	RTC_NAL_SEPARATOR_LENGTH = 0,               // first 4 bytes are NAL unit length
 	RTC_NAL_SEPARATOR_LENGTH = 0,               // first 4 bytes are NAL unit length
 	RTC_NAL_SEPARATOR_LONG_START_SEQUENCE = 1,  // 0x00, 0x00, 0x00, 0x01
 	RTC_NAL_SEPARATOR_LONG_START_SEQUENCE = 1,  // 0x00, 0x00, 0x00, 0x01
@@ -315,7 +316,7 @@ typedef struct {
 	uint16_t sequenceNumber;
 	uint16_t sequenceNumber;
 	uint32_t timestamp;
 	uint32_t timestamp;
 
 
-	// H264
+	// H264/H265
 	rtcNalUnitSeparator nalSeparator; // NAL unit separator
 	rtcNalUnitSeparator nalSeparator; // NAL unit separator
 	uint16_t maxFragmentSize;         // Maximum NAL unit fragment size
 	uint16_t maxFragmentSize;         // Maximum NAL unit fragment size
 
 
@@ -345,6 +346,9 @@ RTC_C_EXPORT int rtcSetMediaInterceptorCallback(int id, rtcInterceptorCallbackFu
 // Set H264PacketizationHandler for track
 // Set H264PacketizationHandler for track
 RTC_C_EXPORT int rtcSetH264PacketizationHandler(int tr, const rtcPacketizationHandlerInit *init);
 RTC_C_EXPORT int rtcSetH264PacketizationHandler(int tr, const rtcPacketizationHandlerInit *init);
 
 
+// Set H265PacketizationHandler for track
+RTC_C_EXPORT int rtcSetH265PacketizationHandler(int tr, const rtcPacketizationHandlerInit *init);
+
 // Set OpusPacketizationHandler for track
 // Set OpusPacketizationHandler for track
 RTC_C_EXPORT int rtcSetOpusPacketizationHandler(int tr, const rtcPacketizationHandlerInit *init);
 RTC_C_EXPORT int rtcSetOpusPacketizationHandler(int tr, const rtcPacketizationHandlerInit *init);
 
 

+ 2 - 1
include/rtc/rtc.hpp

@@ -33,8 +33,9 @@
 #include "rtcpreceivingsession.hpp"
 #include "rtcpreceivingsession.hpp"
 #include "rtcpsrreporter.hpp"
 #include "rtcpsrreporter.hpp"
 
 
-// Opus/h264/AV1 streaming
+// Opus/h264/AV1/h265 streaming
 #include "h264packetizationhandler.hpp"
 #include "h264packetizationhandler.hpp"
+#include "h265packetizationhandler.hpp"
 #include "av1packetizationhandler.hpp"
 #include "av1packetizationhandler.hpp"
 #include "opuspacketizationhandler.hpp"
 #include "opuspacketizationhandler.hpp"
 #include "aacrtppacketizer.hpp"
 #include "aacrtppacketizer.hpp"

+ 28 - 1
src/capi.cpp

@@ -1019,6 +1019,7 @@ int rtcAddTrackEx(int pc, const rtcTrackInit *init) {
 		} else {
 		} else {
 			switch (init->codec) {
 			switch (init->codec) {
 			case RTC_CODEC_H264:
 			case RTC_CODEC_H264:
+			case RTC_CODEC_H265:
 			case RTC_CODEC_VP8:
 			case RTC_CODEC_VP8:
 			case RTC_CODEC_VP9:
 			case RTC_CODEC_VP9:
 				mid = "video";
 				mid = "video";
@@ -1039,6 +1040,7 @@ int rtcAddTrackEx(int pc, const rtcTrackInit *init) {
 
 
 		switch (init->codec) {
 		switch (init->codec) {
 		case RTC_CODEC_H264:
 		case RTC_CODEC_H264:
+		case RTC_CODEC_H265:
 		case RTC_CODEC_VP8:
 		case RTC_CODEC_VP8:
 		case RTC_CODEC_VP9: {
 		case RTC_CODEC_VP9: {
 			auto desc = Description::Video(mid, direction);
 			auto desc = Description::Video(mid, direction);
@@ -1046,6 +1048,9 @@ int rtcAddTrackEx(int pc, const rtcTrackInit *init) {
 			case RTC_CODEC_H264:
 			case RTC_CODEC_H264:
 				desc.addH264Codec(init->payloadType);
 				desc.addH264Codec(init->payloadType);
 				break;
 				break;
+			case RTC_CODEC_H265:
+				desc.addH265Codec(init->payloadType);
+				break;
 			case RTC_CODEC_VP8:
 			case RTC_CODEC_VP8:
 				desc.addVP8Codec(init->payloadType);
 				desc.addVP8Codec(init->payloadType);
 				break;
 				break;
@@ -1203,7 +1208,7 @@ int rtcSetH264PacketizationHandler(int tr, const rtcPacketizationHandlerInit *in
 		auto maxFragmentSize = init && init->maxFragmentSize ? init->maxFragmentSize
 		auto maxFragmentSize = init && init->maxFragmentSize ? init->maxFragmentSize
 		                                                     : RTC_DEFAULT_MAXIMUM_FRAGMENT_SIZE;
 		                                                     : RTC_DEFAULT_MAXIMUM_FRAGMENT_SIZE;
 		auto packetizer = std::make_shared<H264RtpPacketizer>(
 		auto packetizer = std::make_shared<H264RtpPacketizer>(
-		    static_cast<rtc::H264RtpPacketizer::Separator>(nalSeparator), rtpConfig,
+		    static_cast<rtc::NalUnit::Separator>(nalSeparator), rtpConfig,
 		    maxFragmentSize);
 		    maxFragmentSize);
 		// create H264 handler
 		// create H264 handler
 		auto h264Handler = std::make_shared<H264PacketizationHandler>(packetizer);
 		auto h264Handler = std::make_shared<H264PacketizationHandler>(packetizer);
@@ -1215,6 +1220,28 @@ int rtcSetH264PacketizationHandler(int tr, const rtcPacketizationHandlerInit *in
 	});
 	});
 }
 }
 
 
+int rtcSetH265PacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) {
+	return wrap([&] {
+		auto track = getTrack(tr);
+		// create RTP configuration
+		auto rtpConfig = createRtpPacketizationConfig(init);
+		// create packetizer
+		auto nalSeparator = init ? init->nalSeparator : RTC_NAL_SEPARATOR_LENGTH;
+		auto maxFragmentSize = init && init->maxFragmentSize ? init->maxFragmentSize
+		                                                     : RTC_DEFAULT_MAXIMUM_FRAGMENT_SIZE;
+		auto packetizer = std::make_shared<H265RtpPacketizer>(
+		    static_cast<rtc::NalUnit::Separator>(nalSeparator), rtpConfig,
+		    maxFragmentSize);
+		// create H265 handler
+		auto h265Handler = std::make_shared<H265PacketizationHandler>(packetizer);
+		emplaceMediaChainableHandler(h265Handler, tr);
+		emplaceRtpConfig(rtpConfig, tr);
+		// set handler
+		track->setMediaHandler(h265Handler);
+		return RTC_ERR_SUCCESS;
+	});
+}
+
 int rtcSetOpusPacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) {
 int rtcSetOpusPacketizationHandler(int tr, const rtcPacketizationHandlerInit *init) {
 	return wrap([&] {
 	return wrap([&] {
 		auto track = getTrack(tr);
 		auto track = getTrack(tr);

+ 4 - 0
src/description.cpp

@@ -1184,6 +1184,10 @@ void Description::Video::addH264Codec(int pt, optional<string> profile) {
 	addVideoCodec(pt, "H264", profile);
 	addVideoCodec(pt, "H264", profile);
 }
 }
 
 
+void Description::Video::addH265Codec(int pt, optional<string> profile) {
+	addVideoCodec(pt, "H265", profile);
+}
+
 void Description::Video::addVP8Codec(int payloadType) {
 void Description::Video::addVP8Codec(int payloadType) {
 	addVideoCodec(payloadType, "VP8", nullopt);
 	addVideoCodec(payloadType, "VP8", nullopt);
 }
 }

+ 3 - 55
src/h264rtppacketizer.cpp

@@ -22,58 +22,6 @@
 
 
 namespace rtc {
 namespace rtc {
 
 
-typedef enum {
-	NUSM_noMatch,
-	NUSM_firstZero,
-	NUSM_secondZero,
-	NUSM_thirdZero,
-	NUSM_shortMatch,
-	NUSM_longMatch
-} NalUnitStartSequenceMatch;
-
-NalUnitStartSequenceMatch StartSequenceMatchSucc(NalUnitStartSequenceMatch match, byte _byte,
-                                                 H264RtpPacketizer::Separator separator) {
-	assert(separator != H264RtpPacketizer::Separator::Length);
-	auto byte = (uint8_t)_byte;
-	auto detectShort = separator == H264RtpPacketizer::Separator::ShortStartSequence ||
-	                   separator == H264RtpPacketizer::Separator::StartSequence;
-	auto detectLong = separator == H264RtpPacketizer::Separator::LongStartSequence ||
-	                  separator == H264RtpPacketizer::Separator::StartSequence;
-	switch (match) {
-	case NUSM_noMatch:
-		if (byte == 0x00) {
-			return NUSM_firstZero;
-		}
-		break;
-	case NUSM_firstZero:
-		if (byte == 0x00) {
-			return NUSM_secondZero;
-		}
-		break;
-	case NUSM_secondZero:
-		if (byte == 0x00 && detectLong) {
-			return NUSM_thirdZero;
-		} else if (byte == 0x00 && detectShort) {
-			return NUSM_secondZero;
-		} else if (byte == 0x01 && detectShort) {
-			return NUSM_shortMatch;
-		}
-		break;
-	case NUSM_thirdZero:
-		if (byte == 0x00 && detectLong) {
-			return NUSM_thirdZero;
-		} else if (byte == 0x01 && detectLong) {
-			return NUSM_longMatch;
-		}
-		break;
-	case NUSM_shortMatch:
-		return NUSM_shortMatch;
-	case NUSM_longMatch:
-		return NUSM_longMatch;
-	}
-	return NUSM_noMatch;
-}
-
 shared_ptr<NalUnits> H264RtpPacketizer::splitMessage(binary_ptr message) {
 shared_ptr<NalUnits> H264RtpPacketizer::splitMessage(binary_ptr message) {
 	auto nalus = std::make_shared<NalUnits>();
 	auto nalus = std::make_shared<NalUnits>();
 	if (separator == Separator::Length) {
 	if (separator == Separator::Length) {
@@ -103,7 +51,7 @@ shared_ptr<NalUnits> H264RtpPacketizer::splitMessage(binary_ptr message) {
 		NalUnitStartSequenceMatch match = NUSM_noMatch;
 		NalUnitStartSequenceMatch match = NUSM_noMatch;
 		size_t index = 0;
 		size_t index = 0;
 		while (index < message->size()) {
 		while (index < message->size()) {
-			match = StartSequenceMatchSucc(match, (*message)[index++], separator);
+			match = NalUnit::StartSequenceMatchSucc(match, (*message)[index++], separator);
 			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
 			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
 				match = NUSM_noMatch;
 				match = NUSM_noMatch;
 				break;
 				break;
@@ -113,7 +61,7 @@ shared_ptr<NalUnits> H264RtpPacketizer::splitMessage(binary_ptr message) {
 		size_t naluStartIndex = index;
 		size_t naluStartIndex = index;
 
 
 		while (index < message->size()) {
 		while (index < message->size()) {
-			match = StartSequenceMatchSucc(match, (*message)[index], separator);
+			match = NalUnit::StartSequenceMatchSucc(match, (*message)[index], separator);
 			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
 			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
 				auto sequenceLength = match == NUSM_longMatch ? 4 : 3;
 				auto sequenceLength = match == NUSM_longMatch ? 4 : 3;
 				size_t naluEndIndex = index - sequenceLength;
 				size_t naluEndIndex = index - sequenceLength;
@@ -137,7 +85,7 @@ H264RtpPacketizer::H264RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfi
     : RtpPacketizer(rtpConfig), MediaHandlerRootElement(), maximumFragmentSize(maximumFragmentSize),
     : RtpPacketizer(rtpConfig), MediaHandlerRootElement(), maximumFragmentSize(maximumFragmentSize),
       separator(Separator::Length) {}
       separator(Separator::Length) {}
 
 
-H264RtpPacketizer::H264RtpPacketizer(H264RtpPacketizer::Separator separator,
+H264RtpPacketizer::H264RtpPacketizer(Separator separator,
                                      shared_ptr<RtpPacketizationConfig> rtpConfig,
                                      shared_ptr<RtpPacketizationConfig> rtpConfig,
                                      uint16_t maximumFragmentSize)
                                      uint16_t maximumFragmentSize)
     : RtpPacketizer(rtpConfig), MediaHandlerRootElement(), maximumFragmentSize(maximumFragmentSize),
     : RtpPacketizer(rtpConfig), MediaHandlerRootElement(), maximumFragmentSize(maximumFragmentSize),

+ 100 - 0
src/h265nalunit.cpp

@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2023 Zita Liao (Dolby)
+ *
+ * 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 "h265nalunit.hpp"
+
+#include "impl/internals.hpp"
+
+#include <cmath>
+
+namespace rtc {
+
+H265NalUnitFragment::H265NalUnitFragment(FragmentType type, bool forbiddenBit, uint8_t nuhLayerId,
+									uint8_t nuhTempIdPlus1, uint8_t unitType, binary data)
+    : H265NalUnit(data.size() + H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE) {
+	setForbiddenBit(forbiddenBit);
+	setNuhLayerId(nuhLayerId);
+	setNuhTempIdPlus1(nuhTempIdPlus1);
+	fragmentIndicator()->setUnitType(H265NalUnitFragment::nal_type_fu);
+	setFragmentType(type);
+	setUnitType(unitType);
+	copy(data.begin(), data.end(), begin() + H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE);
+}
+
+std::vector<shared_ptr<H265NalUnitFragment>>
+H265NalUnitFragment::fragmentsFrom(shared_ptr<H265NalUnit> nalu, uint16_t maximumFragmentSize) {
+	assert(nalu->size() > maximumFragmentSize);
+	auto fragments_count = ceil(double(nalu->size()) / maximumFragmentSize);
+	maximumFragmentSize = uint16_t(int(ceil(nalu->size() / fragments_count)));
+
+	// 3 bytes for FU indicator and FU header
+	maximumFragmentSize -= (H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE);
+	auto f = nalu->forbiddenBit();
+	uint8_t nuhLayerId = nalu->nuhLayerId() & 0x3F; // 6 bits
+	uint8_t nuhTempIdPlus1 = nalu->nuhTempIdPlus1() & 0xE; // 3 bits
+	uint8_t naluType = nalu->unitType() & 0x3F; // 6 bits
+	auto payload = nalu->payload();
+	vector<shared_ptr<H265NalUnitFragment>> result{};
+	uint64_t offset = 0;
+	while (offset < payload.size()) {
+		vector<byte> fragmentData;
+		FragmentType fragmentType;
+		if (offset == 0) {
+			fragmentType = FragmentType::Start;
+		} else if (offset + maximumFragmentSize < payload.size()) {
+			fragmentType = FragmentType::Middle;
+		} else {
+			if (offset + maximumFragmentSize > payload.size()) {
+				maximumFragmentSize = uint16_t(payload.size() - offset);
+			}
+			fragmentType = FragmentType::End;
+		}
+		fragmentData = {payload.begin() + offset, payload.begin() + offset + maximumFragmentSize};
+		auto fragment =
+		    std::make_shared<H265NalUnitFragment>(fragmentType, f, nuhLayerId, nuhTempIdPlus1, naluType, fragmentData);
+		result.push_back(fragment);
+		offset += maximumFragmentSize;
+	}
+	return result;
+}
+
+void H265NalUnitFragment::setFragmentType(FragmentType type) {
+	switch (type) {
+	case FragmentType::Start:
+		fragmentHeader()->setStart(true);
+		fragmentHeader()->setEnd(false);
+		break;
+	case FragmentType::End:
+		fragmentHeader()->setStart(false);
+		fragmentHeader()->setEnd(true);
+		break;
+	default:
+		fragmentHeader()->setStart(false);
+		fragmentHeader()->setEnd(false);
+	}
+}
+
+std::vector<shared_ptr<binary>> H265NalUnits::generateFragments(uint16_t maximumFragmentSize) {
+	vector<shared_ptr<binary>> result{};
+	for (auto nalu : *this) {
+		if (nalu->size() > maximumFragmentSize) {
+			std::vector<shared_ptr<H265NalUnitFragment>> fragments =
+			    H265NalUnitFragment::fragmentsFrom(nalu, maximumFragmentSize);
+			result.insert(result.end(), fragments.begin(), fragments.end());
+		} else {
+			result.push_back(nalu);
+		}
+	}
+	return result;
+}
+
+} // namespace rtc
+
+#endif /* RTC_ENABLE_MEDIA */

+ 20 - 0
src/h265packetizationhandler.cpp

@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2023 Zita Liao (Dolby)
+ *
+ * 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 "h265packetizationhandler.hpp"
+
+namespace rtc {
+
+H265PacketizationHandler::H265PacketizationHandler(shared_ptr<H265RtpPacketizer> packetizer)
+    : MediaChainableHandler(packetizer) {}
+
+} // namespace rtc
+
+#endif /* RTC_ENABLE_MEDIA */

+ 115 - 0
src/h265rtppacketizer.cpp

@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2023 Zita Liao (Dolby)
+ *
+ * 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 "h265rtppacketizer.hpp"
+
+#include "impl/internals.hpp"
+
+#include <cassert>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+
+namespace rtc {
+
+shared_ptr<H265NalUnits> H265RtpPacketizer::splitMessage(binary_ptr message) {
+	auto nalus = std::make_shared<H265NalUnits>();
+	if (separator == NalUnit::Separator::Length) {
+		size_t index = 0;
+		while (index < message->size()) {
+			assert(index + 4 < message->size());
+			if (index + 4 >= message->size()) {
+				LOG_WARNING << "Invalid NAL Unit data (incomplete length), ignoring!";
+				break;
+			}
+			auto lengthPtr = (uint32_t *)(message->data() + index);
+			uint32_t length = ntohl(*lengthPtr);
+			auto naluStartIndex = index + 4;
+			auto naluEndIndex = naluStartIndex + length;
+
+			assert(naluEndIndex <= message->size());
+			if (naluEndIndex > message->size()) {
+				LOG_WARNING << "Invalid NAL Unit data (incomplete unit), ignoring!";
+				break;
+			}
+			auto begin = message->begin() + naluStartIndex;
+			auto end = message->begin() + naluEndIndex;
+			nalus->push_back(std::make_shared<H265NalUnit>(begin, end));
+			index = naluEndIndex;
+		}
+	} else {
+		NalUnitStartSequenceMatch match = NUSM_noMatch;
+		size_t index = 0;
+		while (index < message->size()) {
+			match = NalUnit::StartSequenceMatchSucc(match, (*message)[index++], separator);
+			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
+				match = NUSM_noMatch;
+				break;
+			}
+		}
+
+		size_t naluStartIndex = index;
+
+		while (index < message->size()) {
+			match = NalUnit::StartSequenceMatchSucc(match, (*message)[index], separator);
+			if (match == NUSM_longMatch || match == NUSM_shortMatch) {
+				auto sequenceLength = match == NUSM_longMatch ? 4 : 3;
+				size_t naluEndIndex = index - sequenceLength;
+				match = NUSM_noMatch;
+				auto begin = message->begin() + naluStartIndex;
+				auto end = message->begin() + naluEndIndex + 1;
+				nalus->push_back(std::make_shared<H265NalUnit>(begin, end));
+				naluStartIndex = index + 1;
+			}
+			index++;
+		}
+		auto begin = message->begin() + naluStartIndex;
+		auto end = message->end();
+		nalus->push_back(std::make_shared<H265NalUnit>(begin, end));
+	}
+	return nalus;
+}
+
+H265RtpPacketizer::H265RtpPacketizer(shared_ptr<RtpPacketizationConfig> rtpConfig,
+                                     uint16_t maximumFragmentSize)
+    : RtpPacketizer(rtpConfig), MediaHandlerRootElement(), maximumFragmentSize(maximumFragmentSize),
+      separator(NalUnit::Separator::Length) {}
+
+H265RtpPacketizer::H265RtpPacketizer(NalUnit::Separator separator,
+                                     shared_ptr<RtpPacketizationConfig> rtpConfig,
+                                     uint16_t maximumFragmentSize)
+    : RtpPacketizer(rtpConfig), MediaHandlerRootElement(), maximumFragmentSize(maximumFragmentSize),
+      separator(separator) {}
+
+ChainedOutgoingProduct
+H265RtpPacketizer::processOutgoingBinaryMessage(ChainedMessagesProduct messages,
+                                                message_ptr control) {
+	ChainedMessagesProduct packets = std::make_shared<std::vector<binary_ptr>>();
+	for (auto message : *messages) {
+		auto nalus = splitMessage(message);
+		auto fragments = nalus->generateFragments(maximumFragmentSize);
+		if (fragments.size() == 0) {
+			return ChainedOutgoingProduct();
+		}
+		unsigned i = 0;
+		for (; i < fragments.size() - 1; i++) {
+			packets->push_back(packetize(fragments[i], false));
+		}
+		packets->push_back(packetize(fragments[i], true));
+	}
+	return {packets, control};
+}
+
+} // namespace rtc
+
+#endif /* RTC_ENABLE_MEDIA */