arduino-audio-tools
Loading...
Searching...
No Matches
CodecMTS.h
Go to the documentation of this file.
1#pragma once
2
3#define TS_PACKET_SIZE 188
4
5#ifndef MTS_WRITE_BUFFER_SIZE
6#define MTS_WRITE_BUFFER_SIZE 2000
7#endif
8
11#include "AudioToolsConfig.h"
12#include "stdlib.h"
13
14namespace audio_tools {
15
20enum class MTSStreamType {
21 NONE = 0x00,
22 VIDEO = 0x01,
23 VIDEO_H262 = 0x02,
24 AUDIO_MP3 = 0x03,
26 PRV_SECTIONS = 0x05,
27 PES_PRV = 0x06,
28 MHEG = 0x07,
29 H222_0_DSM_CC = 0x08,
30 H222_1 = 0x09,
31 A = 0x0A,
32 B = 0x0B,
33 C = 0x0C,
34 D = 0x0D,
35 H222_0_AUX = 0x0E,
36 AUDIO_AAC = 0x0F,
37 VISUAL = 0x10,
38 AUDIO_AAC_LATM = 0x11,
39 SL_PES = 0x12,
40 SL_SECTIONS = 0x13,
41 SYNC_DOWNLOAD = 0x14,
42 PES_METADATA = 0x15,
43 METDATA_SECTIONS = 0x16,
47 IPMP = 0x1A,
48 VIDEO_AVC = 0X1B,
49 VIDEO_H222_0 = 0x1C,
50 DCII_VIDEO = 0x80,
51 AUDIO_A53 = 0x81,
52 SCTE_STD_SUBTITLE = 0x82,
53 SCTE_ISOCH_DATA = 0x83,
54 ATSC_PROG_ID = 0x85,
55 SCTE_25 = 0x86,
56 AUDIO_EAC3 = 0x87,
57 AUDIO_DTS_HD = 0x88,
58 DVB_MPE_FEC = 0x90,
59 ULE = 0x91,
60 VEI = 0x92,
62 SCTE_IP_DATA = 0xA0,
63 DCII_TEXT = 0xC0,
64 ATSC_SYNC_DATA = 0xC2,
65 SCTE_AYSNC_DATA = 0xC3,
67 VC1 = 0xEA,
68 ATSC_USER_PRIV = 0xEB,
69};
70
71// enum class AACProfile : uint8_t {
72// MAIN = 0, // AAC Main (High complexity, rarely used)
73// LC = 1, // AAC Low Complexity (Most common)
74// SSR = 2, // AAC Scalable Sample Rate (Rare)
75// LTP = 3 // AAC Long Term Prediction (Not widely supported)
76// };
77
90class MTSDecoder : public AudioDecoder {
91 public:
93 MTSDecoder() = default;
95 MTSDecoder(AudioDecoder &dec) { p_dec = &dec; };
97 bool begin() override {
98 TRACED();
99 pmt_pid = 0xFFFF; // undefined
100 pes_count = 0;
101 is_adts_missing = false;
103 frame_length = 0;
105
106 // default supported stream types
107 if (stream_types.empty()) {
110 }
111
112 // automatically close when called multiple times
113 if (is_active) {
114 end();
115 }
116
117 if (p_dec) p_dec->begin();
118 is_active = true;
119 return true;
120 }
121
123 void end() override {
124 TRACED();
125 if (p_dec) p_dec->end();
126 is_active = false;
127 }
128
129 virtual operator bool() override { return is_active; }
130
132 const char *mime() { return "video/MP2T"; }
133
134 size_t write(const uint8_t *data, size_t len) override {
135 // only process when open
136 if (!is_active) {
137 TRACEE();
138 return 0;
139 }
140
141 // free up space if the buffer can't currently hold everything
142 if ((size_t)buffer.availableForWrite() < len) {
143 demux();
144 }
145
146 // write as much as currently fits: len may exceed the total buffer
147 // capacity, so a full write can never be guaranteed. Returning a
148 // partial count (standard Print::write contract) lets the caller
149 // resend the remainder instead of stalling forever.
150 size_t toWrite = (size_t)buffer.availableForWrite() < len
151 ? (size_t)buffer.availableForWrite()
152 : len;
153 if (toWrite == 0) {
154 LOGI("MTSDecoder::write: Buffer full");
155 return 0;
156 }
157 LOGI("MTSDecoder::write: %d", (int)toWrite);
158 size_t result = buffer.writeArray((uint8_t *)data, toWrite);
159 demux();
160 return result;
161 }
162
164 void resizeBuffer(int size) { buffer.resize(size); }
165
168 TRACED();
170 }
171
174 TRACED();
176 }
177
180 for (int j = 0; j < stream_types.size(); j++) {
181 if (stream_types[j] == type) return true;
182 }
183 return false;
184 }
185
187 void setOutput(AudioStream &out_stream) override {
188 if (p_dec) {
189 p_dec->setOutput(out_stream);
190 } else {
191 AudioDecoder::setOutput(out_stream);
192 }
193 }
194
196 void setOutput(AudioOutput &out_stream) override {
197 if (p_dec) {
198 p_dec->setOutput(out_stream);
199 } else {
200 AudioDecoder::setOutput(out_stream);
201 }
202 }
203
205 void setOutput(Print &out_stream) override {
206 if (p_dec) {
207 p_dec->setOutput(out_stream);
208 } else {
209 AudioDecoder::setOutput(out_stream);
210 }
211 }
212
213 protected:
214 bool is_active = false;
218 AudioDecoder *p_dec = nullptr;
219 uint16_t pmt_pid = 0xFFFF;
220 // AACProfile aac_profile = AACProfile::LC;
224 bool is_adts_missing = false;
225 size_t pes_count = 0;
226
229 void addPID(uint16_t pid) {
230 if (pid == 0) return;
231 for (int j = 0; j < pids.size(); j++) {
232 if (pids[j] == pid) return;
233 }
234 LOGI("-> PMT PID: 0x%04X(%d)", pid, pid);
235 pids.push_back(pid);
236 }
237
239 void demux() {
240 TRACED();
241 int count = 0;
242 while (parse()) {
243 LOGI("demux: step #%d with PES #%d", ++count, (int)pes_count);
244 }
245 LOGI("Number of demux calls: %d", count);
246 }
247
249 int syncPos() {
250 int len = buffer.available();
251 if (len < TS_PACKET_SIZE) return -1;
252 for (int j = 0; j < len; j++) {
253 if (buffer.data()[j] == 0x47) {
254 return j;
255 }
256 }
257 return -1;
258 }
259
261 bool parse() {
262 int pos = syncPos();
263 if (pos < 0) return false;
264 if (pos != 0) {
265 LOGW("Sync byte not found at position 0. Skipping %d bytes", pos);
266 buffer.clearArray(pos);
267 }
268 // not enough data left for a full packet after skipping to the sync byte
269 if (buffer.available() < TS_PACKET_SIZE) return false;
270
271 // parse data
272 uint8_t *packet = buffer.data();
273 int pid = ((packet[1] & 0x1F) << 8) | (packet[2] & 0xFF);
274 LOGI("PID: 0x%04X(%d)", pid, pid);
275
276 // PES contains the audio data
277 if (!is_adts_missing && pids.contains(pid)) {
278 parsePES(packet, pid);
279 } else {
280 parsePacket(packet, pid);
281 }
282
283 // remove processed data
285 return true;
286 }
287
289 void parsePacket(uint8_t *packet, int pid) {
290 TRACEI();
291 bool payloadUnitStartIndicator = false;
292
293 int payloadStart =
294 getPayloadStart(packet, false, payloadUnitStartIndicator);
295 int len = TS_PACKET_SIZE - payloadStart;
296
297 // if we are at the beginning we start with a pat
298 if (pid == 0 && payloadUnitStartIndicator) {
299 pids.clear();
300 }
301
302 // PID 0 is for PAT
303 if (pid == 0) {
304 parsePAT(&packet[payloadStart], len);
305 } else if (pid == pmt_pid && packet[payloadStart] == 0x02) {
306 parsePMT(&packet[payloadStart], len);
307 } else {
308 LOGE("-> Packet ignored for PID 0x%x", pid);
309 }
310 }
311
312 int getPayloadStart(uint8_t *packet, bool isPES,
313 bool &payloadUnitStartIndicator) {
314 uint8_t adaptionField = (packet[3] & 0x30) >> 4;
315 int adaptationSize = 0;
316 int offset = 4; // Start after TS header (4 bytes)
317
318 // Check for adaptation field
319 // 00 (0) → Invalid (should never happen).
320 // 01 (1) → Payload only (no adaptation field).
321 // 10 (2) → Adaptation field only (no payload).
322 // 11 (3) → Adaptation field + payload.
323 if (adaptionField == 0b11) { // Adaptation field exists
324 adaptationSize = packet[4] + 1;
325 offset += adaptationSize;
326 }
327
328 // If PUSI is set, there's a pointer field (skip it)
329 if (packet[1] & 0x40) {
330 if (!isPES) offset += packet[offset] + 1;
331 payloadUnitStartIndicator = true;
332 }
333
334 LOGI("Payload Unit Start Indicator (PUSI): %d", payloadUnitStartIndicator);
335 LOGI("Adaption Field Control: 0x%x / size: %d", adaptionField,
336 adaptationSize);
337
338 return offset;
339 }
340
341 void parsePAT(uint8_t *pat, int len) {
342 TRACEI();
343 assert(pat[0] == 0); // Program Association section
344 int startOfProgramNums = 8;
345 int lengthOfPATValue = 4;
346 int sectionLength = ((pat[1] & 0x0F) << 8) | (pat[2] & 0xFF);
347 LOGI("PAT Section Length: %d", sectionLength);
348 if (sectionLength >= len) {
349 LOGE("Unexpected PAT Section Length: %d", sectionLength);
350 sectionLength = len;
351 }
352 int indexOfPids = 0;
353 for (int i = startOfProgramNums; i <= sectionLength;
354 i += lengthOfPATValue) {
355 int program_number = ((pat[i] & 0xFF) << 8) | (pat[i + 1] & 0xFF);
356 int pid = ((pat[i + 2] & 0x1F) << 8) | (pat[i + 3] & 0xFF);
357 LOGI("Program Num: 0x%04X(%d) / PID: 0x%04X(%d) ", program_number,
358 program_number, pid, pid);
359
360 if (pmt_pid == 0xFFFF && pid >= 0x0020 && pid <= 0x1FFE) {
361 pmt_pid = pid;
362 }
363 }
364 LOGI("Using PMT PID: 0x%04X(%d)", pmt_pid, pmt_pid);
365 }
366
367 void parsePMT(uint8_t *pmt, int len) {
368 TRACEI();
369 assert(pmt[0] == 0x02); // Program Association section
370 int staticLengthOfPMT = 12;
371 int sectionLength = ((pmt[1] & 0x0F) << 8) | (pmt[2] & 0xFF);
372 LOGI("- PMT Section Length: %d", sectionLength);
373 if (sectionLength >= len) {
374 LOGE("Unexpected PMT Section Length: %d", sectionLength);
375 sectionLength = len;
376 }
377 int programInfoLength = ((pmt[10] & 0x0F) << 8) | (pmt[11] & 0xFF);
378 LOGI("- PMT Program Info Length: %d", programInfoLength);
379
380 int cursor = staticLengthOfPMT + programInfoLength;
381 while (cursor < sectionLength - 1) {
382 if (cursor + 4 >= len) break;
383 MTSStreamType streamType = static_cast<MTSStreamType>(pmt[cursor] & 0xFF);
384 int elementaryPID =
385 ((pmt[cursor + 1] & 0x1F) << 8) | (pmt[cursor + 2] & 0xFF);
386 LOGI("-- Stream Type: 0x%02X(%d) [%s] for Elementary PID: 0x%04X(%d)",
387 (int)streamType, (int)streamType, toStr(streamType), elementaryPID,
388 elementaryPID);
389
390 if (isStreamTypeActive(streamType)) {
391 selected_stream_type = streamType;
392 addPID(elementaryPID);
393 }
394
395 int esInfoLength =
396 ((pmt[cursor + 3] & 0x0F) << 8) | (pmt[cursor + 4] & 0xFF);
397 LOGI("-- ES Info Length: 0x%04X(%d)", esInfoLength, esInfoLength);
398 cursor += 5 + esInfoLength;
399 }
400 }
401
402 void parsePES(uint8_t *packet, int pid) {
403 LOGI("parsePES: %d", pid);
404 ++pes_count;
405
406 // calculate payload start
407 bool payloadUnitStartIndicator = false;
408 int payloadStart = getPayloadStart(packet, true, payloadUnitStartIndicator);
409
410 // PES
411 uint8_t *pes = packet + payloadStart;
412 int len = TS_PACKET_SIZE - payloadStart;
413 // PES (AAC) data
414 uint8_t *pesData = nullptr;
415 int pesDataSize = 0;
416
417 if (payloadUnitStartIndicator) {
418 if (len < 6) {
419 LOGE("PES packet too short: %d", len);
420 return;
421 }
422 // PES header is not alligned correctly
423 if (!isPESStartCodeValid(pes)) {
424 LOGE("PES header not aligned correctly");
425 return;
426 }
427
428 int pesPacketLength =
429 (static_cast<int>(pes[4]) << 8) | static_cast<int>(pes[5]);
430
431 // PES Header size is at least 6 bytes, but can be larger with optional
432 // fields. Only inspect the optional fields when they are actually
433 // within the available data (pes[6..8]).
434 int pesHeaderSize = 6;
435 if (len >= 9 && (pes[6] & 0xC0) != 0) { // Check for PTS/DTS flags
436 pesHeaderSize += 3 + ((pes[7] & 0xC0) == 0xC0 ? 5 : 0);
437 pesHeaderSize += pes[8]; // PES header stuffing size
438 }
439 LOGI("- PES Header Size: %d", pesHeaderSize);
440
441 // pesHeaderSize is derived from stream-controlled bytes and can
442 // legally encode a value that exceeds the available payload
443 if (pesHeaderSize >= len) {
444 LOGE("Unexpected PES Header Size: %d (len: %d)", pesHeaderSize, len);
445 return;
446 }
447 pesData = pes + pesHeaderSize;
448 pesDataSize = len - pesHeaderSize;
449
452 is_adts_missing = findSyncWord(pesData, pesDataSize) == -1;
453 }
454
455 open_pes_data_size = pesPacketLength;
456
457 } else {
458 pesData = pes;
459 pesDataSize = len;
460 }
461
462 // Recalculate the open data
463 open_pes_data_size -= pesDataSize;
464 if (open_pes_data_size < 0) {
465 return;
466 }
467
469 LOGI("- writing %d bytes (open: %d)", pesDataSize, open_pes_data_size);
470 if (p_print) {
471 size_t result = writeData<uint8_t>(p_print, pesData, pesDataSize);
472 assert(result == pesDataSize);
473 }
474 if (p_dec) {
475 size_t result =
476 writeDataT<uint8_t, AudioDecoder>(p_dec, pesData, pesDataSize);
477 assert(result == pesDataSize);
478 }
479 }
480
482 bool isPESStartCodeValid(uint8_t *pes) {
483 if (pes[0] != 0) return false;
484 if (pes[1] != 0) return false;
485 if (pes[2] != 0x1) return false;
486 return true;
487 }
488
490 const char *toStr(MTSStreamType type) {
491 switch (type) {
493 return "AUDIO_MP3";
495 return "AUDIO_MP3_LOW_BITRATE";
497 return "AUDIO_AAC";
499 return "AUDIO_AAC_LATM";
500 default:
501 return "UNKNOWN";
502 }
503 }
504
506 int findSyncWord(const uint8_t *buf, size_t nBytes, uint8_t synch = 0xFF,
507 uint8_t syncl = 0xF0) {
508 // nBytes is unsigned: bail out up front so "nBytes - 1" can never
509 // underflow into a huge loop bound below
510 if (nBytes < 2) return -1;
511 for (size_t i = 0; i < nBytes - 1; i++) {
512 if ((buf[i + 0] & synch) == synch && (buf[i + 1] & syncl) == syncl)
513 return (int)i;
514 }
515 return -1;
516 }
517};
518
522
523} // namespace audio_tools
#define LOGW(...)
Definition AudioLoggerIDF.h:29
#define TRACEI()
Definition AudioLoggerIDF.h:32
#define TRACED()
Definition AudioLoggerIDF.h:31
#define LOGI(...)
Definition AudioLoggerIDF.h:28
#define TRACEE()
Definition AudioLoggerIDF.h:34
#define LOGE(...)
Definition AudioLoggerIDF.h:30
#define MTS_WRITE_BUFFER_SIZE
Definition CodecMTS.h:6
#define TS_PACKET_SIZE
Definition CodecMTS.h:3
#define assert(T)
Definition avr.h:10
Definition Arduino.h:56
Decoding of encoded audio into PCM data.
Definition AudioCodecsBase.h:18
virtual bool begin(AudioInfo info) override
Definition AudioCodecsBase.h:54
void end() override
Definition AudioCodecsBase.h:59
virtual void setOutput(AudioStream &out_stream)
Defines where the decoded result is written to.
Definition AudioCodecsBase.h:36
Print * p_print
Definition AudioCodecsBase.h:75
Abstract Audio Ouptut class.
Definition AudioOutput.h:25
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition BaseStream.h:120
MPEG-TS (MTS) decoder. Extracts (demuxes) the indicated audio/video data from a MPEG-TS (MTS) data st...
Definition CodecMTS.h:90
void setOutput(Print &out_stream) override
Defines where the decoded result is written to.
Definition CodecMTS.h:205
void resizeBuffer(int size)
Set a new write buffer size (default is 2000)
Definition CodecMTS.h:164
const char * toStr(MTSStreamType type)
Convert the relevant MTSStreamType to a string.
Definition CodecMTS.h:490
MTSStreamType selected_stream_type
Definition CodecMTS.h:221
void addStreamType(MTSStreamType type)
Defines the stream type that should be extracted.
Definition CodecMTS.h:173
void addPID(uint16_t pid)
Definition CodecMTS.h:229
bool parse()
Parse a single packet and remove the processed data.
Definition CodecMTS.h:261
bool is_active
Definition CodecMTS.h:214
void parsePMT(uint8_t *pmt, int len)
Definition CodecMTS.h:367
uint16_t pmt_pid
Definition CodecMTS.h:219
bool isStreamTypeActive(MTSStreamType type)
Checks if the stream type is active.
Definition CodecMTS.h:179
bool isPESStartCodeValid(uint8_t *pes)
check for PES packet start code prefix
Definition CodecMTS.h:482
void end() override
Stops the processing.
Definition CodecMTS.h:123
void parsePES(uint8_t *packet, int pid)
Definition CodecMTS.h:402
size_t write(const uint8_t *data, size_t len) override
Definition CodecMTS.h:134
const char * mime()
Provides the mime type: "video/MP2T";.
Definition CodecMTS.h:132
bool is_adts_missing
Definition CodecMTS.h:224
void parsePAT(uint8_t *pat, int len)
Definition CodecMTS.h:341
SingleBuffer< uint8_t > buffer
Definition CodecMTS.h:215
int open_pes_data_size
Definition CodecMTS.h:222
size_t pes_count
Definition CodecMTS.h:225
int findSyncWord(const uint8_t *buf, size_t nBytes, uint8_t synch=0xFF, uint8_t syncl=0xF0)
Finds the mp3/aac sync word.
Definition CodecMTS.h:506
void setOutput(AudioStream &out_stream) override
Defines where the decoded result is written to.
Definition CodecMTS.h:187
bool begin() override
Start the prcessor.
Definition CodecMTS.h:97
int getPayloadStart(uint8_t *packet, bool isPES, bool &payloadUnitStartIndicator)
Definition CodecMTS.h:312
int syncPos()
Find the position of the next sync byte: Usually on position 0.
Definition CodecMTS.h:249
Vector< int > pids
Definition CodecMTS.h:217
Vector< MTSStreamType > stream_types
Definition CodecMTS.h:216
void demux()
demux the available data
Definition CodecMTS.h:239
void parsePacket(uint8_t *packet, int pid)
Detailed processing for parsing a single packet.
Definition CodecMTS.h:289
MTSDecoder(AudioDecoder &dec)
Provide the AAC decoder (or MP3 Decoder) to receive the extracted content.
Definition CodecMTS.h:95
void setOutput(AudioOutput &out_stream) override
Defines where the decoded result is written to.
Definition CodecMTS.h:196
MTSDecoder()=default
Default constructor.
int frame_length
Definition CodecMTS.h:223
void clearStreamTypes()
Clears the stream type filter.
Definition CodecMTS.h:167
AudioDecoder * p_dec
Definition CodecMTS.h:218
A simple Buffer implementation which just uses a (dynamically sized) array.
Definition Buffers.h:184
int available() override
provides the number of entries that are available to read
Definition Buffers.h:245
int availableForWrite() override
provides the number of entries that are available to write
Definition Buffers.h:250
int writeArray(const T data[], int len) override
Fills the buffer data.
Definition Buffers.h:213
T * data()
Provides address of actual data.
Definition Buffers.h:296
bool resize(size_t size)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:317
int clearArray(int len) override
consumes len bytes and moves current data to the beginning
Definition Buffers.h:264
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
bool contains(T obj)
Definition Vector.h:327
bool empty()
Definition Vector.h:180
void push_back(T &&value)
Definition Vector.h:182
void clear()
Definition Vector.h:176
int size()
Definition Vector.h:178
MTSStreamType
PMT Program Element Stream Types.
Definition CodecMTS.h:20
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6