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