node-av 6.2.0-beta.2 → 6.2.0-beta.21
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 +13 -8
- package/dist/api/audio-frame-buffer.d.ts +73 -1
- package/dist/api/audio-frame-buffer.js +127 -1
- package/dist/api/audio-frame-buffer.js.map +1 -1
- package/dist/api/bitstream-filter.d.ts +82 -4
- package/dist/api/bitstream-filter.js +197 -100
- package/dist/api/bitstream-filter.js.map +1 -1
- package/dist/api/decoder.d.ts +5 -0
- package/dist/api/decoder.js +13 -6
- package/dist/api/decoder.js.map +1 -1
- package/dist/api/demuxer.d.ts +15 -1
- package/dist/api/demuxer.js +165 -54
- package/dist/api/demuxer.js.map +1 -1
- package/dist/api/encoder-pool.d.ts +43 -1
- package/dist/api/encoder-pool.js +121 -9
- package/dist/api/encoder-pool.js.map +1 -1
- package/dist/api/encoder.d.ts +19 -0
- package/dist/api/encoder.js +112 -45
- package/dist/api/encoder.js.map +1 -1
- package/dist/api/filter.js +15 -6
- package/dist/api/filter.js.map +1 -1
- package/dist/api/fmp4-stream.d.ts +167 -3
- package/dist/api/fmp4-stream.js +423 -90
- package/dist/api/fmp4-stream.js.map +1 -1
- package/dist/api/hardware.d.ts +2 -0
- package/dist/api/hardware.js +25 -4
- package/dist/api/hardware.js.map +1 -1
- package/dist/api/io-stream.d.ts +3 -2
- package/dist/api/io-stream.js +61 -5
- package/dist/api/io-stream.js.map +1 -1
- package/dist/api/muxer.d.ts +14 -0
- package/dist/api/muxer.js +131 -51
- package/dist/api/muxer.js.map +1 -1
- package/dist/api/pipeline.d.ts +24 -0
- package/dist/api/pipeline.js +76 -5
- package/dist/api/pipeline.js.map +1 -1
- package/dist/api/rtp-stream.d.ts +56 -6
- package/dist/api/rtp-stream.js +252 -80
- package/dist/api/rtp-stream.js.map +1 -1
- package/dist/api/scaler.d.ts +105 -17
- package/dist/api/scaler.js +326 -108
- package/dist/api/scaler.js.map +1 -1
- package/dist/api/utilities/async-queue.d.ts +3 -0
- package/dist/api/utilities/async-queue.js +8 -0
- package/dist/api/utilities/async-queue.js.map +1 -1
- package/dist/api/webrtc-stream.d.ts +2 -0
- package/dist/api/webrtc-stream.js +46 -12
- package/dist/api/webrtc-stream.js.map +1 -1
- package/dist/api/whisper.d.ts +2 -0
- package/dist/api/whisper.js +63 -23
- package/dist/api/whisper.js.map +1 -1
- package/dist/constants/constants.d.ts +1 -0
- package/dist/constants/constants.js +1 -0
- package/dist/constants/constants.js.map +1 -1
- package/dist/constants/filter-options.d.ts +2 -0
- package/dist/ffmpeg/install.js +115 -29
- package/dist/ffmpeg/install.js.map +1 -1
- package/dist/ffmpeg/utils.js +7 -4
- package/dist/ffmpeg/utils.js.map +1 -1
- package/dist/lib/binding.d.ts +1 -0
- package/dist/lib/binding.js +12 -16
- package/dist/lib/binding.js.map +1 -1
- package/dist/lib/codec-context.d.ts +0 -2
- package/dist/lib/codec-context.js +4 -22
- package/dist/lib/codec-context.js.map +1 -1
- package/dist/lib/filter-context.d.ts +0 -1
- package/dist/lib/filter-context.js +2 -11
- package/dist/lib/filter-context.js.map +1 -1
- package/dist/lib/format-context.d.ts +63 -7
- package/dist/lib/format-context.js +70 -21
- package/dist/lib/format-context.js.map +1 -1
- package/dist/lib/frame.d.ts +11 -11
- package/dist/lib/frame.js +13 -20
- package/dist/lib/frame.js.map +1 -1
- package/dist/lib/hardware-frames-context.d.ts +0 -1
- package/dist/lib/hardware-frames-context.js +2 -8
- package/dist/lib/hardware-frames-context.js.map +1 -1
- package/dist/lib/io-context.d.ts +4 -2
- package/dist/lib/io-context.js +4 -2
- package/dist/lib/io-context.js.map +1 -1
- package/dist/lib/native-types.d.ts +4 -4
- package/dist/lib/option.d.ts +26 -0
- package/dist/lib/option.js +42 -18
- package/dist/lib/option.js.map +1 -1
- package/dist/lib/packet.d.ts +5 -0
- package/dist/lib/packet.js +5 -0
- package/dist/lib/packet.js.map +1 -1
- package/dist/lib/stream.d.ts +3 -4
- package/dist/lib/stream.js +17 -24
- package/dist/lib/stream.js.map +1 -1
- package/install/check.js +40 -79
- package/package.json +43 -32
- package/build_mac_local.sh +0 -69
|
@@ -2,6 +2,24 @@ import type { AVCodecFlag, AVCodecID, FFEncoderCodec } from '../constants/index.
|
|
|
2
2
|
import type { Codec } from '../lib/codec.js';
|
|
3
3
|
import type { Frame } from '../lib/frame.js';
|
|
4
4
|
import type { EncoderOptions } from './encoder.js';
|
|
5
|
+
/**
|
|
6
|
+
* Minimal promise-chain mutex serializing async critical sections.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare class Mutex {
|
|
11
|
+
private tail;
|
|
12
|
+
/**
|
|
13
|
+
* Run a callback exclusively, after all previously queued callbacks.
|
|
14
|
+
*
|
|
15
|
+
* @param fn - Critical section to execute
|
|
16
|
+
*
|
|
17
|
+
* @returns The callback's result
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
run<T>(fn: () => T | Promise<T>): Promise<T>;
|
|
22
|
+
}
|
|
5
23
|
/**
|
|
6
24
|
* Options for encoder pool creation.
|
|
7
25
|
*
|
|
@@ -98,7 +116,9 @@ export declare class EncoderPool<const C extends FFEncoderCodec | AVCodecID | Co
|
|
|
98
116
|
* Encode a frame into a self-contained image buffer.
|
|
99
117
|
*
|
|
100
118
|
* Routes the frame to the encoder matching its dimensions and pixel format,
|
|
101
|
-
* creating and caching one if necessary.
|
|
119
|
+
* creating and caching one if necessary. Safe to call concurrently: encodes
|
|
120
|
+
* on the same key are serialized per encoder, different keys run in parallel,
|
|
121
|
+
* and eviction never closes an encoder with an encode in flight.
|
|
102
122
|
*
|
|
103
123
|
* @param frame - Frame to encode
|
|
104
124
|
*
|
|
@@ -120,6 +140,9 @@ export declare class EncoderPool<const C extends FFEncoderCodec | AVCodecID | Co
|
|
|
120
140
|
* Encode a frame into a self-contained image buffer synchronously.
|
|
121
141
|
* Synchronous version of encode.
|
|
122
142
|
*
|
|
143
|
+
* If the pooled encoder for the frame's key has an async encode in flight,
|
|
144
|
+
* a throwaway encoder handles this call so the two cannot interleave.
|
|
145
|
+
*
|
|
123
146
|
* @param frame - Frame to encode
|
|
124
147
|
*
|
|
125
148
|
* @returns Encoded image bytes
|
|
@@ -184,6 +207,25 @@ export declare class EncoderPool<const C extends FFEncoderCodec | AVCodecID | Co
|
|
|
184
207
|
* @internal
|
|
185
208
|
*/
|
|
186
209
|
private set;
|
|
210
|
+
/**
|
|
211
|
+
* Take an entry out of service, closing it now or on last release.
|
|
212
|
+
*
|
|
213
|
+
* An entry with in-flight encodes is only marked; the final {@link release}
|
|
214
|
+
* closes the encoder once nothing uses it anymore.
|
|
215
|
+
*
|
|
216
|
+
* @param pooled - Entry removed from the pool
|
|
217
|
+
*
|
|
218
|
+
* @internal
|
|
219
|
+
*/
|
|
220
|
+
private retire;
|
|
221
|
+
/**
|
|
222
|
+
* Drop an in-flight reference, closing the encoder if it was retired meanwhile.
|
|
223
|
+
*
|
|
224
|
+
* @param pooled - Entry acquired by {@link encode}
|
|
225
|
+
*
|
|
226
|
+
* @internal
|
|
227
|
+
*/
|
|
228
|
+
private release;
|
|
187
229
|
/**
|
|
188
230
|
* Extract the encoded bytes from the produced packets and free them.
|
|
189
231
|
*
|
package/dist/api/encoder-pool.js
CHANGED
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
import { Encoder } from './encoder.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal promise-chain mutex serializing async critical sections.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export class Mutex {
|
|
8
|
+
tail = Promise.resolve();
|
|
9
|
+
/**
|
|
10
|
+
* Run a callback exclusively, after all previously queued callbacks.
|
|
11
|
+
*
|
|
12
|
+
* @param fn - Critical section to execute
|
|
13
|
+
*
|
|
14
|
+
* @returns The callback's result
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
async run(fn) {
|
|
19
|
+
const prev = this.tail;
|
|
20
|
+
let release;
|
|
21
|
+
this.tail = new Promise((resolve) => (release = resolve));
|
|
22
|
+
await prev;
|
|
23
|
+
try {
|
|
24
|
+
return await fn();
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
release();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
2
31
|
/**
|
|
3
32
|
* Bounded LRU pool of image encoders keyed by resolution and pixel format.
|
|
4
33
|
*
|
|
@@ -85,7 +114,9 @@ export class EncoderPool {
|
|
|
85
114
|
* Encode a frame into a self-contained image buffer.
|
|
86
115
|
*
|
|
87
116
|
* Routes the frame to the encoder matching its dimensions and pixel format,
|
|
88
|
-
* creating and caching one if necessary.
|
|
117
|
+
* creating and caching one if necessary. Safe to call concurrently: encodes
|
|
118
|
+
* on the same key are serialized per encoder, different keys run in parallel,
|
|
119
|
+
* and eviction never closes an encoder with an encode in flight.
|
|
89
120
|
*
|
|
90
121
|
* @param frame - Frame to encode
|
|
91
122
|
*
|
|
@@ -107,6 +138,8 @@ export class EncoderPool {
|
|
|
107
138
|
let pooled = this.touch(key);
|
|
108
139
|
if (!pooled) {
|
|
109
140
|
const encoder = await Encoder.create(this.encoderCodec, this.options);
|
|
141
|
+
// A concurrent encode may have created and cached the same entry while we
|
|
142
|
+
// awaited; prefer the cached one and discard ours.
|
|
110
143
|
pooled = this.touch(key);
|
|
111
144
|
if (pooled) {
|
|
112
145
|
encoder.close();
|
|
@@ -115,19 +148,39 @@ export class EncoderPool {
|
|
|
115
148
|
pooled = this.set(key, encoder);
|
|
116
149
|
}
|
|
117
150
|
}
|
|
118
|
-
|
|
119
|
-
|
|
151
|
+
// Hold the entry across the awaits below: LRU eviction (and close()) defer
|
|
152
|
+
// closing an in-use encoder to release(), never mid-encode.
|
|
153
|
+
const entry = pooled;
|
|
154
|
+
entry.refs++;
|
|
120
155
|
try {
|
|
121
|
-
|
|
156
|
+
// Serialize send + drain per encoder: concurrent encodes on the same key
|
|
157
|
+
// would otherwise interleave and hand packets to the wrong caller.
|
|
158
|
+
return await entry.lock.run(async () => {
|
|
159
|
+
const savedPts = frame.pts;
|
|
160
|
+
const savedTimeBase = frame.timeBase;
|
|
161
|
+
entry.timeBase ??= savedTimeBase;
|
|
162
|
+
frame.pts = entry.nextPts++;
|
|
163
|
+
frame.timeBase = entry.timeBase;
|
|
164
|
+
try {
|
|
165
|
+
return this.extract(await entry.encoder.encodeAll(frame), entry.encoder);
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
frame.pts = savedPts;
|
|
169
|
+
frame.timeBase = savedTimeBase;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
122
172
|
}
|
|
123
173
|
finally {
|
|
124
|
-
|
|
174
|
+
this.release(entry);
|
|
125
175
|
}
|
|
126
176
|
}
|
|
127
177
|
/**
|
|
128
178
|
* Encode a frame into a self-contained image buffer synchronously.
|
|
129
179
|
* Synchronous version of encode.
|
|
130
180
|
*
|
|
181
|
+
* If the pooled encoder for the frame's key has an async encode in flight,
|
|
182
|
+
* a throwaway encoder handles this call so the two cannot interleave.
|
|
183
|
+
*
|
|
131
184
|
* @param frame - Frame to encode
|
|
132
185
|
*
|
|
133
186
|
* @returns Encoded image bytes
|
|
@@ -147,13 +200,39 @@ export class EncoderPool {
|
|
|
147
200
|
const key = this.keyFor(frame);
|
|
148
201
|
let pooled = this.touch(key);
|
|
149
202
|
pooled ??= this.set(key, Encoder.createSync(this.encoderCodec, this.options));
|
|
203
|
+
// Sync encodes cannot interleave with each other (they block the event loop),
|
|
204
|
+
// but they can land between the awaits of an in-flight async encode on the
|
|
205
|
+
// same pooled encoder. Use a throwaway encoder in that case.
|
|
206
|
+
if (pooled.refs > 0) {
|
|
207
|
+
const encoder = Encoder.createSync(this.encoderCodec, this.options);
|
|
208
|
+
if (this.flags.length > 0) {
|
|
209
|
+
encoder.setCodecFlags(...this.flags);
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const savedPts = frame.pts;
|
|
213
|
+
frame.pts = 0n;
|
|
214
|
+
try {
|
|
215
|
+
return this.extract(encoder.encodeAllSync(frame), encoder);
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
frame.pts = savedPts;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
encoder.close();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
150
225
|
const savedPts = frame.pts;
|
|
226
|
+
const savedTimeBase = frame.timeBase;
|
|
227
|
+
pooled.timeBase ??= savedTimeBase;
|
|
151
228
|
frame.pts = pooled.nextPts++;
|
|
229
|
+
frame.timeBase = pooled.timeBase;
|
|
152
230
|
try {
|
|
153
231
|
return this.extract(pooled.encoder.encodeAllSync(frame), pooled.encoder);
|
|
154
232
|
}
|
|
155
233
|
finally {
|
|
156
234
|
frame.pts = savedPts;
|
|
235
|
+
frame.timeBase = savedTimeBase;
|
|
157
236
|
}
|
|
158
237
|
}
|
|
159
238
|
/**
|
|
@@ -169,8 +248,10 @@ export class EncoderPool {
|
|
|
169
248
|
* @see {@link Symbol.dispose} For automatic cleanup
|
|
170
249
|
*/
|
|
171
250
|
close() {
|
|
172
|
-
|
|
173
|
-
|
|
251
|
+
// In-use encoders are retired, not closed: the last in-flight encode closes
|
|
252
|
+
// them on release, so close() during concurrent encodes never crashes them.
|
|
253
|
+
for (const pooled of this.encoders.values()) {
|
|
254
|
+
this.retire(pooled);
|
|
174
255
|
}
|
|
175
256
|
this.encoders.clear();
|
|
176
257
|
}
|
|
@@ -221,18 +302,49 @@ export class EncoderPool {
|
|
|
221
302
|
if (this.flags.length > 0) {
|
|
222
303
|
encoder.setCodecFlags(...this.flags);
|
|
223
304
|
}
|
|
224
|
-
const pooled = { encoder, nextPts: 0n };
|
|
305
|
+
const pooled = { encoder, nextPts: 0n, timeBase: null, lock: new Mutex(), refs: 0, evicted: false };
|
|
225
306
|
this.encoders.set(key, pooled);
|
|
226
307
|
if (this.encoders.size > this.maxSize) {
|
|
227
308
|
const oldestKey = this.encoders.keys().next().value;
|
|
228
309
|
if (oldestKey !== undefined) {
|
|
229
310
|
const oldest = this.encoders.get(oldestKey);
|
|
230
311
|
this.encoders.delete(oldestKey);
|
|
231
|
-
oldest
|
|
312
|
+
if (oldest) {
|
|
313
|
+
this.retire(oldest);
|
|
314
|
+
}
|
|
232
315
|
}
|
|
233
316
|
}
|
|
234
317
|
return pooled;
|
|
235
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Take an entry out of service, closing it now or on last release.
|
|
321
|
+
*
|
|
322
|
+
* An entry with in-flight encodes is only marked; the final {@link release}
|
|
323
|
+
* closes the encoder once nothing uses it anymore.
|
|
324
|
+
*
|
|
325
|
+
* @param pooled - Entry removed from the pool
|
|
326
|
+
*
|
|
327
|
+
* @internal
|
|
328
|
+
*/
|
|
329
|
+
retire(pooled) {
|
|
330
|
+
pooled.evicted = true;
|
|
331
|
+
if (pooled.refs === 0) {
|
|
332
|
+
pooled.encoder.close();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Drop an in-flight reference, closing the encoder if it was retired meanwhile.
|
|
337
|
+
*
|
|
338
|
+
* @param pooled - Entry acquired by {@link encode}
|
|
339
|
+
*
|
|
340
|
+
* @internal
|
|
341
|
+
*/
|
|
342
|
+
release(pooled) {
|
|
343
|
+
pooled.refs--;
|
|
344
|
+
if (pooled.refs === 0 && pooled.evicted) {
|
|
345
|
+
pooled.encoder.close();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
236
348
|
/**
|
|
237
349
|
* Extract the encoded bytes from the produced packets and free them.
|
|
238
350
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encoder-pool.js","sourceRoot":"","sources":["../../src/api/encoder-pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"encoder-pool.js","sourceRoot":"","sources":["../../src/api/encoder-pool.ts"],"names":[],"mappings":"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"}
|
package/dist/api/encoder.d.ts
CHANGED
|
@@ -204,6 +204,23 @@ export interface EncoderOptions<C = unknown> {
|
|
|
204
204
|
* @default false
|
|
205
205
|
*/
|
|
206
206
|
autoFormat?: boolean;
|
|
207
|
+
/**
|
|
208
|
+
* Keep each frame's `pictType` instead of clearing it before encoding.
|
|
209
|
+
*
|
|
210
|
+
* By default the encoder resets `frame.pictType` to `AV_PICTURE_TYPE_NONE`,
|
|
211
|
+
* since decoded input frames carry frame-type hints that should not drive the
|
|
212
|
+
* output GOP structure when re-encoding. That clearing also discards a
|
|
213
|
+
* deliberately requested keyframe: setting `frame.pictType = AV_PICTURE_TYPE_I`
|
|
214
|
+
* (with `forced-idr` on encoders that support it) is the standard FFmpeg way
|
|
215
|
+
* to force an IDR for a specific frame.
|
|
216
|
+
*
|
|
217
|
+
* Set to `true` to pass `pictType` through — needed for on-demand keyframes in
|
|
218
|
+
* live streaming (packet-loss recovery, a late-joining viewer). Leave `false`
|
|
219
|
+
* for transcoding, where the input's frame types should be ignored.
|
|
220
|
+
*
|
|
221
|
+
* @default false
|
|
222
|
+
*/
|
|
223
|
+
preserveFramePictType?: boolean;
|
|
207
224
|
/**
|
|
208
225
|
* Additional codec-specific options.
|
|
209
226
|
*
|
|
@@ -291,6 +308,8 @@ export declare class Encoder implements Disposable {
|
|
|
291
308
|
private autoResample;
|
|
292
309
|
private audioResampler?;
|
|
293
310
|
private resampledFrame?;
|
|
311
|
+
private resampleChangesRate;
|
|
312
|
+
private resampledPts;
|
|
294
313
|
private audioInputLayout?;
|
|
295
314
|
private autoFormat;
|
|
296
315
|
private videoScaler?;
|
package/dist/api/encoder.js
CHANGED
|
@@ -51,7 +51,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
53
|
/* eslint-disable @stylistic/indent-binary-ops */
|
|
54
|
-
import { AV_CHANNEL_ORDER_UNSPEC, AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE, AV_CODEC_CAP_PARAM_CHANGE, AV_CODEC_FLAG_COPY_OPAQUE, AV_CODEC_FLAG_FRAME_DURATION, AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX, AV_PICTURE_TYPE_NONE, AV_PIX_FMT_NONE, AV_PKT_FLAG_TRUSTED, AVCHROMA_LOC_UNSPECIFIED, AVERROR_EAGAIN, AVERROR_ENCODER_NOT_FOUND, AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO, EOF, SWS_BILINEAR, } from '../constants/constants.js';
|
|
54
|
+
import { AV_CHANNEL_ORDER_UNSPEC, AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE, AV_CODEC_CAP_PARAM_CHANGE, AV_CODEC_FLAG_COPY_OPAQUE, AV_CODEC_FLAG_FRAME_DURATION, AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX, AV_NOPTS_VALUE, AV_PICTURE_TYPE_NONE, AV_PIX_FMT_NONE, AV_PKT_FLAG_TRUSTED, AVCHROMA_LOC_UNSPECIFIED, AVERROR_EAGAIN, AVERROR_ENCODER_NOT_FOUND, AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO, EOF, SWS_BILINEAR, } from '../constants/constants.js';
|
|
55
55
|
import { CodecContext } from '../lib/codec-context.js';
|
|
56
56
|
import { Codec } from '../lib/codec.js';
|
|
57
57
|
import { Dictionary } from '../lib/dictionary.js';
|
|
@@ -158,6 +158,8 @@ export class Encoder {
|
|
|
158
158
|
autoResample;
|
|
159
159
|
audioResampler;
|
|
160
160
|
resampledFrame;
|
|
161
|
+
resampleChangesRate = false;
|
|
162
|
+
resampledPts = 0n;
|
|
161
163
|
audioInputLayout;
|
|
162
164
|
autoFormat;
|
|
163
165
|
videoScaler;
|
|
@@ -1243,9 +1245,8 @@ export class Encoder {
|
|
|
1243
1245
|
}
|
|
1244
1246
|
}
|
|
1245
1247
|
// If using AudioFrameBuffer, flush remaining buffered samples first
|
|
1246
|
-
if (this.audioFrameBuffer
|
|
1247
|
-
//
|
|
1248
|
-
// For the final frame, we pad or truncate as needed
|
|
1248
|
+
if (this.audioFrameBuffer) {
|
|
1249
|
+
// Drain all complete frames
|
|
1249
1250
|
let _bufferedFrame;
|
|
1250
1251
|
while (!this.isClosed && (_bufferedFrame = await this.audioFrameBuffer.pull()) !== null) {
|
|
1251
1252
|
const env_5 = { stack: [], error: void 0, hasError: false };
|
|
@@ -1261,6 +1262,23 @@ export class Encoder {
|
|
|
1261
1262
|
__disposeResources(env_5);
|
|
1262
1263
|
}
|
|
1263
1264
|
}
|
|
1265
|
+
// Encode the final partial frame padded with silence - otherwise the tail
|
|
1266
|
+
// (up to frameSize - 1 samples, ~21 ms at 48 kHz/1024) would be dropped.
|
|
1267
|
+
const _partialFrame = !this.isClosed ? await this.audioFrameBuffer.pullPartial() : null;
|
|
1268
|
+
if (_partialFrame) {
|
|
1269
|
+
const env_6 = { stack: [], error: void 0, hasError: false };
|
|
1270
|
+
try {
|
|
1271
|
+
const partialFrame = __addDisposableResource(env_6, _partialFrame, false);
|
|
1272
|
+
await this.codecContext.sendFrame(partialFrame);
|
|
1273
|
+
}
|
|
1274
|
+
catch (e_6) {
|
|
1275
|
+
env_6.error = e_6;
|
|
1276
|
+
env_6.hasError = true;
|
|
1277
|
+
}
|
|
1278
|
+
finally {
|
|
1279
|
+
__disposeResources(env_6);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1264
1282
|
}
|
|
1265
1283
|
// Send flush frame (null)
|
|
1266
1284
|
const ret = await this.codecContext.sendFrame(null);
|
|
@@ -1313,22 +1331,37 @@ export class Encoder {
|
|
|
1313
1331
|
}
|
|
1314
1332
|
}
|
|
1315
1333
|
// If using AudioFrameBuffer, flush remaining buffered samples first
|
|
1316
|
-
if (this.audioFrameBuffer
|
|
1317
|
-
//
|
|
1318
|
-
// For the final frame, we pad or truncate as needed
|
|
1334
|
+
if (this.audioFrameBuffer) {
|
|
1335
|
+
// Drain all complete frames
|
|
1319
1336
|
let _bufferedFrame;
|
|
1320
1337
|
while (!this.isClosed && (_bufferedFrame = this.audioFrameBuffer.pullSync()) !== null) {
|
|
1321
|
-
const
|
|
1338
|
+
const env_7 = { stack: [], error: void 0, hasError: false };
|
|
1322
1339
|
try {
|
|
1323
|
-
const bufferedFrame = __addDisposableResource(
|
|
1340
|
+
const bufferedFrame = __addDisposableResource(env_7, _bufferedFrame, false);
|
|
1324
1341
|
this.codecContext.sendFrameSync(bufferedFrame);
|
|
1325
1342
|
}
|
|
1326
|
-
catch (
|
|
1327
|
-
|
|
1328
|
-
|
|
1343
|
+
catch (e_7) {
|
|
1344
|
+
env_7.error = e_7;
|
|
1345
|
+
env_7.hasError = true;
|
|
1329
1346
|
}
|
|
1330
1347
|
finally {
|
|
1331
|
-
__disposeResources(
|
|
1348
|
+
__disposeResources(env_7);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
// Encode the final partial frame padded with silence (see flush)
|
|
1352
|
+
const _partialFrame = !this.isClosed ? this.audioFrameBuffer.pullPartialSync() : null;
|
|
1353
|
+
if (_partialFrame) {
|
|
1354
|
+
const env_8 = { stack: [], error: void 0, hasError: false };
|
|
1355
|
+
try {
|
|
1356
|
+
const partialFrame = __addDisposableResource(env_8, _partialFrame, false);
|
|
1357
|
+
this.codecContext.sendFrameSync(partialFrame);
|
|
1358
|
+
}
|
|
1359
|
+
catch (e_8) {
|
|
1360
|
+
env_8.error = e_8;
|
|
1361
|
+
env_8.hasError = true;
|
|
1362
|
+
}
|
|
1363
|
+
finally {
|
|
1364
|
+
__disposeResources(env_8);
|
|
1332
1365
|
}
|
|
1333
1366
|
}
|
|
1334
1367
|
}
|
|
@@ -1471,19 +1504,19 @@ export class Encoder {
|
|
|
1471
1504
|
// Clear previous packet data
|
|
1472
1505
|
this.packet.unref();
|
|
1473
1506
|
if (this.audioFrameBuffer?.hasFrame()) {
|
|
1474
|
-
const
|
|
1507
|
+
const env_9 = { stack: [], error: void 0, hasError: false };
|
|
1475
1508
|
try {
|
|
1476
|
-
const bufferedFrame = __addDisposableResource(
|
|
1509
|
+
const bufferedFrame = __addDisposableResource(env_9, await this.audioFrameBuffer.pull(), false);
|
|
1477
1510
|
if (bufferedFrame) {
|
|
1478
1511
|
await this.codecContext.sendFrame(bufferedFrame);
|
|
1479
1512
|
}
|
|
1480
1513
|
}
|
|
1481
|
-
catch (
|
|
1482
|
-
|
|
1483
|
-
|
|
1514
|
+
catch (e_9) {
|
|
1515
|
+
env_9.error = e_9;
|
|
1516
|
+
env_9.hasError = true;
|
|
1484
1517
|
}
|
|
1485
1518
|
finally {
|
|
1486
|
-
__disposeResources(
|
|
1519
|
+
__disposeResources(env_9);
|
|
1487
1520
|
}
|
|
1488
1521
|
}
|
|
1489
1522
|
const ret = await this.codecContext.receivePacket(this.packet);
|
|
@@ -1576,19 +1609,19 @@ export class Encoder {
|
|
|
1576
1609
|
// Clear previous packet data
|
|
1577
1610
|
this.packet.unref();
|
|
1578
1611
|
if (this.audioFrameBuffer?.hasFrame()) {
|
|
1579
|
-
const
|
|
1612
|
+
const env_10 = { stack: [], error: void 0, hasError: false };
|
|
1580
1613
|
try {
|
|
1581
|
-
const bufferedFrame = __addDisposableResource(
|
|
1614
|
+
const bufferedFrame = __addDisposableResource(env_10, this.audioFrameBuffer.pullSync(), false);
|
|
1582
1615
|
if (bufferedFrame) {
|
|
1583
1616
|
this.codecContext.sendFrameSync(bufferedFrame);
|
|
1584
1617
|
}
|
|
1585
1618
|
}
|
|
1586
|
-
catch (
|
|
1587
|
-
|
|
1588
|
-
|
|
1619
|
+
catch (e_10) {
|
|
1620
|
+
env_10.error = e_10;
|
|
1621
|
+
env_10.hasError = true;
|
|
1589
1622
|
}
|
|
1590
1623
|
finally {
|
|
1591
|
-
__disposeResources(
|
|
1624
|
+
__disposeResources(env_10);
|
|
1592
1625
|
}
|
|
1593
1626
|
}
|
|
1594
1627
|
const ret = this.codecContext.receivePacketSync(this.packet);
|
|
@@ -1641,7 +1674,13 @@ export class Encoder {
|
|
|
1641
1674
|
const packet = await this.receiveFromQueue();
|
|
1642
1675
|
if (!packet)
|
|
1643
1676
|
break;
|
|
1644
|
-
|
|
1677
|
+
try {
|
|
1678
|
+
// writePacket clones internally; the queue-owned packet is ours to free
|
|
1679
|
+
await target.writePacket(packet, streamIndex);
|
|
1680
|
+
}
|
|
1681
|
+
finally {
|
|
1682
|
+
packet.free();
|
|
1683
|
+
}
|
|
1645
1684
|
}
|
|
1646
1685
|
})();
|
|
1647
1686
|
// Return control without pipeTo (terminal stage)
|
|
@@ -1675,7 +1714,6 @@ export class Encoder {
|
|
|
1675
1714
|
this.outputQueue.close();
|
|
1676
1715
|
this.inputQueue.clear();
|
|
1677
1716
|
this.outputQueue.clear();
|
|
1678
|
-
this.packet.free();
|
|
1679
1717
|
this.audioFrameBuffer?.[Symbol.dispose]();
|
|
1680
1718
|
this.audioFrameBuffer = undefined;
|
|
1681
1719
|
this.audioResampler?.[Symbol.dispose]();
|
|
@@ -1687,6 +1725,7 @@ export class Encoder {
|
|
|
1687
1725
|
this.scaledFrame?.free();
|
|
1688
1726
|
this.scaledFrame = undefined;
|
|
1689
1727
|
this.codecContext.freeContext();
|
|
1728
|
+
this.packet.free();
|
|
1690
1729
|
this.initialized = false;
|
|
1691
1730
|
}
|
|
1692
1731
|
/**
|
|
@@ -1729,9 +1768,9 @@ export class Encoder {
|
|
|
1729
1768
|
try {
|
|
1730
1769
|
// Outer loop - receive frames
|
|
1731
1770
|
while (!this.inputQueue.isClosed) {
|
|
1732
|
-
const
|
|
1771
|
+
const env_11 = { stack: [], error: void 0, hasError: false };
|
|
1733
1772
|
try {
|
|
1734
|
-
const frame = __addDisposableResource(
|
|
1773
|
+
const frame = __addDisposableResource(env_11, await this.inputQueue.receive(), false);
|
|
1735
1774
|
if (!frame)
|
|
1736
1775
|
break;
|
|
1737
1776
|
// Open encoder if not already done
|
|
@@ -1750,12 +1789,12 @@ export class Encoder {
|
|
|
1750
1789
|
await this.outputQueue.send(packet); // Only send actual packets
|
|
1751
1790
|
}
|
|
1752
1791
|
}
|
|
1753
|
-
catch (
|
|
1754
|
-
|
|
1755
|
-
|
|
1792
|
+
catch (e_11) {
|
|
1793
|
+
env_11.error = e_11;
|
|
1794
|
+
env_11.hasError = true;
|
|
1756
1795
|
}
|
|
1757
1796
|
finally {
|
|
1758
|
-
__disposeResources(
|
|
1797
|
+
__disposeResources(env_11);
|
|
1759
1798
|
}
|
|
1760
1799
|
}
|
|
1761
1800
|
// Flush encoder at end
|
|
@@ -2119,8 +2158,6 @@ export class Encoder {
|
|
|
2119
2158
|
* @internal
|
|
2120
2159
|
*/
|
|
2121
2160
|
setupAudioParams(frame) {
|
|
2122
|
-
// Always use frame timebase (typically 1/sample_rate) for correct audio PTS.
|
|
2123
|
-
this.codecContext.timeBase = frame.timeBase;
|
|
2124
2161
|
const inRate = frame.sampleRate;
|
|
2125
2162
|
const inFmt = frame.format;
|
|
2126
2163
|
// Codec open and swr both need a concrete layout. PCM/raw frames often carry
|
|
@@ -2135,6 +2172,14 @@ export class Encoder {
|
|
|
2135
2172
|
const targetRate = pickSupportedRate(inRate, this.codec.supportedSamplerates);
|
|
2136
2173
|
const targetFmt = pickSupportedSampleFormat(inFmt, this.codec.sampleFormats);
|
|
2137
2174
|
const targetLayout = pickSupportedLayout(inLayout, this.codec.channelLayouts);
|
|
2175
|
+
// When the codec forces a different sample rate, the frames that reach it
|
|
2176
|
+
// carry PTS as a running sample counter at that rate (resampler and FIFO
|
|
2177
|
+
// paths), so the codec timebase must be 1/target_rate - the input timebase
|
|
2178
|
+
// would misread those counts (e.g. 44.1 kHz in -> Opus-forced 48 kHz would
|
|
2179
|
+
// play ~8.8% slow). Without rate conversion, keep the frame timebase
|
|
2180
|
+
// (typically 1/sample_rate).
|
|
2181
|
+
this.resampleChangesRate = targetRate !== inRate;
|
|
2182
|
+
this.codecContext.timeBase = this.resampleChangesRate ? new Rational(1, targetRate) : frame.timeBase;
|
|
2138
2183
|
const needsResample = targetRate !== inRate || targetFmt !== inFmt || targetLayout.nbChannels !== inLayout.nbChannels;
|
|
2139
2184
|
if (needsResample && !this.autoResample) {
|
|
2140
2185
|
const mismatches = [];
|
|
@@ -2199,8 +2244,19 @@ export class Encoder {
|
|
|
2199
2244
|
out.sampleRate = this.codecContext.sampleRate;
|
|
2200
2245
|
out.channelLayout = this.codecContext.channelLayout;
|
|
2201
2246
|
FFmpegError.throwIfError(this.audioResampler.convertFrame(out, frame), 'Failed to resample audio frame');
|
|
2202
|
-
|
|
2203
|
-
|
|
2247
|
+
if (this.resampleChangesRate) {
|
|
2248
|
+
// Rate conversion changes the sample count, so the input PTS no longer
|
|
2249
|
+
// matches the output. Stamp from a running output-sample counter in
|
|
2250
|
+
// 1/out_rate (the codec timebase) - the same scheme the FIFO path uses
|
|
2251
|
+
// when re-stamping - instead of replicating swr_next_pts() delay tracking.
|
|
2252
|
+
out.timeBase = this.codecContext.timeBase;
|
|
2253
|
+
out.pts = this.resampledPts;
|
|
2254
|
+
this.resampledPts += BigInt(out.nbSamples);
|
|
2255
|
+
}
|
|
2256
|
+
else {
|
|
2257
|
+
out.timeBase = frame.timeBase;
|
|
2258
|
+
out.pts = frame.pts;
|
|
2259
|
+
}
|
|
2204
2260
|
return out;
|
|
2205
2261
|
}
|
|
2206
2262
|
/**
|
|
@@ -2224,6 +2280,12 @@ export class Encoder {
|
|
|
2224
2280
|
if (ret < 0 || out.nbSamples <= 0) {
|
|
2225
2281
|
return null;
|
|
2226
2282
|
}
|
|
2283
|
+
if (this.resampleChangesRate) {
|
|
2284
|
+
// Continue the running output-sample counter (see resampleAudio)
|
|
2285
|
+
out.timeBase = this.codecContext.timeBase;
|
|
2286
|
+
out.pts = this.resampledPts;
|
|
2287
|
+
this.resampledPts += BigInt(out.nbSamples);
|
|
2288
|
+
}
|
|
2227
2289
|
return out;
|
|
2228
2290
|
}
|
|
2229
2291
|
/**
|
|
@@ -2324,9 +2386,12 @@ export class Encoder {
|
|
|
2324
2386
|
* @internal
|
|
2325
2387
|
*/
|
|
2326
2388
|
prepareFrameForEncoding(frame) {
|
|
2327
|
-
// Clear pict_type - encoder will determine frame types based on its own settings
|
|
2328
|
-
// Input stream's frame type hints are irrelevant when re-encoding
|
|
2329
|
-
frame.pictType =
|
|
2389
|
+
// Clear pict_type - encoder will determine frame types based on its own settings.
|
|
2390
|
+
// Input stream's frame type hints are irrelevant when re-encoding. Opt out with
|
|
2391
|
+
// { preserveFramePictType: true } to force keyframes via frame.pictType = I.
|
|
2392
|
+
if (!this.options.preserveFramePictType) {
|
|
2393
|
+
frame.pictType = AV_PICTURE_TYPE_NONE;
|
|
2394
|
+
}
|
|
2330
2395
|
// Adjust frame PTS and timebase to encoder timebase
|
|
2331
2396
|
// This matches FFmpeg's adjust_frame_pts_to_encoder_tb() behavior which:
|
|
2332
2397
|
// 1. Converts PTS from frame's timebase to encoder's timebase (av_rescale_q)
|
|
@@ -2354,14 +2419,16 @@ export class Encoder {
|
|
|
2354
2419
|
// Matches FFmpeg's CFR mode: frame->duration = 1
|
|
2355
2420
|
frameDuration = 1n;
|
|
2356
2421
|
}
|
|
2357
|
-
|
|
2422
|
+
// AV_NOPTS_VALUE (INT64_MIN) must pass through untouched - rescaling it
|
|
2423
|
+
// yields a garbage timestamp instead of the "unset" marker.
|
|
2424
|
+
if (pts !== AV_NOPTS_VALUE) {
|
|
2358
2425
|
// Convert PTS to encoder timebase
|
|
2359
2426
|
frame.pts = avRescaleQ(pts, oldTimebase, encoderTimebase);
|
|
2360
|
-
// IMPORTANT: Set frame timebase to encoder timebase
|
|
2361
|
-
// FFmpeg does this in adjust_frame_pts_to_encoder_tb(): frame->time_base = tb_dst
|
|
2362
|
-
// This ensures encoder gets frames with correct timebase (1/framerate for video, 1/sample_rate for audio)
|
|
2363
|
-
frame.timeBase = encoderTimebase;
|
|
2364
2427
|
}
|
|
2428
|
+
// IMPORTANT: Set frame timebase to encoder timebase
|
|
2429
|
+
// FFmpeg does this in adjust_frame_pts_to_encoder_tb(): frame->time_base = tb_dst
|
|
2430
|
+
// This ensures encoder gets frames with correct timebase (1/framerate for video, 1/sample_rate for audio)
|
|
2431
|
+
frame.timeBase = encoderTimebase;
|
|
2365
2432
|
// Set frame duration in encoder timebase
|
|
2366
2433
|
// This matches FFmpeg's video_sync_process() which sets frame->duration
|
|
2367
2434
|
// based on vsync_method (CFR: 1, VFR: calculated, PASSTHROUGH: calculated)
|