arduino-audio-tools
Loading...
Searching...
No Matches
JPEGRtpEncoder.h
Go to the documentation of this file.
1/*
2 * Author: Phil Schatzmann
3 *
4 * JPEG RTP Encoder for RTSP Video Streaming
5 * Implements RFC 2435 compliant JPEG over RTP fragmentation
6 */
7
8#pragma once
9
12#include "RTSPFormat.h"
13#include "RTSPFragmentQueue.h"
14#include "RTSPVideoEncoder.h"
15
16namespace audio_tools {
17
54 public:
55 JPEGRtpEncoder() = default;
56
62 JPEGRtpEncoder(size_t maxFragmentSize) : m_maxFragmentSize(maxFragmentSize) {}
63
65 void setMaxFragmentSize(size_t size) { m_maxFragmentSize = size; }
66
68 void setMaxQueuedFrames(size_t frames) { m_maxQueuedFrames = frames; }
69
71 void setFormat(RTSPFormat &format) override { p_format = &format; }
72
73 // -- IMediaSource --------------------------------------------------
74
75 RTSPFormat &getFormat() override {
76 return p_format != nullptr ? *p_format : default_format;
77 }
78
79 void start() override { begin(); }
80 void stop() override { end(); }
81
82 int packetSize() override { return queuePacketSize(); }
83
86 int readBytes(void *dest, int maxBytes) override {
87 return queueReadBytes(dest, maxBytes);
88 }
89
90 // -- AudioEncoder ----------------------------------------------------
91
92 bool begin() override {
93 LOGI("Starting JPEG RTP Encoder");
94 m_isStarted = true;
96 m_frameComplete = true;
97 clearQueue();
98 return true;
99 }
100
101 void end() override {
102 LOGI("Stopping JPEG RTP Encoder");
103 m_isStarted = false;
105 clearQueue();
106 }
107
108 void setAudioInfo(AudioInfo info) override {
109 // Video encoder: dimensions/framerate come from the RTSPFormat, not
110 // AudioInfo
111 (void)info;
112 }
113
114 const char *mime() override { return "video/jpeg"; }
115
123 size_t write(const uint8_t *data, size_t len) override {
124 if (!m_isStarted || len == 0) {
125 return 0;
126 }
127
128 if (m_frameComplete) {
130 m_frameComplete = false;
131 }
132
133 size_t oldSize = m_currentFrame.size();
134 m_currentFrame.resize(oldSize + len);
135 memcpy(m_currentFrame.data() + oldSize, data, len);
136
137 if (isCompleteJpegFrame()) {
139 m_frameComplete = true;
140 }
141
142 return len;
143 }
144
145 operator bool() override { return m_isStarted; }
146
147 protected:
149 RTSPFormatMJPEG default_format; // fallback so getFormat() is never null
150
151 bool m_isStarted = false;
152 size_t m_maxFragmentSize = 1400; // Typical MTU minus IP/UDP/RTP headers
153
154 // Frame accumulation state
156 bool m_frameComplete = true;
157
158 // Reused scratch buffer for building one fragment at a time
160
166 size_t size = m_currentFrame.size();
167 if (size < 4) return false;
168 return (m_currentFrame[size - 2] == 0xFF && m_currentFrame[size - 1] == 0xD9);
169 }
170
179 size_t size = m_currentFrame.size();
180 if (size < 4) return 0;
181 for (size_t i = 0; i + 4 <= size; i++) {
182 if (m_currentFrame[i] == 0xFF && m_currentFrame[i + 1] == 0xDA) {
183 size_t sosLength = (m_currentFrame[i + 2] << 8) | m_currentFrame[i + 3];
184 size_t dataStart = i + 2 + sosLength;
185 return dataStart <= size ? dataStart : 0;
186 }
187 }
188 return 0;
189 }
190
208 int extractQuantTables(const uint8_t *data, size_t headerLen, uint8_t *out) {
209 uint8_t table0[64];
210 uint8_t table1[64];
211 bool has0 = false, has1 = false;
212
213 size_t i = 2; // skip SOI (0xFFD8)
214 while (i + 4 <= headerLen) {
215 if (data[i] != 0xFF) {
216 ++i;
217 continue;
218 }
219 uint8_t marker = data[i + 1];
220 if (marker == 0xD8 || marker == 0x01 || (marker >= 0xD0 && marker <= 0xD7)) {
221 i += 2;
222 continue;
223 }
224 size_t segLen = (data[i + 2] << 8) | data[i + 3];
225 if (segLen < 2 || i + 2 + segLen > headerLen) break;
226
227 if (marker == 0xDB) { // DQT
228 size_t p = i + 4;
229 size_t segEnd = i + 2 + segLen;
230 while (p < segEnd) {
231 uint8_t pqtq = data[p++];
232 uint8_t precision = pqtq >> 4;
233 uint8_t id = pqtq & 0x0F;
234 size_t tableBytes = precision == 0 ? 64 : 128;
235 if (p + tableBytes > segEnd) break;
236 if (precision == 0) {
237 if (id == 0 && !has0) {
238 memcpy(table0, data + p, 64);
239 has0 = true;
240 } else if (id == 1 && !has1) {
241 memcpy(table1, data + p, 64);
242 has1 = true;
243 }
244 }
245 p += tableBytes;
246 }
247 } else if (marker == 0xDA) {
248 break; // reached scan data, no more tables before it
249 }
250 i += 2 + segLen;
251 }
252
253 if (!has0) return 0;
254 memcpy(out, table0, 64);
255 memcpy(out + 64, has1 ? table1 : table0, 64);
256 return 128;
257 }
258
264 uint8_t detectJpegType(const uint8_t *data, size_t headerLen) {
265 size_t i = 2;
266 while (i + 4 <= headerLen) {
267 if (data[i] != 0xFF) {
268 ++i;
269 continue;
270 }
271 uint8_t marker = data[i + 1];
272 if (marker == 0xD8 || marker == 0x01 || (marker >= 0xD0 && marker <= 0xD7)) {
273 i += 2;
274 continue;
275 }
276 size_t segLen = (data[i + 2] << 8) | data[i + 3];
277 if (segLen < 2 || i + 2 + segLen > headerLen) break;
278
279 if (marker == 0xC0 || marker == 0xC1) { // SOF0 / SOF1 (baseline)
280 size_t p = i + 4;
281 size_t segEnd = i + 2 + segLen;
282 // layout from p: precision(1) Y(2) X(2) Nf(1) [Cid(1) HV(1) Tq(1)]...
283 if (p + 8 <= segEnd) {
284 uint8_t hv = data[p + 7];
285 uint8_t h = hv >> 4;
286 uint8_t v = hv & 0x0F;
287 if (h == 2 && v == 1) return 0; // 4:2:2
288 if (h == 2 && v == 2) return 1; // 4:2:0
289 LOGW(
290 "JPEGRtpEncoder: unsupported chroma subsampling H=%d V=%d; "
291 "assuming 4:2:2",
292 h, v);
293 }
294 return 0;
295 }
296 if (marker == 0xDA) break;
297 i += 2 + segLen;
298 }
299 LOGW("JPEGRtpEncoder: SOF0 marker not found; assuming 4:2:2 subsampling");
300 return 0;
301 }
302
303 void buildJpegRtpHeader(uint8_t *buffer, size_t fragmentOffset, int width,
304 int height, uint8_t type, uint8_t qValue) {
305 buffer[0] = 0; // Type-specific field (0 for baseline JPEG)
306 buffer[1] = (fragmentOffset >> 16) & 0xFF;
307 buffer[2] = (fragmentOffset >> 8) & 0xFF;
308 buffer[3] = fragmentOffset & 0xFF;
309 buffer[4] = type;
310 buffer[5] = qValue;
311 buffer[6] = (uint8_t)((width / 8) & 0xFF);
312 buffer[7] = (uint8_t)((height / 8) & 0xFF);
313 }
314
317 void writeQuantTableHeader(uint8_t *buffer, const uint8_t *qTable, int qTableLen) {
318 buffer[0] = 0; // MBZ
319 buffer[1] = 0; // Precision: 0 = 8-bit for all tables
320 buffer[2] = (qTableLen >> 8) & 0xFF;
321 buffer[3] = qTableLen & 0xFF;
322 memcpy(buffer + 4, qTable, qTableLen);
323 }
324
330 if (p_format == nullptr) {
331 LOGE("JPEGRtpEncoder: no RTSPFormat configured (call setFormat()); dropping frame");
332 return;
333 }
334
335 size_t headerSize = findJpegDataStart();
336 if (headerSize == 0 || headerSize >= m_currentFrame.size()) {
337 LOGW("JPEGRtpEncoder: could not locate JPEG scan data (SOS marker); dropping frame");
338 return;
339 }
340
341 uint8_t qTable[128];
342 int qTableLen = extractQuantTables(m_currentFrame.data(), headerSize, qTable);
343 uint8_t qValue = qTableLen > 0 ? 255 : 127;
344 if (qTableLen == 0) {
345 LOGW(
346 "JPEGRtpEncoder: could not extract quantization tables from frame; "
347 "using approximate standard tables (Q=%d)",
348 qValue);
349 }
350 uint8_t type = detectJpegType(m_currentFrame.data(), headerSize);
351
353 int width = vi.width;
354 int height = vi.height;
355
356 const uint8_t *frameData = m_currentFrame.data() + headerSize;
357 size_t frameSize = m_currentFrame.size() - headerSize;
358 size_t processedOffset = 0;
359 int fragmentCount = 0;
360
361 while (processedOffset < frameSize) {
362 bool isFirst = (processedOffset == 0);
363 size_t qLen = isFirst ? (size_t)qTableLen : 0;
364 size_t qHeaderLen = qLen > 0 ? 4 + qLen : 0;
365
366 if (m_maxFragmentSize <= 8 + qHeaderLen) {
367 LOGE("JPEGRtpEncoder: maxFragmentSize too small to fit RTP/JPEG headers");
368 return;
369 }
370 size_t maxData = m_maxFragmentSize - 8 - qHeaderLen;
371 size_t chunk = frameSize - processedOffset;
372 if (chunk > maxData) chunk = maxData;
373 bool isLast = (processedOffset + chunk >= frameSize);
374
375 m_scratch.resize(8 + qHeaderLen + chunk);
376 buildJpegRtpHeader(m_scratch.data(), processedOffset, width, height, type, qValue);
377 if (qHeaderLen > 0) {
378 writeQuantTableHeader(m_scratch.data() + 8, qTable, qTableLen);
379 }
380 memcpy(m_scratch.data() + 8 + qHeaderLen, frameData + processedOffset, chunk);
381
383
384 processedOffset += chunk;
385 ++fragmentCount;
386 }
387
388 LOGI("JPEGRtpEncoder: queued %d RTP fragments for %u byte frame",
389 fragmentCount, (unsigned)frameSize);
390 }
391};
392
393} // namespace audio_tools
#define LOGW(...)
Definition AudioLoggerIDF.h:29
#define LOGI(...)
Definition AudioLoggerIDF.h:28
#define LOGE(...)
Definition AudioLoggerIDF.h:30
AudioInfo info
Definition AudioCodecsBase.h:129
virtual int frameSize()
Definition AudioCodecsBase.h:126
JPEG RTP Encoder - Fragments JPEG frames per RFC 2435 for RTSP video streaming.
Definition JPEGRtpEncoder.h:53
bool isCompleteJpegFrame()
Check if current buffer contains a complete JPEG frame.
Definition JPEGRtpEncoder.h:165
void writeQuantTableHeader(uint8_t *buffer, const uint8_t *qTable, int qTableLen)
Definition JPEGRtpEncoder.h:317
bool m_isStarted
Definition JPEGRtpEncoder.h:151
RTSPFormat & getFormat() override
Get the media format configuration.
Definition JPEGRtpEncoder.h:75
void setFormat(RTSPFormat &format) override
Associates the RTSPFormat (e.g. RTSPFormatMJPEG) that supplies width/height.
Definition JPEGRtpEncoder.h:71
int extractQuantTables(const uint8_t *data, size_t headerLen, uint8_t *out)
Extract the (8-bit precision) quantization tables 0 and 1 from the JPEG DQT segments found in [data,...
Definition JPEGRtpEncoder.h:208
void processCompleteFrame()
Strip the JPEG headers, fragment the scan data and queue one RFC 2435 RTP payload per fragment.
Definition JPEGRtpEncoder.h:329
void setMaxQueuedFrames(size_t frames)
Maximum number of complete frames buffered when the consumer falls behind.
Definition JPEGRtpEncoder.h:68
RTSPFormatMJPEG default_format
Definition JPEGRtpEncoder.h:149
Vector< uint8_t > m_currentFrame
Definition JPEGRtpEncoder.h:155
void start() override
Initialize media source for streaming.
Definition JPEGRtpEncoder.h:79
uint8_t detectJpegType(const uint8_t *data, size_t headerLen)
Determine the RFC 2435 Type (0 = 4:2:2, 1 = 4:2:0) from the JPEG's SOF0/SOF1 marker so the receiver r...
Definition JPEGRtpEncoder.h:264
void end() override
Definition JPEGRtpEncoder.h:101
int packetSize() override
Size of the next queued, ready-to-send fragment, for sources that provide already RTP-payload-ready,...
Definition JPEGRtpEncoder.h:82
Vector< uint8_t > m_scratch
Definition JPEGRtpEncoder.h:159
size_t write(const uint8_t *data, size_t len) override
Process JPEG frame data and queue the resulting RTP fragments.
Definition JPEGRtpEncoder.h:123
bool m_frameComplete
Definition JPEGRtpEncoder.h:156
size_t findJpegDataStart()
Find the start of the entropy-coded scan data, i.e. the first byte after the SOS (Start Of Scan) mark...
Definition JPEGRtpEncoder.h:178
JPEGRtpEncoder(size_t maxFragmentSize)
Constructor with maximum fragment size.
Definition JPEGRtpEncoder.h:62
const char * mime() override
Provides the mime type of the encoded result.
Definition JPEGRtpEncoder.h:114
void stop() override
Cleanup media source after streaming.
Definition JPEGRtpEncoder.h:80
void setMaxFragmentSize(size_t size)
Set the maximum RTP payload size per packet (header + data)
Definition JPEGRtpEncoder.h:65
void buildJpegRtpHeader(uint8_t *buffer, size_t fragmentOffset, int width, int height, uint8_t type, uint8_t qValue)
Definition JPEGRtpEncoder.h:303
bool begin() override
Definition JPEGRtpEncoder.h:92
void setAudioInfo(AudioInfo info) override
Defines the sample rate, number of channels and bits per sample.
Definition JPEGRtpEncoder.h:108
int readBytes(void *dest, int maxBytes) override
Definition JPEGRtpEncoder.h:86
RTSPFormat * p_format
Definition JPEGRtpEncoder.h:148
size_t m_maxFragmentSize
Definition JPEGRtpEncoder.h:152
Audio Format Definition - Base class for RTSP audio formats.
Definition RTSPFormat.h:57
virtual RTSPVideoInfo videoInfo()
Definition RTSPFormat.h:84
MJPEG (Motion JPEG) format for RTSP video streaming.
Definition RTSPFormat.h:771
Self-delimited fragment queue shared by packetized RTP video encoders (JPEGRtpEncoder,...
Definition RTSPFragmentQueue.h:36
void appendFragment(const uint8_t *payload, size_t len, bool last)
Definition RTSPFragmentQueue.h:60
void clearQueue()
Definition RTSPFragmentQueue.h:49
int queueReadBytes(void *dest, int maxBytes)
Definition RTSPFragmentQueue.h:102
int queuePacketSize()
Definition RTSPFragmentQueue.h:92
size_t m_maxQueuedFrames
Definition RTSPFragmentQueue.h:43
Common base for RTP video payloaders (JPEGRtpEncoder, H264RtpEncoder, ...): an AudioEncoder that acce...
Definition RTSPVideoEncoder.h:31
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
void clear()
Definition Vector.h:176
bool resize(size_t newSize, T value)
Definition Vector.h:266
T * data()
Definition Vector.h:316
int size()
Definition Vector.h:178
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:56
Video Information Structure.
Definition VideoInfo.h:43
uint16_t height
Video height in pixels.
Definition VideoInfo.h:45
uint16_t width
Video width in pixels.
Definition VideoInfo.h:44