@invintusmedia/tomp4 1.0.8 → 1.1.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/README.md +25 -1
- package/dist/tomp4.js +312 -363
- package/package.json +6 -3
- package/src/fmp4/converter.js +323 -0
- package/src/fmp4/index.js +25 -0
- package/src/fmp4/stitcher.js +615 -0
- package/src/fmp4/utils.js +201 -0
- package/src/index.js +41 -20
- package/src/mpegts/index.js +7 -0
- package/src/mpegts/stitcher.js +251 -0
- package/src/muxers/mp4.js +85 -85
- package/src/muxers/mpegts.js +101 -19
- package/src/parsers/mp4.js +691 -0
- package/src/parsers/mpegts.js +42 -42
- package/src/remote/index.js +444 -0
- package/src/transcode.js +20 -36
- package/src/ts-to-mp4.js +37 -37
- package/src/fmp4-to-mp4.js +0 -375
package/src/muxers/mpegts.js
CHANGED
|
@@ -80,13 +80,68 @@ export class TSMuxer {
|
|
|
80
80
|
this.pendingAudio.push({ data: new Uint8Array(adtsData), pts: pts90k });
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Add H.264 video sample from NAL units (Annex B format)
|
|
85
|
+
* Used when re-muxing parsed MPEG-TS data
|
|
86
|
+
* @param {Uint8Array[]} nalUnits - Array of NAL units (without start codes)
|
|
87
|
+
* @param {boolean} isKey - Is this a keyframe
|
|
88
|
+
* @param {number} pts90k - Presentation timestamp in 90kHz ticks
|
|
89
|
+
* @param {number} [dts90k] - Decode timestamp in 90kHz ticks (defaults to pts90k)
|
|
90
|
+
*/
|
|
91
|
+
addVideoNalUnits(nalUnits, isKey, pts90k, dts90k = pts90k) {
|
|
92
|
+
const parts = [];
|
|
93
|
+
|
|
94
|
+
// Add AUD (Access Unit Delimiter) at start of each access unit
|
|
95
|
+
parts.push(new Uint8Array([0, 0, 0, 1, 0x09, 0xF0]));
|
|
96
|
+
|
|
97
|
+
// If keyframe, prepend SPS/PPS
|
|
98
|
+
if (isKey && this.sps && this.pps) {
|
|
99
|
+
parts.push(new Uint8Array([0, 0, 0, 1]));
|
|
100
|
+
parts.push(this.sps);
|
|
101
|
+
parts.push(new Uint8Array([0, 0, 0, 1]));
|
|
102
|
+
parts.push(this.pps);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Add each NAL unit with start code
|
|
106
|
+
for (const nalUnit of nalUnits) {
|
|
107
|
+
// Skip SPS/PPS from source if we're adding our own
|
|
108
|
+
const nalType = nalUnit[0] & 0x1F;
|
|
109
|
+
if (isKey && this.sps && this.pps && (nalType === 7 || nalType === 8)) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
parts.push(new Uint8Array([0, 0, 0, 1]));
|
|
113
|
+
parts.push(nalUnit);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Build PES packet
|
|
117
|
+
const annexB = concat(parts);
|
|
118
|
+
const pes = this._buildVideoPES(annexB, pts90k, dts90k);
|
|
119
|
+
|
|
120
|
+
// Write PAT/PMT before keyframes
|
|
121
|
+
if (isKey) {
|
|
122
|
+
this.packets.push(this._buildPAT());
|
|
123
|
+
this.packets.push(this._buildPMT());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Write pending audio with PTS <= this video frame
|
|
127
|
+
while (this.pendingAudio.length > 0 && this.pendingAudio[0].pts <= pts90k) {
|
|
128
|
+
const audio = this.pendingAudio.shift();
|
|
129
|
+
const audioPes = this._buildAudioPES(audio.data, audio.pts);
|
|
130
|
+
this._packetizePES(audioPes, 0x102, false, audio.pts, 'audio');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Packetize video PES into 188-byte TS packets
|
|
134
|
+
this._packetizePES(pes, 0x101, isKey, dts90k, 'video');
|
|
135
|
+
}
|
|
136
|
+
|
|
83
137
|
/**
|
|
84
138
|
* Add H.264 video sample from WebCodecs encoder (AVCC format with length prefixes)
|
|
85
139
|
* @param {Uint8Array} avccData - AVCC-formatted NAL units
|
|
86
140
|
* @param {boolean} isKey - Is this a keyframe
|
|
87
141
|
* @param {number} pts90k - Presentation timestamp in 90kHz ticks
|
|
142
|
+
* @param {number} [dts90k] - Decode timestamp in 90kHz ticks (defaults to pts90k)
|
|
88
143
|
*/
|
|
89
|
-
addVideoSample(avccData, isKey, pts90k) {
|
|
144
|
+
addVideoSample(avccData, isKey, pts90k, dts90k = pts90k) {
|
|
90
145
|
const nalUnits = [];
|
|
91
146
|
|
|
92
147
|
// Add AUD (Access Unit Delimiter) at start of each access unit
|
|
@@ -102,20 +157,28 @@ export class TSMuxer {
|
|
|
102
157
|
|
|
103
158
|
// Parse AVCC NALs and convert to Annex B
|
|
104
159
|
let offset = 0;
|
|
105
|
-
|
|
160
|
+
let iterations = 0;
|
|
161
|
+
const maxIterations = 10000;
|
|
162
|
+
|
|
163
|
+
while (offset + 4 <= avccData.length && iterations < maxIterations) {
|
|
164
|
+
iterations++;
|
|
106
165
|
const len = (avccData[offset] << 24) | (avccData[offset + 1] << 16) |
|
|
107
166
|
(avccData[offset + 2] << 8) | avccData[offset + 3];
|
|
108
167
|
offset += 4;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
168
|
+
|
|
169
|
+
// Safety: bail on invalid NAL length
|
|
170
|
+
if (len <= 0 || len > avccData.length - offset) {
|
|
171
|
+
break;
|
|
112
172
|
}
|
|
173
|
+
|
|
174
|
+
nalUnits.push(new Uint8Array([0, 0, 0, 1]));
|
|
175
|
+
nalUnits.push(avccData.slice(offset, offset + len));
|
|
113
176
|
offset += len;
|
|
114
177
|
}
|
|
115
178
|
|
|
116
|
-
// Build PES packet
|
|
179
|
+
// Build PES packet with both PTS and DTS
|
|
117
180
|
const annexB = concat(nalUnits);
|
|
118
|
-
const pes = this._buildVideoPES(annexB, pts90k);
|
|
181
|
+
const pes = this._buildVideoPES(annexB, pts90k, dts90k);
|
|
119
182
|
|
|
120
183
|
// Write PAT/PMT before keyframes
|
|
121
184
|
if (isKey) {
|
|
@@ -130,8 +193,8 @@ export class TSMuxer {
|
|
|
130
193
|
this._packetizePES(audioPes, 0x102, false, audio.pts, 'audio');
|
|
131
194
|
}
|
|
132
195
|
|
|
133
|
-
// Packetize video PES into 188-byte TS packets
|
|
134
|
-
this._packetizePES(pes, 0x101, isKey,
|
|
196
|
+
// Packetize video PES into 188-byte TS packets (use DTS for PCR)
|
|
197
|
+
this._packetizePES(pes, 0x101, isKey, dts90k, 'video');
|
|
135
198
|
}
|
|
136
199
|
|
|
137
200
|
/**
|
|
@@ -160,16 +223,32 @@ export class TSMuxer {
|
|
|
160
223
|
|
|
161
224
|
// --- Private methods ---
|
|
162
225
|
|
|
163
|
-
_buildVideoPES(payload, pts90k) {
|
|
164
|
-
|
|
226
|
+
_buildVideoPES(payload, pts90k, dts90k) {
|
|
227
|
+
// If PTS == DTS, only write PTS (saves 5 bytes per frame)
|
|
228
|
+
const hasDts = pts90k !== dts90k;
|
|
229
|
+
const headerLen = hasDts ? 10 : 5;
|
|
230
|
+
const pes = new Uint8Array(9 + headerLen + payload.length);
|
|
231
|
+
|
|
165
232
|
pes[0] = 0; pes[1] = 0; pes[2] = 1; // Start code
|
|
166
233
|
pes[3] = 0xE0; // Stream ID (video)
|
|
167
234
|
pes[4] = 0; pes[5] = 0; // Length = 0 (unbounded)
|
|
168
|
-
pes[6] = 0x80; // Flags
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
235
|
+
pes[6] = 0x80; // Flags: data_alignment
|
|
236
|
+
|
|
237
|
+
if (hasDts) {
|
|
238
|
+
// PTS + DTS present
|
|
239
|
+
pes[7] = 0xC0; // PTS_DTS_flags = 11
|
|
240
|
+
pes[8] = 10; // Header length: 5 (PTS) + 5 (DTS)
|
|
241
|
+
this._writePTS(pes, 9, pts90k, 0x31); // PTS marker = 0011
|
|
242
|
+
this._writePTS(pes, 14, dts90k, 0x11); // DTS marker = 0001
|
|
243
|
+
pes.set(payload, 19);
|
|
244
|
+
} else {
|
|
245
|
+
// PTS only
|
|
246
|
+
pes[7] = 0x80; // PTS_DTS_flags = 10
|
|
247
|
+
pes[8] = 5; // Header length: 5 (PTS)
|
|
248
|
+
this._writePTS(pes, 9, pts90k, 0x21); // PTS marker = 0010
|
|
249
|
+
pes.set(payload, 14);
|
|
250
|
+
}
|
|
251
|
+
|
|
173
252
|
return pes;
|
|
174
253
|
}
|
|
175
254
|
|
|
@@ -220,14 +299,17 @@ export class TSMuxer {
|
|
|
220
299
|
|
|
221
300
|
pkt[3] = 0x30 | (this.cc[cc] & 0x0F);
|
|
222
301
|
pkt[4] = afLen;
|
|
223
|
-
pkt[5] = 0x50; // PCR +
|
|
302
|
+
pkt[5] = 0x50; // PCR flag + random_access_indicator
|
|
303
|
+
|
|
304
|
+
// PCR = 33-bit base (90kHz) + 6 reserved bits + 9-bit extension (27MHz)
|
|
305
|
+
// We only use the base, extension = 0
|
|
224
306
|
const pcrBase = BigInt(pts90k);
|
|
225
307
|
pkt[6] = Number((pcrBase >> 25n) & 0xFFn);
|
|
226
308
|
pkt[7] = Number((pcrBase >> 17n) & 0xFFn);
|
|
227
309
|
pkt[8] = Number((pcrBase >> 9n) & 0xFFn);
|
|
228
310
|
pkt[9] = Number((pcrBase >> 1n) & 0xFFn);
|
|
229
|
-
pkt[10] = (Number(pcrBase & 1n) << 7) | 0x7E;
|
|
230
|
-
pkt[11] = 0;
|
|
311
|
+
pkt[10] = (Number(pcrBase & 1n) << 7) | 0x7E; // LSB of base + 6 reserved (111111)
|
|
312
|
+
pkt[11] = 0; // 9-bit extension = 0
|
|
231
313
|
|
|
232
314
|
pkt.set(pes.slice(offset, offset + payloadLen), 12);
|
|
233
315
|
offset += payloadLen;
|