H.264 Codec for ESP32-S3
Loading...
Searching...
No Matches
RDPPacketizer.h
Go to the documentation of this file.
1#pragma once
2#include <WiFiUdp.h>
3
4#include <vector>
5
6namespace esp_h264 {
7/**
8 * @brief RDPPacketizer class to packetize H.264 NAL units into RTP packets.
9 * This class takes raw H.264 NAL units (in Annex-B format) and packetizes them
10 * into RTP packets suitable for streaming over UDP.
11 *
12 * This way you can e.g. Send H264 frames to QGroundcontrol
13 */
14class RDPPacketizer : public Print {
15 public:
16 static constexpr size_t MTU = 1200;
17 static constexpr uint8_t RTP_VERSION = 2;
18 static constexpr uint8_t RTP_PAYLOAD_TYPE = 96;
19
20 RDPPacketizer(Print &out,
21 uint32_t ssrc = 0x12345678)
22 : out_(out), ssrc_(ssrc) {
23 buffer_.reserve(MTU);
24 }
25
26 // Call once per encoded frame
28 if (!data || len < 5) return 0;
29 ESP_LOGI(TAG, "Packetizing frame of size %u bytes", (unsigned)len);
30
31 // Stable timestamp (better than millis jitter)
32 timestamp_ += 3000; // ~30 FPS (90000 / 30)
33
34 // Parse Annex-B and extract ALL NAL units in the frame
35 std::vector<std::pair<const uint8_t*, size_t>> nals;
36 extractNALs(data, len, nals);
37 if (nals.empty()) return 0;
38
39 // Detect IDR
40 bool is_idr = false;
41 for (auto& n : nals) {
42 uint8_t type = n.first[0] & 0x1F;
43 if (type == 5) is_idr = true;
44 if (type == 7) sps_.assign(n.first, n.first + n.second);
45 if (type == 8) pps_.assign(n.first, n.first + n.second);
46 }
47
48 // Inject SPS/PPS before IDR
49 std::vector<std::pair<const uint8_t*, size_t>> send_list;
50
51 if (is_idr && !sps_.empty() && !pps_.empty()) {
52 send_list.emplace_back(sps_.data(), sps_.size());
53 send_list.emplace_back(pps_.data(), pps_.size());
54 }
55
56 for (auto& n : nals) {
57 send_list.push_back(n);
58 }
59
60 // Send all NALs, only LAST packet gets marker bit
61 for (size_t i = 0; i < send_list.size(); ++i) {
62 bool last_nal = (i == send_list.size() - 1);
63 sendNAL(send_list[i].first, send_list[i].second, last_nal);
64 }
65 return len;
66 }
67
68 virtual size_t write(uint8_t) {
69 // Not used, we override writeFrame() instead
70 ESP_LOGE("RTPPacketizer", "write(uint8_t) is not implemented, use write() instead");
71 return 0;
72 }
73
74
75 private:
76 Print &out_;
77 std::vector<uint8_t> buffer_;
78
79 uint16_t sequence_number_ = 0;
80 uint32_t timestamp_ = 0;
81 uint32_t ssrc_;
82
83 std::vector<uint8_t> sps_;
84 std::vector<uint8_t> pps_;
85 const char* TAG = "RDPPacketizer";
86
87 // -------------------------
88 // RTP send helpers
89 // -------------------------
90
91 void sendPacket(const uint8_t* payload, size_t payload_len, bool marker) {
92 ESP_LOGD(TAG, "sendPacket: %u bytes", (unsigned)payload_len);
93 uint8_t rtp[12];
94 buildHeader(rtp, marker);
95 buffer_.clear();
96 buffer_.insert(buffer_.end(), rtp, rtp + 12);
97 buffer_.insert(buffer_.end(), payload, payload + payload_len);
98 out_.write(buffer_.data(), buffer_.size());
99
100 sequence_number_++;
101 }
102
103 void sendNAL(const uint8_t* data, size_t len, bool last_nal_of_frame) {
104 ESP_LOGD(TAG, "sendNAL: size=%u bytes, last_nal_of_frame=%d", (unsigned)len, last_nal_of_frame);
105 if (len <= MTU - 12) {
106 sendPacket(data, len, last_nal_of_frame);
107 return;
108 }
109
110 // FU-A fragmentation
111 uint8_t nal_header = data[0];
112 uint8_t nal_type = nal_header & 0x1F;
113
114 size_t payload_max = MTU - 12 - 2;
115 size_t pos = 1;
116 bool start = true;
117
118 while (pos < len) {
119 size_t frag_len = std::min(payload_max, len - pos);
120 bool end = (pos + frag_len) >= len;
121
122 uint8_t fua[2];
123 fua[0] = (nal_header & 0xE0) | 28;
124 fua[1] = (start ? 0x80 : 0x00) | (end ? 0x40 : 0x00) | nal_type;
125
126 uint8_t buffer[MTU];
127 memcpy(buffer, fua, 2);
128 memcpy(buffer + 2, data + pos, frag_len);
129
130 bool marker = last_nal_of_frame && end;
131 sendPacket(buffer, frag_len + 2, marker);
132
133 pos += frag_len;
134 start = false;
135 }
136 }
137
138 void buildHeader(uint8_t* hdr, bool marker) {
139 hdr[0] = (RTP_VERSION << 6);
140 hdr[1] = (marker ? 0x80 : 0x00) | (RTP_PAYLOAD_TYPE & 0x7F);
141
142 hdr[2] = sequence_number_ >> 8;
143 hdr[3] = sequence_number_ & 0xFF;
144
145 hdr[4] = (timestamp_ >> 24) & 0xFF;
146 hdr[5] = (timestamp_ >> 16) & 0xFF;
147 hdr[6] = (timestamp_ >> 8) & 0xFF;
148 hdr[7] = (timestamp_) & 0xFF;
149
150 hdr[8] = (ssrc_ >> 24) & 0xFF;
151 hdr[9] = (ssrc_ >> 16) & 0xFF;
152 hdr[10] = (ssrc_ >> 8) & 0xFF;
153 hdr[11] = (ssrc_) & 0xFF;
154 }
155
156 // -------------------------
157 // Annex-B parser (multi-NAL safe)
158 // -------------------------
159
160 void extractNALs(const uint8_t* data, size_t len,
161 std::vector<std::pair<const uint8_t*, size_t>>& out) {
162 size_t i = 0;
163
164 while (i + 4 < len) {
165 // find start code
166 size_t start = i;
167 while (start + 3 < len &&
168 !(data[start] == 0 && data[start + 1] == 0 &&
169 (data[start + 2] == 1 ||
170 (data[start + 2] == 0 && data[start + 3] == 1)))) {
171 start++;
172 }
173
174 if (start + 3 >= len) break;
175
176 size_t sc_size = (data[start + 2] == 1) ? 3 : 4;
177 size_t nal_start = start + sc_size;
178
179 size_t next = nal_start;
180 while (next + 3 < len &&
181 !(data[next] == 0 && data[next + 1] == 0 &&
182 (data[next + 2] == 1 ||
183 (data[next + 2] == 0 && data[next + 3] == 1)))) {
184 next++;
185 }
186
187 size_t nal_len = next - nal_start;
188 if (nal_len > 0) {
189 out.emplace_back(data + nal_start, nal_len);
190 }
191
192 i = next;
193 }
194 }
195};
196
197} // namespace esp_h264
RDPPacketizer class to packetize H.264 NAL units into RTP packets. This class takes raw H....
Definition: RDPPacketizer.h:14
virtual size_t write(uint8_t)
Definition: RDPPacketizer.h:68
static constexpr uint8_t RTP_PAYLOAD_TYPE
Definition: RDPPacketizer.h:18
static constexpr size_t MTU
Definition: RDPPacketizer.h:16
static constexpr uint8_t RTP_VERSION
Definition: RDPPacketizer.h:17
RDPPacketizer(Print &out, uint32_t ssrc=0x12345678)
Definition: RDPPacketizer.h:20
size_t write(const uint8_t *data, size_t len)
Definition: RDPPacketizer.h:27
Definition: H264Decoder.h:31