node-av 5.2.4 → 6.0.0-beta.11
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 +15 -1
- package/dist/api/bitstream-filter.d.ts +110 -87
- package/dist/api/bitstream-filter.js +161 -103
- package/dist/api/bitstream-filter.js.map +1 -1
- package/dist/api/decoder.d.ts +177 -15
- package/dist/api/decoder.js +335 -28
- package/dist/api/decoder.js.map +1 -1
- package/dist/api/demuxer.d.ts +30 -24
- package/dist/api/demuxer.js +4 -5
- package/dist/api/demuxer.js.map +1 -1
- package/dist/api/device.js.map +1 -1
- package/dist/api/encoder-pool.d.ts +220 -0
- package/dist/api/encoder-pool.js +285 -0
- package/dist/api/encoder-pool.js.map +1 -0
- package/dist/api/encoder.d.ts +194 -7
- package/dist/api/encoder.js +431 -71
- package/dist/api/encoder.js.map +1 -1
- package/dist/api/filter-complex.d.ts +2 -1
- package/dist/api/filter-complex.js +11 -7
- package/dist/api/filter-complex.js.map +1 -1
- package/dist/api/filter-presets.d.ts +130 -654
- package/dist/api/filter-presets.js +180 -858
- package/dist/api/filter-presets.js.map +1 -1
- package/dist/api/filter.js +12 -9
- package/dist/api/filter.js.map +1 -1
- package/dist/api/fmp4-stream.js +1 -1
- package/dist/api/fmp4-stream.js.map +1 -1
- package/dist/api/index.d.ts +6 -6
- package/dist/api/index.js +8 -8
- package/dist/api/index.js.map +1 -1
- package/dist/api/muxer.d.ts +43 -15
- package/dist/api/muxer.js +79 -27
- package/dist/api/muxer.js.map +1 -1
- package/dist/api/pipeline.d.ts +50 -0
- package/dist/api/pipeline.js +138 -22
- package/dist/api/pipeline.js.map +1 -1
- package/dist/api/probe.d.ts +128 -0
- package/dist/api/probe.js +227 -0
- package/dist/api/probe.js.map +1 -0
- package/dist/api/rtp-stream.d.ts +14 -11
- package/dist/api/rtp-stream.js +23 -48
- package/dist/api/rtp-stream.js.map +1 -1
- package/dist/api/scaler.d.ts +431 -0
- package/dist/api/scaler.js +620 -0
- package/dist/api/scaler.js.map +1 -0
- package/dist/api/utilities/async-queue.d.ts +27 -1
- package/dist/api/utilities/async-queue.js +38 -3
- package/dist/api/utilities/async-queue.js.map +1 -1
- package/dist/api/utilities/codec-format.d.ts +87 -0
- package/dist/api/utilities/codec-format.js +117 -0
- package/dist/api/utilities/codec-format.js.map +1 -0
- package/dist/api/utilities/electron-shared-texture.d.ts +41 -1
- package/dist/api/utilities/electron-shared-texture.js +41 -4
- package/dist/api/utilities/electron-shared-texture.js.map +1 -1
- package/dist/api/utilities/index.d.ts +2 -1
- package/dist/api/utilities/index.js +2 -0
- package/dist/api/utilities/index.js.map +1 -1
- package/dist/api/webrtc-stream.d.ts +0 -1
- package/dist/api/webrtc-stream.js +0 -1
- package/dist/api/webrtc-stream.js.map +1 -1
- package/dist/constants/bsf-options.d.ts +333 -0
- package/dist/constants/bsf-options.js +7 -0
- package/dist/constants/bsf-options.js.map +1 -0
- package/dist/constants/constants.d.ts +109 -0
- package/dist/constants/constants.js +110 -0
- package/dist/constants/constants.js.map +1 -1
- package/dist/constants/decoders.d.ts +636 -618
- package/dist/constants/decoders.js +1 -3
- package/dist/constants/decoders.js.map +1 -1
- package/dist/constants/encoders.d.ts +300 -282
- package/dist/constants/encoders.js +0 -2
- package/dist/constants/encoders.js.map +1 -1
- package/dist/constants/filter-options.d.ts +10915 -0
- package/dist/constants/filter-options.js +7 -0
- package/dist/constants/filter-options.js.map +1 -0
- package/dist/constants/format-options.d.ts +3056 -0
- package/dist/constants/format-options.js +7 -0
- package/dist/constants/format-options.js.map +1 -0
- package/dist/constants/formats.d.ts +18 -0
- package/dist/constants/formats.js +7 -0
- package/dist/constants/formats.js.map +1 -0
- package/dist/constants/index.d.ts +5 -0
- package/dist/constants/options.d.ts +4073 -0
- package/dist/constants/options.js +7 -0
- package/dist/constants/options.js.map +1 -0
- package/dist/lib/binding.d.ts +5 -1
- package/dist/lib/binding.js.map +1 -1
- package/dist/lib/codec.d.ts +36 -5
- package/dist/lib/codec.js +37 -4
- package/dist/lib/codec.js.map +1 -1
- package/dist/lib/dictionary.d.ts +1 -1
- package/dist/lib/dictionary.js.map +1 -1
- package/dist/lib/error.d.ts +69 -0
- package/dist/lib/error.js +92 -0
- package/dist/lib/error.js.map +1 -1
- package/dist/lib/frame.d.ts +55 -3
- package/dist/lib/frame.js +59 -3
- package/dist/lib/frame.js.map +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/native-types.d.ts +68 -0
- package/dist/lib/packet.d.ts +22 -3
- package/dist/lib/packet.js +24 -3
- package/dist/lib/packet.js.map +1 -1
- package/dist/lib/utilities.d.ts +45 -0
- package/dist/lib/utilities.js +49 -0
- package/dist/lib/utilities.js.map +1 -1
- package/dist/webrtc/index.d.ts +3 -0
- package/dist/webrtc/index.js +7 -0
- package/dist/webrtc/index.js.map +1 -0
- package/package.json +34 -23
package/dist/api/encoder.js
CHANGED
|
@@ -50,7 +50,8 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
-
|
|
53
|
+
/* eslint-disable @stylistic/indent-binary-ops */
|
|
54
|
+
import { AV_CHANNEL_ORDER_UNSPEC, AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE, AV_CODEC_CAP_PARAM_CHANGE, AV_CODEC_FLAG_COPY_OPAQUE, AV_CODEC_FLAG_FRAME_DURATION, AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX, AV_PICTURE_TYPE_NONE, AV_PIX_FMT_NONE, AV_PKT_FLAG_TRUSTED, AVCHROMA_LOC_UNSPECIFIED, AVERROR_EAGAIN, AVERROR_ENCODER_NOT_FOUND, AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO, EOF, SWS_BILINEAR, } from '../constants/constants.js';
|
|
54
55
|
import { CodecContext } from '../lib/codec-context.js';
|
|
55
56
|
import { Codec } from '../lib/codec.js';
|
|
56
57
|
import { Dictionary } from '../lib/dictionary.js';
|
|
@@ -58,10 +59,13 @@ import { FFmpegError } from '../lib/error.js';
|
|
|
58
59
|
import { Frame } from '../lib/frame.js';
|
|
59
60
|
import { Packet } from '../lib/packet.js';
|
|
60
61
|
import { Rational } from '../lib/rational.js';
|
|
61
|
-
import {
|
|
62
|
+
import { SoftwareResampleContext } from '../lib/software-resample-context.js';
|
|
63
|
+
import { SoftwareScaleContext } from '../lib/software-scale-context.js';
|
|
64
|
+
import { avChannelLayoutDefault, avGetPixFmtName, avGetSampleFmtName, avRescaleQ } from '../lib/utilities.js';
|
|
62
65
|
import { AudioFrameBuffer } from './audio-frame-buffer.js';
|
|
63
66
|
import { FRAME_THREAD_QUEUE_SIZE, PACKET_THREAD_QUEUE_SIZE } from './constants.js';
|
|
64
67
|
import { AsyncQueue } from './utilities/async-queue.js';
|
|
68
|
+
import { pickSupportedLayout, pickSupportedPixelFormat, pickSupportedRate, pickSupportedSampleFormat } from './utilities/codec-format.js';
|
|
65
69
|
import { SchedulerControl } from './utilities/scheduler.js';
|
|
66
70
|
import { parseBitrate } from './utils.js';
|
|
67
71
|
/**
|
|
@@ -132,6 +136,14 @@ export class Encoder {
|
|
|
132
136
|
opts;
|
|
133
137
|
options;
|
|
134
138
|
audioFrameBuffer;
|
|
139
|
+
autoResample;
|
|
140
|
+
audioResampler;
|
|
141
|
+
resampledFrame;
|
|
142
|
+
audioInputLayout;
|
|
143
|
+
autoFormat;
|
|
144
|
+
videoScaler;
|
|
145
|
+
scaledFrame;
|
|
146
|
+
videoTargetFormat;
|
|
135
147
|
// Worker pattern for push-based processing
|
|
136
148
|
inputQueue;
|
|
137
149
|
outputQueue;
|
|
@@ -154,10 +166,12 @@ export class Encoder {
|
|
|
154
166
|
this.codec = codec;
|
|
155
167
|
this.options = options;
|
|
156
168
|
this.opts = opts;
|
|
169
|
+
this.autoResample = options.autoResample ?? false;
|
|
170
|
+
this.autoFormat = options.autoFormat ?? false;
|
|
157
171
|
this.packet = new Packet();
|
|
158
172
|
this.packet.alloc();
|
|
159
|
-
this.inputQueue = new AsyncQueue(FRAME_THREAD_QUEUE_SIZE);
|
|
160
|
-
this.outputQueue = new AsyncQueue(PACKET_THREAD_QUEUE_SIZE);
|
|
173
|
+
this.inputQueue = new AsyncQueue(FRAME_THREAD_QUEUE_SIZE, (f) => f.free());
|
|
174
|
+
this.outputQueue = new AsyncQueue(PACKET_THREAD_QUEUE_SIZE, (p) => p.free());
|
|
161
175
|
}
|
|
162
176
|
/**
|
|
163
177
|
* Create an encoder with specified codec and options.
|
|
@@ -262,8 +276,11 @@ export class Encoder {
|
|
|
262
276
|
if (options.threadType !== undefined) {
|
|
263
277
|
codecContext.threadType = options.threadType;
|
|
264
278
|
}
|
|
265
|
-
|
|
266
|
-
|
|
279
|
+
// Loose view for internal use: the public signature narrows `options` to the
|
|
280
|
+
// codec, but internally codec options are handled as a generic dictionary.
|
|
281
|
+
const looseOptions = options;
|
|
282
|
+
const opts = looseOptions.options ? Dictionary.fromObject(looseOptions.options) : undefined;
|
|
283
|
+
const encoder = new Encoder(codecContext, codec, looseOptions, opts);
|
|
267
284
|
if (options.signal) {
|
|
268
285
|
options.signal.throwIfAborted();
|
|
269
286
|
encoder.signal = options.signal;
|
|
@@ -375,14 +392,125 @@ export class Encoder {
|
|
|
375
392
|
if (options.threadType !== undefined) {
|
|
376
393
|
codecContext.threadType = options.threadType;
|
|
377
394
|
}
|
|
378
|
-
|
|
379
|
-
|
|
395
|
+
// Loose view for internal use: the public signature narrows `options` to the
|
|
396
|
+
// codec, but internally codec options are handled as a generic dictionary.
|
|
397
|
+
const looseOptions = options;
|
|
398
|
+
const opts = looseOptions.options ? Dictionary.fromObject(looseOptions.options) : undefined;
|
|
399
|
+
const encoder = new Encoder(codecContext, codec, looseOptions, opts);
|
|
380
400
|
if (options.signal) {
|
|
381
401
|
options.signal.throwIfAborted();
|
|
382
402
|
encoder.signal = options.signal;
|
|
383
403
|
}
|
|
384
404
|
return encoder;
|
|
385
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* Encode a single frame into a self-contained image buffer.
|
|
408
|
+
*
|
|
409
|
+
* One-shot, stateless helper for intra-only image codecs (MJPEG, PNG, WebP, ...).
|
|
410
|
+
* Creates a fresh encoder, encodes the frame, flushes and frees everything in one call.
|
|
411
|
+
* The encoder adopts dimensions, pixel format and hardware context from the frame,
|
|
412
|
+
* so any frame size works without reconfiguration.
|
|
413
|
+
*
|
|
414
|
+
* @param encoderCodec - Encoder codec (name, ID, branded constant, or Codec)
|
|
415
|
+
*
|
|
416
|
+
* @param frame - Frame to encode
|
|
417
|
+
*
|
|
418
|
+
* @param options - Optional encoder configuration (e.g. `{ options: { q: 3 } }` for MJPEG quality)
|
|
419
|
+
*
|
|
420
|
+
* @returns Encoded image bytes
|
|
421
|
+
*
|
|
422
|
+
* @throws {FFmpegError} If the encoder is not found or encoding fails
|
|
423
|
+
*
|
|
424
|
+
* @throws {Error} If the encoder produced no output
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* ```typescript
|
|
428
|
+
* const jpeg = await Encoder.encodeOne(FF_ENCODER_MJPEG, frame, { options: { q: 3 } });
|
|
429
|
+
* ```
|
|
430
|
+
*
|
|
431
|
+
* @see {@link EncoderPool} For reusing encoders across recurring resolutions
|
|
432
|
+
*/
|
|
433
|
+
static async encodeOne(encoderCodec, frame, options = {}) {
|
|
434
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
435
|
+
try {
|
|
436
|
+
const encoder = __addDisposableResource(env_1, await Encoder.create(encoderCodec, options), false);
|
|
437
|
+
const packets = [...(await encoder.encodeAll(frame)), ...(await encoder.encodeAll(null))];
|
|
438
|
+
try {
|
|
439
|
+
const data = packets[0]?.data;
|
|
440
|
+
if (!data) {
|
|
441
|
+
throw new Error(`Encoder '${encoder.getCodec().name}' produced no output for frame`);
|
|
442
|
+
}
|
|
443
|
+
return data;
|
|
444
|
+
}
|
|
445
|
+
finally {
|
|
446
|
+
for (const packet of packets) {
|
|
447
|
+
packet.free();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
catch (e_1) {
|
|
452
|
+
env_1.error = e_1;
|
|
453
|
+
env_1.hasError = true;
|
|
454
|
+
}
|
|
455
|
+
finally {
|
|
456
|
+
__disposeResources(env_1);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Encode a single frame into a self-contained image buffer synchronously.
|
|
461
|
+
* Synchronous version of encodeOne.
|
|
462
|
+
*
|
|
463
|
+
* One-shot, stateless helper for intra-only image codecs (MJPEG, PNG, WebP, ...).
|
|
464
|
+
* Creates a fresh encoder, encodes the frame, flushes and frees everything in one call.
|
|
465
|
+
* The encoder adopts dimensions, pixel format and hardware context from the frame,
|
|
466
|
+
* so any frame size works without reconfiguration.
|
|
467
|
+
*
|
|
468
|
+
* @param encoderCodec - Encoder codec (name, ID, branded constant, or Codec)
|
|
469
|
+
*
|
|
470
|
+
* @param frame - Frame to encode
|
|
471
|
+
*
|
|
472
|
+
* @param options - Optional encoder configuration (e.g. `{ options: { q: 3 } }` for MJPEG quality)
|
|
473
|
+
*
|
|
474
|
+
* @returns Encoded image bytes
|
|
475
|
+
*
|
|
476
|
+
* @throws {FFmpegError} If the encoder is not found or encoding fails
|
|
477
|
+
*
|
|
478
|
+
* @throws {Error} If the encoder produced no output
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* ```typescript
|
|
482
|
+
* const jpeg = Encoder.encodeOneSync(FF_ENCODER_MJPEG, frame, { options: { q: 3 } });
|
|
483
|
+
* ```
|
|
484
|
+
*
|
|
485
|
+
* @see {@link encodeOne} For async version
|
|
486
|
+
* @see {@link EncoderPool} For reusing encoders across recurring resolutions
|
|
487
|
+
*/
|
|
488
|
+
static encodeOneSync(encoderCodec, frame, options = {}) {
|
|
489
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
490
|
+
try {
|
|
491
|
+
const encoder = __addDisposableResource(env_2, Encoder.createSync(encoderCodec, options), false);
|
|
492
|
+
const packets = [...encoder.encodeAllSync(frame), ...encoder.encodeAllSync(null)];
|
|
493
|
+
try {
|
|
494
|
+
const data = packets[0]?.data;
|
|
495
|
+
if (!data) {
|
|
496
|
+
throw new Error(`Encoder '${encoder.getCodec().name}' produced no output for frame`);
|
|
497
|
+
}
|
|
498
|
+
return data;
|
|
499
|
+
}
|
|
500
|
+
finally {
|
|
501
|
+
for (const packet of packets) {
|
|
502
|
+
packet.free();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
catch (e_2) {
|
|
507
|
+
env_2.error = e_2;
|
|
508
|
+
env_2.hasError = true;
|
|
509
|
+
}
|
|
510
|
+
finally {
|
|
511
|
+
__disposeResources(env_2);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
386
514
|
/**
|
|
387
515
|
* Check if encoder is open.
|
|
388
516
|
*
|
|
@@ -622,8 +750,15 @@ export class Encoder {
|
|
|
622
750
|
// Open encoder if not already done
|
|
623
751
|
this.initializePromise ??= this.initialize(frame);
|
|
624
752
|
await this.initializePromise;
|
|
753
|
+
// Give an unspecified-layout frame the concrete native layout the codec was
|
|
754
|
+
// opened with (and the resampler configured for), so both accept it.
|
|
755
|
+
if (this.audioInputLayout) {
|
|
756
|
+
frame.channelLayout = this.audioInputLayout;
|
|
757
|
+
}
|
|
758
|
+
// Convert to the codec's format first (audio resample / video pixfmt).
|
|
759
|
+
const input = this.audioResampler ? this.resampleAudio(frame) : this.videoScaler ? this.scaleVideo(frame) : frame;
|
|
625
760
|
// Prepare frame for encoding (set quality, validate channel count)
|
|
626
|
-
this.prepareFrameForEncoding(
|
|
761
|
+
this.prepareFrameForEncoding(input);
|
|
627
762
|
const encode = async (newFrame) => {
|
|
628
763
|
const sendRet = await this.codecContext.sendFrame(newFrame);
|
|
629
764
|
if (sendRet < 0 && sendRet !== AVERROR_EOF) {
|
|
@@ -633,10 +768,10 @@ export class Encoder {
|
|
|
633
768
|
};
|
|
634
769
|
if (this.audioFrameBuffer) {
|
|
635
770
|
// Push frame into buffer - actual sending happens in receive()
|
|
636
|
-
await this.audioFrameBuffer.push(
|
|
771
|
+
await this.audioFrameBuffer.push(input);
|
|
637
772
|
}
|
|
638
773
|
else {
|
|
639
|
-
await encode(
|
|
774
|
+
await encode(input);
|
|
640
775
|
}
|
|
641
776
|
}
|
|
642
777
|
/**
|
|
@@ -690,8 +825,15 @@ export class Encoder {
|
|
|
690
825
|
if (!this.initialized) {
|
|
691
826
|
this.initializeSync(frame);
|
|
692
827
|
}
|
|
828
|
+
// Give an unspecified-layout frame the concrete native layout the codec was
|
|
829
|
+
// opened with (and the resampler configured for), so both accept it.
|
|
830
|
+
if (this.audioInputLayout) {
|
|
831
|
+
frame.channelLayout = this.audioInputLayout;
|
|
832
|
+
}
|
|
833
|
+
// Convert to the codec's format first (audio resample / video pixfmt).
|
|
834
|
+
const input = this.audioResampler ? this.resampleAudio(frame) : this.videoScaler ? this.scaleVideo(frame) : frame;
|
|
693
835
|
// Prepare frame for encoding (set quality, validate channel count)
|
|
694
|
-
this.prepareFrameForEncoding(
|
|
836
|
+
this.prepareFrameForEncoding(input);
|
|
695
837
|
const encode = (newFrame) => {
|
|
696
838
|
const sendRet = this.codecContext.sendFrameSync(newFrame);
|
|
697
839
|
if (sendRet < 0 && sendRet !== AVERROR_EOF) {
|
|
@@ -701,10 +843,10 @@ export class Encoder {
|
|
|
701
843
|
};
|
|
702
844
|
if (this.audioFrameBuffer) {
|
|
703
845
|
// Push frame into buffer - actual sending happens in receiveSync()
|
|
704
|
-
this.audioFrameBuffer.pushSync(
|
|
846
|
+
this.audioFrameBuffer.pushSync(input);
|
|
705
847
|
}
|
|
706
848
|
else {
|
|
707
|
-
encode(
|
|
849
|
+
encode(input);
|
|
708
850
|
}
|
|
709
851
|
}
|
|
710
852
|
/**
|
|
@@ -906,9 +1048,9 @@ export class Encoder {
|
|
|
906
1048
|
return;
|
|
907
1049
|
}
|
|
908
1050
|
for await (const frame_1 of frames) {
|
|
909
|
-
const
|
|
1051
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
910
1052
|
try {
|
|
911
|
-
const frame = __addDisposableResource(
|
|
1053
|
+
const frame = __addDisposableResource(env_3, frame_1, false);
|
|
912
1054
|
this.signal?.throwIfAborted();
|
|
913
1055
|
if (frame === null) {
|
|
914
1056
|
yield* finalize();
|
|
@@ -916,12 +1058,12 @@ export class Encoder {
|
|
|
916
1058
|
}
|
|
917
1059
|
yield* processFrame(frame);
|
|
918
1060
|
}
|
|
919
|
-
catch (
|
|
920
|
-
|
|
921
|
-
|
|
1061
|
+
catch (e_3) {
|
|
1062
|
+
env_3.error = e_3;
|
|
1063
|
+
env_3.hasError = true;
|
|
922
1064
|
}
|
|
923
1065
|
finally {
|
|
924
|
-
__disposeResources(
|
|
1066
|
+
__disposeResources(env_3);
|
|
925
1067
|
}
|
|
926
1068
|
}
|
|
927
1069
|
}
|
|
@@ -1017,9 +1159,9 @@ export class Encoder {
|
|
|
1017
1159
|
}
|
|
1018
1160
|
// Case 3: Iterable of frames
|
|
1019
1161
|
for (const frame_2 of frames) {
|
|
1020
|
-
const
|
|
1162
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
1021
1163
|
try {
|
|
1022
|
-
const frame = __addDisposableResource(
|
|
1164
|
+
const frame = __addDisposableResource(env_4, frame_2, false);
|
|
1023
1165
|
// Check for EOF signal from upstream
|
|
1024
1166
|
if (frame === null) {
|
|
1025
1167
|
yield* finalize();
|
|
@@ -1027,12 +1169,12 @@ export class Encoder {
|
|
|
1027
1169
|
}
|
|
1028
1170
|
yield* processFrame(frame);
|
|
1029
1171
|
}
|
|
1030
|
-
catch (
|
|
1031
|
-
|
|
1032
|
-
|
|
1172
|
+
catch (e_4) {
|
|
1173
|
+
env_4.error = e_4;
|
|
1174
|
+
env_4.hasError = true;
|
|
1033
1175
|
}
|
|
1034
1176
|
finally {
|
|
1035
|
-
__disposeResources(
|
|
1177
|
+
__disposeResources(env_4);
|
|
1036
1178
|
}
|
|
1037
1179
|
}
|
|
1038
1180
|
// No fallback flush - only flush on explicit EOF
|
|
@@ -1069,23 +1211,33 @@ export class Encoder {
|
|
|
1069
1211
|
if (this.isClosed || !this.initialized) {
|
|
1070
1212
|
return;
|
|
1071
1213
|
}
|
|
1214
|
+
// Drain samples buffered inside the resampler into the FIFO/encoder first.
|
|
1215
|
+
const drained = this.drainResampler();
|
|
1216
|
+
if (drained) {
|
|
1217
|
+
if (this.audioFrameBuffer) {
|
|
1218
|
+
await this.audioFrameBuffer.push(drained);
|
|
1219
|
+
}
|
|
1220
|
+
else {
|
|
1221
|
+
await this.codecContext.sendFrame(drained);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1072
1224
|
// If using AudioFrameBuffer, flush remaining buffered samples first
|
|
1073
1225
|
if (this.audioFrameBuffer && this.audioFrameBuffer.size > 0) {
|
|
1074
1226
|
// Pull any remaining partial frame (may be less than frameSize)
|
|
1075
1227
|
// For the final frame, we pad or truncate as needed
|
|
1076
1228
|
let _bufferedFrame;
|
|
1077
1229
|
while (!this.isClosed && (_bufferedFrame = await this.audioFrameBuffer.pull()) !== null) {
|
|
1078
|
-
const
|
|
1230
|
+
const env_5 = { stack: [], error: void 0, hasError: false };
|
|
1079
1231
|
try {
|
|
1080
|
-
const bufferedFrame = __addDisposableResource(
|
|
1232
|
+
const bufferedFrame = __addDisposableResource(env_5, _bufferedFrame, false);
|
|
1081
1233
|
await this.codecContext.sendFrame(bufferedFrame);
|
|
1082
1234
|
}
|
|
1083
|
-
catch (
|
|
1084
|
-
|
|
1085
|
-
|
|
1235
|
+
catch (e_5) {
|
|
1236
|
+
env_5.error = e_5;
|
|
1237
|
+
env_5.hasError = true;
|
|
1086
1238
|
}
|
|
1087
1239
|
finally {
|
|
1088
|
-
__disposeResources(
|
|
1240
|
+
__disposeResources(env_5);
|
|
1089
1241
|
}
|
|
1090
1242
|
}
|
|
1091
1243
|
}
|
|
@@ -1129,23 +1281,33 @@ export class Encoder {
|
|
|
1129
1281
|
if (this.isClosed || !this.initialized) {
|
|
1130
1282
|
return;
|
|
1131
1283
|
}
|
|
1284
|
+
// Drain samples buffered inside the resampler into the FIFO/encoder first.
|
|
1285
|
+
const drained = this.drainResampler();
|
|
1286
|
+
if (drained) {
|
|
1287
|
+
if (this.audioFrameBuffer) {
|
|
1288
|
+
this.audioFrameBuffer.pushSync(drained);
|
|
1289
|
+
}
|
|
1290
|
+
else {
|
|
1291
|
+
this.codecContext.sendFrameSync(drained);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1132
1294
|
// If using AudioFrameBuffer, flush remaining buffered samples first
|
|
1133
1295
|
if (this.audioFrameBuffer && this.audioFrameBuffer.size > 0) {
|
|
1134
1296
|
// Pull any remaining partial frame (may be less than frameSize)
|
|
1135
1297
|
// For the final frame, we pad or truncate as needed
|
|
1136
1298
|
let _bufferedFrame;
|
|
1137
1299
|
while (!this.isClosed && (_bufferedFrame = this.audioFrameBuffer.pullSync()) !== null) {
|
|
1138
|
-
const
|
|
1300
|
+
const env_6 = { stack: [], error: void 0, hasError: false };
|
|
1139
1301
|
try {
|
|
1140
|
-
const bufferedFrame = __addDisposableResource(
|
|
1302
|
+
const bufferedFrame = __addDisposableResource(env_6, _bufferedFrame, false);
|
|
1141
1303
|
this.codecContext.sendFrameSync(bufferedFrame);
|
|
1142
1304
|
}
|
|
1143
|
-
catch (
|
|
1144
|
-
|
|
1145
|
-
|
|
1305
|
+
catch (e_6) {
|
|
1306
|
+
env_6.error = e_6;
|
|
1307
|
+
env_6.hasError = true;
|
|
1146
1308
|
}
|
|
1147
1309
|
finally {
|
|
1148
|
-
__disposeResources(
|
|
1310
|
+
__disposeResources(env_6);
|
|
1149
1311
|
}
|
|
1150
1312
|
}
|
|
1151
1313
|
}
|
|
@@ -1288,19 +1450,19 @@ export class Encoder {
|
|
|
1288
1450
|
// Clear previous packet data
|
|
1289
1451
|
this.packet.unref();
|
|
1290
1452
|
if (this.audioFrameBuffer?.hasFrame()) {
|
|
1291
|
-
const
|
|
1453
|
+
const env_7 = { stack: [], error: void 0, hasError: false };
|
|
1292
1454
|
try {
|
|
1293
|
-
const bufferedFrame = __addDisposableResource(
|
|
1455
|
+
const bufferedFrame = __addDisposableResource(env_7, await this.audioFrameBuffer.pull(), false);
|
|
1294
1456
|
if (bufferedFrame) {
|
|
1295
1457
|
await this.codecContext.sendFrame(bufferedFrame);
|
|
1296
1458
|
}
|
|
1297
1459
|
}
|
|
1298
|
-
catch (
|
|
1299
|
-
|
|
1300
|
-
|
|
1460
|
+
catch (e_7) {
|
|
1461
|
+
env_7.error = e_7;
|
|
1462
|
+
env_7.hasError = true;
|
|
1301
1463
|
}
|
|
1302
1464
|
finally {
|
|
1303
|
-
__disposeResources(
|
|
1465
|
+
__disposeResources(env_7);
|
|
1304
1466
|
}
|
|
1305
1467
|
}
|
|
1306
1468
|
const ret = await this.codecContext.receivePacket(this.packet);
|
|
@@ -1393,19 +1555,19 @@ export class Encoder {
|
|
|
1393
1555
|
// Clear previous packet data
|
|
1394
1556
|
this.packet.unref();
|
|
1395
1557
|
if (this.audioFrameBuffer?.hasFrame()) {
|
|
1396
|
-
const
|
|
1558
|
+
const env_8 = { stack: [], error: void 0, hasError: false };
|
|
1397
1559
|
try {
|
|
1398
|
-
const bufferedFrame = __addDisposableResource(
|
|
1560
|
+
const bufferedFrame = __addDisposableResource(env_8, this.audioFrameBuffer.pullSync(), false);
|
|
1399
1561
|
if (bufferedFrame) {
|
|
1400
1562
|
this.codecContext.sendFrameSync(bufferedFrame);
|
|
1401
1563
|
}
|
|
1402
1564
|
}
|
|
1403
|
-
catch (
|
|
1404
|
-
|
|
1405
|
-
|
|
1565
|
+
catch (e_8) {
|
|
1566
|
+
env_8.error = e_8;
|
|
1567
|
+
env_8.hasError = true;
|
|
1406
1568
|
}
|
|
1407
1569
|
finally {
|
|
1408
|
-
__disposeResources(
|
|
1570
|
+
__disposeResources(env_8);
|
|
1409
1571
|
}
|
|
1410
1572
|
}
|
|
1411
1573
|
const ret = this.codecContext.receivePacketSync(this.packet);
|
|
@@ -1488,10 +1650,21 @@ export class Encoder {
|
|
|
1488
1650
|
return;
|
|
1489
1651
|
}
|
|
1490
1652
|
this.isClosed = true;
|
|
1491
|
-
// Close queues
|
|
1492
1653
|
this.inputQueue.close();
|
|
1493
1654
|
this.outputQueue.close();
|
|
1655
|
+
this.inputQueue.clear();
|
|
1656
|
+
this.outputQueue.clear();
|
|
1494
1657
|
this.packet.free();
|
|
1658
|
+
this.audioFrameBuffer?.[Symbol.dispose]();
|
|
1659
|
+
this.audioFrameBuffer = undefined;
|
|
1660
|
+
this.audioResampler?.[Symbol.dispose]();
|
|
1661
|
+
this.audioResampler = undefined;
|
|
1662
|
+
this.resampledFrame?.free();
|
|
1663
|
+
this.resampledFrame = undefined;
|
|
1664
|
+
this.videoScaler?.[Symbol.dispose]();
|
|
1665
|
+
this.videoScaler = undefined;
|
|
1666
|
+
this.scaledFrame?.free();
|
|
1667
|
+
this.scaledFrame = undefined;
|
|
1495
1668
|
this.codecContext.freeContext();
|
|
1496
1669
|
this.initialized = false;
|
|
1497
1670
|
}
|
|
@@ -1535,9 +1708,9 @@ export class Encoder {
|
|
|
1535
1708
|
try {
|
|
1536
1709
|
// Outer loop - receive frames
|
|
1537
1710
|
while (!this.inputQueue.isClosed) {
|
|
1538
|
-
const
|
|
1711
|
+
const env_9 = { stack: [], error: void 0, hasError: false };
|
|
1539
1712
|
try {
|
|
1540
|
-
const frame = __addDisposableResource(
|
|
1713
|
+
const frame = __addDisposableResource(env_9, await this.inputQueue.receive(), false);
|
|
1541
1714
|
if (!frame)
|
|
1542
1715
|
break;
|
|
1543
1716
|
// Open encoder if not already done
|
|
@@ -1556,12 +1729,12 @@ export class Encoder {
|
|
|
1556
1729
|
await this.outputQueue.send(packet); // Only send actual packets
|
|
1557
1730
|
}
|
|
1558
1731
|
}
|
|
1559
|
-
catch (
|
|
1560
|
-
|
|
1561
|
-
|
|
1732
|
+
catch (e_9) {
|
|
1733
|
+
env_9.error = e_9;
|
|
1734
|
+
env_9.hasError = true;
|
|
1562
1735
|
}
|
|
1563
1736
|
finally {
|
|
1564
|
-
__disposeResources(
|
|
1737
|
+
__disposeResources(env_9);
|
|
1565
1738
|
}
|
|
1566
1739
|
}
|
|
1567
1740
|
// Flush encoder at end
|
|
@@ -1685,7 +1858,8 @@ export class Encoder {
|
|
|
1685
1858
|
}
|
|
1686
1859
|
this.codecContext.width = frame.width;
|
|
1687
1860
|
this.codecContext.height = frame.height;
|
|
1688
|
-
|
|
1861
|
+
// Pick a codec-supported pixel format (converting on demand when autoFormat).
|
|
1862
|
+
this.setupVideoFormat(frame);
|
|
1689
1863
|
this.codecContext.sampleAspectRatio = frame.sampleAspectRatio;
|
|
1690
1864
|
this.codecContext.colorRange = frame.colorRange;
|
|
1691
1865
|
this.codecContext.colorPrimaries = frame.colorPrimaries;
|
|
@@ -1697,12 +1871,8 @@ export class Encoder {
|
|
|
1697
1871
|
}
|
|
1698
1872
|
}
|
|
1699
1873
|
else {
|
|
1700
|
-
// Audio:
|
|
1701
|
-
|
|
1702
|
-
this.codecContext.timeBase = frame.timeBase;
|
|
1703
|
-
this.codecContext.sampleRate = frame.sampleRate;
|
|
1704
|
-
this.codecContext.sampleFormat = frame.format;
|
|
1705
|
-
this.codecContext.channelLayout = frame.channelLayout;
|
|
1874
|
+
// Audio: pick codec-supported sample rate/format/layout (resampling on demand).
|
|
1875
|
+
this.setupAudioParams(frame);
|
|
1706
1876
|
}
|
|
1707
1877
|
// Setup hardware acceleration with validation
|
|
1708
1878
|
this.setupHardwareAcceleration(frame);
|
|
@@ -1778,7 +1948,8 @@ export class Encoder {
|
|
|
1778
1948
|
}
|
|
1779
1949
|
this.codecContext.width = frame.width;
|
|
1780
1950
|
this.codecContext.height = frame.height;
|
|
1781
|
-
|
|
1951
|
+
// Pick a codec-supported pixel format (converting on demand when autoFormat).
|
|
1952
|
+
this.setupVideoFormat(frame);
|
|
1782
1953
|
this.codecContext.sampleAspectRatio = frame.sampleAspectRatio;
|
|
1783
1954
|
this.codecContext.colorRange = frame.colorRange;
|
|
1784
1955
|
this.codecContext.colorPrimaries = frame.colorPrimaries;
|
|
@@ -1790,12 +1961,8 @@ export class Encoder {
|
|
|
1790
1961
|
}
|
|
1791
1962
|
}
|
|
1792
1963
|
else {
|
|
1793
|
-
// Audio:
|
|
1794
|
-
|
|
1795
|
-
this.codecContext.timeBase = frame.timeBase;
|
|
1796
|
-
this.codecContext.sampleRate = frame.sampleRate;
|
|
1797
|
-
this.codecContext.sampleFormat = frame.format;
|
|
1798
|
-
this.codecContext.channelLayout = frame.channelLayout;
|
|
1964
|
+
// Audio: pick codec-supported sample rate/format/layout (resampling on demand).
|
|
1965
|
+
this.setupAudioParams(frame);
|
|
1799
1966
|
}
|
|
1800
1967
|
// Setup hardware acceleration with validation
|
|
1801
1968
|
this.setupHardwareAcceleration(frame);
|
|
@@ -1888,6 +2055,199 @@ export class Encoder {
|
|
|
1888
2055
|
}
|
|
1889
2056
|
}
|
|
1890
2057
|
}
|
|
2058
|
+
/**
|
|
2059
|
+
* Configure the codec context's audio parameters from the first frame.
|
|
2060
|
+
*
|
|
2061
|
+
* Audio encoders only accept specific sample rates / sample formats / channel
|
|
2062
|
+
* layouts. This picks codec-supported targets; if they differ from the input it
|
|
2063
|
+
* either sets up a resampler (when `autoResample`) or throws a descriptive error.
|
|
2064
|
+
*
|
|
2065
|
+
* @param frame - First audio frame
|
|
2066
|
+
*
|
|
2067
|
+
* @throws {Error} If the input is unsupported and `autoResample` is disabled
|
|
2068
|
+
*
|
|
2069
|
+
* @throws {FFmpegError} If the resampler fails to configure
|
|
2070
|
+
*
|
|
2071
|
+
* @internal
|
|
2072
|
+
*/
|
|
2073
|
+
setupAudioParams(frame) {
|
|
2074
|
+
// Always use frame timebase (typically 1/sample_rate) for correct audio PTS.
|
|
2075
|
+
this.codecContext.timeBase = frame.timeBase;
|
|
2076
|
+
const inRate = frame.sampleRate;
|
|
2077
|
+
const inFmt = frame.format;
|
|
2078
|
+
// Codec open and swr both need a concrete layout. PCM/raw frames often carry
|
|
2079
|
+
// an unspecified layout (order UNSPEC, mask 0); normalize it to the canonical
|
|
2080
|
+
// native layout and re-apply it to each incoming frame (see encode()) so it
|
|
2081
|
+
// matches the opened codec context / resampler input.
|
|
2082
|
+
let inLayout = frame.channelLayout;
|
|
2083
|
+
if (inLayout.order === AV_CHANNEL_ORDER_UNSPEC) {
|
|
2084
|
+
inLayout = avChannelLayoutDefault(inLayout.nbChannels);
|
|
2085
|
+
this.audioInputLayout = inLayout;
|
|
2086
|
+
}
|
|
2087
|
+
const targetRate = pickSupportedRate(inRate, this.codec.supportedSamplerates);
|
|
2088
|
+
const targetFmt = pickSupportedSampleFormat(inFmt, this.codec.sampleFormats);
|
|
2089
|
+
const targetLayout = pickSupportedLayout(inLayout, this.codec.channelLayouts);
|
|
2090
|
+
const needsResample = targetRate !== inRate || targetFmt !== inFmt || targetLayout.nbChannels !== inLayout.nbChannels;
|
|
2091
|
+
if (needsResample && !this.autoResample) {
|
|
2092
|
+
const rates = this.codec.supportedSamplerates;
|
|
2093
|
+
throw new Error(`Encoder '${this.codec.name}' does not support the input audio format ` +
|
|
2094
|
+
`(${inRate} Hz, ${avGetSampleFmtName(inFmt) ?? inFmt}, ${inLayout.nbChannels}ch)` +
|
|
2095
|
+
(rates && rates.length > 0 ? `. Supported sample rates: ${rates.join(', ')}` : '') +
|
|
2096
|
+
'. Set { autoResample: true } on the encoder, or convert the input with an aresample/aformat filter first.');
|
|
2097
|
+
}
|
|
2098
|
+
this.codecContext.sampleRate = targetRate;
|
|
2099
|
+
this.codecContext.sampleFormat = targetFmt;
|
|
2100
|
+
this.codecContext.channelLayout = targetLayout;
|
|
2101
|
+
if (needsResample) {
|
|
2102
|
+
const swr = new SoftwareResampleContext();
|
|
2103
|
+
FFmpegError.throwIfError(swr.allocSetOpts2(targetLayout, targetFmt, targetRate, inLayout, inFmt, inRate), 'Failed to configure audio resampler');
|
|
2104
|
+
FFmpegError.throwIfError(swr.init(), 'Failed to initialize audio resampler');
|
|
2105
|
+
this.audioResampler = swr;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* Lazily allocate the reused resampler output frame.
|
|
2110
|
+
*
|
|
2111
|
+
* @returns The allocated output frame
|
|
2112
|
+
*
|
|
2113
|
+
* @internal
|
|
2114
|
+
*/
|
|
2115
|
+
getResampleFrame() {
|
|
2116
|
+
if (!this.resampledFrame) {
|
|
2117
|
+
this.resampledFrame = new Frame();
|
|
2118
|
+
this.resampledFrame.alloc();
|
|
2119
|
+
}
|
|
2120
|
+
return this.resampledFrame;
|
|
2121
|
+
}
|
|
2122
|
+
/**
|
|
2123
|
+
* Resample an incoming audio frame to the codec's target format.
|
|
2124
|
+
*
|
|
2125
|
+
* Reuses a single output frame; `swr_convert_frame` allocates/sizes its buffer.
|
|
2126
|
+
* The (fixed-frame-size) audio FIFO copies the samples and re-stamps PTS, so the
|
|
2127
|
+
* reused frame and its carried timing are only relevant on the non-FIFO path.
|
|
2128
|
+
*
|
|
2129
|
+
* @param frame - Source audio frame
|
|
2130
|
+
*
|
|
2131
|
+
* @returns The resampled frame (owned by the encoder, reused across calls)
|
|
2132
|
+
*
|
|
2133
|
+
* @internal
|
|
2134
|
+
*/
|
|
2135
|
+
resampleAudio(frame) {
|
|
2136
|
+
const out = this.getResampleFrame();
|
|
2137
|
+
out.unref();
|
|
2138
|
+
out.format = this.codecContext.sampleFormat;
|
|
2139
|
+
out.sampleRate = this.codecContext.sampleRate;
|
|
2140
|
+
out.channelLayout = this.codecContext.channelLayout;
|
|
2141
|
+
FFmpegError.throwIfError(this.audioResampler.convertFrame(out, frame), 'Failed to resample audio frame');
|
|
2142
|
+
out.timeBase = frame.timeBase;
|
|
2143
|
+
out.pts = frame.pts;
|
|
2144
|
+
return out;
|
|
2145
|
+
}
|
|
2146
|
+
/**
|
|
2147
|
+
* Drain samples buffered inside the resampler (rate-conversion delay) into the
|
|
2148
|
+
* encoder path. Returns the drained frame if any, else null.
|
|
2149
|
+
*
|
|
2150
|
+
* @returns The drained frame (reused), or null when the resampler is empty
|
|
2151
|
+
*
|
|
2152
|
+
* @internal
|
|
2153
|
+
*/
|
|
2154
|
+
drainResampler() {
|
|
2155
|
+
if (!this.audioResampler) {
|
|
2156
|
+
return null;
|
|
2157
|
+
}
|
|
2158
|
+
const out = this.getResampleFrame();
|
|
2159
|
+
out.unref();
|
|
2160
|
+
out.format = this.codecContext.sampleFormat;
|
|
2161
|
+
out.sampleRate = this.codecContext.sampleRate;
|
|
2162
|
+
out.channelLayout = this.codecContext.channelLayout;
|
|
2163
|
+
const ret = this.audioResampler.convertFrame(out, null);
|
|
2164
|
+
if (ret < 0 || out.nbSamples <= 0) {
|
|
2165
|
+
return null;
|
|
2166
|
+
}
|
|
2167
|
+
return out;
|
|
2168
|
+
}
|
|
2169
|
+
/**
|
|
2170
|
+
* Configure the codec context's pixel format from the first video frame.
|
|
2171
|
+
*
|
|
2172
|
+
* Video encoders only accept specific pixel formats. This keeps the input format
|
|
2173
|
+
* when the codec accepts it; otherwise it either sets up a swscale converter to
|
|
2174
|
+
* the least-loss supported format (when `autoFormat`) or throws a descriptive
|
|
2175
|
+
* error. Hardware frames are left untouched - their format is negotiated through
|
|
2176
|
+
* the hardware frames context, not swscale.
|
|
2177
|
+
*
|
|
2178
|
+
* @param frame - First video frame
|
|
2179
|
+
*
|
|
2180
|
+
* @throws {Error} If the input is unsupported and `autoFormat` is disabled
|
|
2181
|
+
*
|
|
2182
|
+
* @throws {FFmpegError} If the converter fails to configure
|
|
2183
|
+
*
|
|
2184
|
+
* @internal
|
|
2185
|
+
*/
|
|
2186
|
+
setupVideoFormat(frame) {
|
|
2187
|
+
const inFmt = frame.format;
|
|
2188
|
+
// Hardware frames carry a hw pixfmt negotiated via hw_frames_ctx; swscale can't
|
|
2189
|
+
// touch them - leave the format untouched.
|
|
2190
|
+
if (frame.isHwFrame()) {
|
|
2191
|
+
this.codecContext.pixelFormat = inFmt;
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
const targetFmt = pickSupportedPixelFormat(inFmt, this.codec.pixelFormats);
|
|
2195
|
+
const needsConversion = targetFmt !== inFmt;
|
|
2196
|
+
if (needsConversion && !this.autoFormat) {
|
|
2197
|
+
const supported = this.codec.pixelFormats;
|
|
2198
|
+
throw new Error(`Encoder '${this.codec.name}' does not support the input pixel format ` +
|
|
2199
|
+
`(${avGetPixFmtName(inFmt) ?? inFmt}). Supported: ${supported.map((f) => avGetPixFmtName(f) ?? f).join(', ')}` +
|
|
2200
|
+
'. Set { autoFormat: true } on the encoder, or convert the input with a scale/format filter first.');
|
|
2201
|
+
}
|
|
2202
|
+
this.codecContext.pixelFormat = targetFmt;
|
|
2203
|
+
// Set up a same-size swscale converter when the codec needs a different format.
|
|
2204
|
+
if (needsConversion) {
|
|
2205
|
+
this.videoTargetFormat = targetFmt;
|
|
2206
|
+
const sws = new SoftwareScaleContext();
|
|
2207
|
+
sws.getContext(frame.width, frame.height, inFmt, frame.width, frame.height, targetFmt, SWS_BILINEAR);
|
|
2208
|
+
FFmpegError.throwIfError(sws.initContext(), 'Failed to configure pixel-format converter');
|
|
2209
|
+
this.videoScaler = sws;
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
/**
|
|
2213
|
+
* Lazily allocate the reused scaler output frame.
|
|
2214
|
+
*
|
|
2215
|
+
* @returns The allocated output frame
|
|
2216
|
+
*
|
|
2217
|
+
* @internal
|
|
2218
|
+
*/
|
|
2219
|
+
getScaledFrame() {
|
|
2220
|
+
if (!this.scaledFrame) {
|
|
2221
|
+
this.scaledFrame = new Frame();
|
|
2222
|
+
this.scaledFrame.alloc();
|
|
2223
|
+
}
|
|
2224
|
+
return this.scaledFrame;
|
|
2225
|
+
}
|
|
2226
|
+
/**
|
|
2227
|
+
* Convert an incoming video frame to the codec's target pixel format.
|
|
2228
|
+
*
|
|
2229
|
+
* Reuses a single output frame; `sws_scale_frame` allocates/sizes its buffer.
|
|
2230
|
+
* Resolution is unchanged - only the pixel format differs. Timing is carried over
|
|
2231
|
+
* explicitly so the encoder's PTS rescale stays correct.
|
|
2232
|
+
*
|
|
2233
|
+
* @param frame - Source video frame
|
|
2234
|
+
*
|
|
2235
|
+
* @returns The converted frame (owned by the encoder, reused across calls)
|
|
2236
|
+
*
|
|
2237
|
+
* @internal
|
|
2238
|
+
*/
|
|
2239
|
+
scaleVideo(frame) {
|
|
2240
|
+
const out = this.getScaledFrame();
|
|
2241
|
+
out.unref();
|
|
2242
|
+
out.format = this.videoTargetFormat;
|
|
2243
|
+
out.width = frame.width;
|
|
2244
|
+
out.height = frame.height;
|
|
2245
|
+
FFmpegError.throwIfError(this.videoScaler.scaleFrameSync(out, frame), 'Failed to convert video frame format');
|
|
2246
|
+
out.timeBase = frame.timeBase;
|
|
2247
|
+
out.pts = frame.pts;
|
|
2248
|
+
out.duration = frame.duration;
|
|
2249
|
+
return out;
|
|
2250
|
+
}
|
|
1891
2251
|
/**
|
|
1892
2252
|
* Prepare frame for encoding.
|
|
1893
2253
|
*
|