node-av 1.3.0 → 2.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.
Files changed (127) hide show
  1. package/README.md +47 -40
  2. package/binding.gyp +12 -0
  3. package/dist/api/bitstream-filter.d.ts +134 -2
  4. package/dist/api/bitstream-filter.js +200 -2
  5. package/dist/api/bitstream-filter.js.map +1 -1
  6. package/dist/api/decoder.d.ts +261 -105
  7. package/dist/api/decoder.js +384 -171
  8. package/dist/api/decoder.js.map +1 -1
  9. package/dist/api/encoder.d.ts +338 -74
  10. package/dist/api/encoder.js +546 -188
  11. package/dist/api/encoder.js.map +1 -1
  12. package/dist/api/filter-presets.d.ts +479 -1513
  13. package/dist/api/filter-presets.js +1044 -2005
  14. package/dist/api/filter-presets.js.map +1 -1
  15. package/dist/api/filter.d.ts +370 -150
  16. package/dist/api/filter.js +647 -364
  17. package/dist/api/filter.js.map +1 -1
  18. package/dist/api/hardware.d.ts +25 -31
  19. package/dist/api/hardware.js +36 -70
  20. package/dist/api/hardware.js.map +1 -1
  21. package/dist/api/index.d.ts +1 -1
  22. package/dist/api/index.js +1 -1
  23. package/dist/api/index.js.map +1 -1
  24. package/dist/api/io-stream.d.ts +6 -0
  25. package/dist/api/io-stream.js +2 -0
  26. package/dist/api/io-stream.js.map +1 -1
  27. package/dist/api/media-input.d.ts +208 -2
  28. package/dist/api/media-input.js +356 -8
  29. package/dist/api/media-input.js.map +1 -1
  30. package/dist/api/media-output.d.ts +142 -104
  31. package/dist/api/media-output.js +446 -179
  32. package/dist/api/media-output.js.map +1 -1
  33. package/dist/api/pipeline.d.ts +82 -17
  34. package/dist/api/pipeline.js +80 -42
  35. package/dist/api/pipeline.js.map +1 -1
  36. package/dist/api/types.d.ts +24 -57
  37. package/dist/api/utils.js +2 -0
  38. package/dist/api/utils.js.map +1 -1
  39. package/dist/lib/audio-fifo.d.ts +103 -0
  40. package/dist/lib/audio-fifo.js +109 -0
  41. package/dist/lib/audio-fifo.js.map +1 -1
  42. package/dist/lib/binding.d.ts +1 -0
  43. package/dist/lib/binding.js.map +1 -1
  44. package/dist/lib/bitstream-filter-context.d.ts +79 -0
  45. package/dist/lib/bitstream-filter-context.js +83 -0
  46. package/dist/lib/bitstream-filter-context.js.map +1 -1
  47. package/dist/lib/bitstream-filter.d.ts +2 -0
  48. package/dist/lib/bitstream-filter.js +2 -0
  49. package/dist/lib/bitstream-filter.js.map +1 -1
  50. package/dist/lib/codec-context.d.ts +168 -0
  51. package/dist/lib/codec-context.js +178 -0
  52. package/dist/lib/codec-context.js.map +1 -1
  53. package/dist/lib/codec-parameters.d.ts +3 -0
  54. package/dist/lib/codec-parameters.js +3 -0
  55. package/dist/lib/codec-parameters.js.map +1 -1
  56. package/dist/lib/codec-parser.d.ts +6 -0
  57. package/dist/lib/codec-parser.js +6 -0
  58. package/dist/lib/codec-parser.js.map +1 -1
  59. package/dist/lib/codec.d.ts +12 -0
  60. package/dist/lib/codec.js +12 -0
  61. package/dist/lib/codec.js.map +1 -1
  62. package/dist/lib/dictionary.d.ts +18 -2
  63. package/dist/lib/dictionary.js +18 -2
  64. package/dist/lib/dictionary.js.map +1 -1
  65. package/dist/lib/error.d.ts +8 -0
  66. package/dist/lib/error.js +9 -0
  67. package/dist/lib/error.js.map +1 -1
  68. package/dist/lib/filter-context.d.ts +119 -2
  69. package/dist/lib/filter-context.js +119 -0
  70. package/dist/lib/filter-context.js.map +1 -1
  71. package/dist/lib/filter-graph.d.ts +80 -0
  72. package/dist/lib/filter-graph.js +84 -0
  73. package/dist/lib/filter-graph.js.map +1 -1
  74. package/dist/lib/filter-inout.d.ts +1 -0
  75. package/dist/lib/filter-inout.js +1 -0
  76. package/dist/lib/filter-inout.js.map +1 -1
  77. package/dist/lib/filter.d.ts +2 -0
  78. package/dist/lib/filter.js +2 -0
  79. package/dist/lib/filter.js.map +1 -1
  80. package/dist/lib/format-context.d.ts +356 -20
  81. package/dist/lib/format-context.js +375 -23
  82. package/dist/lib/format-context.js.map +1 -1
  83. package/dist/lib/frame.d.ts +84 -1
  84. package/dist/lib/frame.js +96 -0
  85. package/dist/lib/frame.js.map +1 -1
  86. package/dist/lib/hardware-device-context.d.ts +8 -0
  87. package/dist/lib/hardware-device-context.js +8 -0
  88. package/dist/lib/hardware-device-context.js.map +1 -1
  89. package/dist/lib/hardware-frames-context.d.ts +55 -0
  90. package/dist/lib/hardware-frames-context.js +57 -0
  91. package/dist/lib/hardware-frames-context.js.map +1 -1
  92. package/dist/lib/input-format.d.ts +43 -3
  93. package/dist/lib/input-format.js +48 -0
  94. package/dist/lib/input-format.js.map +1 -1
  95. package/dist/lib/io-context.d.ts +212 -0
  96. package/dist/lib/io-context.js +228 -0
  97. package/dist/lib/io-context.js.map +1 -1
  98. package/dist/lib/log.d.ts +2 -0
  99. package/dist/lib/log.js +2 -0
  100. package/dist/lib/log.js.map +1 -1
  101. package/dist/lib/native-types.d.ts +39 -1
  102. package/dist/lib/option.d.ts +90 -0
  103. package/dist/lib/option.js +97 -0
  104. package/dist/lib/option.js.map +1 -1
  105. package/dist/lib/output-format.d.ts +4 -0
  106. package/dist/lib/output-format.js +4 -0
  107. package/dist/lib/output-format.js.map +1 -1
  108. package/dist/lib/packet.d.ts +7 -0
  109. package/dist/lib/packet.js +7 -0
  110. package/dist/lib/packet.js.map +1 -1
  111. package/dist/lib/rational.d.ts +1 -0
  112. package/dist/lib/rational.js +1 -0
  113. package/dist/lib/rational.js.map +1 -1
  114. package/dist/lib/software-resample-context.d.ts +64 -0
  115. package/dist/lib/software-resample-context.js +66 -0
  116. package/dist/lib/software-resample-context.js.map +1 -1
  117. package/dist/lib/software-scale-context.d.ts +98 -0
  118. package/dist/lib/software-scale-context.js +102 -0
  119. package/dist/lib/software-scale-context.js.map +1 -1
  120. package/dist/lib/stream.d.ts +1 -0
  121. package/dist/lib/stream.js +1 -0
  122. package/dist/lib/stream.js.map +1 -1
  123. package/dist/lib/utilities.d.ts +60 -0
  124. package/dist/lib/utilities.js +60 -0
  125. package/dist/lib/utilities.js.map +1 -1
  126. package/package.json +18 -18
  127. package/release_notes.md +0 -29
@@ -1,5 +1,5 @@
1
- import { AVERROR_EAGAIN, AVERROR_EOF, AVMEDIA_TYPE_VIDEO } from '../constants/constants.js';
2
- import { Codec, CodecContext, FFmpegError, Frame } from '../lib/index.js';
1
+ import { AVERROR_EAGAIN, AVERROR_EOF } from '../constants/constants.js';
2
+ import { Codec, CodecContext, Dictionary, FFmpegError, Frame } from '../lib/index.js';
3
3
  /**
4
4
  * High-level decoder for audio and video streams.
5
5
  *
@@ -44,23 +44,28 @@ import { Codec, CodecContext, FFmpegError, Frame } from '../lib/index.js';
44
44
  */
45
45
  export class Decoder {
46
46
  codecContext;
47
+ codec;
47
48
  frame;
48
- streamIndex;
49
49
  stream;
50
- isOpen = true;
50
+ initialized = true;
51
+ isClosed = false;
51
52
  hardware;
52
53
  /**
53
54
  * @param codecContext - Configured codec context
55
+ *
56
+ * @param codec - Codec being used
57
+ *
54
58
  * @param stream - Media stream being decoded
59
+ *
55
60
  * @param hardware - Optional hardware context
56
61
  * Use {@link create} factory method
57
62
  *
58
63
  * @internal
59
64
  */
60
- constructor(codecContext, stream, hardware) {
65
+ constructor(codecContext, codec, stream, hardware) {
61
66
  this.codecContext = codecContext;
67
+ this.codec = codec;
62
68
  this.stream = stream;
63
- this.streamIndex = stream.index;
64
69
  this.hardware = hardware;
65
70
  this.frame = new Frame();
66
71
  this.frame.alloc();
@@ -73,7 +78,9 @@ export class Decoder {
73
78
  * Applies custom codec options and threading configuration.
74
79
  *
75
80
  * @param stream - Media stream to decode
81
+ *
76
82
  * @param options - Decoder configuration options
83
+ *
77
84
  * @returns Configured decoder instance
78
85
  *
79
86
  * @throws {Error} If decoder not found for codec
@@ -142,20 +149,14 @@ export class Decoder {
142
149
  if (isHWDecoder && options.hardware) {
143
150
  codecContext.hwDeviceCtx = options.hardware.deviceContext;
144
151
  }
145
- // Apply codec-specific options via AVOptions
146
- if (options.options) {
147
- for (const [key, value] of Object.entries(options.options)) {
148
- codecContext.setOption(key, value.toString());
149
- }
150
- }
152
+ const opts = options.options ? Dictionary.fromObject(options.options) : undefined;
151
153
  // Open codec
152
- const openRet = await codecContext.open2(codec, null);
154
+ const openRet = await codecContext.open2(codec, opts);
153
155
  if (openRet < 0) {
154
156
  codecContext.freeContext();
155
157
  FFmpegError.throwIfError(openRet, 'Failed to open codec');
156
158
  }
157
- const decoder = new Decoder(codecContext, stream, isHWDecoder ? options.hardware : undefined);
158
- return decoder;
159
+ return new Decoder(codecContext, codec, stream, isHWDecoder ? options.hardware : undefined);
159
160
  }
160
161
  /**
161
162
  * Check if decoder is open.
@@ -170,59 +171,25 @@ export class Decoder {
170
171
  * ```
171
172
  */
172
173
  get isDecoderOpen() {
173
- return this.isOpen;
174
+ return !this.isClosed;
174
175
  }
175
176
  /**
176
- * Get output stream information.
177
+ * Check if decoder has been initialized.
177
178
  *
178
- * Returns format information about decoded frames.
179
- * For hardware decoding, returns the hardware pixel format.
180
- * Essential for configuring downstream components like encoders or filters.
179
+ * Returns true if decoder is initialized (true by default for decoders).
180
+ * Decoders are pre-initialized from stream parameters.
181
181
  *
182
- * @returns Stream format information
182
+ * @returns true if decoder has been initialized
183
183
  *
184
184
  * @example
185
185
  * ```typescript
186
- * const info = decoder.getOutputStreamInfo();
187
- * if (info.type === 'video') {
188
- * console.log(`Video: ${info.width}x${info.height} @ ${info.pixelFormat}`);
189
- * console.log(`Frame rate: ${info.frameRate.num}/${info.frameRate.den}`);
186
+ * if (decoder.isDecoderInitialized) {
187
+ * console.log('Decoder is ready to process frames');
190
188
  * }
191
189
  * ```
192
- *
193
- * @example
194
- * ```typescript
195
- * const info = decoder.getOutputStreamInfo();
196
- * if (info.type === 'audio') {
197
- * console.log(`Audio: ${info.sampleRate}Hz ${info.sampleFormat}`);
198
- * console.log(`Channels: ${info.channelLayout}`);
199
- * }
200
- * ```
201
- *
202
- * @see {@link StreamInfo} For format details
203
- * @see {@link Encoder.create} For matching encoder configuration
204
190
  */
205
- getOutputStreamInfo() {
206
- if (this.stream.codecpar.codecType === AVMEDIA_TYPE_VIDEO) {
207
- return {
208
- type: 'video',
209
- width: this.codecContext.width,
210
- height: this.codecContext.height,
211
- pixelFormat: this.hardware?.devicePixelFormat ?? this.codecContext.pixelFormat,
212
- timeBase: this.stream.timeBase,
213
- frameRate: this.stream.rFrameRate,
214
- sampleAspectRatio: this.codecContext.sampleAspectRatio,
215
- };
216
- }
217
- else {
218
- return {
219
- type: 'audio',
220
- sampleRate: this.codecContext.sampleRate,
221
- sampleFormat: this.codecContext.sampleFormat,
222
- channelLayout: this.codecContext.channelLayout,
223
- timeBase: this.stream.timeBase,
224
- };
225
- }
191
+ get isDecoderInitialized() {
192
+ return this.initialized;
226
193
  }
227
194
  /**
228
195
  * Check if decoder uses hardware acceleration.
@@ -239,7 +206,22 @@ export class Decoder {
239
206
  * @see {@link HardwareContext} For hardware setup
240
207
  */
241
208
  isHardware() {
242
- return !!this.hardware;
209
+ return !!this.hardware && this.codec.isHardwareAcceleratedDecoder();
210
+ }
211
+ /**
212
+ * Check if decoder is ready for processing.
213
+ *
214
+ * @returns true if initialized and ready
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * if (decoder.isReady()) {
219
+ * const frame = await decoder.decode(packet);
220
+ * }
221
+ * ```
222
+ */
223
+ isReady() {
224
+ return this.initialized && !this.isClosed;
243
225
  }
244
226
  /**
245
227
  * Decode a packet to a frame.
@@ -251,6 +233,7 @@ export class Decoder {
251
233
  * Direct mapping to avcodec_send_packet() and avcodec_receive_frame().
252
234
  *
253
235
  * @param packet - Compressed packet to decode
236
+ *
254
237
  * @returns Decoded frame or null if more data needed
255
238
  *
256
239
  * @throws {Error} If decoder is closed
@@ -269,7 +252,7 @@ export class Decoder {
269
252
  * @example
270
253
  * ```typescript
271
254
  * for await (const packet of input.packets()) {
272
- * if (packet.streamIndex === decoder.getStreamIndex()) {
255
+ * if (packet.streamIndex === decoder.getStream().index) {
273
256
  * const frame = await decoder.decode(packet);
274
257
  * if (frame) {
275
258
  * await processFrame(frame);
@@ -284,14 +267,14 @@ export class Decoder {
284
267
  * @see {@link flush} For end-of-stream handling
285
268
  */
286
269
  async decode(packet) {
287
- if (!this.isOpen) {
270
+ if (this.isClosed) {
288
271
  throw new Error('Decoder is closed');
289
272
  }
290
273
  // Send packet to decoder
291
274
  const sendRet = await this.codecContext.sendPacket(packet);
292
275
  if (sendRet < 0 && sendRet !== AVERROR_EOF) {
293
276
  // Decoder might be full, try to receive first
294
- const frame = await this.receiveFrameInternal();
277
+ const frame = await this.receive();
295
278
  if (frame) {
296
279
  return frame;
297
280
  }
@@ -301,77 +284,55 @@ export class Decoder {
301
284
  }
302
285
  }
303
286
  // Try to receive frame
304
- const frame = await this.receiveFrameInternal();
287
+ const frame = await this.receive();
305
288
  return frame;
306
289
  }
307
290
  /**
308
- * Flush decoder and get buffered frame.
291
+ * Decode a packet to frame synchronously.
292
+ * Synchronous version of decode.
309
293
  *
310
- * Signals end-of-stream and retrieves remaining frames.
311
- * Call repeatedly until null to get all buffered frames.
312
- * Essential for ensuring all frames are decoded.
294
+ * Send packet to decoder and attempt to receive frame.
295
+ * Handles decoder buffering and error conditions.
296
+ * May return null if decoder needs more data.
313
297
  *
314
- * Direct mapping to avcodec_send_packet(NULL).
298
+ * @param packet - Compressed packet to decode
315
299
  *
316
- * @returns Buffered frame or null if none remaining
300
+ * @returns Decoded frame or null
317
301
  *
318
302
  * @throws {Error} If decoder is closed
319
303
  *
320
- * @example
321
- * ```typescript
322
- * // After all packets processed
323
- * let frame;
324
- * while ((frame = await decoder.flush()) !== null) {
325
- * console.log('Got buffered frame');
326
- * await processFrame(frame);
327
- * frame.free();
328
- * }
329
- * ```
330
- *
331
- * @see {@link flushFrames} For async iteration
332
- * @see {@link frames} For complete decoding pipeline
333
- */
334
- async flush() {
335
- if (!this.isOpen) {
336
- throw new Error('Decoder is closed');
337
- }
338
- // Send flush packet (null)
339
- await this.codecContext.sendPacket(null);
340
- // Receive frame
341
- const frame = await this.receiveFrameInternal();
342
- return frame;
343
- }
344
- /**
345
- * Flush all buffered frames as async generator.
346
- *
347
- * Convenient async iteration over remaining frames.
348
- * Automatically handles repeated flush calls.
349
- * Useful for end-of-stream processing.
350
- *
351
- * @yields Buffered frames
352
- * @throws {Error} If decoder is closed
304
+ * @throws {FFmpegError} If decoding fails
353
305
  *
354
306
  * @example
355
307
  * ```typescript
356
- * // Flush at end of decoding
357
- * for await (const frame of decoder.flushFrames()) {
358
- * console.log('Processing buffered frame');
359
- * await encoder.encode(frame);
360
- * frame.free();
308
+ * const frame = decoder.decodeSync(packet);
309
+ * if (frame) {
310
+ * console.log(`Decoded: ${frame.width}x${frame.height}`);
361
311
  * }
362
312
  * ```
363
313
  *
364
- * @see {@link flush} For single frame flush
365
- * @see {@link frames} For complete pipeline
314
+ * @see {@link decode} For async version
366
315
  */
367
- async *flushFrames() {
368
- if (!this.isOpen) {
316
+ decodeSync(packet) {
317
+ if (this.isClosed) {
369
318
  throw new Error('Decoder is closed');
370
319
  }
371
- let frame;
372
- while ((frame = await this.flush()) !== null) {
373
- yield frame;
320
+ // Send packet to decoder
321
+ const sendRet = this.codecContext.sendPacketSync(packet);
322
+ if (sendRet < 0 && sendRet !== AVERROR_EOF) {
323
+ // Decoder might be full, try to receive first
324
+ const frame = this.receiveSync();
325
+ if (frame) {
326
+ return frame;
327
+ }
328
+ // If still failing, it's an error
329
+ if (sendRet !== AVERROR_EAGAIN) {
330
+ FFmpegError.throwIfError(sendRet, 'Failed to send packet');
331
+ }
374
332
  }
333
+ // Try to receive frame
334
+ const frame = this.receiveSync();
335
+ return frame;
375
336
  }
376
337
  /**
377
338
  * Decode packet stream to frame stream.
@@ -382,7 +343,9 @@ export class Decoder {
382
343
  * Primary interface for stream-based decoding.
383
344
  *
384
345
  * @param packets - Async iterable of packets
385
- * @yields Decoded frames
346
+ *
347
+ * @yields {Frame} Decoded frames
348
+ *
386
349
  * @throws {Error} If decoder is closed
387
350
  *
388
351
  * @throws {FFmpegError} If decoding fails
@@ -402,7 +365,7 @@ export class Decoder {
402
365
  * ```typescript
403
366
  * for await (const frame of decoder.frames(input.packets())) {
404
367
  * // Process frame
405
- * await filter.filterFrame(frame);
368
+ * await filter.process(frame);
406
369
  *
407
370
  * // Frame automatically freed
408
371
  * frame.free();
@@ -426,14 +389,11 @@ export class Decoder {
426
389
  * @see {@link MediaInput.packets} For packet source
427
390
  */
428
391
  async *frames(packets) {
429
- if (!this.isOpen) {
430
- throw new Error('Decoder is closed');
431
- }
432
392
  // Process packets
433
393
  for await (const packet of packets) {
434
394
  try {
435
395
  // Only process packets for our stream
436
- if (packet.streamIndex === this.streamIndex) {
396
+ if (packet.streamIndex === this.stream.index) {
437
397
  const frame = await this.decode(packet);
438
398
  if (frame) {
439
399
  yield frame;
@@ -446,115 +406,240 @@ export class Decoder {
446
406
  }
447
407
  }
448
408
  // Flush decoder after all packets
449
- let frame;
450
- while ((frame = await this.flush()) !== null) {
451
- yield frame;
409
+ await this.flush();
410
+ while (true) {
411
+ const remaining = await this.receive();
412
+ if (!remaining)
413
+ break;
414
+ yield remaining;
452
415
  }
453
416
  }
454
417
  /**
455
- * Close decoder and free resources.
418
+ * Decode packet stream to frame stream synchronously.
419
+ * Synchronous version of frames.
456
420
  *
457
- * Releases codec context and internal frame buffer.
458
- * Safe to call multiple times.
459
- * Automatically called by Symbol.dispose.
421
+ * High-level sync generator for complete decoding pipeline.
422
+ * Automatically filters packets for this stream, manages memory,
423
+ * and flushes buffered frames at end.
424
+ *
425
+ * @param packets - Iterable of packets
426
+ *
427
+ * @yields {Frame} Decoded frames
428
+ *
429
+ * @throws {Error} If decoder is closed
430
+ *
431
+ * @throws {FFmpegError} If decoding fails
460
432
  *
461
433
  * @example
462
434
  * ```typescript
463
- * const decoder = await Decoder.create(stream);
464
- * try {
465
- * // Use decoder
466
- * } finally {
467
- * decoder.close();
435
+ * for (const frame of decoder.framesSync(packets)) {
436
+ * console.log(`Frame: ${frame.width}x${frame.height}`);
437
+ * // Process frame...
468
438
  * }
469
439
  * ```
470
440
  *
471
- * @see {@link Symbol.dispose} For automatic cleanup
441
+ * @see {@link frames} For async version
472
442
  */
473
- close() {
474
- if (!this.isOpen) {
443
+ *framesSync(packets) {
444
+ // Process packets
445
+ for (const packet of packets) {
446
+ try {
447
+ // Only process packets for our stream
448
+ if (packet.streamIndex === this.stream.index) {
449
+ const frame = this.decodeSync(packet);
450
+ if (frame) {
451
+ yield frame;
452
+ }
453
+ }
454
+ }
455
+ finally {
456
+ // Free the input packet after processing
457
+ packet.free();
458
+ }
459
+ }
460
+ // Flush decoder after all packets
461
+ this.flushSync();
462
+ while (true) {
463
+ const remaining = this.receiveSync();
464
+ if (!remaining)
465
+ break;
466
+ yield remaining;
467
+ }
468
+ }
469
+ /**
470
+ * Flush decoder and signal end-of-stream.
471
+ *
472
+ * Sends null packet to decoder to signal end-of-stream.
473
+ * Does nothing if decoder is closed.
474
+ * Must use receive() or flushFrames() to get remaining buffered frames.
475
+ *
476
+ * Direct mapping to avcodec_send_packet(NULL).
477
+ *
478
+ * @throws {FFmpegError} If flush fails
479
+ *
480
+ * @example
481
+ * ```typescript
482
+ * // Signal end of stream
483
+ * await decoder.flush();
484
+ *
485
+ * // Then get remaining frames
486
+ * let frame;
487
+ * while ((frame = await decoder.receive()) !== null) {
488
+ * console.log('Got buffered frame');
489
+ * frame.free();
490
+ * }
491
+ * ```
492
+ *
493
+ * @see {@link flushFrames} For convenient async iteration
494
+ * @see {@link receive} For getting buffered frames
495
+ */
496
+ async flush() {
497
+ if (this.isClosed) {
475
498
  return;
476
499
  }
477
- this.frame.free();
478
- this.codecContext.freeContext();
479
- this.isOpen = false;
500
+ // Send flush packet (null)
501
+ const ret = await this.codecContext.sendPacket(null);
502
+ if (ret < 0 && ret !== AVERROR_EOF) {
503
+ if (ret !== AVERROR_EAGAIN) {
504
+ FFmpegError.throwIfError(ret, 'Failed to flush decoder');
505
+ }
506
+ }
480
507
  }
481
508
  /**
482
- * Get stream index.
509
+ * Flush decoder and signal end-of-stream synchronously.
510
+ * Synchronous version of flush.
483
511
  *
484
- * Returns the index of the stream being decoded.
485
- * Used for packet filtering in multi-stream files.
512
+ * Send null packet to signal end of input stream.
513
+ * Decoder may still have buffered frames.
514
+ * Call receiveSync() repeatedly to get remaining frames.
486
515
  *
487
- * @returns Stream index
516
+ * @throws {FFmpegError} If flush fails
488
517
  *
489
518
  * @example
490
519
  * ```typescript
491
- * if (packet.streamIndex === decoder.getStreamIndex()) {
492
- * const frame = await decoder.decode(packet);
520
+ * decoder.flushSync();
521
+ * // Get remaining frames
522
+ * let frame;
523
+ * while ((frame = decoder.receiveSync()) !== null) {
524
+ * console.log('Buffered frame');
493
525
  * }
494
526
  * ```
495
527
  *
496
- * @see {@link getStream} For full stream object
528
+ * @see {@link flush} For async version
497
529
  */
498
- getStreamIndex() {
499
- return this.streamIndex;
530
+ flushSync() {
531
+ if (this.isClosed) {
532
+ return;
533
+ }
534
+ // Send flush packet (null)
535
+ const ret = this.codecContext.sendPacketSync(null);
536
+ if (ret < 0 && ret !== AVERROR_EOF) {
537
+ if (ret !== AVERROR_EAGAIN) {
538
+ FFmpegError.throwIfError(ret, 'Failed to flush decoder');
539
+ }
540
+ }
500
541
  }
501
542
  /**
502
- * Get stream object.
543
+ * Flush all buffered frames as async generator.
503
544
  *
504
- * Returns the underlying stream being decoded.
505
- * Provides access to stream metadata and parameters.
545
+ * Convenient async iteration over remaining frames.
546
+ * Automatically sends flush signal and retrieves buffered frames.
547
+ * Useful for end-of-stream processing.
506
548
  *
507
- * @returns Stream object
549
+ * @yields {Frame} Buffered frames
508
550
  *
509
551
  * @example
510
552
  * ```typescript
511
- * const stream = decoder.getStream();
512
- * console.log(`Duration: ${stream.duration}`);
513
- * console.log(`Time base: ${stream.timeBase.num}/${stream.timeBase.den}`);
553
+ * // Flush at end of decoding
554
+ * for await (const frame of decoder.flushFrames()) {
555
+ * console.log('Processing buffered frame');
556
+ * await encoder.encode(frame);
557
+ * frame.free();
558
+ * }
514
559
  * ```
515
560
  *
516
- * @see {@link Stream} For stream properties
517
- * @see {@link getStreamIndex} For index only
561
+ * @see {@link flush} For signaling end-of-stream
562
+ * @see {@link frames} For complete pipeline
518
563
  */
519
- getStream() {
520
- return this.stream;
564
+ async *flushFrames() {
565
+ // Send flush signal
566
+ await this.flush();
567
+ let frame;
568
+ while ((frame = await this.receive()) !== null) {
569
+ yield frame;
570
+ }
521
571
  }
522
572
  /**
523
- * Get underlying codec context.
573
+ * Flush all buffered frames as generator synchronously.
574
+ * Synchronous version of flushFrames.
524
575
  *
525
- * Returns the internal codec context for advanced operations.
526
- * Returns null if decoder is closed.
576
+ * Convenient sync iteration over remaining frames.
577
+ * Automatically sends flush signal and retrieves buffered frames.
578
+ * Useful for end-of-stream processing.
527
579
  *
528
- * @returns Codec context or null
580
+ * @yields {Frame} Buffered frames
529
581
  *
530
- * @internal
582
+ * @example
583
+ * ```typescript
584
+ * // Flush at end of decoding
585
+ * for (const frame of decoder.flushFramesSync()) {
586
+ * console.log('Processing buffered frame');
587
+ * encoder.encodeSync(frame);
588
+ * frame.free();
589
+ * }
590
+ * ```
591
+ *
592
+ * @see {@link flushFrames} For async version
531
593
  */
532
- getCodecContext() {
533
- return this.isOpen ? this.codecContext : null;
594
+ *flushFramesSync() {
595
+ // Send flush signal
596
+ this.flushSync();
597
+ let frame;
598
+ while ((frame = this.receiveSync()) !== null) {
599
+ yield frame;
600
+ }
534
601
  }
535
602
  /**
536
603
  * Receive frame from decoder.
537
604
  *
538
- * Internal method to get decoded frames from codec.
605
+ * Gets decoded frames from the codec's internal buffer.
539
606
  * Handles frame cloning and error checking.
540
607
  * Hardware frames include hw_frames_ctx reference.
608
+ * Call repeatedly until null to drain all buffered frames.
541
609
  *
542
610
  * Direct mapping to avcodec_receive_frame().
543
611
  *
544
- * @returns Cloned frame or null
612
+ * @returns Cloned frame or null if no frames available
545
613
  *
546
614
  * @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
547
615
  *
548
- * @internal
616
+ * @example
617
+ * ```typescript
618
+ * const frame = await decoder.receive();
619
+ * if (frame) {
620
+ * console.log('Got decoded frame');
621
+ * frame.free();
622
+ * }
623
+ * ```
549
624
  *
625
+ * @example
626
+ * ```typescript
627
+ * // Drain all buffered frames
628
+ * let frame;
629
+ * while ((frame = await decoder.receive()) !== null) {
630
+ * console.log(`Frame PTS: ${frame.pts}`);
631
+ * frame.free();
632
+ * }
633
+ * ```
634
+ *
635
+ * @see {@link decode} For sending packets and receiving frames
636
+ * @see {@link flush} For signaling end-of-stream
550
637
  */
551
- async receiveFrameInternal() {
638
+ async receive() {
552
639
  // Clear previous frame data
553
640
  this.frame.unref();
554
641
  const ret = await this.codecContext.receiveFrame(this.frame);
555
642
  if (ret === 0) {
556
- // Note: hw_frames_ctx is now available in the frame
557
- // Other components should get it directly from frames, not from HardwareContext
558
643
  // Got a frame, clone it for the user
559
644
  return this.frame.clone();
560
645
  }
@@ -568,6 +653,134 @@ export class Decoder {
568
653
  return null;
569
654
  }
570
655
  }
656
+ /**
657
+ * Receive frame from decoder synchronously.
658
+ * Synchronous version of receive.
659
+ *
660
+ * Gets decoded frames from the codec's internal buffer.
661
+ * Handles frame cloning and error checking.
662
+ * Hardware frames include hw_frames_ctx reference.
663
+ * Call repeatedly until null to drain all buffered frames.
664
+ *
665
+ * Direct mapping to avcodec_receive_frame().
666
+ *
667
+ * @returns Cloned frame or null if no frames available
668
+ *
669
+ * @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
670
+ *
671
+ * @example
672
+ * ```typescript
673
+ * const frame = decoder.receiveSync();
674
+ * if (frame) {
675
+ * console.log('Got decoded frame');
676
+ * frame.free();
677
+ * }
678
+ * ```
679
+ *
680
+ * @example
681
+ * ```typescript
682
+ * // Drain all buffered frames
683
+ * let frame;
684
+ * while ((frame = decoder.receiveSync()) !== null) {
685
+ * console.log(`Frame PTS: ${frame.pts}`);
686
+ * frame.free();
687
+ * }
688
+ * ```
689
+ *
690
+ * @see {@link receive} For async version
691
+ */
692
+ receiveSync() {
693
+ // Clear previous frame data
694
+ this.frame.unref();
695
+ const ret = this.codecContext.receiveFrameSync(this.frame);
696
+ if (ret === 0) {
697
+ // Got a frame, clone it for the user
698
+ return this.frame.clone();
699
+ }
700
+ else if (ret === AVERROR_EAGAIN || ret === AVERROR_EOF) {
701
+ // Need more data or end of stream
702
+ return null;
703
+ }
704
+ else {
705
+ // Error
706
+ FFmpegError.throwIfError(ret, 'Failed to receive frame');
707
+ return null;
708
+ }
709
+ }
710
+ /**
711
+ * Close decoder and free resources.
712
+ *
713
+ * Releases codec context and internal frame buffer.
714
+ * Safe to call multiple times.
715
+ * Automatically called by Symbol.dispose.
716
+ *
717
+ * @example
718
+ * ```typescript
719
+ * const decoder = await Decoder.create(stream);
720
+ * try {
721
+ * // Use decoder
722
+ * } finally {
723
+ * decoder.close();
724
+ * }
725
+ * ```
726
+ *
727
+ * @see {@link Symbol.dispose} For automatic cleanup
728
+ */
729
+ close() {
730
+ if (this.isClosed) {
731
+ return;
732
+ }
733
+ this.isClosed = true;
734
+ this.frame.free();
735
+ this.codecContext.freeContext();
736
+ this.initialized = false;
737
+ }
738
+ /**
739
+ * Get stream object.
740
+ *
741
+ * Returns the underlying stream being decoded.
742
+ * Provides access to stream metadata and parameters.
743
+ *
744
+ * @returns Stream object
745
+ *
746
+ * @internal
747
+ *
748
+ * @see {@link Stream} For stream details
749
+ */
750
+ getStream() {
751
+ return this.stream;
752
+ }
753
+ /**
754
+ * Get decoder codec.
755
+ *
756
+ * Returns the codec used by this decoder.
757
+ * Useful for checking codec capabilities and properties.
758
+ *
759
+ * @returns Codec instance
760
+ *
761
+ * @internal
762
+ *
763
+ * @see {@link Codec} For codec details
764
+ */
765
+ getCodec() {
766
+ return this.codec;
767
+ }
768
+ /**
769
+ * Get underlying codec context.
770
+ *
771
+ * Returns the codec context for advanced operations.
772
+ * Useful for accessing low-level codec properties and settings.
773
+ * Returns null if decoder is closed.
774
+ *
775
+ * @returns Codec context or null if closed
776
+ *
777
+ * @internal
778
+ *
779
+ * @see {@link CodecContext} For context details
780
+ */
781
+ getCodecContext() {
782
+ return !this.isClosed && this.initialized ? this.codecContext : null;
783
+ }
571
784
  /**
572
785
  * Dispose of decoder.
573
786
  *