@amazon/vinyl-transmux 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1150 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +1130 -0
- package/dist/index.js.map +7 -0
- package/dist/types/current/aac/adts.d.ts +38 -0
- package/dist/types/current/aac/adts.d.ts.map +1 -0
- package/dist/types/current/aac/adts.js +63 -0
- package/dist/types/current/index.d.ts +7 -0
- package/dist/types/current/index.d.ts.map +1 -0
- package/dist/types/current/index.js +6 -0
- package/dist/types/current/mp4/mp4.d.ts +70 -0
- package/dist/types/current/mp4/mp4.d.ts.map +1 -0
- package/dist/types/current/mp4/mp4.js +338 -0
- package/dist/types/current/mpegts/mpegts.d.ts +68 -0
- package/dist/types/current/mpegts/mpegts.d.ts.map +1 -0
- package/dist/types/current/mpegts/mpegts.js +202 -0
- package/dist/types/current/transmux/sampleEntry.d.ts +25 -0
- package/dist/types/current/transmux/sampleEntry.d.ts.map +1 -0
- package/dist/types/current/transmux/sampleEntry.js +114 -0
- package/dist/types/current/transmux/transmux.d.ts +17 -0
- package/dist/types/current/transmux/transmux.d.ts.map +1 -0
- package/dist/types/current/transmux/transmux.js +568 -0
- package/dist/types/current/tsconfig.tsbuildinfo +1 -0
- package/dist/types/ts3.7/aac/adts.d.ts +38 -0
- package/dist/types/ts3.7/index.d.ts +7 -0
- package/dist/types/ts3.7/mp4/mp4.d.ts +70 -0
- package/dist/types/ts3.7/mpegts/mpegts.d.ts +68 -0
- package/dist/types/ts3.7/transmux/sampleEntry.d.ts +25 -0
- package/dist/types/ts3.7/transmux/transmux.d.ts +17 -0
- package/dist/types/ts4.1/aac/adts.d.ts +38 -0
- package/dist/types/ts4.1/index.d.ts +7 -0
- package/dist/types/ts4.1/mp4/mp4.d.ts +70 -0
- package/dist/types/ts4.1/mpegts/mpegts.d.ts +68 -0
- package/dist/types/ts4.1/transmux/sampleEntry.d.ts +25 -0
- package/dist/types/ts4.1/transmux/transmux.d.ts +17 -0
- package/dist/types/ts4.7/aac/adts.d.ts +38 -0
- package/dist/types/ts4.7/index.d.ts +7 -0
- package/dist/types/ts4.7/mp4/mp4.d.ts +70 -0
- package/dist/types/ts4.7/mpegts/mpegts.d.ts +68 -0
- package/dist/types/ts4.7/transmux/sampleEntry.d.ts +25 -0
- package/dist/types/ts4.7/transmux/transmux.d.ts +17 -0
- package/docs/TRANSMUX.md +131 -0
- package/package.json +82 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* ISO BMFF (MP4) box writing utilities.
|
|
7
|
+
*
|
|
8
|
+
* Provides functions to construct the fMP4 boxes required by MSE:
|
|
9
|
+
* - `ftyp`: file type
|
|
10
|
+
* - `moov` / `mvhd` / `trak` / `mdia` / `minf` / `stbl`: track metadata
|
|
11
|
+
* - `moof` / `mdat`: media fragments
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import { BufferWriter } from '@amazon/vinyl-util';
|
|
16
|
+
/** ISO 14496-12 identity matrix in 16.16 / 2.30 fixed point. */
|
|
17
|
+
const UNITY_MATRIX = [
|
|
18
|
+
0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000,
|
|
19
|
+
];
|
|
20
|
+
/** 90 kHz timescale from ISO 13818-1 (MPEG system clock). */
|
|
21
|
+
export const MPEG_TIMESCALE = 90000;
|
|
22
|
+
/** ISO 639-2/T packed language code for 'und' (undetermined). */
|
|
23
|
+
const LANGUAGE_UNDETERMINED = 0x55c4;
|
|
24
|
+
/**
|
|
25
|
+
* Writes a generic MP4 box (atom). The size field is automatically computed.
|
|
26
|
+
*/
|
|
27
|
+
export function box(type, ...payloads) {
|
|
28
|
+
let size = 8;
|
|
29
|
+
for (const p of payloads)
|
|
30
|
+
size += p.byteLength;
|
|
31
|
+
const writer = new BufferWriter(size);
|
|
32
|
+
writer.writeUint32(size);
|
|
33
|
+
writer.writeString(type);
|
|
34
|
+
for (const p of payloads)
|
|
35
|
+
writer.writeBytes(p);
|
|
36
|
+
return writer.toUint8Array();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Writes a full box with version and flags.
|
|
40
|
+
*/
|
|
41
|
+
export function fullBox(type, version, flags, payload) {
|
|
42
|
+
const size = 12 + payload.byteLength;
|
|
43
|
+
const writer = new BufferWriter(size);
|
|
44
|
+
writer.writeUint32(size);
|
|
45
|
+
writer.writeString(type);
|
|
46
|
+
writer.writeUint8(version);
|
|
47
|
+
// 24-bit flags
|
|
48
|
+
writer.writeUint8((flags >> 16) & 0xff);
|
|
49
|
+
writer.writeUint8((flags >> 8) & 0xff);
|
|
50
|
+
writer.writeUint8(flags & 0xff);
|
|
51
|
+
writer.writeBytes(payload);
|
|
52
|
+
return writer.toUint8Array();
|
|
53
|
+
}
|
|
54
|
+
const DEFAULT_FTYP = {
|
|
55
|
+
majorBrand: 'isom',
|
|
56
|
+
minorVersion: 0x200,
|
|
57
|
+
compatibleBrands: ['isom', 'iso6', 'mp41'],
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Generates the ftyp box.
|
|
61
|
+
*/
|
|
62
|
+
export function ftyp(options) {
|
|
63
|
+
const { majorBrand, minorVersion, compatibleBrands } = {
|
|
64
|
+
...DEFAULT_FTYP,
|
|
65
|
+
...options,
|
|
66
|
+
};
|
|
67
|
+
const writer = new BufferWriter(8 + compatibleBrands.length * 4);
|
|
68
|
+
writer.writeString(majorBrand);
|
|
69
|
+
writer.writeUint32(minorVersion);
|
|
70
|
+
for (const brand of compatibleBrands)
|
|
71
|
+
writer.writeString(brand);
|
|
72
|
+
return box('ftyp', writer.toUint8Array());
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Generates a moov box from pre-built track boxes.
|
|
76
|
+
*/
|
|
77
|
+
export function moov(...traks) {
|
|
78
|
+
const trakAndMvex = [];
|
|
79
|
+
for (let i = 0; i < traks.length; i++) {
|
|
80
|
+
trakAndMvex.push(traks[i], mvex(i + 1));
|
|
81
|
+
}
|
|
82
|
+
return box('moov', mvhd(), ...trakAndMvex);
|
|
83
|
+
}
|
|
84
|
+
const DEFAULT_MVHD = {
|
|
85
|
+
timescale: MPEG_TIMESCALE,
|
|
86
|
+
duration: 0,
|
|
87
|
+
rate: 1.0,
|
|
88
|
+
volume: 1.0,
|
|
89
|
+
};
|
|
90
|
+
function mvhd(options) {
|
|
91
|
+
const { timescale, duration, rate, volume } = {
|
|
92
|
+
...DEFAULT_MVHD,
|
|
93
|
+
...options,
|
|
94
|
+
};
|
|
95
|
+
const writer = new BufferWriter(96);
|
|
96
|
+
writer.writeUint32(0); // creation_time
|
|
97
|
+
writer.writeUint32(0); // modification_time
|
|
98
|
+
writer.writeUint32(timescale);
|
|
99
|
+
writer.writeUint32(duration);
|
|
100
|
+
writer.writeUint32(rate * 0x10000); // 16.16 fixed point
|
|
101
|
+
writer.writeUint16(volume * 0x100); // 8.8 fixed point
|
|
102
|
+
// 10 bytes reserved
|
|
103
|
+
for (let i = 0; i < 10; i++)
|
|
104
|
+
writer.writeUint8(0);
|
|
105
|
+
for (const v of UNITY_MATRIX)
|
|
106
|
+
writer.writeUint32(v);
|
|
107
|
+
// 6 x uint32 pre_defined
|
|
108
|
+
for (let i = 0; i < 6; i++)
|
|
109
|
+
writer.writeUint32(0);
|
|
110
|
+
writer.writeUint32(0xffffffff); // next_track_ID
|
|
111
|
+
return fullBox('mvhd', 0, 0, writer.toUint8Array());
|
|
112
|
+
}
|
|
113
|
+
export function audioTrak(options) {
|
|
114
|
+
return box('trak', tkhd(options.trackId, 0, 0, options.channelCount > 0 ? 1.0 : 0), box('mdia', mdhd(options.sampleRate), hdlr('soun', 'SoundHandler'), box('minf', smhd(), dinf(), audioStbl(options.sampleEntry))));
|
|
115
|
+
}
|
|
116
|
+
export function videoTrak(options) {
|
|
117
|
+
return box('trak', tkhd(options.trackId, options.width, options.height, 0), box('mdia', mdhd(options.timescale ?? MPEG_TIMESCALE), hdlr('vide', 'VideoHandler'), box('minf', vmhd(), dinf(), videoStbl(options.sampleEntry))));
|
|
118
|
+
}
|
|
119
|
+
function tkhd(trackId, width, height, volume) {
|
|
120
|
+
const writer = new BufferWriter(80);
|
|
121
|
+
writer.writeUint32(0); // creation_time
|
|
122
|
+
writer.writeUint32(0); // modification_time
|
|
123
|
+
writer.writeUint32(trackId);
|
|
124
|
+
writer.writeUint32(0); // reserved
|
|
125
|
+
writer.writeUint32(0); // duration
|
|
126
|
+
writer.writeUint32(0); // reserved
|
|
127
|
+
writer.writeUint32(0); // reserved
|
|
128
|
+
writer.writeUint16(0); // layer
|
|
129
|
+
writer.writeUint16(0); // alternate_group
|
|
130
|
+
writer.writeUint16(volume * 0x100); // volume (8.8 fixed point)
|
|
131
|
+
writer.writeUint16(0); // reserved
|
|
132
|
+
// unity matrix
|
|
133
|
+
for (const v of UNITY_MATRIX)
|
|
134
|
+
writer.writeUint32(v);
|
|
135
|
+
writer.writeUint32(width * 0x10000); // width (16.16 fixed point)
|
|
136
|
+
writer.writeUint32(height * 0x10000); // height (16.16 fixed point)
|
|
137
|
+
return fullBox('tkhd', 0, 0x000003, writer.toUint8Array());
|
|
138
|
+
}
|
|
139
|
+
function mdhd(timescale) {
|
|
140
|
+
const writer = new BufferWriter(20);
|
|
141
|
+
writer.writeUint32(0); // creation_time
|
|
142
|
+
writer.writeUint32(0); // modification_time
|
|
143
|
+
writer.writeUint32(timescale);
|
|
144
|
+
writer.writeUint32(0); // duration
|
|
145
|
+
writer.writeUint16(LANGUAGE_UNDETERMINED);
|
|
146
|
+
writer.writeUint16(0); // pre_defined
|
|
147
|
+
return fullBox('mdhd', 0, 0, writer.toUint8Array());
|
|
148
|
+
}
|
|
149
|
+
function hdlr(handlerType, name) {
|
|
150
|
+
const writer = new BufferWriter(21 + name.length);
|
|
151
|
+
writer.writeUint32(0); // pre_defined
|
|
152
|
+
writer.writeString(handlerType);
|
|
153
|
+
writer.writeUint32(0); // reserved
|
|
154
|
+
writer.writeUint32(0); // reserved
|
|
155
|
+
writer.writeUint32(0); // reserved
|
|
156
|
+
writer.writeString(name);
|
|
157
|
+
writer.writeUint8(0); // null terminator
|
|
158
|
+
return fullBox('hdlr', 0, 0, writer.toUint8Array());
|
|
159
|
+
}
|
|
160
|
+
function smhd() {
|
|
161
|
+
const writer = new BufferWriter(4);
|
|
162
|
+
writer.writeUint16(0); // balance
|
|
163
|
+
writer.writeUint16(0); // reserved
|
|
164
|
+
return fullBox('smhd', 0, 0, writer.toUint8Array());
|
|
165
|
+
}
|
|
166
|
+
function vmhd() {
|
|
167
|
+
const writer = new BufferWriter(8);
|
|
168
|
+
writer.writeUint16(0); // graphicsmode
|
|
169
|
+
writer.writeUint16(0); // opcolor
|
|
170
|
+
writer.writeUint16(0);
|
|
171
|
+
writer.writeUint16(0);
|
|
172
|
+
return fullBox('vmhd', 0, 0x000001, writer.toUint8Array());
|
|
173
|
+
}
|
|
174
|
+
function dinf() {
|
|
175
|
+
const writer = new BufferWriter(4);
|
|
176
|
+
writer.writeUint32(1); // entry_count
|
|
177
|
+
const urlBox = fullBox('url ', 0, 0x000001, new Uint8Array(0));
|
|
178
|
+
return box('dinf', fullBox('dref', 0, 0, concatUint8(writer.toUint8Array(), urlBox)));
|
|
179
|
+
}
|
|
180
|
+
function audioStbl(sampleEntry) {
|
|
181
|
+
return box('stbl', stsd(sampleEntry), stts(), stsc(), stsz(), stco());
|
|
182
|
+
}
|
|
183
|
+
function videoStbl(sampleEntry) {
|
|
184
|
+
return box('stbl', stsd(sampleEntry), stts(), stsc(), stsz(), stco());
|
|
185
|
+
}
|
|
186
|
+
function stsd(sampleEntry) {
|
|
187
|
+
const writer = new BufferWriter(4);
|
|
188
|
+
writer.writeUint32(1); // entry_count
|
|
189
|
+
return fullBox('stsd', 0, 0, concatUint8(writer.toUint8Array(), sampleEntry));
|
|
190
|
+
}
|
|
191
|
+
function stts() {
|
|
192
|
+
const writer = new BufferWriter(4);
|
|
193
|
+
writer.writeUint32(0); // entry_count
|
|
194
|
+
return fullBox('stts', 0, 0, writer.toUint8Array());
|
|
195
|
+
}
|
|
196
|
+
function stsc() {
|
|
197
|
+
const writer = new BufferWriter(4);
|
|
198
|
+
writer.writeUint32(0); // entry_count
|
|
199
|
+
return fullBox('stsc', 0, 0, writer.toUint8Array());
|
|
200
|
+
}
|
|
201
|
+
function stsz() {
|
|
202
|
+
const writer = new BufferWriter(8);
|
|
203
|
+
writer.writeUint32(0); // sample_size
|
|
204
|
+
writer.writeUint32(0); // sample_count
|
|
205
|
+
return fullBox('stsz', 0, 0, writer.toUint8Array());
|
|
206
|
+
}
|
|
207
|
+
function stco() {
|
|
208
|
+
const writer = new BufferWriter(4);
|
|
209
|
+
writer.writeUint32(0); // entry_count
|
|
210
|
+
return fullBox('stco', 0, 0, writer.toUint8Array());
|
|
211
|
+
}
|
|
212
|
+
function mvex(trackId) {
|
|
213
|
+
return box('mvex', trex(trackId));
|
|
214
|
+
}
|
|
215
|
+
function trex(trackId) {
|
|
216
|
+
const writer = new BufferWriter(20);
|
|
217
|
+
writer.writeUint32(trackId);
|
|
218
|
+
writer.writeUint32(1); // default_sample_description_index
|
|
219
|
+
writer.writeUint32(0); // default_sample_duration
|
|
220
|
+
writer.writeUint32(0); // default_sample_size
|
|
221
|
+
writer.writeUint32(0); // default_sample_flags
|
|
222
|
+
return fullBox('trex', 0, 0, writer.toUint8Array());
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Generates a moof (movie fragment) box.
|
|
226
|
+
*/
|
|
227
|
+
export function moof(options) {
|
|
228
|
+
return box('moof', mfhd(options.sequenceNumber), traf(options));
|
|
229
|
+
}
|
|
230
|
+
function mfhd(sequenceNumber) {
|
|
231
|
+
const writer = new BufferWriter(4);
|
|
232
|
+
writer.writeUint32(sequenceNumber);
|
|
233
|
+
return fullBox('mfhd', 0, 0, writer.toUint8Array());
|
|
234
|
+
}
|
|
235
|
+
function traf(options) {
|
|
236
|
+
return box('traf', tfhd(options.trackId), tfdt(options.baseDecodeTime), trun(options.samples));
|
|
237
|
+
}
|
|
238
|
+
function tfhd(trackId) {
|
|
239
|
+
const writer = new BufferWriter(4);
|
|
240
|
+
writer.writeUint32(trackId);
|
|
241
|
+
// flags: default-base-is-moof (0x020000)
|
|
242
|
+
return fullBox('tfhd', 0, 0x020000, writer.toUint8Array());
|
|
243
|
+
}
|
|
244
|
+
function tfdt(baseDecodeTime) {
|
|
245
|
+
const writer = new BufferWriter(4);
|
|
246
|
+
writer.writeUint32(baseDecodeTime);
|
|
247
|
+
return fullBox('tfdt', 0, 0, writer.toUint8Array());
|
|
248
|
+
}
|
|
249
|
+
function trun(samples) {
|
|
250
|
+
// flags: data-offset-present (0x01), sample-duration-present (0x100),
|
|
251
|
+
// sample-size-present (0x200), sample-flags-present (0x400)
|
|
252
|
+
const hasFlags = samples.some((s) => s.flags != null);
|
|
253
|
+
const hasCto = samples.some((s) => s.compositionTimeOffset != null);
|
|
254
|
+
let flags = 0x000001 | 0x000100 | 0x000200;
|
|
255
|
+
if (hasFlags)
|
|
256
|
+
flags |= 0x000400;
|
|
257
|
+
if (hasCto)
|
|
258
|
+
flags |= 0x000800;
|
|
259
|
+
let entrySize = 8; // duration + size
|
|
260
|
+
if (hasFlags)
|
|
261
|
+
entrySize += 4;
|
|
262
|
+
if (hasCto)
|
|
263
|
+
entrySize += 4;
|
|
264
|
+
const writer = new BufferWriter(8 + samples.length * entrySize);
|
|
265
|
+
writer.writeUint32(samples.length); // sample_count
|
|
266
|
+
writer.writeUint32(0); // data_offset (placeholder, patched later)
|
|
267
|
+
for (const sample of samples) {
|
|
268
|
+
writer.writeUint32(sample.duration);
|
|
269
|
+
writer.writeUint32(sample.size);
|
|
270
|
+
if (hasFlags)
|
|
271
|
+
writer.writeUint32(sample.flags ?? 0);
|
|
272
|
+
if (hasCto)
|
|
273
|
+
writer.writeInt32(sample.compositionTimeOffset ?? 0);
|
|
274
|
+
}
|
|
275
|
+
const result = fullBox('trun', hasCto ? 1 : 0, flags, writer.toUint8Array());
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Generates an mdat box wrapping raw media data.
|
|
280
|
+
*/
|
|
281
|
+
export function mdat(data) {
|
|
282
|
+
return box('mdat', data);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Patches the trun data_offset field. The data_offset is the byte offset from
|
|
286
|
+
* the start of the moof to the first byte of the mdat payload.
|
|
287
|
+
*/
|
|
288
|
+
export function patchDataOffset(moofData, moofSize) {
|
|
289
|
+
// data_offset = moofSize + 8 (mdat header)
|
|
290
|
+
const dataOffset = moofSize + 8;
|
|
291
|
+
// Find trun box and patch the data_offset field.
|
|
292
|
+
// trun is inside moof > traf. Walk the box tree.
|
|
293
|
+
let pos = 8; // skip moof header
|
|
294
|
+
while (pos < moofData.length) {
|
|
295
|
+
const boxSize = ((moofData[pos] << 24) |
|
|
296
|
+
(moofData[pos + 1] << 16) |
|
|
297
|
+
(moofData[pos + 2] << 8) |
|
|
298
|
+
moofData[pos + 3]) >>>
|
|
299
|
+
0;
|
|
300
|
+
const boxType = String.fromCharCode(moofData[pos + 4], moofData[pos + 5], moofData[pos + 6], moofData[pos + 7]);
|
|
301
|
+
if (boxType === 'traf') {
|
|
302
|
+
// Recurse into traf
|
|
303
|
+
let trafPos = pos + 8;
|
|
304
|
+
while (trafPos < pos + boxSize) {
|
|
305
|
+
const innerSize = ((moofData[trafPos] << 24) |
|
|
306
|
+
(moofData[trafPos + 1] << 16) |
|
|
307
|
+
(moofData[trafPos + 2] << 8) |
|
|
308
|
+
moofData[trafPos + 3]) >>>
|
|
309
|
+
0;
|
|
310
|
+
const innerType = String.fromCharCode(moofData[trafPos + 4], moofData[trafPos + 5], moofData[trafPos + 6], moofData[trafPos + 7]);
|
|
311
|
+
if (innerType === 'trun') {
|
|
312
|
+
// data_offset is at offset 12+4 = 16 from box start
|
|
313
|
+
// (8 box header + 4 version/flags + 4 sample_count)
|
|
314
|
+
const offsetPos = trafPos + 16;
|
|
315
|
+
moofData[offsetPos] = (dataOffset >> 24) & 0xff;
|
|
316
|
+
moofData[offsetPos + 1] = (dataOffset >> 16) & 0xff;
|
|
317
|
+
moofData[offsetPos + 2] = (dataOffset >> 8) & 0xff;
|
|
318
|
+
moofData[offsetPos + 3] = dataOffset & 0xff;
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
trafPos += innerSize;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
pos += boxSize;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function concatUint8(...arrays) {
|
|
328
|
+
let total = 0;
|
|
329
|
+
for (const a of arrays)
|
|
330
|
+
total += a.byteLength;
|
|
331
|
+
const result = new Uint8Array(total);
|
|
332
|
+
let offset = 0;
|
|
333
|
+
for (const a of arrays) {
|
|
334
|
+
result.set(a, offset);
|
|
335
|
+
offset += a.byteLength;
|
|
336
|
+
}
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MPEG-TS constants and packet-level parsing.
|
|
3
|
+
*
|
|
4
|
+
* An MPEG Transport Stream consists of fixed 188-byte packets. Each packet
|
|
5
|
+
* starts with a sync byte (0x47) and carries a 13-bit PID identifying the
|
|
6
|
+
* elementary stream. The PAT (PID 0) maps program numbers to PMT PIDs, and
|
|
7
|
+
* the PMT lists the elementary stream PIDs with their stream types.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
export declare const TS_PACKET_SIZE = 188;
|
|
12
|
+
export declare const TS_SYNC_BYTE = 71;
|
|
13
|
+
/** Well-known stream type identifiers from ISO/IEC 13818-1. */
|
|
14
|
+
export declare const STREAM_TYPE_AAC = 15;
|
|
15
|
+
export declare const STREAM_TYPE_H264 = 27;
|
|
16
|
+
export declare const STREAM_TYPE_H265 = 36;
|
|
17
|
+
export declare const STREAM_TYPE_ID3 = 21;
|
|
18
|
+
export interface TsPacketHeader {
|
|
19
|
+
readonly pid: number;
|
|
20
|
+
readonly payloadUnitStartIndicator: boolean;
|
|
21
|
+
readonly adaptationFieldControl: number;
|
|
22
|
+
readonly continuityCounter: number;
|
|
23
|
+
}
|
|
24
|
+
export interface PmtEntry {
|
|
25
|
+
readonly pid: number;
|
|
26
|
+
readonly streamType: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parses a single 188-byte TS packet header.
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseTsPacketHeader(data: Uint8Array, offset: number): TsPacketHeader;
|
|
32
|
+
/**
|
|
33
|
+
* Returns the offset of the payload within a TS packet.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getPayloadOffset(data: Uint8Array, offset: number, adaptationFieldControl: number): number;
|
|
36
|
+
/**
|
|
37
|
+
* Parses the PAT (Program Association Table) to find the PMT PID.
|
|
38
|
+
*/
|
|
39
|
+
export declare function parsePat(data: Uint8Array, offset: number): number;
|
|
40
|
+
/**
|
|
41
|
+
* Parses the PMT (Program Map Table) to extract elementary stream entries.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parsePmt(data: Uint8Array, offset: number): PmtEntry[];
|
|
44
|
+
export interface DemuxedStream {
|
|
45
|
+
readonly streamType: number;
|
|
46
|
+
readonly pid: number;
|
|
47
|
+
readonly data: Uint8Array[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Demuxes a complete MPEG-TS segment into elementary streams.
|
|
51
|
+
* Reassembles PES packets from TS packets for each PID.
|
|
52
|
+
*/
|
|
53
|
+
export declare function demuxTsSegment(segment: Uint8Array): DemuxedStream[];
|
|
54
|
+
/**
|
|
55
|
+
* Concatenates an array of Uint8Arrays into a single Uint8Array.
|
|
56
|
+
*/
|
|
57
|
+
export declare function concatBuffers(buffers: Uint8Array[]): Uint8Array;
|
|
58
|
+
/**
|
|
59
|
+
* Strips the PES header from raw PES packet data, returning the elementary
|
|
60
|
+
* stream payload.
|
|
61
|
+
*/
|
|
62
|
+
export declare function stripPesHeader(pesData: Uint8Array): Uint8Array;
|
|
63
|
+
/**
|
|
64
|
+
* Extracts the PTS (Presentation Time Stamp) from a PES header.
|
|
65
|
+
* Returns the PTS in 90kHz clock ticks, or -1 if not present.
|
|
66
|
+
*/
|
|
67
|
+
export declare function parsePesPts(pesData: Uint8Array): number;
|
|
68
|
+
//# sourceMappingURL=mpegts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mpegts.d.ts","sourceRoot":"","sources":["../../../../src/mpegts/mpegts.ts"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AAEH,eAAO,MAAM,cAAc,MAAM,CAAA;AACjC,eAAO,MAAM,YAAY,KAAO,CAAA;AAEhC,+DAA+D;AAC/D,eAAO,MAAM,eAAe,KAAO,CAAA;AACnC,eAAO,MAAM,gBAAgB,KAAO,CAAA;AACpC,eAAO,MAAM,gBAAgB,KAAO,CAAA;AACpC,eAAO,MAAM,eAAe,KAAO,CAAA;AAEnC,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,yBAAyB,EAAE,OAAO,CAAA;IAC3C,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAA;IACvC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;CACrC;AAED,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC9B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,GACf,cAAc,CAUhB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC5B,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EACd,sBAAsB,EAAE,MAAM,GAC/B,MAAM,CAOR;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAWjE;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,EAAE,CA6BrE;AAED,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,CAAA;CAC9B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,aAAa,EAAE,CAoEnE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,UAAU,CAU/D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,UAAU,CAS9D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAiBvD"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* MPEG-TS constants and packet-level parsing.
|
|
7
|
+
*
|
|
8
|
+
* An MPEG Transport Stream consists of fixed 188-byte packets. Each packet
|
|
9
|
+
* starts with a sync byte (0x47) and carries a 13-bit PID identifying the
|
|
10
|
+
* elementary stream. The PAT (PID 0) maps program numbers to PMT PIDs, and
|
|
11
|
+
* the PMT lists the elementary stream PIDs with their stream types.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
export const TS_PACKET_SIZE = 188;
|
|
16
|
+
export const TS_SYNC_BYTE = 0x47;
|
|
17
|
+
/** Well-known stream type identifiers from ISO/IEC 13818-1. */
|
|
18
|
+
export const STREAM_TYPE_AAC = 0x0f;
|
|
19
|
+
export const STREAM_TYPE_H264 = 0x1b;
|
|
20
|
+
export const STREAM_TYPE_H265 = 0x24;
|
|
21
|
+
export const STREAM_TYPE_ID3 = 0x15;
|
|
22
|
+
/**
|
|
23
|
+
* Parses a single 188-byte TS packet header.
|
|
24
|
+
*/
|
|
25
|
+
export function parseTsPacketHeader(data, offset) {
|
|
26
|
+
const byte1 = data[offset + 1];
|
|
27
|
+
const byte2 = data[offset + 2];
|
|
28
|
+
const byte3 = data[offset + 3];
|
|
29
|
+
return {
|
|
30
|
+
pid: ((byte1 & 0x1f) << 8) | byte2,
|
|
31
|
+
payloadUnitStartIndicator: (byte1 & 0x40) !== 0,
|
|
32
|
+
adaptationFieldControl: (byte3 >> 4) & 0x03,
|
|
33
|
+
continuityCounter: byte3 & 0x0f,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns the offset of the payload within a TS packet.
|
|
38
|
+
*/
|
|
39
|
+
export function getPayloadOffset(data, offset, adaptationFieldControl) {
|
|
40
|
+
let payloadOffset = offset + 4;
|
|
41
|
+
if (adaptationFieldControl >= 2) {
|
|
42
|
+
// Adaptation field present
|
|
43
|
+
payloadOffset += 1 + data[offset + 4];
|
|
44
|
+
}
|
|
45
|
+
return payloadOffset;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parses the PAT (Program Association Table) to find the PMT PID.
|
|
49
|
+
*/
|
|
50
|
+
export function parsePat(data, offset) {
|
|
51
|
+
// Skip pointer field
|
|
52
|
+
const pointerField = data[offset];
|
|
53
|
+
let pos = offset + 1 + pointerField;
|
|
54
|
+
// Skip table_id(1), section_syntax_indicator+length(2), transport_stream_id(2),
|
|
55
|
+
// version/current(1), section_number(1), last_section_number(1)
|
|
56
|
+
pos += 8;
|
|
57
|
+
// First program entry: program_number(2) + PMT_PID(2)
|
|
58
|
+
// Skip program_number
|
|
59
|
+
pos += 2;
|
|
60
|
+
return ((data[pos] & 0x1f) << 8) | data[pos + 1];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Parses the PMT (Program Map Table) to extract elementary stream entries.
|
|
64
|
+
*/
|
|
65
|
+
export function parsePmt(data, offset) {
|
|
66
|
+
const pointerField = data[offset];
|
|
67
|
+
let pos = offset + 1 + pointerField;
|
|
68
|
+
// table_id(1)
|
|
69
|
+
pos += 1;
|
|
70
|
+
const sectionLength = ((data[pos] & 0x0f) << 8) | data[pos + 1];
|
|
71
|
+
pos += 2;
|
|
72
|
+
const sectionEnd = pos + sectionLength;
|
|
73
|
+
// transport_stream_id(2), version/current(1), section_number(1), last_section_number(1)
|
|
74
|
+
pos += 5;
|
|
75
|
+
// PCR_PID(2)
|
|
76
|
+
pos += 2;
|
|
77
|
+
// program_info_length
|
|
78
|
+
const programInfoLength = ((data[pos] & 0x0f) << 8) | data[pos + 1];
|
|
79
|
+
pos += 2 + programInfoLength;
|
|
80
|
+
const entries = [];
|
|
81
|
+
// -4 for CRC32
|
|
82
|
+
while (pos < sectionEnd - 4) {
|
|
83
|
+
const streamType = data[pos];
|
|
84
|
+
pos += 1;
|
|
85
|
+
const pid = ((data[pos] & 0x1f) << 8) | data[pos + 1];
|
|
86
|
+
pos += 2;
|
|
87
|
+
const esInfoLength = ((data[pos] & 0x0f) << 8) | data[pos + 1];
|
|
88
|
+
pos += 2 + esInfoLength;
|
|
89
|
+
entries.push({ pid, streamType });
|
|
90
|
+
}
|
|
91
|
+
return entries;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Demuxes a complete MPEG-TS segment into elementary streams.
|
|
95
|
+
* Reassembles PES packets from TS packets for each PID.
|
|
96
|
+
*/
|
|
97
|
+
export function demuxTsSegment(segment) {
|
|
98
|
+
let pmtPid = -1;
|
|
99
|
+
const pmtEntries = [];
|
|
100
|
+
const pesBuffers = new Map();
|
|
101
|
+
// First pass: find PAT and PMT
|
|
102
|
+
for (let i = 0; i + TS_PACKET_SIZE <= segment.length; i += TS_PACKET_SIZE) {
|
|
103
|
+
if (segment[i] !== TS_SYNC_BYTE)
|
|
104
|
+
continue;
|
|
105
|
+
const header = parseTsPacketHeader(segment, i);
|
|
106
|
+
const payloadStart = getPayloadOffset(segment, i, header.adaptationFieldControl);
|
|
107
|
+
if (header.adaptationFieldControl === 0 ||
|
|
108
|
+
header.adaptationFieldControl === 2)
|
|
109
|
+
continue;
|
|
110
|
+
if (header.pid === 0) {
|
|
111
|
+
pmtPid = parsePat(segment, payloadStart);
|
|
112
|
+
}
|
|
113
|
+
else if (header.pid === pmtPid && pmtEntries.length === 0) {
|
|
114
|
+
pmtEntries.push(...parsePmt(segment, payloadStart));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Initialize buffers for known elementary streams
|
|
118
|
+
const pidToStreamType = new Map();
|
|
119
|
+
for (const entry of pmtEntries) {
|
|
120
|
+
pidToStreamType.set(entry.pid, entry.streamType);
|
|
121
|
+
pesBuffers.set(entry.pid, []);
|
|
122
|
+
}
|
|
123
|
+
// Second pass: collect PES data
|
|
124
|
+
for (let i = 0; i + TS_PACKET_SIZE <= segment.length; i += TS_PACKET_SIZE) {
|
|
125
|
+
if (segment[i] !== TS_SYNC_BYTE)
|
|
126
|
+
continue;
|
|
127
|
+
const header = parseTsPacketHeader(segment, i);
|
|
128
|
+
if (header.adaptationFieldControl === 0 ||
|
|
129
|
+
header.adaptationFieldControl === 2)
|
|
130
|
+
continue;
|
|
131
|
+
const buffers = pesBuffers.get(header.pid);
|
|
132
|
+
if (!buffers)
|
|
133
|
+
continue;
|
|
134
|
+
const payloadStart = getPayloadOffset(segment, i, header.adaptationFieldControl);
|
|
135
|
+
const payloadEnd = i + TS_PACKET_SIZE;
|
|
136
|
+
if (payloadStart < payloadEnd) {
|
|
137
|
+
buffers.push(segment.slice(payloadStart, payloadEnd));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const streams = [];
|
|
141
|
+
for (const [pid, buffers] of pesBuffers) {
|
|
142
|
+
if (buffers.length === 0)
|
|
143
|
+
continue;
|
|
144
|
+
streams.push({
|
|
145
|
+
pid,
|
|
146
|
+
streamType: pidToStreamType.get(pid),
|
|
147
|
+
data: buffers,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return streams;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Concatenates an array of Uint8Arrays into a single Uint8Array.
|
|
154
|
+
*/
|
|
155
|
+
export function concatBuffers(buffers) {
|
|
156
|
+
let totalLength = 0;
|
|
157
|
+
for (const buf of buffers)
|
|
158
|
+
totalLength += buf.byteLength;
|
|
159
|
+
const result = new Uint8Array(totalLength);
|
|
160
|
+
let offset = 0;
|
|
161
|
+
for (const buf of buffers) {
|
|
162
|
+
result.set(buf, offset);
|
|
163
|
+
offset += buf.byteLength;
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Strips the PES header from raw PES packet data, returning the elementary
|
|
169
|
+
* stream payload.
|
|
170
|
+
*/
|
|
171
|
+
export function stripPesHeader(pesData) {
|
|
172
|
+
// PES start code: 0x00 0x00 0x01
|
|
173
|
+
if (pesData[0] !== 0 || pesData[1] !== 0 || pesData[2] !== 1) {
|
|
174
|
+
// No PES header, return as-is (continuation data)
|
|
175
|
+
return pesData;
|
|
176
|
+
}
|
|
177
|
+
// PES header data length is at byte 8
|
|
178
|
+
const pesHeaderDataLength = pesData[8];
|
|
179
|
+
return pesData.subarray(9 + pesHeaderDataLength);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Extracts the PTS (Presentation Time Stamp) from a PES header.
|
|
183
|
+
* Returns the PTS in 90kHz clock ticks, or -1 if not present.
|
|
184
|
+
*/
|
|
185
|
+
export function parsePesPts(pesData) {
|
|
186
|
+
if (pesData[0] !== 0 || pesData[1] !== 0 || pesData[2] !== 1)
|
|
187
|
+
return -1;
|
|
188
|
+
const flags = pesData[7];
|
|
189
|
+
if ((flags & 0x80) === 0)
|
|
190
|
+
return -1;
|
|
191
|
+
// PTS is encoded in 5 bytes starting at byte 9
|
|
192
|
+
const byte9 = pesData[9];
|
|
193
|
+
const byte10 = pesData[10];
|
|
194
|
+
const byte11 = pesData[11];
|
|
195
|
+
const byte12 = pesData[12];
|
|
196
|
+
const byte13 = pesData[13];
|
|
197
|
+
return ((byte9 & 0x0e) * 0x20000000 + // (byte9 & 0x0E) << 29
|
|
198
|
+
((byte10 & 0xff) << 22) +
|
|
199
|
+
((byte11 & 0xfe) << 14) +
|
|
200
|
+
((byte12 & 0xff) << 7) +
|
|
201
|
+
((byte13 & 0xfe) >>> 1));
|
|
202
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface AudioTrackConfig {
|
|
2
|
+
readonly audioObjectType: number;
|
|
3
|
+
readonly samplingFrequencyIndex: number;
|
|
4
|
+
readonly channelConfiguration: number;
|
|
5
|
+
readonly sampleRate: number;
|
|
6
|
+
readonly audioSpecificConfig: Uint8Array;
|
|
7
|
+
}
|
|
8
|
+
export interface VideoTrackConfig {
|
|
9
|
+
readonly width: number;
|
|
10
|
+
readonly height: number;
|
|
11
|
+
readonly sps: Uint8Array;
|
|
12
|
+
readonly pps: Uint8Array;
|
|
13
|
+
readonly profileIdc: number;
|
|
14
|
+
readonly profileCompatibility: number;
|
|
15
|
+
readonly levelIdc: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Builds an mp4a sample entry box for AAC audio.
|
|
19
|
+
*/
|
|
20
|
+
export declare function mp4aSampleEntry(config: AudioTrackConfig): Uint8Array;
|
|
21
|
+
/**
|
|
22
|
+
* Builds an avc1 sample entry box for H.264 video.
|
|
23
|
+
*/
|
|
24
|
+
export declare function avc1SampleEntry(config: VideoTrackConfig): Uint8Array;
|
|
25
|
+
//# sourceMappingURL=sampleEntry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampleEntry.d.ts","sourceRoot":"","sources":["../../../../src/transmux/sampleEntry.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAA;IACvC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,mBAAmB,EAAE,UAAU,CAAA;CAC3C;AAED,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;IACxB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC5B;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAapE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAkBpE"}
|