node-av 1.0.3 → 1.2.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 +56 -41
- package/dist/api/bitstream-filter.d.ts +180 -123
- package/dist/api/bitstream-filter.js +182 -126
- package/dist/api/bitstream-filter.js.map +1 -1
- package/dist/api/decoder.d.ts +286 -130
- package/dist/api/decoder.js +321 -159
- package/dist/api/decoder.js.map +1 -1
- package/dist/api/encoder.d.ts +254 -158
- package/dist/api/encoder.js +326 -298
- package/dist/api/encoder.js.map +1 -1
- package/dist/api/filter-presets.d.ts +912 -0
- package/dist/api/filter-presets.js +1407 -0
- package/dist/api/filter-presets.js.map +1 -0
- package/dist/api/filter.d.ts +280 -284
- package/dist/api/filter.js +435 -509
- package/dist/api/filter.js.map +1 -1
- package/dist/api/hardware.d.ts +226 -159
- package/dist/api/hardware.js +405 -287
- package/dist/api/hardware.js.map +1 -1
- package/dist/api/index.d.ts +3 -2
- package/dist/api/index.js +1 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/io-stream.d.ts +65 -61
- package/dist/api/io-stream.js +45 -47
- package/dist/api/io-stream.js.map +1 -1
- package/dist/api/media-input.d.ts +244 -141
- package/dist/api/media-input.js +207 -104
- package/dist/api/media-input.js.map +1 -1
- package/dist/api/media-output.d.ts +206 -128
- package/dist/api/media-output.js +212 -129
- package/dist/api/media-output.js.map +1 -1
- package/dist/api/pipeline.d.ts +168 -38
- package/dist/api/pipeline.js +238 -14
- package/dist/api/pipeline.js.map +1 -1
- package/dist/api/types.d.ts +22 -182
- package/dist/api/utilities/audio-sample.d.ts +1 -1
- package/dist/api/utilities/image.d.ts +1 -1
- package/dist/api/utilities/media-type.d.ts +1 -1
- package/dist/api/utilities/pixel-format.d.ts +1 -1
- package/dist/api/utilities/sample-format.d.ts +1 -1
- package/dist/api/utilities/timestamp.d.ts +1 -1
- package/dist/api/utils.d.ts +1 -2
- package/dist/api/utils.js +9 -0
- package/dist/api/utils.js.map +1 -1
- package/dist/{lib → constants}/channel-layouts.d.ts +1 -1
- package/dist/constants/channel-layouts.js.map +1 -0
- package/dist/{lib → constants}/constants.d.ts +19 -4
- package/dist/{lib → constants}/constants.js +15 -1
- package/dist/constants/constants.js.map +1 -0
- package/dist/constants/decoders.d.ts +609 -0
- package/dist/constants/decoders.js +617 -0
- package/dist/constants/decoders.js.map +1 -0
- package/dist/constants/encoders.d.ts +285 -0
- package/dist/constants/encoders.js +298 -0
- package/dist/constants/encoders.js.map +1 -0
- package/dist/constants/index.d.ts +4 -0
- package/dist/constants/index.js +5 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/audio-fifo.d.ts +128 -171
- package/dist/lib/audio-fifo.js +130 -173
- package/dist/lib/audio-fifo.js.map +1 -1
- package/dist/lib/binding.d.ts +7 -5
- package/dist/lib/binding.js +5 -0
- package/dist/lib/binding.js.map +1 -1
- package/dist/lib/bitstream-filter-context.d.ts +139 -184
- package/dist/lib/bitstream-filter-context.js +139 -188
- package/dist/lib/bitstream-filter-context.js.map +1 -1
- package/dist/lib/bitstream-filter.d.ts +69 -55
- package/dist/lib/bitstream-filter.js +68 -54
- package/dist/lib/bitstream-filter.js.map +1 -1
- package/dist/lib/codec-context.d.ts +317 -381
- package/dist/lib/codec-context.js +316 -381
- package/dist/lib/codec-context.js.map +1 -1
- package/dist/lib/codec-parameters.d.ts +161 -171
- package/dist/lib/codec-parameters.js +162 -172
- package/dist/lib/codec-parameters.js.map +1 -1
- package/dist/lib/codec-parser.d.ts +92 -105
- package/dist/lib/codec-parser.js +92 -103
- package/dist/lib/codec-parser.js.map +1 -1
- package/dist/lib/codec.d.ts +328 -217
- package/dist/lib/codec.js +392 -218
- package/dist/lib/codec.js.map +1 -1
- package/dist/lib/dictionary.d.ts +150 -204
- package/dist/lib/dictionary.js +159 -213
- package/dist/lib/dictionary.js.map +1 -1
- package/dist/lib/error.d.ts +97 -131
- package/dist/lib/error.js +98 -128
- package/dist/lib/error.js.map +1 -1
- package/dist/lib/filter-context.d.ts +317 -194
- package/dist/lib/filter-context.js +335 -200
- package/dist/lib/filter-context.js.map +1 -1
- package/dist/lib/filter-graph.d.ts +252 -293
- package/dist/lib/filter-graph.js +253 -294
- package/dist/lib/filter-graph.js.map +1 -1
- package/dist/lib/filter-inout.d.ts +87 -95
- package/dist/lib/filter-inout.js +87 -95
- package/dist/lib/filter-inout.js.map +1 -1
- package/dist/lib/filter.d.ts +93 -111
- package/dist/lib/filter.js +94 -112
- package/dist/lib/filter.js.map +1 -1
- package/dist/lib/format-context.d.ts +321 -429
- package/dist/lib/format-context.js +314 -386
- package/dist/lib/format-context.js.map +1 -1
- package/dist/lib/frame.d.ts +263 -406
- package/dist/lib/frame.js +263 -408
- package/dist/lib/frame.js.map +1 -1
- package/dist/lib/hardware-device-context.d.ts +150 -204
- package/dist/lib/hardware-device-context.js +149 -203
- package/dist/lib/hardware-device-context.js.map +1 -1
- package/dist/lib/hardware-frames-context.d.ts +171 -181
- package/dist/lib/hardware-frames-context.js +171 -181
- package/dist/lib/hardware-frames-context.js.map +1 -1
- package/dist/lib/index.d.ts +2 -3
- package/dist/lib/index.js +2 -5
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/input-format.d.ts +90 -118
- package/dist/lib/input-format.js +89 -117
- package/dist/lib/input-format.js.map +1 -1
- package/dist/lib/io-context.d.ts +210 -242
- package/dist/lib/io-context.js +221 -253
- package/dist/lib/io-context.js.map +1 -1
- package/dist/lib/log.d.ts +86 -120
- package/dist/lib/log.js +85 -122
- package/dist/lib/log.js.map +1 -1
- package/dist/lib/native-types.d.ts +127 -112
- package/dist/lib/native-types.js +9 -0
- package/dist/lib/native-types.js.map +1 -1
- package/dist/lib/option.d.ts +285 -242
- package/dist/lib/option.js +310 -250
- package/dist/lib/option.js.map +1 -1
- package/dist/lib/output-format.d.ts +78 -102
- package/dist/lib/output-format.js +77 -101
- package/dist/lib/output-format.js.map +1 -1
- package/dist/lib/packet.d.ts +173 -241
- package/dist/lib/packet.js +172 -241
- package/dist/lib/packet.js.map +1 -1
- package/dist/lib/rational.d.ts +0 -2
- package/dist/lib/rational.js +0 -2
- package/dist/lib/rational.js.map +1 -1
- package/dist/lib/software-resample-context.d.ts +242 -326
- package/dist/lib/software-resample-context.js +242 -326
- package/dist/lib/software-resample-context.js.map +1 -1
- package/dist/lib/software-scale-context.d.ts +130 -174
- package/dist/lib/software-scale-context.js +132 -176
- package/dist/lib/software-scale-context.js.map +1 -1
- package/dist/lib/stream.d.ts +88 -198
- package/dist/lib/stream.js +87 -197
- package/dist/lib/stream.js.map +1 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/utilities.d.ts +372 -181
- package/dist/lib/utilities.js +373 -182
- package/dist/lib/utilities.js.map +1 -1
- package/install/check.js +0 -1
- package/package.json +32 -24
- package/release_notes.md +43 -13
- package/CHANGELOG.md +0 -8
- package/dist/lib/channel-layouts.js.map +0 -1
- package/dist/lib/constants.js.map +0 -1
- /package/dist/{lib → constants}/channel-layouts.js +0 -0
package/dist/api/encoder.js
CHANGED
|
@@ -1,92 +1,78 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Simplifies FFmpeg's encoding API with automatic codec selection,
|
|
5
|
-
* parameter configuration, and packet management.
|
|
6
|
-
*
|
|
7
|
-
* Handles codec initialization, frame encoding, and packet output.
|
|
8
|
-
* Supports hardware acceleration and zero-copy transcoding.
|
|
9
|
-
*
|
|
10
|
-
* @module api/encoder
|
|
11
|
-
*/
|
|
12
|
-
import { AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX, AVERROR_EAGAIN, AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO, Codec, CodecContext, FFmpegError, Packet, Rational, } from '../lib/index.js';
|
|
13
|
-
import { Stream } from '../lib/stream.js';
|
|
1
|
+
import { AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO } from '../constants/constants.js';
|
|
2
|
+
import { AVERROR_EAGAIN, avGetPixFmtName, avGetSampleFmtName, Codec, CodecContext, FFmpegError, Packet, Rational } from '../lib/index.js';
|
|
14
3
|
import { parseBitrate } from './utils.js';
|
|
15
4
|
/**
|
|
16
|
-
* High-level encoder for
|
|
17
|
-
*
|
|
18
|
-
* Handles codec initialization, frame encoding, and packet output.
|
|
19
|
-
* Supports various codecs with flexible configuration options.
|
|
5
|
+
* High-level encoder for audio and video streams.
|
|
20
6
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
7
|
+
* Provides a simplified interface for encoding media frames to packets.
|
|
8
|
+
* Handles codec initialization, hardware acceleration setup, and packet management.
|
|
9
|
+
* Supports both synchronous frame-by-frame encoding and async iteration over packets.
|
|
10
|
+
* Essential component in media processing pipelines for converting raw frames to compressed data.
|
|
23
11
|
*
|
|
24
12
|
* @example
|
|
25
13
|
* ```typescript
|
|
14
|
+
* import { Encoder } from 'node-av/api';
|
|
15
|
+
* import { AV_CODEC_ID_H264 } from 'node-av/constants';
|
|
16
|
+
*
|
|
26
17
|
* // Create H.264 encoder
|
|
27
18
|
* const encoder = await Encoder.create('libx264', {
|
|
19
|
+
* type: 'video',
|
|
28
20
|
* width: 1920,
|
|
29
21
|
* height: 1080,
|
|
30
|
-
* pixelFormat:
|
|
22
|
+
* pixelFormat: AV_PIX_FMT_YUV420P,
|
|
23
|
+
* timeBase: { num: 1, den: 30 },
|
|
24
|
+
* frameRate: { num: 30, den: 1 }
|
|
25
|
+
* }, {
|
|
31
26
|
* bitrate: '5M',
|
|
32
|
-
* gopSize: 60
|
|
33
|
-
* options: {
|
|
34
|
-
* preset: 'fast',
|
|
35
|
-
* crf: 23
|
|
36
|
-
* }
|
|
27
|
+
* gopSize: 60
|
|
37
28
|
* });
|
|
38
29
|
*
|
|
39
30
|
* // Encode frames
|
|
40
31
|
* const packet = await encoder.encode(frame);
|
|
41
32
|
* if (packet) {
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* // Flush encoder
|
|
46
|
-
* let packet;
|
|
47
|
-
* while ((packet = await encoder.flush()) !== null) {
|
|
48
|
-
* // Process final packets
|
|
33
|
+
* await output.writePacket(packet);
|
|
34
|
+
* packet.free();
|
|
49
35
|
* }
|
|
50
|
-
* encoder.close();
|
|
51
36
|
* ```
|
|
52
37
|
*
|
|
53
38
|
* @example
|
|
54
39
|
* ```typescript
|
|
55
|
-
* //
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
40
|
+
* // Hardware-accelerated encoding
|
|
41
|
+
* import { HardwareContext } from 'node-av/api';
|
|
42
|
+
* import { AV_HWDEVICE_TYPE_CUDA } from 'node-av/constants';
|
|
43
|
+
*
|
|
44
|
+
* const hw = HardwareContext.create(AV_HWDEVICE_TYPE_CUDA);
|
|
45
|
+
* const encoder = await Encoder.create('h264_nvenc', streamInfo, {
|
|
46
|
+
* hardware: hw,
|
|
47
|
+
* bitrate: '10M'
|
|
63
48
|
* });
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
49
|
+
*
|
|
50
|
+
* // Frames with hw_frames_ctx will be encoded on GPU
|
|
51
|
+
* for await (const packet of encoder.packets(frames)) {
|
|
52
|
+
* await output.writePacket(packet);
|
|
53
|
+
* packet.free();
|
|
54
|
+
* }
|
|
67
55
|
* ```
|
|
56
|
+
*
|
|
57
|
+
* @see {@link Decoder} For decoding packets to frames
|
|
58
|
+
* @see {@link MediaOutput} For writing encoded packets
|
|
59
|
+
* @see {@link HardwareContext} For GPU acceleration
|
|
68
60
|
*/
|
|
69
61
|
export class Encoder {
|
|
70
62
|
codecContext;
|
|
71
63
|
packet;
|
|
72
|
-
|
|
64
|
+
codec;
|
|
73
65
|
isOpen = true;
|
|
74
|
-
|
|
75
|
-
preferredFormat;
|
|
76
|
-
hardware; // Store reference to check for late framesContext
|
|
77
|
-
isHardwareEncoder = false; // Track if this is a hardware encoder
|
|
66
|
+
hardware;
|
|
78
67
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* @param codecContext - Initialized codec context
|
|
84
|
-
* @param codecName - Name of the codec
|
|
85
|
-
* @param hardware - Optional hardware context for late framesContext binding
|
|
68
|
+
* @param codecContext - Configured codec context
|
|
69
|
+
* @param codec - Encoder codec
|
|
70
|
+
* @param hardware - Optional hardware context
|
|
71
|
+
* @internal
|
|
86
72
|
*/
|
|
87
|
-
constructor(codecContext,
|
|
73
|
+
constructor(codecContext, codec, hardware) {
|
|
88
74
|
this.codecContext = codecContext;
|
|
89
|
-
this.
|
|
75
|
+
this.codec = codec;
|
|
90
76
|
this.hardware = hardware;
|
|
91
77
|
this.packet = new Packet();
|
|
92
78
|
this.packet.alloc();
|
|
@@ -94,122 +80,128 @@ export class Encoder {
|
|
|
94
80
|
/**
|
|
95
81
|
* Create an encoder with specified codec and options.
|
|
96
82
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
83
|
+
* Initializes an encoder with the appropriate codec and configuration.
|
|
84
|
+
* Automatically configures parameters based on input stream info.
|
|
85
|
+
* Handles hardware acceleration setup if provided.
|
|
99
86
|
*
|
|
100
|
-
*
|
|
101
|
-
* configures the context with provided options, and opens it.
|
|
102
|
-
* Handles hardware setup including shared frames context for zero-copy.
|
|
87
|
+
* Direct mapping to avcodec_find_encoder_by_name() or avcodec_find_encoder().
|
|
103
88
|
*
|
|
104
|
-
* @param
|
|
105
|
-
* @param input - Stream
|
|
89
|
+
* @param encoderCodec - Codec name, ID, or instance to use for encoding
|
|
90
|
+
* @param input - Stream information to configure encoder
|
|
106
91
|
* @param options - Encoder configuration options
|
|
92
|
+
* @returns Configured encoder instance
|
|
107
93
|
*
|
|
108
|
-
* @
|
|
109
|
-
*
|
|
110
|
-
* @throws {Error} If codec not found or configuration fails
|
|
94
|
+
* @throws {Error} If encoder not found or unsupported format
|
|
95
|
+
* @throws {FFmpegError} If codec initialization fails
|
|
111
96
|
*
|
|
112
97
|
* @example
|
|
113
98
|
* ```typescript
|
|
114
|
-
* //
|
|
115
|
-
* const
|
|
116
|
-
* const
|
|
99
|
+
* // From decoder stream info
|
|
100
|
+
* const streamInfo = decoder.getOutputStreamInfo();
|
|
101
|
+
* const encoder = await Encoder.create('libx264', streamInfo, {
|
|
117
102
|
* bitrate: '5M',
|
|
118
|
-
* gopSize: 60
|
|
103
|
+
* gopSize: 60,
|
|
104
|
+
* options: {
|
|
105
|
+
* preset: 'fast',
|
|
106
|
+
* crf: '23'
|
|
107
|
+
* }
|
|
119
108
|
* });
|
|
109
|
+
* ```
|
|
120
110
|
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* // With custom stream info
|
|
114
|
+
* const encoder = await Encoder.create('aac', {
|
|
115
|
+
* type: 'audio',
|
|
116
|
+
* sampleRate: 48000,
|
|
117
|
+
* sampleFormat: AV_SAMPLE_FMT_FLTP,
|
|
118
|
+
* channelLayout: AV_CH_LAYOUT_STEREO,
|
|
119
|
+
* timeBase: { num: 1, den: 48000 }
|
|
120
|
+
* }, {
|
|
124
121
|
* bitrate: '192k'
|
|
125
122
|
* });
|
|
123
|
+
* ```
|
|
126
124
|
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* // Hardware encoder
|
|
128
|
+
* const hw = HardwareContext.auto();
|
|
129
|
+
* const encoder = await Encoder.create('hevc_videotoolbox', streamInfo, {
|
|
130
|
+
* hardware: hw,
|
|
131
|
+
* bitrate: '8M'
|
|
132
|
+
* });
|
|
127
133
|
* ```
|
|
134
|
+
*
|
|
135
|
+
* @see {@link Decoder.getOutputStreamInfo} For stream info source
|
|
136
|
+
* @see {@link EncoderOptions} For configuration options
|
|
128
137
|
*/
|
|
129
|
-
static async create(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
138
|
+
static async create(encoderCodec, input, options = {}) {
|
|
139
|
+
let codec = null;
|
|
140
|
+
let codecName = '';
|
|
141
|
+
if (encoderCodec instanceof Codec) {
|
|
142
|
+
codec = encoderCodec;
|
|
143
|
+
codecName = codec.name ?? 'Unknown';
|
|
144
|
+
}
|
|
145
|
+
else if (typeof encoderCodec === 'string') {
|
|
146
|
+
codec = Codec.findEncoderByName(encoderCodec);
|
|
147
|
+
codecName = codec?.name ?? encoderCodec;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
codec = Codec.findEncoder(encoderCodec);
|
|
151
|
+
codecName = codec?.name ?? encoderCodec.toString();
|
|
152
|
+
}
|
|
133
153
|
if (!codec) {
|
|
134
|
-
throw new Error(`Encoder ${
|
|
154
|
+
throw new Error(`Encoder ${codecName} not found`);
|
|
135
155
|
}
|
|
136
156
|
// Allocate codec context
|
|
137
157
|
const codecContext = new CodecContext();
|
|
138
158
|
codecContext.allocContext3(codec);
|
|
139
|
-
//
|
|
140
|
-
if (input
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// Set timing information
|
|
149
|
-
codecContext.pktTimebase = input.timeBase;
|
|
150
|
-
codecContext.timeBase = input.timeBase;
|
|
151
|
-
// Set framerate if available
|
|
152
|
-
if (input.avgFrameRate && input.avgFrameRate.num > 0 && input.avgFrameRate.den > 0) {
|
|
153
|
-
codecContext.framerate = new Rational(input.avgFrameRate.num, input.avgFrameRate.den);
|
|
154
|
-
}
|
|
155
|
-
else if (input.rFrameRate && input.rFrameRate.num > 0 && input.rFrameRate.den > 0) {
|
|
156
|
-
codecContext.framerate = new Rational(input.rFrameRate.num, input.rFrameRate.den);
|
|
157
|
-
}
|
|
158
|
-
// Copy sample aspect ratio if present
|
|
159
|
-
if (input.sampleAspectRatio && input.sampleAspectRatio.num > 0) {
|
|
160
|
-
codecContext.sampleAspectRatio = new Rational(input.sampleAspectRatio.num, input.sampleAspectRatio.den);
|
|
161
|
-
}
|
|
159
|
+
// It's StreamInfo - apply manually
|
|
160
|
+
if (input.type === 'video' && codec.type === AVMEDIA_TYPE_VIDEO) {
|
|
161
|
+
const videoInfo = input;
|
|
162
|
+
const codecPixelformats = codec.pixelFormats;
|
|
163
|
+
if (codecPixelformats && !codecPixelformats.includes(videoInfo.pixelFormat)) {
|
|
164
|
+
codecContext.freeContext();
|
|
165
|
+
const pixelFormatName = avGetPixFmtName(videoInfo.pixelFormat) ?? 'unknown';
|
|
166
|
+
const codecPixFmtNames = codecPixelformats.map(avGetPixFmtName).filter(Boolean).join(', ');
|
|
167
|
+
throw new Error(`Unsupported pixel format for '${codecName}' encoder: ${pixelFormatName}! Supported formats: ${codecPixFmtNames}`);
|
|
162
168
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
codecContext.
|
|
171
|
-
// Set timing information
|
|
172
|
-
codecContext.pktTimebase = input.timeBase;
|
|
173
|
-
codecContext.timeBase = input.timeBase;
|
|
169
|
+
codecContext.width = videoInfo.width;
|
|
170
|
+
codecContext.height = videoInfo.height;
|
|
171
|
+
codecContext.pixelFormat = videoInfo.pixelFormat;
|
|
172
|
+
// Set pkt_timebase and timeBase to input timebase
|
|
173
|
+
codecContext.pktTimebase = new Rational(videoInfo.timeBase.num, videoInfo.timeBase.den);
|
|
174
|
+
codecContext.timeBase = new Rational(videoInfo.timeBase.num, videoInfo.timeBase.den);
|
|
175
|
+
if (videoInfo.frameRate) {
|
|
176
|
+
codecContext.framerate = new Rational(videoInfo.frameRate.num, videoInfo.frameRate.den);
|
|
174
177
|
}
|
|
175
|
-
|
|
176
|
-
codecContext.
|
|
177
|
-
throw new Error('Unsupported codec type for encoder');
|
|
178
|
+
if (videoInfo.sampleAspectRatio) {
|
|
179
|
+
codecContext.sampleAspectRatio = new Rational(videoInfo.sampleAspectRatio.num, videoInfo.sampleAspectRatio.den);
|
|
178
180
|
}
|
|
179
181
|
}
|
|
180
|
-
else {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
codecContext.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
codecContext.pktTimebase = new Rational(videoInfo.timeBase.num, videoInfo.timeBase.den);
|
|
189
|
-
codecContext.timeBase = new Rational(videoInfo.timeBase.num, videoInfo.timeBase.den);
|
|
190
|
-
if (videoInfo.frameRate) {
|
|
191
|
-
codecContext.framerate = new Rational(videoInfo.frameRate.num, videoInfo.frameRate.den);
|
|
192
|
-
}
|
|
193
|
-
if (videoInfo.sampleAspectRatio) {
|
|
194
|
-
codecContext.sampleAspectRatio = new Rational(videoInfo.sampleAspectRatio.num, videoInfo.sampleAspectRatio.den);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
else if (input.type === 'audio' && codec.type === AVMEDIA_TYPE_AUDIO) {
|
|
198
|
-
const audioInfo = input;
|
|
199
|
-
codecContext.sampleRate = audioInfo.sampleRate;
|
|
200
|
-
codecContext.sampleFormat = audioInfo.sampleFormat;
|
|
201
|
-
codecContext.channelLayout = audioInfo.channelLayout;
|
|
202
|
-
// Set both pkt_timebase and timeBase for audio
|
|
203
|
-
codecContext.pktTimebase = new Rational(audioInfo.timeBase.num, audioInfo.timeBase.den);
|
|
204
|
-
codecContext.timeBase = new Rational(audioInfo.timeBase.num, audioInfo.timeBase.den);
|
|
205
|
-
if (audioInfo.frameSize) {
|
|
206
|
-
codecContext.frameSize = audioInfo.frameSize;
|
|
207
|
-
}
|
|
182
|
+
else if (input.type === 'audio' && codec.type === AVMEDIA_TYPE_AUDIO) {
|
|
183
|
+
const audioInfo = input;
|
|
184
|
+
const codecSampleFormats = codec.sampleFormats;
|
|
185
|
+
if (codecSampleFormats && !codecSampleFormats.includes(audioInfo.sampleFormat)) {
|
|
186
|
+
codecContext.freeContext();
|
|
187
|
+
const sampleFormatName = avGetSampleFmtName(audioInfo.sampleFormat) ?? 'unknown';
|
|
188
|
+
const supportedFormats = codecSampleFormats.map(avGetSampleFmtName).filter(Boolean).join(', ');
|
|
189
|
+
throw new Error(`Unsupported sample format for '${codecName}' encoder: ${sampleFormatName}! Supported formats: ${supportedFormats}`);
|
|
208
190
|
}
|
|
209
|
-
|
|
210
|
-
|
|
191
|
+
codecContext.sampleRate = audioInfo.sampleRate;
|
|
192
|
+
codecContext.sampleFormat = audioInfo.sampleFormat;
|
|
193
|
+
codecContext.channelLayout = audioInfo.channelLayout;
|
|
194
|
+
// Set both pkt_timebase and timeBase for audio
|
|
195
|
+
codecContext.pktTimebase = new Rational(audioInfo.timeBase.num, audioInfo.timeBase.den);
|
|
196
|
+
codecContext.timeBase = new Rational(audioInfo.timeBase.num, audioInfo.timeBase.den);
|
|
197
|
+
if (audioInfo.frameSize) {
|
|
198
|
+
codecContext.frameSize = audioInfo.frameSize;
|
|
211
199
|
}
|
|
212
200
|
}
|
|
201
|
+
else {
|
|
202
|
+
codecContext.freeContext();
|
|
203
|
+
throw new Error(`Unsupported codec type for encoder! Input type: ${input.type}, Codec type: ${codec.type}`);
|
|
204
|
+
}
|
|
213
205
|
// Apply encoder-specific options
|
|
214
206
|
if (options.gopSize !== undefined) {
|
|
215
207
|
codecContext.gopSize = options.gopSize;
|
|
@@ -235,106 +227,102 @@ export class Encoder {
|
|
|
235
227
|
codecContext.setOption(key, value.toString());
|
|
236
228
|
}
|
|
237
229
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
for (let i = 0;; i++) {
|
|
243
|
-
const config = codec.getHwConfig(i);
|
|
244
|
-
if (!config)
|
|
245
|
-
break;
|
|
246
|
-
// Check if encoder supports HW_FRAMES_CTX method
|
|
247
|
-
if ((config.methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) !== 0) {
|
|
248
|
-
supportsHardware = true;
|
|
249
|
-
// If hardware context provided, check if it matches this encoder
|
|
250
|
-
if (options.hardware && config.deviceType === options.hardware.deviceType) {
|
|
251
|
-
isHardwareEncoder = true;
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
// Validation: Hardware encoder MUST have HardwareContext
|
|
257
|
-
if (supportsHardware && !options.hardware) {
|
|
258
|
-
throw new Error(`Hardware encoder '${actualCodecName}' requires a hardware context. ` + 'Please provide one via options.hardware');
|
|
259
|
-
}
|
|
260
|
-
// Apply hardware acceleration if provided and encoder supports it
|
|
261
|
-
if (options.hardware && isHardwareEncoder) {
|
|
262
|
-
// Check if frames context already available (shared from decoder)
|
|
263
|
-
if (options.hardware.framesContext) {
|
|
264
|
-
codecContext.hwFramesCtx = options.hardware.framesContext;
|
|
265
|
-
codecContext.pixelFormat = options.hardware.getHardwarePixelFormat();
|
|
266
|
-
}
|
|
267
|
-
// Else: Will set hwFramesCtx later in encode() when first frame arrives
|
|
268
|
-
// DO NOT create frames context here - wait for first frame!
|
|
230
|
+
const isHWEncoder = codec.isHardwareAcceleratedEncoder();
|
|
231
|
+
if (isHWEncoder && !options.hardware) {
|
|
232
|
+
codecContext.freeContext();
|
|
233
|
+
throw new Error(`Hardware encoder '${codecName}' requires a hardware context`);
|
|
269
234
|
}
|
|
270
|
-
// Note: Software encoder silently ignores hardware context
|
|
271
235
|
// Open codec
|
|
272
236
|
const openRet = await codecContext.open2(codec, null);
|
|
273
237
|
if (openRet < 0) {
|
|
274
238
|
codecContext.freeContext();
|
|
275
239
|
FFmpegError.throwIfError(openRet, 'Failed to open encoder');
|
|
276
240
|
}
|
|
277
|
-
const encoder = new Encoder(codecContext,
|
|
278
|
-
encoder.isHardwareEncoder = isHardwareEncoder;
|
|
279
|
-
// Get supported formats from codec (for validation and helpers)
|
|
280
|
-
if (codec.pixelFormats) {
|
|
281
|
-
encoder.supportedFormats = codec.pixelFormats;
|
|
282
|
-
encoder.preferredFormat = encoder.supportedFormats[0];
|
|
283
|
-
}
|
|
241
|
+
const encoder = new Encoder(codecContext, codec, isHWEncoder ? options.hardware : undefined);
|
|
284
242
|
return encoder;
|
|
285
243
|
}
|
|
286
244
|
/**
|
|
287
245
|
* Check if encoder is open.
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* if (encoder.isEncoderOpen) {
|
|
250
|
+
* const packet = await encoder.encode(frame);
|
|
251
|
+
* }
|
|
252
|
+
* ```
|
|
288
253
|
*/
|
|
289
254
|
get isEncoderOpen() {
|
|
290
255
|
return this.isOpen;
|
|
291
256
|
}
|
|
292
257
|
/**
|
|
293
|
-
*
|
|
258
|
+
* Check if encoder uses hardware acceleration.
|
|
259
|
+
*
|
|
260
|
+
* @returns true if hardware-accelerated
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```typescript
|
|
264
|
+
* if (encoder.isHardware()) {
|
|
265
|
+
* console.log('Using GPU acceleration');
|
|
266
|
+
* }
|
|
267
|
+
* ```
|
|
294
268
|
*
|
|
295
|
-
*
|
|
296
|
-
|
|
269
|
+
* @see {@link HardwareContext} For hardware setup
|
|
270
|
+
*/
|
|
271
|
+
isHardware() {
|
|
272
|
+
return !!this.hardware;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Encode a frame to a packet.
|
|
297
276
|
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
277
|
+
* Sends a frame to the encoder and attempts to receive an encoded packet.
|
|
278
|
+
* Handles internal buffering - may return null if more frames needed.
|
|
279
|
+
* Automatically manages encoder state and hardware context binding.
|
|
300
280
|
*
|
|
301
|
-
*
|
|
281
|
+
* Direct mapping to avcodec_send_frame() and avcodec_receive_packet().
|
|
302
282
|
*
|
|
303
|
-
* @
|
|
283
|
+
* @param frame - Raw frame to encode (or null to flush)
|
|
284
|
+
* @returns Encoded packet or null if more data needed
|
|
304
285
|
*
|
|
305
|
-
* @throws {Error} If encoder is closed
|
|
286
|
+
* @throws {Error} If encoder is closed
|
|
287
|
+
* @throws {FFmpegError} If encoding fails
|
|
306
288
|
*
|
|
307
289
|
* @example
|
|
308
290
|
* ```typescript
|
|
309
291
|
* const packet = await encoder.encode(frame);
|
|
310
292
|
* if (packet) {
|
|
311
|
-
*
|
|
293
|
+
* console.log(`Encoded packet with PTS: ${packet.pts}`);
|
|
312
294
|
* await output.writePacket(packet);
|
|
295
|
+
* packet.free();
|
|
296
|
+
* }
|
|
297
|
+
* ```
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```typescript
|
|
301
|
+
* // Encode loop
|
|
302
|
+
* for await (const frame of decoder.frames(input.packets())) {
|
|
303
|
+
* const packet = await encoder.encode(frame);
|
|
304
|
+
* if (packet) {
|
|
305
|
+
* await output.writePacket(packet);
|
|
306
|
+
* packet.free();
|
|
307
|
+
* }
|
|
308
|
+
* frame.free();
|
|
313
309
|
* }
|
|
314
310
|
* ```
|
|
311
|
+
*
|
|
312
|
+
* @see {@link packets} For automatic frame iteration
|
|
313
|
+
* @see {@link flush} For end-of-stream handling
|
|
315
314
|
*/
|
|
316
315
|
async encode(frame) {
|
|
317
316
|
if (!this.isOpen) {
|
|
318
317
|
throw new Error('Encoder is closed');
|
|
319
318
|
}
|
|
320
|
-
// Late binding of hw_frames_ctx
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
else if (frame?.width && frame.height) {
|
|
327
|
-
// First frame - create frames context for upload
|
|
328
|
-
this.hardware.ensureFramesContext(frame.width, frame.height);
|
|
329
|
-
this.codecContext.hwFramesCtx = this.hardware.framesContext ?? null;
|
|
330
|
-
}
|
|
331
|
-
if (this.codecContext.hwFramesCtx) {
|
|
332
|
-
this.codecContext.pixelFormat = this.hardware.getHardwarePixelFormat();
|
|
333
|
-
}
|
|
319
|
+
// Late binding of hw_frames_ctx for hardware encoders
|
|
320
|
+
// Hardware encoders get hw_frames_ctx from the frames they receive
|
|
321
|
+
if (this.hardware && frame?.hwFramesCtx && !this.codecContext.hwFramesCtx) {
|
|
322
|
+
// Use the hw_frames_ctx from the frame
|
|
323
|
+
this.codecContext.hwFramesCtx = frame.hwFramesCtx;
|
|
324
|
+
this.codecContext.pixelFormat = this.hardware.devicePixelFormat;
|
|
334
325
|
}
|
|
335
|
-
// Software encoder completely ignores hardware context
|
|
336
|
-
// NO MANUAL RESCALING! FFmpeg's avcodec_send_frame() does this internally
|
|
337
|
-
// The encoder needs pkt_timebase to be set for proper rescaling
|
|
338
326
|
// Send frame to encoder
|
|
339
327
|
const sendRet = await this.codecContext.sendFrame(frame);
|
|
340
328
|
if (sendRet < 0 && sendRet !== AVERROR_EOF) {
|
|
@@ -348,33 +336,65 @@ export class Encoder {
|
|
|
348
336
|
}
|
|
349
337
|
}
|
|
350
338
|
// Try to receive packet
|
|
351
|
-
return this.receivePacket();
|
|
339
|
+
return await this.receivePacket();
|
|
352
340
|
}
|
|
353
341
|
/**
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
* Encodes all provided frames and yields resulting packets.
|
|
357
|
-
* Automatically handles encoder flushing at the end.
|
|
358
|
-
* Input frames are automatically freed after encoding.
|
|
342
|
+
* Encode frame stream to packet stream.
|
|
359
343
|
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
344
|
+
* High-level async generator for complete encoding pipeline.
|
|
345
|
+
* Automatically manages frame memory, encoder state,
|
|
346
|
+
* and flushes buffered packets at end.
|
|
347
|
+
* Primary interface for stream-based encoding.
|
|
362
348
|
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* @
|
|
349
|
+
* @param frames - Async iterable of frames (freed automatically)
|
|
350
|
+
* @yields Encoded packets (caller must free)
|
|
351
|
+
* @throws {Error} If encoder is closed
|
|
352
|
+
* @throws {FFmpegError} If encoding fails
|
|
367
353
|
*
|
|
368
|
-
* @
|
|
354
|
+
* @example
|
|
355
|
+
* ```typescript
|
|
356
|
+
* // Basic encoding pipeline
|
|
357
|
+
* for await (const packet of encoder.packets(decoder.frames(input.packets()))) {
|
|
358
|
+
* await output.writePacket(packet);
|
|
359
|
+
* packet.free(); // Must free output packets
|
|
360
|
+
* }
|
|
361
|
+
* ```
|
|
369
362
|
*
|
|
370
363
|
* @example
|
|
371
364
|
* ```typescript
|
|
372
|
-
* //
|
|
373
|
-
*
|
|
365
|
+
* // With frame filtering
|
|
366
|
+
* async function* filteredFrames() {
|
|
367
|
+
* for await (const frame of decoder.frames(input.packets())) {
|
|
368
|
+
* await filter.filterFrame(frame);
|
|
369
|
+
* const filtered = await filter.getFrame();
|
|
370
|
+
* if (filtered) {
|
|
371
|
+
* yield filtered;
|
|
372
|
+
* }
|
|
373
|
+
* }
|
|
374
|
+
* }
|
|
375
|
+
*
|
|
376
|
+
* for await (const packet of encoder.packets(filteredFrames())) {
|
|
374
377
|
* await output.writePacket(packet);
|
|
375
|
-
* packet.free();
|
|
378
|
+
* packet.free();
|
|
376
379
|
* }
|
|
377
380
|
* ```
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* ```typescript
|
|
384
|
+
* // Pipeline integration
|
|
385
|
+
* import { pipeline } from 'node-av/api';
|
|
386
|
+
*
|
|
387
|
+
* const control = pipeline(
|
|
388
|
+
* input,
|
|
389
|
+
* decoder,
|
|
390
|
+
* encoder,
|
|
391
|
+
* output
|
|
392
|
+
* );
|
|
393
|
+
* await control.completion;
|
|
394
|
+
* ```
|
|
395
|
+
*
|
|
396
|
+
* @see {@link encode} For single frame encoding
|
|
397
|
+
* @see {@link Decoder.frames} For frame source
|
|
378
398
|
*/
|
|
379
399
|
async *packets(frames) {
|
|
380
400
|
if (!this.isOpen) {
|
|
@@ -400,27 +420,31 @@ export class Encoder {
|
|
|
400
420
|
}
|
|
401
421
|
}
|
|
402
422
|
/**
|
|
403
|
-
* Flush encoder and get
|
|
423
|
+
* Flush encoder and get buffered packet.
|
|
404
424
|
*
|
|
405
|
-
*
|
|
406
|
-
* Call repeatedly until
|
|
425
|
+
* Signals end-of-stream and retrieves remaining packets.
|
|
426
|
+
* Call repeatedly until null to get all buffered packets.
|
|
427
|
+
* Essential for ensuring all frames are encoded.
|
|
407
428
|
*
|
|
408
|
-
*
|
|
409
|
-
* Retrieves buffered packets from the encoder.
|
|
429
|
+
* Direct mapping to avcodec_send_frame(NULL).
|
|
410
430
|
*
|
|
411
|
-
* @returns
|
|
431
|
+
* @returns Buffered packet or null if none remaining
|
|
412
432
|
*
|
|
413
433
|
* @throws {Error} If encoder is closed
|
|
414
434
|
*
|
|
415
435
|
* @example
|
|
416
436
|
* ```typescript
|
|
417
|
-
* // Flush
|
|
437
|
+
* // Flush remaining packets
|
|
418
438
|
* let packet;
|
|
419
439
|
* while ((packet = await encoder.flush()) !== null) {
|
|
420
|
-
*
|
|
440
|
+
* console.log('Got buffered packet');
|
|
421
441
|
* await output.writePacket(packet);
|
|
442
|
+
* packet.free();
|
|
422
443
|
* }
|
|
423
444
|
* ```
|
|
445
|
+
*
|
|
446
|
+
* @see {@link flushPackets} For async iteration
|
|
447
|
+
* @see {@link packets} For complete encoding pipeline
|
|
424
448
|
*/
|
|
425
449
|
async flush() {
|
|
426
450
|
if (!this.isOpen) {
|
|
@@ -429,26 +453,30 @@ export class Encoder {
|
|
|
429
453
|
// Send flush frame (null)
|
|
430
454
|
await this.codecContext.sendFrame(null);
|
|
431
455
|
// Receive packet
|
|
432
|
-
return this.receivePacket();
|
|
456
|
+
return await this.receivePacket();
|
|
433
457
|
}
|
|
434
458
|
/**
|
|
435
|
-
* Flush
|
|
436
|
-
*
|
|
437
|
-
* More convenient than calling flush() in a loop.
|
|
438
|
-
* Automatically sends flush signal and yields all buffered packets.
|
|
459
|
+
* Flush all buffered packets as async generator.
|
|
439
460
|
*
|
|
440
|
-
*
|
|
461
|
+
* Convenient async iteration over remaining packets.
|
|
462
|
+
* Automatically handles repeated flush calls.
|
|
463
|
+
* Useful for end-of-stream processing.
|
|
441
464
|
*
|
|
465
|
+
* @yields Buffered packets
|
|
442
466
|
* @throws {Error} If encoder is closed
|
|
443
467
|
*
|
|
444
468
|
* @example
|
|
445
469
|
* ```typescript
|
|
446
|
-
* //
|
|
470
|
+
* // Flush at end of encoding
|
|
447
471
|
* for await (const packet of encoder.flushPackets()) {
|
|
448
|
-
*
|
|
449
|
-
*
|
|
472
|
+
* console.log('Processing buffered packet');
|
|
473
|
+
* await output.writePacket(packet);
|
|
474
|
+
* packet.free();
|
|
450
475
|
* }
|
|
451
476
|
* ```
|
|
477
|
+
*
|
|
478
|
+
* @see {@link flush} For single packet flush
|
|
479
|
+
* @see {@link packets} For complete pipeline
|
|
452
480
|
*/
|
|
453
481
|
async *flushPackets() {
|
|
454
482
|
if (!this.isOpen) {
|
|
@@ -462,84 +490,74 @@ export class Encoder {
|
|
|
462
490
|
/**
|
|
463
491
|
* Close encoder and free resources.
|
|
464
492
|
*
|
|
465
|
-
*
|
|
493
|
+
* Releases codec context and internal packet buffer.
|
|
494
|
+
* Safe to call multiple times.
|
|
495
|
+
* Does NOT dispose hardware context - caller is responsible.
|
|
496
|
+
* Automatically called by Symbol.dispose.
|
|
466
497
|
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
498
|
+
* @example
|
|
499
|
+
* ```typescript
|
|
500
|
+
* const encoder = await Encoder.create('libx264', streamInfo);
|
|
501
|
+
* try {
|
|
502
|
+
* // Use encoder
|
|
503
|
+
* } finally {
|
|
504
|
+
* encoder.close();
|
|
505
|
+
* }
|
|
506
|
+
* ```
|
|
507
|
+
*
|
|
508
|
+
* @see {@link Symbol.dispose} For automatic cleanup
|
|
469
509
|
*/
|
|
470
510
|
close() {
|
|
471
511
|
if (!this.isOpen)
|
|
472
512
|
return;
|
|
473
513
|
this.packet.free();
|
|
474
514
|
this.codecContext.freeContext();
|
|
475
|
-
// NOTE: We do NOT dispose the hardware context here anymore
|
|
476
|
-
// The caller who created the HardwareContext is responsible for disposing it
|
|
477
|
-
// This allows reusing the same HardwareContext for multiple encoders
|
|
478
515
|
this.isOpen = false;
|
|
479
516
|
}
|
|
480
517
|
/**
|
|
481
|
-
* Get
|
|
482
|
-
*/
|
|
483
|
-
getCodecName() {
|
|
484
|
-
return this.codecName;
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Get codec context for advanced configuration.
|
|
488
|
-
*
|
|
489
|
-
* Use with caution - direct manipulation may cause issues.
|
|
490
|
-
*
|
|
491
|
-
* Provides access to the underlying AVCodecContext for advanced operations.
|
|
492
|
-
*
|
|
493
|
-
* @returns CodecContext or null if closed
|
|
494
|
-
*
|
|
495
|
-
* @internal
|
|
496
|
-
*/
|
|
497
|
-
getCodecContext() {
|
|
498
|
-
return this.isOpen ? this.codecContext : null;
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* Get the preferred pixel format for this encoder.
|
|
518
|
+
* Get encoder codec.
|
|
502
519
|
*
|
|
503
|
-
* Returns the
|
|
520
|
+
* Returns the codec used by this encoder.
|
|
521
|
+
* Useful for checking codec capabilities and properties.
|
|
504
522
|
*
|
|
505
|
-
* @returns
|
|
523
|
+
* @returns Codec instance
|
|
506
524
|
*
|
|
507
525
|
* @example
|
|
508
526
|
* ```typescript
|
|
509
|
-
* const
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
* }
|
|
527
|
+
* const codec = encoder.getCodec();
|
|
528
|
+
* console.log(`Using codec: ${codec.name}`);
|
|
529
|
+
* console.log(`Capabilities: ${codec.capabilities}`);
|
|
513
530
|
* ```
|
|
531
|
+
*
|
|
532
|
+
* @see {@link Codec} For codec properties
|
|
514
533
|
*/
|
|
515
|
-
|
|
516
|
-
return this.
|
|
534
|
+
getCodec() {
|
|
535
|
+
return this.codec;
|
|
517
536
|
}
|
|
518
537
|
/**
|
|
519
|
-
* Get
|
|
538
|
+
* Get underlying codec context.
|
|
520
539
|
*
|
|
521
|
-
* Returns
|
|
540
|
+
* Returns the internal codec context for advanced operations.
|
|
541
|
+
* Returns null if encoder is closed.
|
|
522
542
|
*
|
|
523
|
-
* @returns
|
|
543
|
+
* @returns Codec context or null
|
|
524
544
|
*
|
|
525
|
-
* @
|
|
526
|
-
* ```typescript
|
|
527
|
-
* const formats = encoder.getSupportedPixelFormats();
|
|
528
|
-
* console.log(`Encoder supports: ${formats.join(', ')}`);
|
|
529
|
-
* ```
|
|
545
|
+
* @internal
|
|
530
546
|
*/
|
|
531
|
-
|
|
532
|
-
return this.
|
|
547
|
+
getCodecContext() {
|
|
548
|
+
return this.isOpen ? this.codecContext : null;
|
|
533
549
|
}
|
|
534
550
|
/**
|
|
535
|
-
* Receive
|
|
551
|
+
* Receive packet from encoder.
|
|
536
552
|
*
|
|
537
|
-
* Internal method to
|
|
553
|
+
* Internal method to get encoded packets from codec.
|
|
554
|
+
* Handles packet cloning and error checking.
|
|
538
555
|
*
|
|
539
|
-
*
|
|
540
|
-
* Clones the packet for the user to prevent internal buffer corruption.
|
|
556
|
+
* Direct mapping to avcodec_receive_packet().
|
|
541
557
|
*
|
|
542
|
-
* @returns
|
|
558
|
+
* @returns Cloned packet or null
|
|
559
|
+
*
|
|
560
|
+
* @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
|
|
543
561
|
*/
|
|
544
562
|
async receivePacket() {
|
|
545
563
|
// Clear previous packet data
|
|
@@ -560,10 +578,20 @@ export class Encoder {
|
|
|
560
578
|
}
|
|
561
579
|
}
|
|
562
580
|
/**
|
|
563
|
-
*
|
|
581
|
+
* Dispose of encoder.
|
|
582
|
+
*
|
|
583
|
+
* Implements Disposable interface for automatic cleanup.
|
|
584
|
+
* Equivalent to calling close().
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* ```typescript
|
|
588
|
+
* {
|
|
589
|
+
* using encoder = await Encoder.create('libx264', streamInfo);
|
|
590
|
+
* // Encode frames...
|
|
591
|
+
* } // Automatically closed
|
|
592
|
+
* ```
|
|
564
593
|
*
|
|
565
|
-
*
|
|
566
|
-
* Calls close() to free all resources.
|
|
594
|
+
* @see {@link close} For manual cleanup
|
|
567
595
|
*/
|
|
568
596
|
[Symbol.dispose]() {
|
|
569
597
|
this.close();
|