node-av 1.1.0 → 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 +51 -38
- package/dist/api/bitstream-filter.d.ts +180 -123
- package/dist/api/bitstream-filter.js +180 -125
- package/dist/api/bitstream-filter.js.map +1 -1
- package/dist/api/decoder.d.ts +279 -132
- package/dist/api/decoder.js +285 -142
- package/dist/api/decoder.js.map +1 -1
- package/dist/api/encoder.d.ts +246 -162
- package/dist/api/encoder.js +272 -208
- package/dist/api/encoder.js.map +1 -1
- package/dist/api/filter-presets.d.ts +690 -94
- package/dist/api/filter-presets.js +686 -102
- package/dist/api/filter-presets.js.map +1 -1
- package/dist/api/filter.d.ts +249 -213
- package/dist/api/filter.js +252 -242
- package/dist/api/filter.js.map +1 -1
- package/dist/api/hardware.d.ts +224 -117
- package/dist/api/hardware.js +380 -214
- package/dist/api/hardware.js.map +1 -1
- package/dist/api/index.d.ts +3 -3
- package/dist/api/index.js +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/io-stream.d.ts +65 -61
- package/dist/api/io-stream.js +43 -46
- package/dist/api/io-stream.js.map +1 -1
- package/dist/api/media-input.d.ts +242 -140
- package/dist/api/media-input.js +205 -103
- package/dist/api/media-input.js.map +1 -1
- package/dist/api/media-output.d.ts +206 -128
- package/dist/api/media-output.js +210 -128
- 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 +21 -187
- 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/audio-fifo.d.ts +127 -170
- package/dist/lib/audio-fifo.js +130 -173
- package/dist/lib/audio-fifo.js.map +1 -1
- 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 +68 -54
- package/dist/lib/bitstream-filter.js +68 -54
- package/dist/lib/bitstream-filter.js.map +1 -1
- package/dist/lib/codec-context.d.ts +316 -380
- package/dist/lib/codec-context.js +316 -381
- package/dist/lib/codec-context.js.map +1 -1
- package/dist/lib/codec-parameters.d.ts +160 -170
- package/dist/lib/codec-parameters.js +162 -172
- package/dist/lib/codec-parameters.js.map +1 -1
- package/dist/lib/codec-parser.d.ts +91 -104
- package/dist/lib/codec-parser.js +92 -103
- package/dist/lib/codec-parser.js.map +1 -1
- package/dist/lib/codec.d.ts +264 -281
- package/dist/lib/codec.js +268 -285
- package/dist/lib/codec.js.map +1 -1
- package/dist/lib/dictionary.d.ts +149 -203
- package/dist/lib/dictionary.js +158 -212
- package/dist/lib/dictionary.js.map +1 -1
- package/dist/lib/error.d.ts +96 -130
- package/dist/lib/error.js +98 -128
- package/dist/lib/error.js.map +1 -1
- package/dist/lib/filter-context.d.ts +284 -218
- package/dist/lib/filter-context.js +290 -227
- package/dist/lib/filter-context.js.map +1 -1
- package/dist/lib/filter-graph.d.ts +251 -292
- 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 +93 -111
- package/dist/lib/filter.js.map +1 -1
- package/dist/lib/format-context.d.ts +320 -428
- package/dist/lib/format-context.js +313 -385
- package/dist/lib/format-context.js.map +1 -1
- package/dist/lib/frame.d.ts +262 -405
- package/dist/lib/frame.js +263 -408
- package/dist/lib/frame.js.map +1 -1
- package/dist/lib/hardware-device-context.d.ts +149 -203
- 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 +170 -180
- 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 -1
- package/dist/lib/index.js +2 -2
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/input-format.d.ts +89 -117
- package/dist/lib/input-format.js +89 -117
- package/dist/lib/input-format.js.map +1 -1
- package/dist/lib/io-context.d.ts +209 -241
- package/dist/lib/io-context.js +220 -252
- package/dist/lib/io-context.js.map +1 -1
- package/dist/lib/log.d.ts +85 -119
- package/dist/lib/log.js +85 -122
- package/dist/lib/log.js.map +1 -1
- package/dist/lib/native-types.d.ts +117 -106
- package/dist/lib/native-types.js +0 -7
- package/dist/lib/native-types.js.map +1 -1
- package/dist/lib/option.d.ts +284 -241
- package/dist/lib/option.js +309 -249
- package/dist/lib/option.js.map +1 -1
- package/dist/lib/output-format.d.ts +77 -101
- package/dist/lib/output-format.js +77 -101
- package/dist/lib/output-format.js.map +1 -1
- package/dist/lib/packet.d.ts +172 -240
- 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 +241 -325
- 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 +129 -173
- package/dist/lib/software-scale-context.js +131 -175
- package/dist/lib/software-scale-context.js.map +1 -1
- package/dist/lib/stream.d.ts +87 -197
- package/dist/lib/stream.js +87 -197
- package/dist/lib/stream.js.map +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 +21 -12
- package/release_notes.md +43 -59
- package/CHANGELOG.md +0 -8
package/dist/api/encoder.js
CHANGED
|
@@ -1,91 +1,78 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Encoder - High-level wrapper for media encoding
|
|
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
1
|
import { AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO } from '../constants/constants.js';
|
|
13
|
-
import { AVERROR_EAGAIN, Codec, CodecContext, FFmpegError, Packet, Rational } from '../lib/index.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
|
-
*
|
|
33
|
+
* await output.writePacket(packet);
|
|
34
|
+
* packet.free();
|
|
43
35
|
* }
|
|
44
|
-
*
|
|
45
|
-
* // Flush encoder
|
|
46
|
-
* let packet;
|
|
47
|
-
* while ((packet = await encoder.flush()) !== null) {
|
|
48
|
-
* // Process final packets
|
|
49
|
-
* }
|
|
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 for hardware pixel format
|
|
66
|
+
hardware;
|
|
77
67
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* @param codecContext - Initialized codec context
|
|
83
|
-
* @param codecName - Name of the codec
|
|
84
|
-
* @param hardware - Optional hardware context for hardware pixel format
|
|
68
|
+
* @param codecContext - Configured codec context
|
|
69
|
+
* @param codec - Encoder codec
|
|
70
|
+
* @param hardware - Optional hardware context
|
|
71
|
+
* @internal
|
|
85
72
|
*/
|
|
86
|
-
constructor(codecContext,
|
|
73
|
+
constructor(codecContext, codec, hardware) {
|
|
87
74
|
this.codecContext = codecContext;
|
|
88
|
-
this.
|
|
75
|
+
this.codec = codec;
|
|
89
76
|
this.hardware = hardware;
|
|
90
77
|
this.packet = new Packet();
|
|
91
78
|
this.packet.alloc();
|
|
@@ -93,37 +80,60 @@ export class Encoder {
|
|
|
93
80
|
/**
|
|
94
81
|
* Create an encoder with specified codec and options.
|
|
95
82
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
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.
|
|
98
86
|
*
|
|
99
|
-
*
|
|
100
|
-
* configures the context with provided options, and opens it.
|
|
101
|
-
* Handles hardware setup including shared frames context for zero-copy.
|
|
87
|
+
* Direct mapping to avcodec_find_encoder_by_name() or avcodec_find_encoder().
|
|
102
88
|
*
|
|
103
|
-
* @param encoderCodec - Codec to use for encoding
|
|
104
|
-
* @param input - Stream
|
|
89
|
+
* @param encoderCodec - Codec name, ID, or instance to use for encoding
|
|
90
|
+
* @param input - Stream information to configure encoder
|
|
105
91
|
* @param options - Encoder configuration options
|
|
92
|
+
* @returns Configured encoder instance
|
|
106
93
|
*
|
|
107
|
-
* @
|
|
108
|
-
*
|
|
109
|
-
* @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
|
|
110
96
|
*
|
|
111
97
|
* @example
|
|
112
98
|
* ```typescript
|
|
113
|
-
* //
|
|
114
|
-
* const
|
|
115
|
-
* const
|
|
99
|
+
* // From decoder stream info
|
|
100
|
+
* const streamInfo = decoder.getOutputStreamInfo();
|
|
101
|
+
* const encoder = await Encoder.create('libx264', streamInfo, {
|
|
116
102
|
* bitrate: '5M',
|
|
117
|
-
* gopSize: 60
|
|
103
|
+
* gopSize: 60,
|
|
104
|
+
* options: {
|
|
105
|
+
* preset: 'fast',
|
|
106
|
+
* crf: '23'
|
|
107
|
+
* }
|
|
118
108
|
* });
|
|
109
|
+
* ```
|
|
119
110
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
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
|
+
* }, {
|
|
123
121
|
* bitrate: '192k'
|
|
124
122
|
* });
|
|
123
|
+
* ```
|
|
125
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
|
+
* });
|
|
126
133
|
* ```
|
|
134
|
+
*
|
|
135
|
+
* @see {@link Decoder.getOutputStreamInfo} For stream info source
|
|
136
|
+
* @see {@link EncoderOptions} For configuration options
|
|
127
137
|
*/
|
|
128
138
|
static async create(encoderCodec, input, options = {}) {
|
|
129
139
|
let codec = null;
|
|
@@ -149,9 +159,16 @@ export class Encoder {
|
|
|
149
159
|
// It's StreamInfo - apply manually
|
|
150
160
|
if (input.type === 'video' && codec.type === AVMEDIA_TYPE_VIDEO) {
|
|
151
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}`);
|
|
168
|
+
}
|
|
152
169
|
codecContext.width = videoInfo.width;
|
|
153
170
|
codecContext.height = videoInfo.height;
|
|
154
|
-
codecContext.pixelFormat = videoInfo.pixelFormat;
|
|
171
|
+
codecContext.pixelFormat = videoInfo.pixelFormat;
|
|
155
172
|
// Set pkt_timebase and timeBase to input timebase
|
|
156
173
|
codecContext.pktTimebase = new Rational(videoInfo.timeBase.num, videoInfo.timeBase.den);
|
|
157
174
|
codecContext.timeBase = new Rational(videoInfo.timeBase.num, videoInfo.timeBase.den);
|
|
@@ -164,6 +181,13 @@ export class Encoder {
|
|
|
164
181
|
}
|
|
165
182
|
else if (input.type === 'audio' && codec.type === AVMEDIA_TYPE_AUDIO) {
|
|
166
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}`);
|
|
190
|
+
}
|
|
167
191
|
codecContext.sampleRate = audioInfo.sampleRate;
|
|
168
192
|
codecContext.sampleFormat = audioInfo.sampleFormat;
|
|
169
193
|
codecContext.channelLayout = audioInfo.channelLayout;
|
|
@@ -205,6 +229,7 @@ export class Encoder {
|
|
|
205
229
|
}
|
|
206
230
|
const isHWEncoder = codec.isHardwareAcceleratedEncoder();
|
|
207
231
|
if (isHWEncoder && !options.hardware) {
|
|
232
|
+
codecContext.freeContext();
|
|
208
233
|
throw new Error(`Hardware encoder '${codecName}' requires a hardware context`);
|
|
209
234
|
}
|
|
210
235
|
// Open codec
|
|
@@ -213,80 +238,79 @@ export class Encoder {
|
|
|
213
238
|
codecContext.freeContext();
|
|
214
239
|
FFmpegError.throwIfError(openRet, 'Failed to open encoder');
|
|
215
240
|
}
|
|
216
|
-
const encoder = new Encoder(codecContext,
|
|
217
|
-
// Get supported formats from codec (for validation and helpers)
|
|
218
|
-
if (codec.pixelFormats) {
|
|
219
|
-
encoder.supportedFormats = codec.pixelFormats;
|
|
220
|
-
encoder.preferredFormat = encoder.supportedFormats[0];
|
|
221
|
-
}
|
|
241
|
+
const encoder = new Encoder(codecContext, codec, isHWEncoder ? options.hardware : undefined);
|
|
222
242
|
return encoder;
|
|
223
243
|
}
|
|
224
244
|
/**
|
|
225
245
|
* Check if encoder is open.
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* if (encoder.isEncoderOpen) {
|
|
250
|
+
* const packet = await encoder.encode(frame);
|
|
251
|
+
* }
|
|
252
|
+
* ```
|
|
226
253
|
*/
|
|
227
254
|
get isEncoderOpen() {
|
|
228
255
|
return this.isOpen;
|
|
229
256
|
}
|
|
230
257
|
/**
|
|
231
|
-
*
|
|
258
|
+
* Check if encoder uses hardware acceleration.
|
|
232
259
|
*
|
|
233
|
-
*
|
|
234
|
-
* Useful for setting up subsequent processing stages.
|
|
260
|
+
* @returns true if hardware-accelerated
|
|
235
261
|
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```typescript
|
|
264
|
+
* if (encoder.isHardware()) {
|
|
265
|
+
* console.log('Using GPU acceleration');
|
|
266
|
+
* }
|
|
267
|
+
* ```
|
|
238
268
|
*
|
|
239
|
-
* @
|
|
269
|
+
* @see {@link HardwareContext} For hardware setup
|
|
240
270
|
*/
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
// For hardware encoders, we need to return the hardware pixel format
|
|
244
|
-
// even if it hasn't been set yet on the codec context
|
|
245
|
-
const pixelFormat = this.hardware?.getHardwarePixelFormat() ?? this.codecContext.pixelFormat;
|
|
246
|
-
return {
|
|
247
|
-
type: 'video',
|
|
248
|
-
width: this.codecContext.width,
|
|
249
|
-
height: this.codecContext.height,
|
|
250
|
-
pixelFormat,
|
|
251
|
-
timeBase: this.codecContext.timeBase,
|
|
252
|
-
frameRate: this.codecContext.framerate,
|
|
253
|
-
sampleAspectRatio: this.codecContext.sampleAspectRatio,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
// For audio
|
|
258
|
-
return {
|
|
259
|
-
type: 'audio',
|
|
260
|
-
sampleRate: this.codecContext.sampleRate,
|
|
261
|
-
sampleFormat: this.codecContext.sampleFormat,
|
|
262
|
-
channelLayout: this.codecContext.channelLayout,
|
|
263
|
-
timeBase: this.codecContext.timeBase,
|
|
264
|
-
};
|
|
265
|
-
}
|
|
271
|
+
isHardware() {
|
|
272
|
+
return !!this.hardware;
|
|
266
273
|
}
|
|
267
274
|
/**
|
|
268
|
-
* Encode a frame
|
|
269
|
-
*
|
|
270
|
-
* Sends frame to encoder and attempts to receive a packet.
|
|
271
|
-
* May return null if encoder needs more data.
|
|
275
|
+
* Encode a frame to a packet.
|
|
272
276
|
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
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.
|
|
275
280
|
*
|
|
276
|
-
*
|
|
281
|
+
* Direct mapping to avcodec_send_frame() and avcodec_receive_packet().
|
|
277
282
|
*
|
|
278
|
-
* @
|
|
283
|
+
* @param frame - Raw frame to encode (or null to flush)
|
|
284
|
+
* @returns Encoded packet or null if more data needed
|
|
279
285
|
*
|
|
280
|
-
* @throws {Error} If encoder is closed
|
|
286
|
+
* @throws {Error} If encoder is closed
|
|
287
|
+
* @throws {FFmpegError} If encoding fails
|
|
281
288
|
*
|
|
282
289
|
* @example
|
|
283
290
|
* ```typescript
|
|
284
291
|
* const packet = await encoder.encode(frame);
|
|
285
292
|
* if (packet) {
|
|
286
|
-
*
|
|
293
|
+
* console.log(`Encoded packet with PTS: ${packet.pts}`);
|
|
287
294
|
* await output.writePacket(packet);
|
|
295
|
+
* packet.free();
|
|
288
296
|
* }
|
|
289
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();
|
|
309
|
+
* }
|
|
310
|
+
* ```
|
|
311
|
+
*
|
|
312
|
+
* @see {@link packets} For automatic frame iteration
|
|
313
|
+
* @see {@link flush} For end-of-stream handling
|
|
290
314
|
*/
|
|
291
315
|
async encode(frame) {
|
|
292
316
|
if (!this.isOpen) {
|
|
@@ -297,7 +321,7 @@ export class Encoder {
|
|
|
297
321
|
if (this.hardware && frame?.hwFramesCtx && !this.codecContext.hwFramesCtx) {
|
|
298
322
|
// Use the hw_frames_ctx from the frame
|
|
299
323
|
this.codecContext.hwFramesCtx = frame.hwFramesCtx;
|
|
300
|
-
this.codecContext.pixelFormat = this.hardware.
|
|
324
|
+
this.codecContext.pixelFormat = this.hardware.devicePixelFormat;
|
|
301
325
|
}
|
|
302
326
|
// Send frame to encoder
|
|
303
327
|
const sendRet = await this.codecContext.sendFrame(frame);
|
|
@@ -312,33 +336,65 @@ export class Encoder {
|
|
|
312
336
|
}
|
|
313
337
|
}
|
|
314
338
|
// Try to receive packet
|
|
315
|
-
return this.receivePacket();
|
|
339
|
+
return await this.receivePacket();
|
|
316
340
|
}
|
|
317
341
|
/**
|
|
318
|
-
*
|
|
342
|
+
* Encode frame stream to packet stream.
|
|
319
343
|
*
|
|
320
|
-
*
|
|
321
|
-
* Automatically
|
|
322
|
-
*
|
|
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.
|
|
323
348
|
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
* Input frames are automatically freed after processing.
|
|
329
|
-
*
|
|
330
|
-
* @param frames - Async iterable of frames to encode (will be freed automatically)
|
|
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
|
|
331
353
|
*
|
|
332
|
-
* @
|
|
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
|
+
* ```
|
|
333
362
|
*
|
|
334
363
|
* @example
|
|
335
364
|
* ```typescript
|
|
336
|
-
* //
|
|
337
|
-
*
|
|
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())) {
|
|
338
377
|
* await output.writePacket(packet);
|
|
339
|
-
* packet.free();
|
|
378
|
+
* packet.free();
|
|
340
379
|
* }
|
|
341
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
|
|
342
398
|
*/
|
|
343
399
|
async *packets(frames) {
|
|
344
400
|
if (!this.isOpen) {
|
|
@@ -364,27 +420,31 @@ export class Encoder {
|
|
|
364
420
|
}
|
|
365
421
|
}
|
|
366
422
|
/**
|
|
367
|
-
* Flush encoder and get
|
|
423
|
+
* Flush encoder and get buffered packet.
|
|
368
424
|
*
|
|
369
|
-
*
|
|
370
|
-
* 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.
|
|
371
428
|
*
|
|
372
|
-
*
|
|
373
|
-
* Retrieves buffered packets from the encoder.
|
|
429
|
+
* Direct mapping to avcodec_send_frame(NULL).
|
|
374
430
|
*
|
|
375
|
-
* @returns
|
|
431
|
+
* @returns Buffered packet or null if none remaining
|
|
376
432
|
*
|
|
377
433
|
* @throws {Error} If encoder is closed
|
|
378
434
|
*
|
|
379
435
|
* @example
|
|
380
436
|
* ```typescript
|
|
381
|
-
* // Flush
|
|
437
|
+
* // Flush remaining packets
|
|
382
438
|
* let packet;
|
|
383
439
|
* while ((packet = await encoder.flush()) !== null) {
|
|
384
|
-
*
|
|
440
|
+
* console.log('Got buffered packet');
|
|
385
441
|
* await output.writePacket(packet);
|
|
442
|
+
* packet.free();
|
|
386
443
|
* }
|
|
387
444
|
* ```
|
|
445
|
+
*
|
|
446
|
+
* @see {@link flushPackets} For async iteration
|
|
447
|
+
* @see {@link packets} For complete encoding pipeline
|
|
388
448
|
*/
|
|
389
449
|
async flush() {
|
|
390
450
|
if (!this.isOpen) {
|
|
@@ -393,26 +453,30 @@ export class Encoder {
|
|
|
393
453
|
// Send flush frame (null)
|
|
394
454
|
await this.codecContext.sendFrame(null);
|
|
395
455
|
// Receive packet
|
|
396
|
-
return this.receivePacket();
|
|
456
|
+
return await this.receivePacket();
|
|
397
457
|
}
|
|
398
458
|
/**
|
|
399
|
-
* Flush
|
|
400
|
-
*
|
|
401
|
-
* More convenient than calling flush() in a loop.
|
|
402
|
-
* Automatically sends flush signal and yields all buffered packets.
|
|
459
|
+
* Flush all buffered packets as async generator.
|
|
403
460
|
*
|
|
404
|
-
*
|
|
461
|
+
* Convenient async iteration over remaining packets.
|
|
462
|
+
* Automatically handles repeated flush calls.
|
|
463
|
+
* Useful for end-of-stream processing.
|
|
405
464
|
*
|
|
465
|
+
* @yields Buffered packets
|
|
406
466
|
* @throws {Error} If encoder is closed
|
|
407
467
|
*
|
|
408
468
|
* @example
|
|
409
469
|
* ```typescript
|
|
410
|
-
* //
|
|
470
|
+
* // Flush at end of encoding
|
|
411
471
|
* for await (const packet of encoder.flushPackets()) {
|
|
412
|
-
*
|
|
413
|
-
*
|
|
472
|
+
* console.log('Processing buffered packet');
|
|
473
|
+
* await output.writePacket(packet);
|
|
474
|
+
* packet.free();
|
|
414
475
|
* }
|
|
415
476
|
* ```
|
|
477
|
+
*
|
|
478
|
+
* @see {@link flush} For single packet flush
|
|
479
|
+
* @see {@link packets} For complete pipeline
|
|
416
480
|
*/
|
|
417
481
|
async *flushPackets() {
|
|
418
482
|
if (!this.isOpen) {
|
|
@@ -426,84 +490,74 @@ export class Encoder {
|
|
|
426
490
|
/**
|
|
427
491
|
* Close encoder and free resources.
|
|
428
492
|
*
|
|
429
|
-
*
|
|
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.
|
|
430
497
|
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
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
|
|
433
509
|
*/
|
|
434
510
|
close() {
|
|
435
511
|
if (!this.isOpen)
|
|
436
512
|
return;
|
|
437
513
|
this.packet.free();
|
|
438
514
|
this.codecContext.freeContext();
|
|
439
|
-
// NOTE: We do NOT dispose the hardware context here anymore
|
|
440
|
-
// The caller who created the HardwareContext is responsible for disposing it
|
|
441
|
-
// This allows reusing the same HardwareContext for multiple encoders
|
|
442
515
|
this.isOpen = false;
|
|
443
516
|
}
|
|
444
517
|
/**
|
|
445
|
-
* Get
|
|
446
|
-
*/
|
|
447
|
-
getCodecName() {
|
|
448
|
-
return this.codecName;
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* Get codec context for advanced configuration.
|
|
452
|
-
*
|
|
453
|
-
* Use with caution - direct manipulation may cause issues.
|
|
518
|
+
* Get encoder codec.
|
|
454
519
|
*
|
|
455
|
-
*
|
|
520
|
+
* Returns the codec used by this encoder.
|
|
521
|
+
* Useful for checking codec capabilities and properties.
|
|
456
522
|
*
|
|
457
|
-
* @returns
|
|
458
|
-
*
|
|
459
|
-
* @internal
|
|
460
|
-
*/
|
|
461
|
-
getCodecContext() {
|
|
462
|
-
return this.isOpen ? this.codecContext : null;
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Get the preferred pixel format for this encoder.
|
|
466
|
-
*
|
|
467
|
-
* Returns the first supported format, which is usually the most efficient.
|
|
468
|
-
*
|
|
469
|
-
* @returns Preferred pixel format or null if not available
|
|
523
|
+
* @returns Codec instance
|
|
470
524
|
*
|
|
471
525
|
* @example
|
|
472
526
|
* ```typescript
|
|
473
|
-
* const
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
* }
|
|
527
|
+
* const codec = encoder.getCodec();
|
|
528
|
+
* console.log(`Using codec: ${codec.name}`);
|
|
529
|
+
* console.log(`Capabilities: ${codec.capabilities}`);
|
|
477
530
|
* ```
|
|
531
|
+
*
|
|
532
|
+
* @see {@link Codec} For codec properties
|
|
478
533
|
*/
|
|
479
|
-
|
|
480
|
-
return this.
|
|
534
|
+
getCodec() {
|
|
535
|
+
return this.codec;
|
|
481
536
|
}
|
|
482
537
|
/**
|
|
483
|
-
* Get
|
|
538
|
+
* Get underlying codec context.
|
|
484
539
|
*
|
|
485
|
-
* Returns
|
|
540
|
+
* Returns the internal codec context for advanced operations.
|
|
541
|
+
* Returns null if encoder is closed.
|
|
486
542
|
*
|
|
487
|
-
* @returns
|
|
543
|
+
* @returns Codec context or null
|
|
488
544
|
*
|
|
489
|
-
* @
|
|
490
|
-
* ```typescript
|
|
491
|
-
* const formats = encoder.getSupportedPixelFormats();
|
|
492
|
-
* console.log(`Encoder supports: ${formats.join(', ')}`);
|
|
493
|
-
* ```
|
|
545
|
+
* @internal
|
|
494
546
|
*/
|
|
495
|
-
|
|
496
|
-
return this.
|
|
547
|
+
getCodecContext() {
|
|
548
|
+
return this.isOpen ? this.codecContext : null;
|
|
497
549
|
}
|
|
498
550
|
/**
|
|
499
|
-
* Receive
|
|
551
|
+
* Receive packet from encoder.
|
|
500
552
|
*
|
|
501
|
-
* Internal method to
|
|
553
|
+
* Internal method to get encoded packets from codec.
|
|
554
|
+
* Handles packet cloning and error checking.
|
|
502
555
|
*
|
|
503
|
-
*
|
|
504
|
-
* Clones the packet for the user to prevent internal buffer corruption.
|
|
556
|
+
* Direct mapping to avcodec_receive_packet().
|
|
505
557
|
*
|
|
506
|
-
* @returns
|
|
558
|
+
* @returns Cloned packet or null
|
|
559
|
+
*
|
|
560
|
+
* @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
|
|
507
561
|
*/
|
|
508
562
|
async receivePacket() {
|
|
509
563
|
// Clear previous packet data
|
|
@@ -524,10 +578,20 @@ export class Encoder {
|
|
|
524
578
|
}
|
|
525
579
|
}
|
|
526
580
|
/**
|
|
527
|
-
*
|
|
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
|
+
* ```
|
|
528
593
|
*
|
|
529
|
-
*
|
|
530
|
-
* Calls close() to free all resources.
|
|
594
|
+
* @see {@link close} For manual cleanup
|
|
531
595
|
*/
|
|
532
596
|
[Symbol.dispose]() {
|
|
533
597
|
this.close();
|