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