node-av 4.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/binding.gyp +19 -11
- package/dist/api/bitstream-filter.d.ts +13 -12
- package/dist/api/bitstream-filter.js +33 -29
- package/dist/api/bitstream-filter.js.map +1 -1
- package/dist/api/decoder.d.ts +211 -96
- package/dist/api/decoder.js +396 -375
- package/dist/api/decoder.js.map +1 -1
- package/dist/api/demuxer.d.ts +10 -10
- package/dist/api/demuxer.js +7 -10
- package/dist/api/demuxer.js.map +1 -1
- package/dist/api/encoder.d.ts +155 -122
- package/dist/api/encoder.js +368 -541
- package/dist/api/encoder.js.map +1 -1
- package/dist/api/filter-complex.d.ts +769 -0
- package/dist/api/filter-complex.js +1596 -0
- package/dist/api/filter-complex.js.map +1 -0
- package/dist/api/filter-presets.d.ts +68 -0
- package/dist/api/filter-presets.js +96 -0
- package/dist/api/filter-presets.js.map +1 -1
- package/dist/api/filter.d.ts +183 -113
- package/dist/api/filter.js +347 -365
- package/dist/api/filter.js.map +1 -1
- package/dist/api/fmp4-stream.d.ts +2 -2
- package/dist/api/fmp4-stream.js.map +1 -1
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +3 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/io-stream.d.ts +3 -3
- package/dist/api/io-stream.js.map +1 -1
- package/dist/api/muxer.d.ts +10 -10
- package/dist/api/muxer.js +6 -6
- package/dist/api/muxer.js.map +1 -1
- package/dist/api/pipeline.d.ts +2 -2
- package/dist/api/pipeline.js +22 -22
- package/dist/api/pipeline.js.map +1 -1
- package/dist/api/rtp-stream.d.ts +2 -2
- package/dist/api/rtp-stream.js.map +1 -1
- package/dist/api/types.d.ts +63 -7
- package/dist/api/utilities/audio-sample.d.ts +10 -0
- package/dist/api/utilities/audio-sample.js +10 -0
- package/dist/api/utilities/audio-sample.js.map +1 -1
- package/dist/api/utilities/channel-layout.d.ts +1 -0
- package/dist/api/utilities/channel-layout.js +1 -0
- package/dist/api/utilities/channel-layout.js.map +1 -1
- package/dist/api/utilities/image.d.ts +38 -0
- package/dist/api/utilities/image.js +38 -0
- package/dist/api/utilities/image.js.map +1 -1
- package/dist/api/utilities/index.d.ts +1 -0
- package/dist/api/utilities/index.js +2 -0
- package/dist/api/utilities/index.js.map +1 -1
- package/dist/api/utilities/media-type.d.ts +1 -0
- package/dist/api/utilities/media-type.js +1 -0
- package/dist/api/utilities/media-type.js.map +1 -1
- package/dist/api/utilities/pixel-format.d.ts +3 -0
- package/dist/api/utilities/pixel-format.js +3 -0
- package/dist/api/utilities/pixel-format.js.map +1 -1
- package/dist/api/utilities/sample-format.d.ts +5 -0
- package/dist/api/utilities/sample-format.js +5 -0
- package/dist/api/utilities/sample-format.js.map +1 -1
- package/dist/api/utilities/scheduler.d.ts +21 -52
- package/dist/api/utilities/scheduler.js +20 -58
- package/dist/api/utilities/scheduler.js.map +1 -1
- package/dist/api/utilities/streaming.d.ts +32 -1
- package/dist/api/utilities/streaming.js +32 -1
- package/dist/api/utilities/streaming.js.map +1 -1
- package/dist/api/utilities/timestamp.d.ts +14 -0
- package/dist/api/utilities/timestamp.js +14 -0
- package/dist/api/utilities/timestamp.js.map +1 -1
- package/dist/api/utilities/whisper-model.d.ts +310 -0
- package/dist/api/utilities/whisper-model.js +528 -0
- package/dist/api/utilities/whisper-model.js.map +1 -0
- package/dist/api/whisper.d.ts +324 -0
- package/dist/api/whisper.js +362 -0
- package/dist/api/whisper.js.map +1 -0
- package/dist/constants/constants.d.ts +3 -1
- package/dist/constants/constants.js +1 -0
- package/dist/constants/constants.js.map +1 -1
- package/dist/ffmpeg/index.d.ts +3 -3
- package/dist/ffmpeg/index.js +3 -3
- package/dist/ffmpeg/utils.d.ts +27 -0
- package/dist/ffmpeg/utils.js +28 -16
- package/dist/ffmpeg/utils.js.map +1 -1
- package/dist/lib/binding.d.ts +4 -4
- package/dist/lib/binding.js.map +1 -1
- package/dist/lib/codec-parameters.d.ts +47 -1
- package/dist/lib/codec-parameters.js +55 -0
- package/dist/lib/codec-parameters.js.map +1 -1
- package/dist/lib/fifo.d.ts +416 -0
- package/dist/lib/fifo.js +453 -0
- package/dist/lib/fifo.js.map +1 -0
- package/dist/lib/frame.d.ts +96 -1
- package/dist/lib/frame.js +139 -1
- package/dist/lib/frame.js.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/native-types.d.ts +29 -2
- package/dist/lib/rational.d.ts +18 -0
- package/dist/lib/rational.js +19 -0
- package/dist/lib/rational.js.map +1 -1
- package/dist/lib/types.d.ts +23 -1
- package/install/check.js +2 -2
- package/package.json +30 -20
package/dist/api/decoder.js
CHANGED
|
@@ -50,12 +50,13 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
-
import { AV_CODEC_FLAG_COPY_OPAQUE, AV_FRAME_FLAG_CORRUPT, AV_NOPTS_VALUE, AV_ROUND_UP, AVERROR_EAGAIN, AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO, INT_MAX, } from '../constants/constants.js';
|
|
53
|
+
import { AV_CODEC_FLAG_COPY_OPAQUE, AV_FRAME_FLAG_CORRUPT, AV_NOPTS_VALUE, AV_ROUND_UP, AVERROR_EAGAIN, AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO, EOF, INT_MAX, } from '../constants/constants.js';
|
|
54
54
|
import { CodecContext } from '../lib/codec-context.js';
|
|
55
55
|
import { Codec } from '../lib/codec.js';
|
|
56
56
|
import { Dictionary } from '../lib/dictionary.js';
|
|
57
57
|
import { FFmpegError } from '../lib/error.js';
|
|
58
58
|
import { Frame } from '../lib/frame.js';
|
|
59
|
+
import { Packet } from '../lib/packet.js';
|
|
59
60
|
import { Rational } from '../lib/rational.js';
|
|
60
61
|
import { avGcd, avInvQ, avMulQ, avRescaleDelta, avRescaleQ, avRescaleQRnd } from '../lib/utilities.js';
|
|
61
62
|
import { FRAME_THREAD_QUEUE_SIZE, PACKET_THREAD_QUEUE_SIZE } from './constants.js';
|
|
@@ -432,28 +433,31 @@ export class Decoder {
|
|
|
432
433
|
return this.initialized && !this.isClosed;
|
|
433
434
|
}
|
|
434
435
|
/**
|
|
435
|
-
*
|
|
436
|
+
* Send a packet to the decoder.
|
|
436
437
|
*
|
|
437
|
-
* Sends a packet to the decoder
|
|
438
|
-
*
|
|
438
|
+
* Sends a compressed packet to the decoder for decoding.
|
|
439
|
+
* Does not return decoded frames - use {@link receive} to retrieve frames.
|
|
440
|
+
* A single packet can produce zero, one, or multiple frames depending on codec buffering.
|
|
439
441
|
* Automatically manages decoder state and error recovery.
|
|
440
442
|
*
|
|
441
|
-
* **
|
|
442
|
-
*
|
|
443
|
-
* To receive all frames from a packet, use {@link decodeAll} or {@link frames} instead.
|
|
443
|
+
* **Important**: This method only SENDS the packet to the decoder.
|
|
444
|
+
* You must call {@link receive} separately (potentially multiple times) to get decoded frames.
|
|
444
445
|
*
|
|
445
|
-
* Direct mapping to avcodec_send_packet()
|
|
446
|
+
* Direct mapping to avcodec_send_packet().
|
|
446
447
|
*
|
|
447
|
-
* @param packet - Compressed packet to
|
|
448
|
+
* @param packet - Compressed packet to send to decoder
|
|
448
449
|
*
|
|
449
|
-
* @
|
|
450
|
-
*
|
|
451
|
-
* @throws {FFmpegError} If decoding fails
|
|
450
|
+
* @throws {FFmpegError} If sending packet fails
|
|
452
451
|
*
|
|
453
452
|
* @example
|
|
454
453
|
* ```typescript
|
|
455
|
-
*
|
|
456
|
-
*
|
|
454
|
+
* // Send packet and receive frames
|
|
455
|
+
* await decoder.decode(packet);
|
|
456
|
+
*
|
|
457
|
+
* // Receive all available frames
|
|
458
|
+
* while (true) {
|
|
459
|
+
* const frame = await decoder.receive();
|
|
460
|
+
* if (!frame) break;
|
|
457
461
|
* console.log(`Decoded frame with PTS: ${frame.pts}`);
|
|
458
462
|
* frame.free();
|
|
459
463
|
* }
|
|
@@ -463,8 +467,12 @@ export class Decoder {
|
|
|
463
467
|
* ```typescript
|
|
464
468
|
* for await (const packet of input.packets()) {
|
|
465
469
|
* if (packet.streamIndex === decoder.getStream().index) {
|
|
466
|
-
*
|
|
467
|
-
*
|
|
470
|
+
* // Send packet
|
|
471
|
+
* await decoder.decode(packet);
|
|
472
|
+
*
|
|
473
|
+
* // Receive available frames
|
|
474
|
+
* let frame;
|
|
475
|
+
* while ((frame = await decoder.receive())) {
|
|
468
476
|
* await processFrame(frame);
|
|
469
477
|
* frame.free();
|
|
470
478
|
* }
|
|
@@ -473,106 +481,119 @@ export class Decoder {
|
|
|
473
481
|
* }
|
|
474
482
|
* ```
|
|
475
483
|
*
|
|
476
|
-
* @see {@link
|
|
484
|
+
* @see {@link receive} For receiving decoded frames
|
|
485
|
+
* @see {@link decodeAll} For combined send+receive operation
|
|
477
486
|
* @see {@link frames} For automatic packet iteration
|
|
478
487
|
* @see {@link flush} For end-of-stream handling
|
|
479
488
|
* @see {@link decodeSync} For synchronous version
|
|
480
489
|
*/
|
|
481
490
|
async decode(packet) {
|
|
482
491
|
if (this.isClosed) {
|
|
483
|
-
return
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (packet.streamIndex !== this.stream.index) {
|
|
495
|
+
return;
|
|
484
496
|
}
|
|
485
497
|
// Skip 0-sized packets
|
|
486
498
|
if (packet.size === 0) {
|
|
487
|
-
return
|
|
499
|
+
return;
|
|
488
500
|
}
|
|
489
501
|
// Send packet to decoder
|
|
490
502
|
const sendRet = await this.codecContext.sendPacket(packet);
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
// only one frame at a time. This means the decoder can still have frames
|
|
494
|
-
// from previous packets when we try to send a new packet.
|
|
503
|
+
// EAGAIN during send_packet is a decoder bug (FFmpeg treats this as AVERROR_BUG)
|
|
504
|
+
// We read all decoded frames with receive() until done, so decoder should never be full
|
|
495
505
|
if (sendRet === AVERROR_EAGAIN) {
|
|
496
|
-
|
|
497
|
-
const frame = await this.receive();
|
|
498
|
-
if (frame) {
|
|
499
|
-
return frame;
|
|
500
|
-
}
|
|
501
|
-
// If receive() returned null, this is unexpected - treat as decoder bug
|
|
502
|
-
throw new Error('Decoder returned EAGAIN on send but no frame available - decoder bug');
|
|
506
|
+
throw new Error('Decoder returned EAGAIN on send - this is a decoder bug');
|
|
503
507
|
}
|
|
504
|
-
// Handle
|
|
508
|
+
// Handle send errors
|
|
505
509
|
if (sendRet < 0 && sendRet !== AVERROR_EOF) {
|
|
506
510
|
if (this.options.exitOnError) {
|
|
507
511
|
FFmpegError.throwIfError(sendRet, 'Failed to send packet to decoder');
|
|
508
512
|
}
|
|
513
|
+
// exitOnError=false: Continue to receive loop to drain any buffered frames
|
|
509
514
|
}
|
|
510
|
-
// Try to receive frame
|
|
511
|
-
return await this.receive();
|
|
512
515
|
}
|
|
513
516
|
/**
|
|
514
|
-
*
|
|
517
|
+
* Send a packet to the decoder synchronously.
|
|
515
518
|
* Synchronous version of decode.
|
|
516
519
|
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
+
* Sends a compressed packet to the decoder for decoding.
|
|
521
|
+
* Does not return decoded frames - use {@link receiveSync} to retrieve frames.
|
|
522
|
+
* A single packet can produce zero, one, or multiple frames depending on codec buffering.
|
|
523
|
+
* Automatically manages decoder state and error recovery.
|
|
520
524
|
*
|
|
521
|
-
* **
|
|
522
|
-
*
|
|
523
|
-
* To receive all frames from a packet, use {@link decodeAllSync} or {@link framesSync} instead.
|
|
525
|
+
* **Important**: This method only SENDS the packet to the decoder.
|
|
526
|
+
* You must call {@link receiveSync} separately (potentially multiple times) to get decoded frames.
|
|
524
527
|
*
|
|
525
|
-
*
|
|
528
|
+
* Direct mapping to avcodec_send_packet().
|
|
526
529
|
*
|
|
527
|
-
* @
|
|
530
|
+
* @param packet - Compressed packet to send to decoder
|
|
528
531
|
*
|
|
529
|
-
* @throws {FFmpegError} If
|
|
532
|
+
* @throws {FFmpegError} If sending packet fails
|
|
530
533
|
*
|
|
531
534
|
* @example
|
|
532
535
|
* ```typescript
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
+
* // Send packet and receive frames
|
|
537
|
+
* await decoder.decode(packet);
|
|
538
|
+
*
|
|
539
|
+
* // Receive all available frames
|
|
540
|
+
* while (true) {
|
|
541
|
+
* const frame = await decoder.receive();
|
|
542
|
+
* if (!frame) break;
|
|
543
|
+
* console.log(`Decoded frame with PTS: ${frame.pts}`);
|
|
544
|
+
* frame.free();
|
|
545
|
+
* }
|
|
546
|
+
* ```
|
|
547
|
+
*
|
|
548
|
+
* @example
|
|
549
|
+
* ```typescript
|
|
550
|
+
* for await (const packet of input.packets()) {
|
|
551
|
+
* if (packet.streamIndex === decoder.getStream().index) {
|
|
552
|
+
* // Send packet
|
|
553
|
+
* await decoder.decode(packet);
|
|
554
|
+
*
|
|
555
|
+
* // Receive available frames
|
|
556
|
+
* let frame;
|
|
557
|
+
* while ((frame = await decoder.receive())) {
|
|
558
|
+
* await processFrame(frame);
|
|
559
|
+
* frame.free();
|
|
560
|
+
* }
|
|
561
|
+
* }
|
|
562
|
+
* packet.free();
|
|
536
563
|
* }
|
|
537
564
|
* ```
|
|
538
565
|
*
|
|
539
|
-
* @see {@link
|
|
566
|
+
* @see {@link receiveSync} For receiving decoded frames
|
|
567
|
+
* @see {@link decodeAllSync} For combined send+receive operation
|
|
540
568
|
* @see {@link framesSync} For automatic packet iteration
|
|
541
569
|
* @see {@link flushSync} For end-of-stream handling
|
|
542
570
|
* @see {@link decode} For async version
|
|
543
571
|
*/
|
|
544
572
|
decodeSync(packet) {
|
|
545
573
|
if (this.isClosed) {
|
|
546
|
-
return
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (packet.streamIndex !== this.stream.index) {
|
|
577
|
+
return;
|
|
547
578
|
}
|
|
548
579
|
// Skip 0-sized packets
|
|
549
580
|
if (packet.size === 0) {
|
|
550
|
-
return
|
|
581
|
+
return;
|
|
551
582
|
}
|
|
552
583
|
// Send packet to decoder
|
|
553
584
|
const sendRet = this.codecContext.sendPacketSync(packet);
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
// only one frame at a time. This means the decoder can still have frames
|
|
557
|
-
// from previous packets when we try to send a new packet.
|
|
585
|
+
// EAGAIN during send_packet is a decoder bug (FFmpeg treats this as AVERROR_BUG)
|
|
586
|
+
// We read all decoded frames with receive() until done, so decoder should never be full
|
|
558
587
|
if (sendRet === AVERROR_EAGAIN) {
|
|
559
|
-
|
|
560
|
-
const frame = this.receiveSync();
|
|
561
|
-
if (frame) {
|
|
562
|
-
return frame;
|
|
563
|
-
}
|
|
564
|
-
// If receive() returned null, this is unexpected - treat as decoder bug
|
|
565
|
-
throw new Error('Decoder returned EAGAIN on send but no frame available - decoder bug');
|
|
588
|
+
throw new Error('Decoder returned EAGAIN on send - this is a decoder bug');
|
|
566
589
|
}
|
|
567
|
-
// Handle
|
|
590
|
+
// Handle send errors
|
|
568
591
|
if (sendRet < 0 && sendRet !== AVERROR_EOF) {
|
|
569
592
|
if (this.options.exitOnError) {
|
|
570
593
|
FFmpegError.throwIfError(sendRet, 'Failed to send packet to decoder');
|
|
571
594
|
}
|
|
572
|
-
// exitOnError=false: Continue to receive
|
|
595
|
+
// exitOnError=false: Continue to receive loop to drain any buffered frames
|
|
573
596
|
}
|
|
574
|
-
// Try to receive frame
|
|
575
|
-
return this.receiveSync();
|
|
576
597
|
}
|
|
577
598
|
/**
|
|
578
599
|
* Decode a packet to frames.
|
|
@@ -602,12 +623,10 @@ export class Decoder {
|
|
|
602
623
|
* @example
|
|
603
624
|
* ```typescript
|
|
604
625
|
* for await (const packet of input.packets()) {
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
609
|
-
* frame.free();
|
|
610
|
-
* }
|
|
626
|
+
* const frames = await decoder.decodeAll(packet);
|
|
627
|
+
* for (const frame of frames) {
|
|
628
|
+
* await processFrame(frame);
|
|
629
|
+
* frame.free();
|
|
611
630
|
* }
|
|
612
631
|
* packet.free();
|
|
613
632
|
* }
|
|
@@ -619,29 +638,14 @@ export class Decoder {
|
|
|
619
638
|
* @see {@link decodeAllSync} For synchronous version
|
|
620
639
|
*/
|
|
621
640
|
async decodeAll(packet) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
// Skip 0-sized packets
|
|
626
|
-
if (packet.size === 0) {
|
|
627
|
-
return [];
|
|
628
|
-
}
|
|
629
|
-
// Send packet to decoder
|
|
630
|
-
const sendRet = await this.codecContext.sendPacket(packet);
|
|
631
|
-
// EAGAIN during send_packet is a decoder bug (FFmpeg treats this as AVERROR_BUG)
|
|
632
|
-
// We read all decoded frames with receive() until done, so decoder should never be full
|
|
633
|
-
if (sendRet === AVERROR_EAGAIN) {
|
|
634
|
-
throw new Error('Decoder returned EAGAIN on send - this is a decoder bug');
|
|
641
|
+
const frames = [];
|
|
642
|
+
if (packet) {
|
|
643
|
+
await this.decode(packet);
|
|
635
644
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if (this.options.exitOnError) {
|
|
639
|
-
FFmpegError.throwIfError(sendRet, 'Failed to send packet to decoder');
|
|
640
|
-
}
|
|
641
|
-
// exitOnError=false: Continue to receive loop to drain any buffered frames
|
|
645
|
+
else {
|
|
646
|
+
await this.flush();
|
|
642
647
|
}
|
|
643
648
|
// Receive all available frames
|
|
644
|
-
const frames = [];
|
|
645
649
|
while (true) {
|
|
646
650
|
const remaining = await this.receive();
|
|
647
651
|
if (!remaining)
|
|
@@ -671,6 +675,17 @@ export class Decoder {
|
|
|
671
675
|
* console.log(`Decoded: ${frame.width}x${frame.height}`);
|
|
672
676
|
* frame.free();
|
|
673
677
|
* }
|
|
678
|
+
*
|
|
679
|
+
* @example
|
|
680
|
+
* ```typescript
|
|
681
|
+
* for (const packet of input.packetsSync()) {
|
|
682
|
+
* const frames = await decoder.decodeAllSync(packet);
|
|
683
|
+
* for (const frame of frames) {
|
|
684
|
+
* processFrame(frame);
|
|
685
|
+
* frame.free();
|
|
686
|
+
* }
|
|
687
|
+
* packet.free();
|
|
688
|
+
* }
|
|
674
689
|
* ```
|
|
675
690
|
*
|
|
676
691
|
* @see {@link decodeSync} For single packet decoding
|
|
@@ -679,29 +694,14 @@ export class Decoder {
|
|
|
679
694
|
* @see {@link decodeAll} For async version
|
|
680
695
|
*/
|
|
681
696
|
decodeAllSync(packet) {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
// Skip 0-sized packets
|
|
686
|
-
if (packet.size === 0) {
|
|
687
|
-
return [];
|
|
688
|
-
}
|
|
689
|
-
// Send packet to decoder
|
|
690
|
-
const sendRet = this.codecContext.sendPacketSync(packet);
|
|
691
|
-
// EAGAIN during send_packet is a decoder bug (FFmpeg treats this as AVERROR_BUG)
|
|
692
|
-
// We read all decoded frames with receive() until done, so decoder should never be full
|
|
693
|
-
if (sendRet === AVERROR_EAGAIN) {
|
|
694
|
-
throw new Error('Decoder returned EAGAIN on send - this is a decoder bug');
|
|
697
|
+
const frames = [];
|
|
698
|
+
if (packet) {
|
|
699
|
+
this.decodeSync(packet);
|
|
695
700
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
if (this.options.exitOnError) {
|
|
699
|
-
FFmpegError.throwIfError(sendRet, 'Failed to send packet to decoder');
|
|
700
|
-
}
|
|
701
|
-
// exitOnError=false: Continue to receive loop to drain any buffered frames
|
|
701
|
+
else {
|
|
702
|
+
this.flushSync();
|
|
702
703
|
}
|
|
703
704
|
// Receive all available frames
|
|
704
|
-
const frames = [];
|
|
705
705
|
while (true) {
|
|
706
706
|
const remaining = this.receiveSync();
|
|
707
707
|
if (!remaining)
|
|
@@ -714,13 +714,17 @@ export class Decoder {
|
|
|
714
714
|
* Decode packet stream to frame stream.
|
|
715
715
|
*
|
|
716
716
|
* High-level async generator for complete decoding pipeline.
|
|
717
|
-
*
|
|
718
|
-
* and flushes buffered frames at end.
|
|
717
|
+
* Decoder is only flushed when EOF (null) signal is explicitly received.
|
|
719
718
|
* Primary interface for stream-based decoding.
|
|
720
719
|
*
|
|
721
|
-
*
|
|
720
|
+
* **EOF Handling:**
|
|
721
|
+
* - Send null to flush decoder and get remaining buffered frames
|
|
722
|
+
* - Generator yields null after flushing when null is received
|
|
723
|
+
* - No automatic flushing - decoder stays open until EOF or close()
|
|
724
|
+
*
|
|
725
|
+
* @param packets - Async iterable of packets, single packet, or null to flush
|
|
722
726
|
*
|
|
723
|
-
* @yields {Frame} Decoded frames
|
|
727
|
+
* @yields {Frame | null} Decoded frames, followed by null when explicitly flushed
|
|
724
728
|
*
|
|
725
729
|
* @throws {Error} If decoder is closed
|
|
726
730
|
*
|
|
@@ -728,10 +732,15 @@ export class Decoder {
|
|
|
728
732
|
*
|
|
729
733
|
* @example
|
|
730
734
|
* ```typescript
|
|
735
|
+
* // Stream of packets with automatic EOF propagation
|
|
731
736
|
* await using input = await Demuxer.open('video.mp4');
|
|
732
737
|
* using decoder = await Decoder.create(input.video());
|
|
733
738
|
*
|
|
734
739
|
* for await (const frame of decoder.frames(input.packets())) {
|
|
740
|
+
* if (frame === null) {
|
|
741
|
+
* console.log('Decoding complete');
|
|
742
|
+
* break;
|
|
743
|
+
* }
|
|
735
744
|
* console.log(`Frame: ${frame.width}x${frame.height}`);
|
|
736
745
|
* frame.free();
|
|
737
746
|
* }
|
|
@@ -739,26 +748,30 @@ export class Decoder {
|
|
|
739
748
|
*
|
|
740
749
|
* @example
|
|
741
750
|
* ```typescript
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
* await
|
|
745
|
-
*
|
|
746
|
-
*
|
|
751
|
+
* // Single packet (no automatic flush)
|
|
752
|
+
* for await (const frame of decoder.frames(singlePacket)) {
|
|
753
|
+
* await encoder.encode(frame);
|
|
754
|
+
* frame.free();
|
|
755
|
+
* }
|
|
756
|
+
* // Decoder still has buffered frames - send null to flush
|
|
757
|
+
* for await (const frame of decoder.frames(null)) {
|
|
758
|
+
* if (frame === null) break;
|
|
759
|
+
* await encoder.encode(frame);
|
|
747
760
|
* frame.free();
|
|
748
761
|
* }
|
|
749
762
|
* ```
|
|
750
763
|
*
|
|
751
764
|
* @example
|
|
752
765
|
* ```typescript
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
766
|
+
* // Explicit flush with EOF
|
|
767
|
+
* for await (const frame of decoder.frames(null)) {
|
|
768
|
+
* if (frame === null) {
|
|
769
|
+
* console.log('All buffered frames flushed');
|
|
770
|
+
* break;
|
|
771
|
+
* }
|
|
772
|
+
* console.log('Buffered frame:', frame.pts);
|
|
773
|
+
* frame.free();
|
|
774
|
+
* }
|
|
762
775
|
* ```
|
|
763
776
|
*
|
|
764
777
|
* @see {@link decode} For single packet decoding
|
|
@@ -766,54 +779,39 @@ export class Decoder {
|
|
|
766
779
|
* @see {@link framesSync} For sync version
|
|
767
780
|
*/
|
|
768
781
|
async *frames(packets) {
|
|
782
|
+
const self = this;
|
|
783
|
+
const processPacket = async function* (packet) {
|
|
784
|
+
await self.decode(packet);
|
|
785
|
+
while (true) {
|
|
786
|
+
const frame = await self.receive();
|
|
787
|
+
if (!frame)
|
|
788
|
+
break;
|
|
789
|
+
yield frame;
|
|
790
|
+
}
|
|
791
|
+
}.bind(this);
|
|
792
|
+
const finalize = async function* () {
|
|
793
|
+
for await (const remaining of self.flushFrames()) {
|
|
794
|
+
yield remaining;
|
|
795
|
+
}
|
|
796
|
+
yield null;
|
|
797
|
+
}.bind(this);
|
|
798
|
+
if (packets === null) {
|
|
799
|
+
yield* finalize();
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
if (packets instanceof Packet) {
|
|
803
|
+
yield* processPacket(packets);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
769
806
|
for await (const packet_1 of packets) {
|
|
770
807
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
771
808
|
try {
|
|
772
809
|
const packet = __addDisposableResource(env_1, packet_1, false);
|
|
773
|
-
// Handle EOF signal
|
|
774
810
|
if (packet === null) {
|
|
775
|
-
|
|
776
|
-
await this.flush();
|
|
777
|
-
while (true) {
|
|
778
|
-
const remaining = await this.receive();
|
|
779
|
-
if (!remaining)
|
|
780
|
-
break;
|
|
781
|
-
yield remaining;
|
|
782
|
-
}
|
|
783
|
-
// Signal EOF and stop processing
|
|
784
|
-
yield null;
|
|
811
|
+
yield* finalize();
|
|
785
812
|
return;
|
|
786
813
|
}
|
|
787
|
-
|
|
788
|
-
if (packet.streamIndex === this.stream.index) {
|
|
789
|
-
if (this.isClosed) {
|
|
790
|
-
break;
|
|
791
|
-
}
|
|
792
|
-
// Skip 0-sized packets
|
|
793
|
-
if (packet.size === 0) {
|
|
794
|
-
continue;
|
|
795
|
-
}
|
|
796
|
-
// Send packet to decoder
|
|
797
|
-
const sendRet = await this.codecContext.sendPacket(packet);
|
|
798
|
-
// EAGAIN during send_packet is a decoder bug
|
|
799
|
-
// We read all decoded frames with receive() until done, so decoder should never be full
|
|
800
|
-
if (sendRet === AVERROR_EAGAIN) {
|
|
801
|
-
throw new Error('Decoder returned EAGAIN but no frame available');
|
|
802
|
-
}
|
|
803
|
-
if (sendRet < 0 && sendRet !== AVERROR_EOF) {
|
|
804
|
-
if (this.options.exitOnError) {
|
|
805
|
-
FFmpegError.throwIfError(sendRet, 'Failed to send packet');
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
// Receive ALL available frames immediately
|
|
809
|
-
// This ensures frames are yielded ASAP without latency
|
|
810
|
-
while (true) {
|
|
811
|
-
const frame = await this.receive();
|
|
812
|
-
if (!frame)
|
|
813
|
-
break; // EAGAIN or EOF
|
|
814
|
-
yield frame;
|
|
815
|
-
}
|
|
816
|
-
}
|
|
814
|
+
yield* processPacket(packet);
|
|
817
815
|
}
|
|
818
816
|
catch (e_1) {
|
|
819
817
|
env_1.error = e_1;
|
|
@@ -823,28 +821,23 @@ export class Decoder {
|
|
|
823
821
|
__disposeResources(env_1);
|
|
824
822
|
}
|
|
825
823
|
}
|
|
826
|
-
// Flush decoder after all packets (fallback if no null was sent)
|
|
827
|
-
await this.flush();
|
|
828
|
-
while (true) {
|
|
829
|
-
const remaining = await this.receive();
|
|
830
|
-
if (!remaining)
|
|
831
|
-
break;
|
|
832
|
-
yield remaining;
|
|
833
|
-
}
|
|
834
|
-
// Signal EOF
|
|
835
|
-
yield null;
|
|
836
824
|
}
|
|
837
825
|
/**
|
|
838
826
|
* Decode packet stream to frame stream synchronously.
|
|
839
827
|
* Synchronous version of frames.
|
|
840
828
|
*
|
|
841
|
-
* High-level
|
|
842
|
-
*
|
|
843
|
-
*
|
|
829
|
+
* High-level async generator for complete decoding pipeline.
|
|
830
|
+
* Decoder is only flushed when EOF (null) signal is explicitly received.
|
|
831
|
+
* Primary interface for stream-based decoding.
|
|
832
|
+
*
|
|
833
|
+
* **EOF Handling:**
|
|
834
|
+
* - Send null to flush decoder and get remaining buffered frames
|
|
835
|
+
* - Generator yields null after flushing when null is received
|
|
836
|
+
* - No automatic flushing - decoder stays open until EOF or close()
|
|
844
837
|
*
|
|
845
|
-
* @param packets - Iterable of packets
|
|
838
|
+
* @param packets - Iterable of packets, single packet, or null to flush
|
|
846
839
|
*
|
|
847
|
-
* @yields {Frame} Decoded frames
|
|
840
|
+
* @yields {Frame | null} Decoded frames, followed by null when explicitly flushed
|
|
848
841
|
*
|
|
849
842
|
* @throws {Error} If decoder is closed
|
|
850
843
|
*
|
|
@@ -852,65 +845,82 @@ export class Decoder {
|
|
|
852
845
|
*
|
|
853
846
|
* @example
|
|
854
847
|
* ```typescript
|
|
855
|
-
*
|
|
848
|
+
* // Stream of packets with automatic EOF propagation
|
|
849
|
+
* await using input = await Demuxer.open('video.mp4');
|
|
850
|
+
* using decoder = await Decoder.create(input.video());
|
|
851
|
+
*
|
|
852
|
+
* for (const frame of decoder.framesSync(input.packetsSync())) {
|
|
853
|
+
* if (frame === null) {
|
|
854
|
+
* console.log('Decoding complete');
|
|
855
|
+
* break;
|
|
856
|
+
* }
|
|
856
857
|
* console.log(`Frame: ${frame.width}x${frame.height}`);
|
|
857
|
-
*
|
|
858
|
+
* frame.free();
|
|
858
859
|
* }
|
|
859
860
|
* ```
|
|
860
861
|
*
|
|
861
|
-
* @
|
|
862
|
-
*
|
|
863
|
-
*
|
|
862
|
+
* @example
|
|
863
|
+
* ```typescript
|
|
864
|
+
* // Single packet (no automatic flush)
|
|
865
|
+
* for (const frame of decoder.framesSync(singlePacket)) {
|
|
866
|
+
* encoder.encodeSync(frame);
|
|
867
|
+
* frame.free();
|
|
868
|
+
* }
|
|
869
|
+
* // Decoder still has buffered frames - send null to flush
|
|
870
|
+
* for (const frame of decoder.framesSync(null)) {
|
|
871
|
+
* if (frame === null) break;
|
|
872
|
+
* encoder.encodeSync(frame);
|
|
873
|
+
* frame.free();
|
|
874
|
+
* }
|
|
875
|
+
* ```
|
|
876
|
+
*
|
|
877
|
+
* @example
|
|
878
|
+
* ```typescript
|
|
879
|
+
* // Explicit flush with EOF
|
|
880
|
+
* for (const frame of decoder.framesSync(null)) {
|
|
881
|
+
* if (frame === null) {
|
|
882
|
+
* console.log('All buffered frames flushed');
|
|
883
|
+
* break;
|
|
884
|
+
* }
|
|
885
|
+
* console.log('Buffered frame:', frame.pts);
|
|
886
|
+
* frame.free();
|
|
887
|
+
* }
|
|
888
|
+
* ```
|
|
864
889
|
*/
|
|
865
890
|
*framesSync(packets) {
|
|
891
|
+
const self = this;
|
|
892
|
+
const processPacket = function* (packet) {
|
|
893
|
+
self.decodeSync(packet);
|
|
894
|
+
while (true) {
|
|
895
|
+
const frame = self.receiveSync();
|
|
896
|
+
if (!frame)
|
|
897
|
+
break;
|
|
898
|
+
yield frame;
|
|
899
|
+
}
|
|
900
|
+
}.bind(this);
|
|
901
|
+
const finalize = function* () {
|
|
902
|
+
for (const remaining of self.flushFramesSync()) {
|
|
903
|
+
yield remaining;
|
|
904
|
+
}
|
|
905
|
+
yield null;
|
|
906
|
+
}.bind(this);
|
|
907
|
+
if (packets === null) {
|
|
908
|
+
yield* finalize();
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
if (packets instanceof Packet) {
|
|
912
|
+
yield* processPacket(packets);
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
866
915
|
for (const packet_2 of packets) {
|
|
867
916
|
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
868
917
|
try {
|
|
869
918
|
const packet = __addDisposableResource(env_2, packet_2, false);
|
|
870
|
-
// Handle EOF signal
|
|
871
919
|
if (packet === null) {
|
|
872
|
-
|
|
873
|
-
this.flushSync();
|
|
874
|
-
while (true) {
|
|
875
|
-
const remaining = this.receiveSync();
|
|
876
|
-
if (!remaining)
|
|
877
|
-
break;
|
|
878
|
-
yield remaining;
|
|
879
|
-
}
|
|
880
|
-
// Signal EOF and stop processing
|
|
881
|
-
yield null;
|
|
920
|
+
yield* finalize();
|
|
882
921
|
return;
|
|
883
922
|
}
|
|
884
|
-
|
|
885
|
-
if (packet.streamIndex === this.stream.index) {
|
|
886
|
-
if (this.isClosed) {
|
|
887
|
-
break;
|
|
888
|
-
}
|
|
889
|
-
// Skip 0-sized packets
|
|
890
|
-
if (packet.size === 0) {
|
|
891
|
-
continue;
|
|
892
|
-
}
|
|
893
|
-
// Send packet to decoder
|
|
894
|
-
const sendRet = this.codecContext.sendPacketSync(packet);
|
|
895
|
-
// EAGAIN during send_packet is a decoder bug
|
|
896
|
-
// We read all decoded frames with receive() until done, so decoder should never be full
|
|
897
|
-
if (sendRet === AVERROR_EAGAIN) {
|
|
898
|
-
throw new Error('Decoder returned EAGAIN but no frame available');
|
|
899
|
-
}
|
|
900
|
-
if (sendRet < 0 && sendRet !== AVERROR_EOF) {
|
|
901
|
-
if (this.options.exitOnError) {
|
|
902
|
-
FFmpegError.throwIfError(sendRet, 'Failed to send packet');
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
// Receive ALL available frames immediately
|
|
906
|
-
// This ensures frames are yielded ASAP without latency
|
|
907
|
-
while (true) {
|
|
908
|
-
const frame = this.receiveSync();
|
|
909
|
-
if (!frame)
|
|
910
|
-
break; // EAGAIN or EOF
|
|
911
|
-
yield frame;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
923
|
+
yield* processPacket(packet);
|
|
914
924
|
}
|
|
915
925
|
catch (e_2) {
|
|
916
926
|
env_2.error = e_2;
|
|
@@ -920,16 +930,6 @@ export class Decoder {
|
|
|
920
930
|
__disposeResources(env_2);
|
|
921
931
|
}
|
|
922
932
|
}
|
|
923
|
-
// Flush decoder after all packets (fallback if no null was sent)
|
|
924
|
-
this.flushSync();
|
|
925
|
-
while (true) {
|
|
926
|
-
const remaining = this.receiveSync();
|
|
927
|
-
if (!remaining)
|
|
928
|
-
break;
|
|
929
|
-
yield remaining;
|
|
930
|
-
}
|
|
931
|
-
// Signal EOF
|
|
932
|
-
yield null;
|
|
933
933
|
}
|
|
934
934
|
/**
|
|
935
935
|
* Flush decoder and signal end-of-stream.
|
|
@@ -1080,79 +1080,92 @@ export class Decoder {
|
|
|
1080
1080
|
* Gets decoded frames from the codec's internal buffer.
|
|
1081
1081
|
* Handles frame cloning and error checking.
|
|
1082
1082
|
* Hardware frames include hw_frames_ctx reference.
|
|
1083
|
-
* Call repeatedly
|
|
1083
|
+
* Call repeatedly to drain all buffered frames.
|
|
1084
|
+
*
|
|
1085
|
+
* **Return Values:**
|
|
1086
|
+
* - `Frame` - Successfully decoded frame
|
|
1087
|
+
* - `null` - No frame available (AVERROR_EAGAIN), send more packets
|
|
1088
|
+
* - `undefined` - End of stream reached (AVERROR_EOF), decoder flushed
|
|
1084
1089
|
*
|
|
1085
1090
|
* Direct mapping to avcodec_receive_frame().
|
|
1086
1091
|
*
|
|
1087
|
-
* @returns
|
|
1092
|
+
* @returns Decoded frame, null (need more data), or undefined (end of stream)
|
|
1088
1093
|
*
|
|
1089
1094
|
* @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
|
|
1090
1095
|
*
|
|
1091
1096
|
* @example
|
|
1092
1097
|
* ```typescript
|
|
1093
1098
|
* const frame = await decoder.receive();
|
|
1094
|
-
* if (frame) {
|
|
1099
|
+
* if (frame === EOF) {
|
|
1100
|
+
* console.log('Decoder flushed, no more frames');
|
|
1101
|
+
* } else if (frame) {
|
|
1095
1102
|
* console.log('Got decoded frame');
|
|
1096
1103
|
* frame.free();
|
|
1104
|
+
* } else {
|
|
1105
|
+
* console.log('Need more packets');
|
|
1097
1106
|
* }
|
|
1098
1107
|
* ```
|
|
1099
1108
|
*
|
|
1100
1109
|
* @example
|
|
1101
1110
|
* ```typescript
|
|
1102
|
-
* // Drain all buffered frames
|
|
1111
|
+
* // Drain all buffered frames (stop on null or EOF)
|
|
1103
1112
|
* let frame;
|
|
1104
|
-
* while ((frame = await decoder.receive()) !==
|
|
1113
|
+
* while ((frame = await decoder.receive()) && frame !== EOF) {
|
|
1105
1114
|
* console.log(`Frame PTS: ${frame.pts}`);
|
|
1106
1115
|
* frame.free();
|
|
1107
1116
|
* }
|
|
1108
1117
|
* ```
|
|
1109
1118
|
*
|
|
1110
|
-
* @see {@link decode} For sending packets
|
|
1119
|
+
* @see {@link decode} For sending packets
|
|
1111
1120
|
* @see {@link flush} For signaling end-of-stream
|
|
1112
1121
|
* @see {@link receiveSync} For synchronous version
|
|
1122
|
+
* @see {@link EOF} For end-of-stream signal
|
|
1113
1123
|
*/
|
|
1114
1124
|
async receive() {
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
// exitOnError=false: skip corrupt frame, continue to next
|
|
1129
|
-
continue;
|
|
1130
|
-
}
|
|
1131
|
-
// Handles PTS assignment, duration estimation, and frame tracking
|
|
1132
|
-
if (this.codecContext.codecType === AVMEDIA_TYPE_VIDEO) {
|
|
1133
|
-
this.processVideoFrame(this.frame);
|
|
1134
|
-
}
|
|
1135
|
-
// Handles timestamp extrapolation, sample rate changes, and duration calculation
|
|
1136
|
-
if (this.codecContext.codecType === AVMEDIA_TYPE_AUDIO) {
|
|
1137
|
-
this.processAudioFrame(this.frame);
|
|
1125
|
+
if (this.isClosed) {
|
|
1126
|
+
return EOF;
|
|
1127
|
+
}
|
|
1128
|
+
// Clear previous frame data
|
|
1129
|
+
this.frame.unref();
|
|
1130
|
+
const ret = await this.codecContext.receiveFrame(this.frame);
|
|
1131
|
+
if (ret === 0) {
|
|
1132
|
+
// Set frame time_base to decoder's packet timebase
|
|
1133
|
+
this.frame.timeBase = this.codecContext.pktTimebase;
|
|
1134
|
+
// Check for corrupt frame
|
|
1135
|
+
if (this.frame.decodeErrorFlags || this.frame.hasFlags(AV_FRAME_FLAG_CORRUPT)) {
|
|
1136
|
+
if (this.options.exitOnError) {
|
|
1137
|
+
throw new Error('Corrupt decoded frame detected');
|
|
1138
1138
|
}
|
|
1139
|
-
//
|
|
1140
|
-
return this.frame.clone();
|
|
1141
|
-
}
|
|
1142
|
-
else if (ret === AVERROR_EAGAIN || ret === AVERROR_EOF) {
|
|
1143
|
-
// Need more data or end of stream
|
|
1139
|
+
// exitOnError=false: skip corrupt frame
|
|
1144
1140
|
return null;
|
|
1145
1141
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
FFmpegError.throwIfError(ret, 'Failed to receive frame');
|
|
1150
|
-
}
|
|
1151
|
-
// exitOnError=false: continue to next frame
|
|
1152
|
-
continue;
|
|
1142
|
+
// Handles PTS assignment, duration estimation, and frame tracking
|
|
1143
|
+
if (this.codecContext.codecType === AVMEDIA_TYPE_VIDEO) {
|
|
1144
|
+
this.processVideoFrame(this.frame);
|
|
1153
1145
|
}
|
|
1146
|
+
// Handles timestamp extrapolation, sample rate changes, and duration calculation
|
|
1147
|
+
if (this.codecContext.codecType === AVMEDIA_TYPE_AUDIO) {
|
|
1148
|
+
this.processAudioFrame(this.frame);
|
|
1149
|
+
}
|
|
1150
|
+
// Got a frame, clone it for the user
|
|
1151
|
+
return this.frame.clone();
|
|
1152
|
+
}
|
|
1153
|
+
else if (ret === AVERROR_EAGAIN) {
|
|
1154
|
+
// Need more data
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
else if (ret === AVERROR_EOF) {
|
|
1158
|
+
// End of stream
|
|
1159
|
+
return EOF;
|
|
1160
|
+
}
|
|
1161
|
+
else {
|
|
1162
|
+
// Error during receive
|
|
1163
|
+
if (this.options.exitOnError) {
|
|
1164
|
+
FFmpegError.throwIfError(ret, 'Failed to receive frame');
|
|
1165
|
+
}
|
|
1166
|
+
// exitOnError=false: return null, caller can retry if desired
|
|
1167
|
+
return null;
|
|
1154
1168
|
}
|
|
1155
|
-
return null;
|
|
1156
1169
|
}
|
|
1157
1170
|
/**
|
|
1158
1171
|
* Receive frame from decoder synchronously.
|
|
@@ -1161,81 +1174,94 @@ export class Decoder {
|
|
|
1161
1174
|
* Gets decoded frames from the codec's internal buffer.
|
|
1162
1175
|
* Handles frame cloning and error checking.
|
|
1163
1176
|
* Hardware frames include hw_frames_ctx reference.
|
|
1164
|
-
* Call repeatedly
|
|
1177
|
+
* Call repeatedly to drain all buffered frames.
|
|
1178
|
+
*
|
|
1179
|
+
* **Return Values:**
|
|
1180
|
+
* - `Frame` - Successfully decoded frame
|
|
1181
|
+
* - `null` - No frame available (AVERROR_EAGAIN), send more packets
|
|
1182
|
+
* - `undefined` - End of stream reached (AVERROR_EOF), decoder flushed
|
|
1165
1183
|
*
|
|
1166
1184
|
* Direct mapping to avcodec_receive_frame().
|
|
1167
1185
|
*
|
|
1168
|
-
* @returns
|
|
1186
|
+
* @returns Decoded frame, null (need more data), or undefined (end of stream)
|
|
1169
1187
|
*
|
|
1170
1188
|
* @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
|
|
1171
1189
|
*
|
|
1172
1190
|
* @example
|
|
1173
1191
|
* ```typescript
|
|
1174
1192
|
* const frame = decoder.receiveSync();
|
|
1175
|
-
* if (frame) {
|
|
1193
|
+
* if (frame === EOF) {
|
|
1194
|
+
* console.log('Decoder flushed, no more frames');
|
|
1195
|
+
* } else if (frame) {
|
|
1176
1196
|
* console.log('Got decoded frame');
|
|
1177
1197
|
* frame.free();
|
|
1198
|
+
* } else {
|
|
1199
|
+
* console.log('Need more packets');
|
|
1178
1200
|
* }
|
|
1179
1201
|
* ```
|
|
1180
1202
|
*
|
|
1181
1203
|
* @example
|
|
1182
1204
|
* ```typescript
|
|
1183
|
-
* // Drain all buffered frames
|
|
1205
|
+
* // Drain all buffered frames (stop on null or EOF)
|
|
1184
1206
|
* let frame;
|
|
1185
|
-
* while ((frame = decoder.receiveSync()) !==
|
|
1207
|
+
* while ((frame = decoder.receiveSync()) && frame !== EOF) {
|
|
1186
1208
|
* console.log(`Frame PTS: ${frame.pts}`);
|
|
1187
1209
|
* frame.free();
|
|
1188
1210
|
* }
|
|
1189
1211
|
* ```
|
|
1190
1212
|
*
|
|
1191
|
-
* @see {@link decodeSync} For sending packets
|
|
1213
|
+
* @see {@link decodeSync} For sending packets
|
|
1192
1214
|
* @see {@link flushSync} For signaling end-of-stream
|
|
1193
1215
|
* @see {@link receive} For async version
|
|
1216
|
+
* @see {@link EOF} For end-of-stream signal
|
|
1194
1217
|
*/
|
|
1195
1218
|
receiveSync() {
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
// exitOnError=false: skip corrupt frame, continue to next
|
|
1210
|
-
continue;
|
|
1211
|
-
}
|
|
1212
|
-
// Process video frame
|
|
1213
|
-
// Handles PTS assignment, duration estimation, and frame tracking
|
|
1214
|
-
if (this.codecContext.codecType === AVMEDIA_TYPE_VIDEO) {
|
|
1215
|
-
this.processVideoFrame(this.frame);
|
|
1216
|
-
}
|
|
1217
|
-
// Process audio frame
|
|
1218
|
-
// Handles timestamp extrapolation, sample rate changes, and duration calculation
|
|
1219
|
-
if (this.codecContext.codecType === AVMEDIA_TYPE_AUDIO) {
|
|
1220
|
-
this.processAudioFrame(this.frame);
|
|
1219
|
+
if (this.isClosed) {
|
|
1220
|
+
return EOF;
|
|
1221
|
+
}
|
|
1222
|
+
// Clear previous frame data
|
|
1223
|
+
this.frame.unref();
|
|
1224
|
+
const ret = this.codecContext.receiveFrameSync(this.frame);
|
|
1225
|
+
if (ret === 0) {
|
|
1226
|
+
// Set frame time_base to decoder's packet timebase
|
|
1227
|
+
this.frame.timeBase = this.codecContext.pktTimebase;
|
|
1228
|
+
// Check for corrupt frame
|
|
1229
|
+
if (this.frame.decodeErrorFlags || this.frame.hasFlags(AV_FRAME_FLAG_CORRUPT)) {
|
|
1230
|
+
if (this.options.exitOnError) {
|
|
1231
|
+
throw new Error('Corrupt decoded frame detected');
|
|
1221
1232
|
}
|
|
1222
|
-
//
|
|
1223
|
-
return this.frame.clone();
|
|
1224
|
-
}
|
|
1225
|
-
else if (ret === AVERROR_EAGAIN || ret === AVERROR_EOF) {
|
|
1226
|
-
// Need more data or end of stream
|
|
1233
|
+
// exitOnError=false: skip corrupt frame
|
|
1227
1234
|
return null;
|
|
1228
1235
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1234
|
-
// exitOnError=false: continue to next frame
|
|
1235
|
-
continue;
|
|
1236
|
+
// Process video frame
|
|
1237
|
+
// Handles PTS assignment, duration estimation, and frame tracking
|
|
1238
|
+
if (this.codecContext.codecType === AVMEDIA_TYPE_VIDEO) {
|
|
1239
|
+
this.processVideoFrame(this.frame);
|
|
1236
1240
|
}
|
|
1241
|
+
// Process audio frame
|
|
1242
|
+
// Handles timestamp extrapolation, sample rate changes, and duration calculation
|
|
1243
|
+
if (this.codecContext.codecType === AVMEDIA_TYPE_AUDIO) {
|
|
1244
|
+
this.processAudioFrame(this.frame);
|
|
1245
|
+
}
|
|
1246
|
+
// Got a frame, clone it for the user
|
|
1247
|
+
return this.frame.clone();
|
|
1248
|
+
}
|
|
1249
|
+
else if (ret === AVERROR_EAGAIN) {
|
|
1250
|
+
// Need more data
|
|
1251
|
+
return null;
|
|
1252
|
+
}
|
|
1253
|
+
else if (ret === AVERROR_EOF) {
|
|
1254
|
+
// End of stream
|
|
1255
|
+
return EOF;
|
|
1256
|
+
}
|
|
1257
|
+
else {
|
|
1258
|
+
// Error during receive
|
|
1259
|
+
if (this.options.exitOnError) {
|
|
1260
|
+
FFmpegError.throwIfError(ret, 'Failed to receive frame');
|
|
1261
|
+
}
|
|
1262
|
+
// exitOnError=false: return null, caller can retry if desired
|
|
1263
|
+
return null;
|
|
1237
1264
|
}
|
|
1238
|
-
return null;
|
|
1239
1265
|
}
|
|
1240
1266
|
pipeTo(target) {
|
|
1241
1267
|
const t = target;
|
|
@@ -1352,18 +1378,7 @@ export class Decoder {
|
|
|
1352
1378
|
if (packet.size === 0) {
|
|
1353
1379
|
continue;
|
|
1354
1380
|
}
|
|
1355
|
-
|
|
1356
|
-
const sendRet = await this.codecContext.sendPacket(packet);
|
|
1357
|
-
// EAGAIN during send_packet is a decoder bug
|
|
1358
|
-
// We read all decoded frames with receive() until done, so decoder should never be full
|
|
1359
|
-
if (sendRet === AVERROR_EAGAIN) {
|
|
1360
|
-
throw new Error('Decoder returned EAGAIN but no frame available');
|
|
1361
|
-
}
|
|
1362
|
-
if (sendRet < 0 && sendRet !== AVERROR_EOF) {
|
|
1363
|
-
if (this.options.exitOnError) {
|
|
1364
|
-
FFmpegError.throwIfError(sendRet, 'Failed to send packet');
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1381
|
+
await this.decode(packet);
|
|
1367
1382
|
// Receive ALL available frames immediately
|
|
1368
1383
|
// This ensures frames are yielded ASAP without latency
|
|
1369
1384
|
while (!this.outputQueue.isClosed) {
|
|
@@ -1399,14 +1414,54 @@ export class Decoder {
|
|
|
1399
1414
|
}
|
|
1400
1415
|
}
|
|
1401
1416
|
/**
|
|
1402
|
-
* Send packet to input queue.
|
|
1417
|
+
* Send packet to input queue or flush the pipeline.
|
|
1403
1418
|
*
|
|
1404
|
-
*
|
|
1419
|
+
* When packet is provided, queues it for processing.
|
|
1420
|
+
* When null is provided, triggers flush sequence:
|
|
1421
|
+
* - Closes input queue
|
|
1422
|
+
* - Waits for worker completion
|
|
1423
|
+
* - Flushes decoder and sends remaining frames to output queue
|
|
1424
|
+
* - Closes output queue
|
|
1425
|
+
* - Waits for pipeTo task completion
|
|
1426
|
+
* - Propagates flush to next component (if any)
|
|
1427
|
+
*
|
|
1428
|
+
* Used by scheduler system for pipeline control.
|
|
1429
|
+
*
|
|
1430
|
+
* @param packet - Packet to send, or null to flush
|
|
1405
1431
|
*
|
|
1406
1432
|
* @internal
|
|
1407
1433
|
*/
|
|
1408
1434
|
async sendToQueue(packet) {
|
|
1409
|
-
|
|
1435
|
+
if (packet) {
|
|
1436
|
+
await this.inputQueue.send(packet);
|
|
1437
|
+
}
|
|
1438
|
+
else {
|
|
1439
|
+
// Close input queue to signal end of stream to worker
|
|
1440
|
+
this.inputQueue.close();
|
|
1441
|
+
// Wait for worker to finish processing all packets (if exists)
|
|
1442
|
+
if (this.workerPromise) {
|
|
1443
|
+
await this.workerPromise;
|
|
1444
|
+
}
|
|
1445
|
+
// Flush decoder at end
|
|
1446
|
+
await this.flush();
|
|
1447
|
+
// Send all flushed frames to output queue
|
|
1448
|
+
while (true) {
|
|
1449
|
+
const frame = await this.receive();
|
|
1450
|
+
if (!frame)
|
|
1451
|
+
break;
|
|
1452
|
+
await this.outputQueue.send(frame);
|
|
1453
|
+
}
|
|
1454
|
+
// Close output queue to signal end of stream to pipeTo() task
|
|
1455
|
+
this.outputQueue.close();
|
|
1456
|
+
// Wait for pipeTo() task to finish processing all frames (if exists)
|
|
1457
|
+
if (this.pipeToPromise) {
|
|
1458
|
+
await this.pipeToPromise;
|
|
1459
|
+
}
|
|
1460
|
+
// Then propagate flush to next component
|
|
1461
|
+
if (this.nextComponent) {
|
|
1462
|
+
await this.nextComponent.sendToQueue(null);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1410
1465
|
}
|
|
1411
1466
|
/**
|
|
1412
1467
|
* Receive frame from output queue.
|
|
@@ -1418,40 +1473,6 @@ export class Decoder {
|
|
|
1418
1473
|
async receiveFromQueue() {
|
|
1419
1474
|
return await this.outputQueue.receive();
|
|
1420
1475
|
}
|
|
1421
|
-
/**
|
|
1422
|
-
* Flush the entire filter pipeline.
|
|
1423
|
-
*
|
|
1424
|
-
* Propagates flush through worker, output queue, and next component.
|
|
1425
|
-
*
|
|
1426
|
-
* @internal
|
|
1427
|
-
*/
|
|
1428
|
-
async flushPipeline() {
|
|
1429
|
-
// Close input queue to signal end of stream to worker
|
|
1430
|
-
this.inputQueue.close();
|
|
1431
|
-
// Wait for worker to finish processing all packets (if exists)
|
|
1432
|
-
if (this.workerPromise) {
|
|
1433
|
-
await this.workerPromise;
|
|
1434
|
-
}
|
|
1435
|
-
// Flush decoder at end
|
|
1436
|
-
await this.flush();
|
|
1437
|
-
// Send all flushed frames to output queue
|
|
1438
|
-
while (true) {
|
|
1439
|
-
const frame = await this.receive();
|
|
1440
|
-
if (!frame)
|
|
1441
|
-
break;
|
|
1442
|
-
await this.outputQueue.send(frame);
|
|
1443
|
-
}
|
|
1444
|
-
// Close output queue to signal end of stream to pipeTo() task
|
|
1445
|
-
this.outputQueue.close();
|
|
1446
|
-
// Wait for pipeTo() task to finish processing all frames (if exists)
|
|
1447
|
-
if (this.pipeToPromise) {
|
|
1448
|
-
await this.pipeToPromise;
|
|
1449
|
-
}
|
|
1450
|
-
// Then propagate flush to next component
|
|
1451
|
-
if (this.nextComponent) {
|
|
1452
|
-
await this.nextComponent.flushPipeline();
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
1476
|
/**
|
|
1456
1477
|
* Estimate video frame duration.
|
|
1457
1478
|
*
|