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.
Files changed (93) hide show
  1. package/README.md +13 -8
  2. package/dist/api/audio-frame-buffer.d.ts +73 -1
  3. package/dist/api/audio-frame-buffer.js +127 -1
  4. package/dist/api/audio-frame-buffer.js.map +1 -1
  5. package/dist/api/bitstream-filter.d.ts +82 -4
  6. package/dist/api/bitstream-filter.js +197 -100
  7. package/dist/api/bitstream-filter.js.map +1 -1
  8. package/dist/api/decoder.d.ts +5 -0
  9. package/dist/api/decoder.js +13 -6
  10. package/dist/api/decoder.js.map +1 -1
  11. package/dist/api/demuxer.d.ts +15 -1
  12. package/dist/api/demuxer.js +165 -54
  13. package/dist/api/demuxer.js.map +1 -1
  14. package/dist/api/encoder-pool.d.ts +43 -1
  15. package/dist/api/encoder-pool.js +121 -9
  16. package/dist/api/encoder-pool.js.map +1 -1
  17. package/dist/api/encoder.d.ts +19 -0
  18. package/dist/api/encoder.js +112 -45
  19. package/dist/api/encoder.js.map +1 -1
  20. package/dist/api/filter.js +15 -6
  21. package/dist/api/filter.js.map +1 -1
  22. package/dist/api/fmp4-stream.d.ts +167 -3
  23. package/dist/api/fmp4-stream.js +423 -90
  24. package/dist/api/fmp4-stream.js.map +1 -1
  25. package/dist/api/hardware.d.ts +2 -0
  26. package/dist/api/hardware.js +25 -4
  27. package/dist/api/hardware.js.map +1 -1
  28. package/dist/api/io-stream.d.ts +3 -2
  29. package/dist/api/io-stream.js +61 -5
  30. package/dist/api/io-stream.js.map +1 -1
  31. package/dist/api/muxer.d.ts +14 -0
  32. package/dist/api/muxer.js +131 -51
  33. package/dist/api/muxer.js.map +1 -1
  34. package/dist/api/pipeline.d.ts +24 -0
  35. package/dist/api/pipeline.js +76 -5
  36. package/dist/api/pipeline.js.map +1 -1
  37. package/dist/api/rtp-stream.d.ts +56 -6
  38. package/dist/api/rtp-stream.js +252 -80
  39. package/dist/api/rtp-stream.js.map +1 -1
  40. package/dist/api/scaler.d.ts +105 -17
  41. package/dist/api/scaler.js +326 -108
  42. package/dist/api/scaler.js.map +1 -1
  43. package/dist/api/utilities/async-queue.d.ts +3 -0
  44. package/dist/api/utilities/async-queue.js +8 -0
  45. package/dist/api/utilities/async-queue.js.map +1 -1
  46. package/dist/api/webrtc-stream.d.ts +2 -0
  47. package/dist/api/webrtc-stream.js +46 -12
  48. package/dist/api/webrtc-stream.js.map +1 -1
  49. package/dist/api/whisper.d.ts +2 -0
  50. package/dist/api/whisper.js +63 -23
  51. package/dist/api/whisper.js.map +1 -1
  52. package/dist/constants/constants.d.ts +1 -0
  53. package/dist/constants/constants.js +1 -0
  54. package/dist/constants/constants.js.map +1 -1
  55. package/dist/constants/filter-options.d.ts +2 -0
  56. package/dist/ffmpeg/install.js +115 -29
  57. package/dist/ffmpeg/install.js.map +1 -1
  58. package/dist/ffmpeg/utils.js +7 -4
  59. package/dist/ffmpeg/utils.js.map +1 -1
  60. package/dist/lib/binding.d.ts +1 -0
  61. package/dist/lib/binding.js +12 -16
  62. package/dist/lib/binding.js.map +1 -1
  63. package/dist/lib/codec-context.d.ts +0 -2
  64. package/dist/lib/codec-context.js +4 -22
  65. package/dist/lib/codec-context.js.map +1 -1
  66. package/dist/lib/filter-context.d.ts +0 -1
  67. package/dist/lib/filter-context.js +2 -11
  68. package/dist/lib/filter-context.js.map +1 -1
  69. package/dist/lib/format-context.d.ts +63 -7
  70. package/dist/lib/format-context.js +70 -21
  71. package/dist/lib/format-context.js.map +1 -1
  72. package/dist/lib/frame.d.ts +11 -11
  73. package/dist/lib/frame.js +13 -20
  74. package/dist/lib/frame.js.map +1 -1
  75. package/dist/lib/hardware-frames-context.d.ts +0 -1
  76. package/dist/lib/hardware-frames-context.js +2 -8
  77. package/dist/lib/hardware-frames-context.js.map +1 -1
  78. package/dist/lib/io-context.d.ts +4 -2
  79. package/dist/lib/io-context.js +4 -2
  80. package/dist/lib/io-context.js.map +1 -1
  81. package/dist/lib/native-types.d.ts +4 -4
  82. package/dist/lib/option.d.ts +26 -0
  83. package/dist/lib/option.js +42 -18
  84. package/dist/lib/option.js.map +1 -1
  85. package/dist/lib/packet.d.ts +5 -0
  86. package/dist/lib/packet.js +5 -0
  87. package/dist/lib/packet.js.map +1 -1
  88. package/dist/lib/stream.d.ts +3 -4
  89. package/dist/lib/stream.js +17 -24
  90. package/dist/lib/stream.js.map +1 -1
  91. package/install/check.js +40 -79
  92. package/package.json +43 -32
  93. 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
  *
@@ -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
- const savedPts = frame.pts;
119
- frame.pts = pooled.nextPts++;
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
- return this.extract(await pooled.encoder.encodeAll(frame), pooled.encoder);
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
- frame.pts = savedPts;
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
- for (const { encoder } of this.encoders.values()) {
173
- encoder.close();
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?.encoder.close();
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":"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"}
1
+ {"version":3,"file":"encoder-pool.js","sourceRoot":"","sources":["../../src/api/encoder-pool.ts"],"names":[],"mappings":"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"}
@@ -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?;
@@ -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 && this.audioFrameBuffer.size > 0) {
1247
- // Pull any remaining partial frame (may be less than frameSize)
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 && this.audioFrameBuffer.size > 0) {
1317
- // Pull any remaining partial frame (may be less than frameSize)
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 env_6 = { stack: [], error: void 0, hasError: false };
1338
+ const env_7 = { stack: [], error: void 0, hasError: false };
1322
1339
  try {
1323
- const bufferedFrame = __addDisposableResource(env_6, _bufferedFrame, false);
1340
+ const bufferedFrame = __addDisposableResource(env_7, _bufferedFrame, false);
1324
1341
  this.codecContext.sendFrameSync(bufferedFrame);
1325
1342
  }
1326
- catch (e_6) {
1327
- env_6.error = e_6;
1328
- env_6.hasError = true;
1343
+ catch (e_7) {
1344
+ env_7.error = e_7;
1345
+ env_7.hasError = true;
1329
1346
  }
1330
1347
  finally {
1331
- __disposeResources(env_6);
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 env_7 = { stack: [], error: void 0, hasError: false };
1507
+ const env_9 = { stack: [], error: void 0, hasError: false };
1475
1508
  try {
1476
- const bufferedFrame = __addDisposableResource(env_7, await this.audioFrameBuffer.pull(), false);
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 (e_7) {
1482
- env_7.error = e_7;
1483
- env_7.hasError = true;
1514
+ catch (e_9) {
1515
+ env_9.error = e_9;
1516
+ env_9.hasError = true;
1484
1517
  }
1485
1518
  finally {
1486
- __disposeResources(env_7);
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 env_8 = { stack: [], error: void 0, hasError: false };
1612
+ const env_10 = { stack: [], error: void 0, hasError: false };
1580
1613
  try {
1581
- const bufferedFrame = __addDisposableResource(env_8, this.audioFrameBuffer.pullSync(), false);
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 (e_8) {
1587
- env_8.error = e_8;
1588
- env_8.hasError = true;
1619
+ catch (e_10) {
1620
+ env_10.error = e_10;
1621
+ env_10.hasError = true;
1589
1622
  }
1590
1623
  finally {
1591
- __disposeResources(env_8);
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
- await target.writePacket(packet, streamIndex);
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 env_9 = { stack: [], error: void 0, hasError: false };
1771
+ const env_11 = { stack: [], error: void 0, hasError: false };
1733
1772
  try {
1734
- const frame = __addDisposableResource(env_9, await this.inputQueue.receive(), false);
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 (e_9) {
1754
- env_9.error = e_9;
1755
- env_9.hasError = true;
1792
+ catch (e_11) {
1793
+ env_11.error = e_11;
1794
+ env_11.hasError = true;
1756
1795
  }
1757
1796
  finally {
1758
- __disposeResources(env_9);
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
- out.timeBase = frame.timeBase;
2203
- out.pts = frame.pts;
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 = AV_PICTURE_TYPE_NONE;
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
- if (pts !== null && pts !== undefined) {
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)