Skip to content

Commit 770d074

Browse files
Merge pull request #1134 from edmonds/h265-rtp-depacketizer
Add H265RtpDepacketizer
2 parents 3c33ea0 + 3dc1a17 commit 770d074

File tree

5 files changed

+246
-1
lines changed

5 files changed

+246
-1
lines changed

CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ set(LIBDATACHANNEL_SOURCES
8282
${CMAKE_CURRENT_SOURCE_DIR}/src/h264rtpdepacketizer.cpp
8383
${CMAKE_CURRENT_SOURCE_DIR}/src/nalunit.cpp
8484
${CMAKE_CURRENT_SOURCE_DIR}/src/h265rtppacketizer.cpp
85+
${CMAKE_CURRENT_SOURCE_DIR}/src/h265rtpdepacketizer.cpp
8586
${CMAKE_CURRENT_SOURCE_DIR}/src/h265nalunit.cpp
8687
${CMAKE_CURRENT_SOURCE_DIR}/src/av1rtppacketizer.cpp
8788
${CMAKE_CURRENT_SOURCE_DIR}/src/rtcpnackresponder.cpp
@@ -120,6 +121,7 @@ set(LIBDATACHANNEL_HEADERS
120121
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h264rtpdepacketizer.hpp
121122
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/nalunit.hpp
122123
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265rtppacketizer.hpp
124+
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265rtpdepacketizer.hpp
123125
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/h265nalunit.hpp
124126
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/av1rtppacketizer.hpp
125127
${CMAKE_CURRENT_SOURCE_DIR}/include/rtc/rtcpnackresponder.hpp

include/rtc/h265rtpdepacketizer.hpp

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Copyright (c) 2020 Staz Modrzynski
3+
* Copyright (c) 2020-2024 Paul-Louis Ageneau
4+
* Copyright (c) 2024 Robert Edmonds
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public
7+
* License, v. 2.0. If a copy of the MPL was not distributed with this
8+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
9+
*/
10+
11+
#ifndef RTC_H265_RTP_DEPACKETIZER_H
12+
#define RTC_H265_RTP_DEPACKETIZER_H
13+
14+
#if RTC_ENABLE_MEDIA
15+
16+
#include "common.hpp"
17+
#include "h265nalunit.hpp"
18+
#include "mediahandler.hpp"
19+
#include "message.hpp"
20+
#include "rtp.hpp"
21+
22+
#include <iterator>
23+
24+
namespace rtc {
25+
26+
/// RTP depacketization for H265
27+
class RTC_CPP_EXPORT H265RtpDepacketizer : public MediaHandler {
28+
public:
29+
using Separator = NalUnit::Separator;
30+
31+
H265RtpDepacketizer(Separator separator = Separator::LongStartSequence);
32+
virtual ~H265RtpDepacketizer() = default;
33+
34+
void incoming(message_vector &messages, const message_callback &send) override;
35+
36+
private:
37+
std::vector<message_ptr> mRtpBuffer;
38+
const NalUnit::Separator separator;
39+
40+
void addSeparator(binary& accessUnit);
41+
message_vector buildFrames(message_vector::iterator firstPkt, message_vector::iterator lastPkt,
42+
uint8_t payloadType, uint32_t timestamp);
43+
};
44+
45+
} // namespace rtc
46+
47+
#endif // RTC_ENABLE_MEDIA
48+
49+
#endif // RTC_H265_RTP_DEPACKETIZER_H

include/rtc/rtc.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "h264rtppacketizer.hpp"
3333
#include "h264rtpdepacketizer.hpp"
3434
#include "h265rtppacketizer.hpp"
35+
#include "h265rtpdepacketizer.hpp"
3536
#include "mediahandler.hpp"
3637
#include "plihandler.hpp"
3738
#include "rembhandler.hpp"

src/h265nalunit.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ H265NalUnitFragment::fragmentsFrom(shared_ptr<H265NalUnit> nalu, uint16_t maxFra
3434
auto fragments_count = ceil(double(nalu->size()) / maxFragmentSize);
3535
maxFragmentSize = uint16_t(int(ceil(nalu->size() / fragments_count)));
3636

37-
// 3 bytes for FU indicator and FU header
37+
// 3 bytes for NALU header and FU header
3838
maxFragmentSize -= (H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE);
3939
auto f = nalu->forbiddenBit();
4040
uint8_t nuhLayerId = nalu->nuhLayerId() & 0x3F; // 6 bits

src/h265rtpdepacketizer.cpp

+193
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/**
2+
* Copyright (c) 2023-2024 Paul-Louis Ageneau
3+
* Copyright (c) 2024 Robert Edmonds
4+
*
5+
* This Source Code Form is subject to the terms of the Mozilla Public
6+
* License, v. 2.0. If a copy of the MPL was not distributed with this
7+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
8+
*/
9+
10+
#if RTC_ENABLE_MEDIA
11+
12+
#include "h265rtpdepacketizer.hpp"
13+
#include "h265nalunit.hpp"
14+
15+
#include "impl/internals.hpp"
16+
17+
#include <algorithm>
18+
19+
namespace rtc {
20+
21+
const binary naluLongStartCode = {byte{0}, byte{0}, byte{0}, byte{1}};
22+
const binary naluShortStartCode = {byte{0}, byte{0}, byte{1}};
23+
24+
const uint8_t naluTypeAP = 48;
25+
const uint8_t naluTypeFU = 49;
26+
27+
H265RtpDepacketizer::H265RtpDepacketizer(Separator separator) : separator(separator) {
28+
switch (separator) {
29+
case Separator::StartSequence: [[fallthrough]];
30+
case Separator::LongStartSequence: [[fallthrough]];
31+
case Separator::ShortStartSequence:
32+
break;
33+
case Separator::Length: [[fallthrough]];
34+
default:
35+
throw std::invalid_argument("Invalid separator");
36+
}
37+
}
38+
39+
void H265RtpDepacketizer::addSeparator(binary& accessUnit)
40+
{
41+
switch (separator) {
42+
case Separator::StartSequence: [[fallthrough]];
43+
case Separator::LongStartSequence:
44+
accessUnit.insert(accessUnit.end(),
45+
naluLongStartCode.begin(),
46+
naluLongStartCode.end());
47+
break;
48+
case Separator::ShortStartSequence:
49+
accessUnit.insert(accessUnit.end(),
50+
naluShortStartCode.begin(),
51+
naluShortStartCode.end());
52+
break;
53+
case Separator::Length: [[fallthrough]];
54+
default:
55+
throw std::invalid_argument("Invalid separator");
56+
}
57+
}
58+
59+
message_vector H265RtpDepacketizer::buildFrames(message_vector::iterator begin,
60+
message_vector::iterator end,
61+
uint8_t payloadType, uint32_t timestamp) {
62+
message_vector out = {};
63+
auto accessUnit = binary{};
64+
auto frameInfo = std::make_shared<FrameInfo>(payloadType, timestamp);
65+
66+
for (auto it = begin; it != end; ++it) {
67+
auto pkt = it->get();
68+
auto pktParsed = reinterpret_cast<const rtc::RtpHeader *>(pkt->data());
69+
auto rtpHeaderSize = pktParsed->getSize() + pktParsed->getExtensionHeaderSize();
70+
auto rtpPaddingSize = 0;
71+
72+
if (pktParsed->padding()) {
73+
rtpPaddingSize = std::to_integer<uint8_t>(pkt->at(pkt->size() - 1));
74+
}
75+
76+
if (pkt->size() == rtpHeaderSize + rtpPaddingSize) {
77+
PLOG_VERBOSE << "H.265 RTP packet has empty payload";
78+
continue;
79+
}
80+
81+
auto nalUnitHeader =
82+
H265NalUnitHeader{std::to_integer<uint8_t>(pkt->at(rtpHeaderSize)),
83+
std::to_integer<uint8_t>(pkt->at(rtpHeaderSize + 1))};
84+
85+
if (nalUnitHeader.unitType() == naluTypeFU) {
86+
auto nalUnitFragmentHeader = H265NalUnitFragmentHeader{
87+
std::to_integer<uint8_t>(pkt->at(rtpHeaderSize + sizeof(H265NalUnitHeader)))};
88+
89+
// RFC 7798: "When set to 1, the S bit indicates the start of a fragmented
90+
// NAL unit, i.e., the first byte of the FU payload is also the first byte of
91+
// the payload of the fragmented NAL unit. When the FU payload is not the start
92+
// of the fragmented NAL unit payload, the S bit MUST be set to 0."
93+
if (nalUnitFragmentHeader.isStart() || accessUnit.empty()) {
94+
addSeparator(accessUnit);
95+
nalUnitHeader.setUnitType(nalUnitFragmentHeader.unitType());
96+
accessUnit.emplace_back(byte(nalUnitHeader._first));
97+
accessUnit.emplace_back(byte(nalUnitHeader._second));
98+
}
99+
100+
accessUnit.insert(accessUnit.end(),
101+
pkt->begin() + rtpHeaderSize + sizeof(H265NalUnitHeader) +
102+
sizeof(H265NalUnitFragmentHeader),
103+
pkt->end());
104+
} else if (nalUnitHeader.unitType() == naluTypeAP) {
105+
auto currOffset = rtpHeaderSize + sizeof(H265NalUnitHeader);
106+
107+
while (currOffset + sizeof(uint16_t) < pkt->size()) {
108+
auto naluSize = std::to_integer<uint16_t>(pkt->at(currOffset)) << 8 |
109+
std::to_integer<uint16_t>(pkt->at(currOffset + 1));
110+
111+
currOffset += sizeof(uint16_t);
112+
113+
if (pkt->size() < currOffset + naluSize) {
114+
throw std::runtime_error("H265 AP declared size is larger than buffer");
115+
}
116+
117+
addSeparator(accessUnit);
118+
accessUnit.insert(accessUnit.end(), pkt->begin() + currOffset,
119+
pkt->begin() + currOffset + naluSize);
120+
121+
currOffset += naluSize;
122+
}
123+
} else if (nalUnitHeader.unitType() < naluTypeAP) {
124+
// "NAL units with NAL unit type values in the range of 0 to 47, inclusive, may be
125+
// passed to the decoder."
126+
addSeparator(accessUnit);
127+
accessUnit.insert(accessUnit.end(), pkt->begin() + rtpHeaderSize, pkt->end());
128+
} else {
129+
// "NAL-unit-like structures with NAL unit type values in the range of 48 to 63,
130+
// inclusive, MUST NOT be passed to the decoder."
131+
}
132+
}
133+
134+
if (!accessUnit.empty()) {
135+
out.emplace_back(make_message(accessUnit.begin(), accessUnit.end(), Message::Binary, 0,
136+
nullptr, frameInfo));
137+
}
138+
139+
return out;
140+
}
141+
142+
void H265RtpDepacketizer::incoming(message_vector &messages, const message_callback &) {
143+
messages.erase(std::remove_if(messages.begin(), messages.end(),
144+
[&](message_ptr message) {
145+
if (message->type == Message::Control) {
146+
return false;
147+
}
148+
149+
if (message->size() < sizeof(RtpHeader)) {
150+
PLOG_VERBOSE << "RTP packet is too small, size="
151+
<< message->size();
152+
return true;
153+
}
154+
155+
mRtpBuffer.push_back(std::move(message));
156+
return true;
157+
}),
158+
messages.end());
159+
160+
while (mRtpBuffer.size() != 0) {
161+
uint8_t payload_type = 0;
162+
uint32_t current_timestamp = 0;
163+
size_t packets_in_timestamp = 0;
164+
165+
for (const auto &pkt : mRtpBuffer) {
166+
auto p = reinterpret_cast<const rtc::RtpHeader *>(pkt->data());
167+
168+
if (current_timestamp == 0) {
169+
current_timestamp = p->timestamp();
170+
payload_type = p->payloadType(); // should all be the same for data of the same codec
171+
} else if (current_timestamp != p->timestamp()) {
172+
break;
173+
}
174+
175+
packets_in_timestamp++;
176+
}
177+
178+
if (packets_in_timestamp == mRtpBuffer.size()) {
179+
break;
180+
}
181+
182+
auto begin = mRtpBuffer.begin();
183+
auto end = mRtpBuffer.begin() + (packets_in_timestamp - 1);
184+
185+
auto frames = buildFrames(begin, end + 1, payload_type, current_timestamp);
186+
messages.insert(messages.end(), frames.begin(), frames.end());
187+
mRtpBuffer.erase(mRtpBuffer.begin(), mRtpBuffer.begin() + packets_in_timestamp);
188+
}
189+
}
190+
191+
} // namespace rtc
192+
193+
#endif // RTC_ENABLE_MEDIA

0 commit comments

Comments
 (0)