node-av 1.1.0 → 1.3.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 -59
- package/dist/api/bitstream-filter.d.ts +183 -123
- package/dist/api/bitstream-filter.js +185 -127
- package/dist/api/bitstream-filter.js.map +1 -1
- package/dist/api/decoder.d.ts +282 -130
- package/dist/api/decoder.js +290 -142
- package/dist/api/decoder.js.map +1 -1
- package/dist/api/encoder.d.ts +249 -160
- package/dist/api/encoder.js +276 -207
- package/dist/api/encoder.js.map +1 -1
- package/dist/api/filter-presets.d.ts +1944 -96
- package/dist/api/filter-presets.js +2059 -105
- package/dist/api/filter-presets.js.map +1 -1
- package/dist/api/filter.d.ts +264 -200
- package/dist/api/filter.js +269 -231
- package/dist/api/filter.js.map +1 -1
- package/dist/api/hardware.d.ts +246 -117
- package/dist/api/hardware.js +440 -217
- 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 -55
- package/dist/api/io-stream.js +43 -40
- package/dist/api/io-stream.js.map +1 -1
- package/dist/api/media-input.d.ts +242 -139
- package/dist/api/media-input.js +205 -103
- package/dist/api/media-input.js.map +1 -1
- package/dist/api/media-output.d.ts +208 -126
- package/dist/api/media-output.js +212 -126
- package/dist/api/media-output.js.map +1 -1
- package/dist/api/pipeline.d.ts +361 -38
- package/dist/api/pipeline.js +255 -14
- package/dist/api/pipeline.js.map +1 -1
- package/dist/api/types.d.ts +26 -187
- package/dist/api/utilities/audio-sample.d.ts +0 -8
- package/dist/api/utilities/audio-sample.js +0 -8
- package/dist/api/utilities/audio-sample.js.map +1 -1
- package/dist/api/utilities/channel-layout.d.ts +0 -8
- package/dist/api/utilities/channel-layout.js +0 -8
- package/dist/api/utilities/channel-layout.js.map +1 -1
- package/dist/api/utilities/image.d.ts +0 -8
- package/dist/api/utilities/image.js +0 -8
- package/dist/api/utilities/image.js.map +1 -1
- package/dist/api/utilities/index.d.ts +3 -3
- package/dist/api/utilities/index.js +3 -3
- package/dist/api/utilities/index.js.map +1 -1
- package/dist/api/utilities/media-type.d.ts +1 -9
- package/dist/api/utilities/media-type.js +1 -9
- package/dist/api/utilities/media-type.js.map +1 -1
- package/dist/api/utilities/pixel-format.d.ts +1 -9
- package/dist/api/utilities/pixel-format.js +1 -9
- package/dist/api/utilities/pixel-format.js.map +1 -1
- package/dist/api/utilities/sample-format.d.ts +1 -9
- package/dist/api/utilities/sample-format.js +1 -9
- package/dist/api/utilities/sample-format.js.map +1 -1
- package/dist/api/utilities/streaming.d.ts +0 -8
- package/dist/api/utilities/streaming.js +0 -8
- package/dist/api/utilities/streaming.js.map +1 -1
- package/dist/api/utilities/timestamp.d.ts +0 -8
- package/dist/api/utilities/timestamp.js +0 -8
- package/dist/api/utilities/timestamp.js.map +1 -1
- package/dist/api/utils.d.ts +1 -2
- package/dist/api/utils.js +11 -0
- package/dist/api/utils.js.map +1 -1
- package/dist/constants/constants.d.ts +1 -1
- package/dist/constants/constants.js +2 -0
- package/dist/constants/constants.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.d.ts +1 -0
- package/dist/lib/binding.js +7 -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 +266 -283
- package/dist/lib/codec.js +270 -287
- 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 +3 -2
- package/dist/lib/index.js +3 -3
- 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 +118 -106
- package/dist/lib/native-types.js +0 -7
- package/dist/lib/native-types.js.map +1 -1
- package/dist/lib/option.d.ts +437 -218
- package/dist/lib/option.js +462 -226
- 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 +435 -181
- package/dist/lib/utilities.js +438 -182
- package/dist/lib/utilities.js.map +1 -1
- package/install/check.js +0 -1
- package/install/ffmpeg.js +0 -11
- package/package.json +25 -18
- package/release_notes.md +24 -59
- package/CHANGELOG.md +0 -8
package/dist/api/encoder.js
CHANGED
|
@@ -1,91 +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 { 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';
|
|
1
|
+
import { AVERROR_EAGAIN, AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO } from '../constants/constants.js';
|
|
2
|
+
import { 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, FF_ENCODER_LIBX264 } from 'node-av/constants';
|
|
16
|
+
*
|
|
26
17
|
* // Create H.264 encoder
|
|
27
|
-
* const encoder = await Encoder.create(
|
|
18
|
+
* const encoder = await Encoder.create(FF_ENCODER_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,61 @@ 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
|
-
* @
|
|
94
|
+
* @throws {Error} If encoder not found or unsupported format
|
|
108
95
|
*
|
|
109
|
-
* @throws {
|
|
96
|
+
* @throws {FFmpegError} If codec initialization fails
|
|
110
97
|
*
|
|
111
98
|
* @example
|
|
112
99
|
* ```typescript
|
|
113
|
-
* //
|
|
114
|
-
* const
|
|
115
|
-
* const
|
|
100
|
+
* // From decoder stream info
|
|
101
|
+
* const streamInfo = decoder.getOutputStreamInfo();
|
|
102
|
+
* const encoder = await Encoder.create(FF_ENCODER_LIBX264, streamInfo, {
|
|
116
103
|
* bitrate: '5M',
|
|
117
|
-
* gopSize: 60
|
|
104
|
+
* gopSize: 60,
|
|
105
|
+
* options: {
|
|
106
|
+
* preset: 'fast',
|
|
107
|
+
* crf: '23'
|
|
108
|
+
* }
|
|
118
109
|
* });
|
|
110
|
+
* ```
|
|
119
111
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* // With custom stream info
|
|
115
|
+
* const encoder = await Encoder.create(FF_ENCODER_AAC, {
|
|
116
|
+
* type: 'audio',
|
|
117
|
+
* sampleRate: 48000,
|
|
118
|
+
* sampleFormat: AV_SAMPLE_FMT_FLTP,
|
|
119
|
+
* channelLayout: AV_CH_LAYOUT_STEREO,
|
|
120
|
+
* timeBase: { num: 1, den: 48000 }
|
|
121
|
+
* }, {
|
|
123
122
|
* bitrate: '192k'
|
|
124
123
|
* });
|
|
124
|
+
* ```
|
|
125
125
|
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* // Hardware encoder
|
|
129
|
+
* const hw = HardwareContext.auto();
|
|
130
|
+
* const encoder = await Encoder.create('hevc_videotoolbox', streamInfo, {
|
|
131
|
+
* hardware: hw,
|
|
132
|
+
* bitrate: '8M'
|
|
133
|
+
* });
|
|
126
134
|
* ```
|
|
135
|
+
*
|
|
136
|
+
* @see {@link Decoder.getOutputStreamInfo} For stream info source
|
|
137
|
+
* @see {@link EncoderOptions} For configuration options
|
|
127
138
|
*/
|
|
128
139
|
static async create(encoderCodec, input, options = {}) {
|
|
129
140
|
let codec = null;
|
|
@@ -149,9 +160,16 @@ export class Encoder {
|
|
|
149
160
|
// It's StreamInfo - apply manually
|
|
150
161
|
if (input.type === 'video' && codec.type === AVMEDIA_TYPE_VIDEO) {
|
|
151
162
|
const videoInfo = input;
|
|
163
|
+
const codecPixelformats = codec.pixelFormats;
|
|
164
|
+
if (codecPixelformats && !codecPixelformats.includes(videoInfo.pixelFormat)) {
|
|
165
|
+
codecContext.freeContext();
|
|
166
|
+
const pixelFormatName = avGetPixFmtName(videoInfo.pixelFormat) ?? 'unknown';
|
|
167
|
+
const codecPixFmtNames = codecPixelformats.map(avGetPixFmtName).filter(Boolean).join(', ');
|
|
168
|
+
throw new Error(`Unsupported pixel format for '${codecName}' encoder: ${pixelFormatName}! Supported formats: ${codecPixFmtNames}`);
|
|
169
|
+
}
|
|
152
170
|
codecContext.width = videoInfo.width;
|
|
153
171
|
codecContext.height = videoInfo.height;
|
|
154
|
-
codecContext.pixelFormat = videoInfo.pixelFormat;
|
|
172
|
+
codecContext.pixelFormat = videoInfo.pixelFormat;
|
|
155
173
|
// Set pkt_timebase and timeBase to input timebase
|
|
156
174
|
codecContext.pktTimebase = new Rational(videoInfo.timeBase.num, videoInfo.timeBase.den);
|
|
157
175
|
codecContext.timeBase = new Rational(videoInfo.timeBase.num, videoInfo.timeBase.den);
|
|
@@ -164,6 +182,13 @@ export class Encoder {
|
|
|
164
182
|
}
|
|
165
183
|
else if (input.type === 'audio' && codec.type === AVMEDIA_TYPE_AUDIO) {
|
|
166
184
|
const audioInfo = input;
|
|
185
|
+
const codecSampleFormats = codec.sampleFormats;
|
|
186
|
+
if (codecSampleFormats && !codecSampleFormats.includes(audioInfo.sampleFormat)) {
|
|
187
|
+
codecContext.freeContext();
|
|
188
|
+
const sampleFormatName = avGetSampleFmtName(audioInfo.sampleFormat) ?? 'unknown';
|
|
189
|
+
const supportedFormats = codecSampleFormats.map(avGetSampleFmtName).filter(Boolean).join(', ');
|
|
190
|
+
throw new Error(`Unsupported sample format for '${codecName}' encoder: ${sampleFormatName}! Supported formats: ${supportedFormats}`);
|
|
191
|
+
}
|
|
167
192
|
codecContext.sampleRate = audioInfo.sampleRate;
|
|
168
193
|
codecContext.sampleFormat = audioInfo.sampleFormat;
|
|
169
194
|
codecContext.channelLayout = audioInfo.channelLayout;
|
|
@@ -205,6 +230,7 @@ export class Encoder {
|
|
|
205
230
|
}
|
|
206
231
|
const isHWEncoder = codec.isHardwareAcceleratedEncoder();
|
|
207
232
|
if (isHWEncoder && !options.hardware) {
|
|
233
|
+
codecContext.freeContext();
|
|
208
234
|
throw new Error(`Hardware encoder '${codecName}' requires a hardware context`);
|
|
209
235
|
}
|
|
210
236
|
// Open codec
|
|
@@ -213,80 +239,80 @@ export class Encoder {
|
|
|
213
239
|
codecContext.freeContext();
|
|
214
240
|
FFmpegError.throwIfError(openRet, 'Failed to open encoder');
|
|
215
241
|
}
|
|
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
|
-
}
|
|
242
|
+
const encoder = new Encoder(codecContext, codec, isHWEncoder ? options.hardware : undefined);
|
|
222
243
|
return encoder;
|
|
223
244
|
}
|
|
224
245
|
/**
|
|
225
246
|
* Check if encoder is open.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```typescript
|
|
250
|
+
* if (encoder.isEncoderOpen) {
|
|
251
|
+
* const packet = await encoder.encode(frame);
|
|
252
|
+
* }
|
|
253
|
+
* ```
|
|
226
254
|
*/
|
|
227
255
|
get isEncoderOpen() {
|
|
228
256
|
return this.isOpen;
|
|
229
257
|
}
|
|
230
258
|
/**
|
|
231
|
-
*
|
|
259
|
+
* Check if encoder uses hardware acceleration.
|
|
232
260
|
*
|
|
233
|
-
*
|
|
234
|
-
* Useful for setting up subsequent processing stages.
|
|
261
|
+
* @returns true if hardware-accelerated
|
|
235
262
|
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
263
|
+
* @example
|
|
264
|
+
* ```typescript
|
|
265
|
+
* if (encoder.isHardware()) {
|
|
266
|
+
* console.log('Using GPU acceleration');
|
|
267
|
+
* }
|
|
268
|
+
* ```
|
|
238
269
|
*
|
|
239
|
-
* @
|
|
270
|
+
* @see {@link HardwareContext} For hardware setup
|
|
240
271
|
*/
|
|
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
|
-
}
|
|
272
|
+
isHardware() {
|
|
273
|
+
return !!this.hardware;
|
|
266
274
|
}
|
|
267
275
|
/**
|
|
268
|
-
* Encode a frame
|
|
276
|
+
* Encode a frame to a packet.
|
|
269
277
|
*
|
|
270
|
-
* Sends frame to encoder and attempts to receive
|
|
271
|
-
*
|
|
278
|
+
* Sends a frame to the encoder and attempts to receive an encoded packet.
|
|
279
|
+
* Handles internal buffering - may return null if more frames needed.
|
|
280
|
+
* Automatically manages encoder state and hardware context binding.
|
|
272
281
|
*
|
|
273
|
-
*
|
|
274
|
-
* The encoder may buffer frames before producing packets.
|
|
282
|
+
* Direct mapping to avcodec_send_frame() and avcodec_receive_packet().
|
|
275
283
|
*
|
|
276
|
-
* @param frame -
|
|
284
|
+
* @param frame - Raw frame to encode (or null to flush)
|
|
285
|
+
* @returns Encoded packet or null if more data needed
|
|
277
286
|
*
|
|
278
|
-
* @
|
|
287
|
+
* @throws {Error} If encoder is closed
|
|
279
288
|
*
|
|
280
|
-
* @throws {
|
|
289
|
+
* @throws {FFmpegError} If encoding fails
|
|
281
290
|
*
|
|
282
291
|
* @example
|
|
283
292
|
* ```typescript
|
|
284
293
|
* const packet = await encoder.encode(frame);
|
|
285
294
|
* if (packet) {
|
|
286
|
-
*
|
|
295
|
+
* console.log(`Encoded packet with PTS: ${packet.pts}`);
|
|
287
296
|
* await output.writePacket(packet);
|
|
297
|
+
* packet.free();
|
|
298
|
+
* }
|
|
299
|
+
* ```
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* // Encode loop
|
|
304
|
+
* for await (const frame of decoder.frames(input.packets())) {
|
|
305
|
+
* const packet = await encoder.encode(frame);
|
|
306
|
+
* if (packet) {
|
|
307
|
+
* await output.writePacket(packet);
|
|
308
|
+
* packet.free();
|
|
309
|
+
* }
|
|
310
|
+
* frame.free();
|
|
288
311
|
* }
|
|
289
312
|
* ```
|
|
313
|
+
*
|
|
314
|
+
* @see {@link packets} For automatic frame iteration
|
|
315
|
+
* @see {@link flush} For end-of-stream handling
|
|
290
316
|
*/
|
|
291
317
|
async encode(frame) {
|
|
292
318
|
if (!this.isOpen) {
|
|
@@ -297,7 +323,7 @@ export class Encoder {
|
|
|
297
323
|
if (this.hardware && frame?.hwFramesCtx && !this.codecContext.hwFramesCtx) {
|
|
298
324
|
// Use the hw_frames_ctx from the frame
|
|
299
325
|
this.codecContext.hwFramesCtx = frame.hwFramesCtx;
|
|
300
|
-
this.codecContext.pixelFormat = this.hardware.
|
|
326
|
+
this.codecContext.pixelFormat = this.hardware.devicePixelFormat;
|
|
301
327
|
}
|
|
302
328
|
// Send frame to encoder
|
|
303
329
|
const sendRet = await this.codecContext.sendFrame(frame);
|
|
@@ -312,33 +338,66 @@ export class Encoder {
|
|
|
312
338
|
}
|
|
313
339
|
}
|
|
314
340
|
// Try to receive packet
|
|
315
|
-
return this.receivePacket();
|
|
341
|
+
return await this.receivePacket();
|
|
316
342
|
}
|
|
317
343
|
/**
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
* Encodes all provided frames and yields resulting packets.
|
|
321
|
-
* Automatically handles encoder flushing at the end.
|
|
322
|
-
* Input frames are automatically freed after encoding.
|
|
344
|
+
* Encode frame stream to packet stream.
|
|
323
345
|
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
346
|
+
* High-level async generator for complete encoding pipeline.
|
|
347
|
+
* Automatically manages frame memory, encoder state,
|
|
348
|
+
* and flushes buffered packets at end.
|
|
349
|
+
* Primary interface for stream-based encoding.
|
|
326
350
|
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
351
|
+
* @param frames - Async iterable of frames (freed automatically)
|
|
352
|
+
* @yields Encoded packets (caller must free)
|
|
353
|
+
* @throws {Error} If encoder is closed
|
|
329
354
|
*
|
|
330
|
-
* @
|
|
355
|
+
* @throws {FFmpegError} If encoding fails
|
|
331
356
|
*
|
|
332
|
-
* @
|
|
357
|
+
* @example
|
|
358
|
+
* ```typescript
|
|
359
|
+
* // Basic encoding pipeline
|
|
360
|
+
* for await (const packet of encoder.packets(decoder.frames(input.packets()))) {
|
|
361
|
+
* await output.writePacket(packet);
|
|
362
|
+
* packet.free(); // Must free output packets
|
|
363
|
+
* }
|
|
364
|
+
* ```
|
|
333
365
|
*
|
|
334
366
|
* @example
|
|
335
367
|
* ```typescript
|
|
336
|
-
* //
|
|
337
|
-
*
|
|
368
|
+
* // With frame filtering
|
|
369
|
+
* async function* filteredFrames() {
|
|
370
|
+
* for await (const frame of decoder.frames(input.packets())) {
|
|
371
|
+
* await filter.filterFrame(frame);
|
|
372
|
+
* const filtered = await filter.getFrame();
|
|
373
|
+
* if (filtered) {
|
|
374
|
+
* yield filtered;
|
|
375
|
+
* }
|
|
376
|
+
* }
|
|
377
|
+
* }
|
|
378
|
+
*
|
|
379
|
+
* for await (const packet of encoder.packets(filteredFrames())) {
|
|
338
380
|
* await output.writePacket(packet);
|
|
339
|
-
* packet.free();
|
|
381
|
+
* packet.free();
|
|
340
382
|
* }
|
|
341
383
|
* ```
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```typescript
|
|
387
|
+
* // Pipeline integration
|
|
388
|
+
* import { pipeline } from 'node-av/api';
|
|
389
|
+
*
|
|
390
|
+
* const control = pipeline(
|
|
391
|
+
* input,
|
|
392
|
+
* decoder,
|
|
393
|
+
* encoder,
|
|
394
|
+
* output
|
|
395
|
+
* );
|
|
396
|
+
* await control.completion;
|
|
397
|
+
* ```
|
|
398
|
+
*
|
|
399
|
+
* @see {@link encode} For single frame encoding
|
|
400
|
+
* @see {@link Decoder.frames} For frame source
|
|
342
401
|
*/
|
|
343
402
|
async *packets(frames) {
|
|
344
403
|
if (!this.isOpen) {
|
|
@@ -364,27 +423,31 @@ export class Encoder {
|
|
|
364
423
|
}
|
|
365
424
|
}
|
|
366
425
|
/**
|
|
367
|
-
* Flush encoder and get
|
|
426
|
+
* Flush encoder and get buffered packet.
|
|
368
427
|
*
|
|
369
|
-
*
|
|
370
|
-
* Call repeatedly until
|
|
428
|
+
* Signals end-of-stream and retrieves remaining packets.
|
|
429
|
+
* Call repeatedly until null to get all buffered packets.
|
|
430
|
+
* Essential for ensuring all frames are encoded.
|
|
371
431
|
*
|
|
372
|
-
*
|
|
373
|
-
* Retrieves buffered packets from the encoder.
|
|
432
|
+
* Direct mapping to avcodec_send_frame(NULL).
|
|
374
433
|
*
|
|
375
|
-
* @returns
|
|
434
|
+
* @returns Buffered packet or null if none remaining
|
|
376
435
|
*
|
|
377
436
|
* @throws {Error} If encoder is closed
|
|
378
437
|
*
|
|
379
438
|
* @example
|
|
380
439
|
* ```typescript
|
|
381
|
-
* // Flush
|
|
440
|
+
* // Flush remaining packets
|
|
382
441
|
* let packet;
|
|
383
442
|
* while ((packet = await encoder.flush()) !== null) {
|
|
384
|
-
*
|
|
443
|
+
* console.log('Got buffered packet');
|
|
385
444
|
* await output.writePacket(packet);
|
|
445
|
+
* packet.free();
|
|
386
446
|
* }
|
|
387
447
|
* ```
|
|
448
|
+
*
|
|
449
|
+
* @see {@link flushPackets} For async iteration
|
|
450
|
+
* @see {@link packets} For complete encoding pipeline
|
|
388
451
|
*/
|
|
389
452
|
async flush() {
|
|
390
453
|
if (!this.isOpen) {
|
|
@@ -393,26 +456,30 @@ export class Encoder {
|
|
|
393
456
|
// Send flush frame (null)
|
|
394
457
|
await this.codecContext.sendFrame(null);
|
|
395
458
|
// Receive packet
|
|
396
|
-
return this.receivePacket();
|
|
459
|
+
return await this.receivePacket();
|
|
397
460
|
}
|
|
398
461
|
/**
|
|
399
|
-
* Flush
|
|
400
|
-
*
|
|
401
|
-
* More convenient than calling flush() in a loop.
|
|
402
|
-
* Automatically sends flush signal and yields all buffered packets.
|
|
462
|
+
* Flush all buffered packets as async generator.
|
|
403
463
|
*
|
|
404
|
-
*
|
|
464
|
+
* Convenient async iteration over remaining packets.
|
|
465
|
+
* Automatically handles repeated flush calls.
|
|
466
|
+
* Useful for end-of-stream processing.
|
|
405
467
|
*
|
|
468
|
+
* @yields Buffered packets
|
|
406
469
|
* @throws {Error} If encoder is closed
|
|
407
470
|
*
|
|
408
471
|
* @example
|
|
409
472
|
* ```typescript
|
|
410
|
-
* //
|
|
473
|
+
* // Flush at end of encoding
|
|
411
474
|
* for await (const packet of encoder.flushPackets()) {
|
|
412
|
-
*
|
|
413
|
-
*
|
|
475
|
+
* console.log('Processing buffered packet');
|
|
476
|
+
* await output.writePacket(packet);
|
|
477
|
+
* packet.free();
|
|
414
478
|
* }
|
|
415
479
|
* ```
|
|
480
|
+
*
|
|
481
|
+
* @see {@link flush} For single packet flush
|
|
482
|
+
* @see {@link packets} For complete pipeline
|
|
416
483
|
*/
|
|
417
484
|
async *flushPackets() {
|
|
418
485
|
if (!this.isOpen) {
|
|
@@ -426,84 +493,76 @@ export class Encoder {
|
|
|
426
493
|
/**
|
|
427
494
|
* Close encoder and free resources.
|
|
428
495
|
*
|
|
429
|
-
*
|
|
496
|
+
* Releases codec context and internal packet buffer.
|
|
497
|
+
* Safe to call multiple times.
|
|
498
|
+
* Does NOT dispose hardware context - caller is responsible.
|
|
499
|
+
* Automatically called by Symbol.dispose.
|
|
430
500
|
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
501
|
+
* @example
|
|
502
|
+
* ```typescript
|
|
503
|
+
* const encoder = await Encoder.create(FF_ENCODER_LIBX264, streamInfo);
|
|
504
|
+
* try {
|
|
505
|
+
* // Use encoder
|
|
506
|
+
* } finally {
|
|
507
|
+
* encoder.close();
|
|
508
|
+
* }
|
|
509
|
+
* ```
|
|
510
|
+
*
|
|
511
|
+
* @see {@link Symbol.dispose} For automatic cleanup
|
|
433
512
|
*/
|
|
434
513
|
close() {
|
|
435
514
|
if (!this.isOpen)
|
|
436
515
|
return;
|
|
437
516
|
this.packet.free();
|
|
438
517
|
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
518
|
this.isOpen = false;
|
|
443
519
|
}
|
|
444
520
|
/**
|
|
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.
|
|
521
|
+
* Get encoder codec.
|
|
454
522
|
*
|
|
455
|
-
*
|
|
523
|
+
* Returns the codec used by this encoder.
|
|
524
|
+
* Useful for checking codec capabilities and properties.
|
|
456
525
|
*
|
|
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
|
|
526
|
+
* @returns Codec instance
|
|
470
527
|
*
|
|
471
528
|
* @example
|
|
472
529
|
* ```typescript
|
|
473
|
-
* const
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
* }
|
|
530
|
+
* const codec = encoder.getCodec();
|
|
531
|
+
* console.log(`Using codec: ${codec.name}`);
|
|
532
|
+
* console.log(`Capabilities: ${codec.capabilities}`);
|
|
477
533
|
* ```
|
|
534
|
+
*
|
|
535
|
+
* @see {@link Codec} For codec properties
|
|
478
536
|
*/
|
|
479
|
-
|
|
480
|
-
return this.
|
|
537
|
+
getCodec() {
|
|
538
|
+
return this.codec;
|
|
481
539
|
}
|
|
482
540
|
/**
|
|
483
|
-
* Get
|
|
541
|
+
* Get underlying codec context.
|
|
484
542
|
*
|
|
485
|
-
* Returns
|
|
543
|
+
* Returns the internal codec context for advanced operations.
|
|
544
|
+
* Returns null if encoder is closed.
|
|
486
545
|
*
|
|
487
|
-
* @returns
|
|
546
|
+
* @returns Codec context or null
|
|
488
547
|
*
|
|
489
|
-
* @
|
|
490
|
-
* ```typescript
|
|
491
|
-
* const formats = encoder.getSupportedPixelFormats();
|
|
492
|
-
* console.log(`Encoder supports: ${formats.join(', ')}`);
|
|
493
|
-
* ```
|
|
548
|
+
* @internal
|
|
494
549
|
*/
|
|
495
|
-
|
|
496
|
-
return this.
|
|
550
|
+
getCodecContext() {
|
|
551
|
+
return this.isOpen ? this.codecContext : null;
|
|
497
552
|
}
|
|
498
553
|
/**
|
|
499
|
-
* Receive
|
|
554
|
+
* Receive packet from encoder.
|
|
555
|
+
*
|
|
556
|
+
* Internal method to get encoded packets from codec.
|
|
557
|
+
* Handles packet cloning and error checking.
|
|
558
|
+
*
|
|
559
|
+
* Direct mapping to avcodec_receive_packet().
|
|
500
560
|
*
|
|
501
|
-
*
|
|
561
|
+
* @returns Cloned packet or null
|
|
502
562
|
*
|
|
503
|
-
*
|
|
504
|
-
* Clones the packet for the user to prevent internal buffer corruption.
|
|
563
|
+
* @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
|
|
505
564
|
*
|
|
506
|
-
* @
|
|
565
|
+
* @internal
|
|
507
566
|
*/
|
|
508
567
|
async receivePacket() {
|
|
509
568
|
// Clear previous packet data
|
|
@@ -524,10 +583,20 @@ export class Encoder {
|
|
|
524
583
|
}
|
|
525
584
|
}
|
|
526
585
|
/**
|
|
527
|
-
*
|
|
586
|
+
* Dispose of encoder.
|
|
587
|
+
*
|
|
588
|
+
* Implements Disposable interface for automatic cleanup.
|
|
589
|
+
* Equivalent to calling close().
|
|
590
|
+
*
|
|
591
|
+
* @example
|
|
592
|
+
* ```typescript
|
|
593
|
+
* {
|
|
594
|
+
* using encoder = await Encoder.create(FF_ENCODER_LIBX264, streamInfo);
|
|
595
|
+
* // Encode frames...
|
|
596
|
+
* } // Automatically closed
|
|
597
|
+
* ```
|
|
528
598
|
*
|
|
529
|
-
*
|
|
530
|
-
* Calls close() to free all resources.
|
|
599
|
+
* @see {@link close} For manual cleanup
|
|
531
600
|
*/
|
|
532
601
|
[Symbol.dispose]() {
|
|
533
602
|
this.close();
|