webcodecs-node 0.2.2 → 0.5.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 +4 -0
- package/dist/__tests__/AudioData.test.js +2 -1
- package/dist/__tests__/AudioData.test.js.map +1 -1
- package/dist/__tests__/AudioDecoder.test.js +8 -8
- package/dist/__tests__/AudioDecoder.test.js.map +1 -1
- package/dist/__tests__/AudioEncoder.test.js +3 -3
- package/dist/__tests__/AudioEncoder.test.js.map +1 -1
- package/dist/__tests__/EncodedChunks.test.js +3 -2
- package/dist/__tests__/EncodedChunks.test.js.map +1 -1
- package/dist/__tests__/HardwareAcceleration.test.js +2 -1
- package/dist/__tests__/HardwareAcceleration.test.js.map +1 -1
- package/dist/__tests__/ImageDecoder.test.js +16 -45
- package/dist/__tests__/ImageDecoder.test.js.map +1 -1
- package/dist/__tests__/ImageDecoder.wpt.test.d.ts +8 -0
- package/dist/__tests__/ImageDecoder.wpt.test.d.ts.map +1 -0
- package/dist/__tests__/ImageDecoder.wpt.test.js +135 -0
- package/dist/__tests__/ImageDecoder.wpt.test.js.map +1 -0
- package/dist/__tests__/NodeAvDecoder.test.d.ts +2 -0
- package/dist/__tests__/NodeAvDecoder.test.d.ts.map +1 -0
- package/dist/__tests__/NodeAvDecoder.test.js +206 -0
- package/dist/__tests__/NodeAvDecoder.test.js.map +1 -0
- package/dist/__tests__/NodeAvEncoder.test.d.ts +2 -0
- package/dist/__tests__/NodeAvEncoder.test.d.ts.map +1 -0
- package/dist/__tests__/NodeAvEncoder.test.js +176 -0
- package/dist/__tests__/NodeAvEncoder.test.js.map +1 -0
- package/dist/__tests__/VideoDecoder.test.js +5 -5
- package/dist/__tests__/VideoDecoder.test.js.map +1 -1
- package/dist/__tests__/VideoEncoder.test.js +3 -3
- package/dist/__tests__/VideoEncoder.test.js.map +1 -1
- package/dist/__tests__/VideoFrame.test.js +4 -1
- package/dist/__tests__/VideoFrame.test.js.map +1 -1
- package/dist/backends/index.d.ts +3 -0
- package/dist/backends/index.d.ts.map +1 -0
- package/dist/backends/index.js +2 -0
- package/dist/backends/index.js.map +1 -0
- package/dist/backends/types.d.ts +168 -0
- package/dist/backends/types.d.ts.map +1 -0
- package/dist/backends/types.js +25 -0
- package/dist/backends/types.js.map +1 -0
- package/dist/codec-utils/audio-codecs.d.ts +60 -0
- package/dist/codec-utils/audio-codecs.d.ts.map +1 -0
- package/dist/codec-utils/audio-codecs.js +117 -0
- package/dist/codec-utils/audio-codecs.js.map +1 -0
- package/dist/codec-utils/formats.d.ts +42 -0
- package/dist/codec-utils/formats.d.ts.map +1 -0
- package/dist/codec-utils/formats.js +147 -0
- package/dist/codec-utils/formats.js.map +1 -0
- package/dist/codec-utils/index.d.ts +9 -0
- package/dist/codec-utils/index.d.ts.map +1 -0
- package/dist/codec-utils/index.js +12 -0
- package/dist/codec-utils/index.js.map +1 -0
- package/dist/codec-utils/types.d.ts +87 -0
- package/dist/codec-utils/types.d.ts.map +1 -0
- package/dist/codec-utils/types.js +10 -0
- package/dist/codec-utils/types.js.map +1 -0
- package/dist/containers/Demuxer.d.ts +114 -0
- package/dist/containers/Demuxer.d.ts.map +1 -0
- package/dist/containers/Demuxer.js +256 -0
- package/dist/containers/Demuxer.js.map +1 -0
- package/dist/containers/Muxer.d.ts +142 -0
- package/dist/containers/Muxer.d.ts.map +1 -0
- package/dist/containers/Muxer.js +295 -0
- package/dist/containers/Muxer.js.map +1 -0
- package/dist/containers/extract.d.ts +25 -0
- package/dist/containers/extract.d.ts.map +1 -0
- package/dist/containers/extract.js +64 -0
- package/dist/containers/extract.js.map +1 -0
- package/dist/containers/index.d.ts +40 -0
- package/dist/containers/index.d.ts.map +1 -0
- package/dist/containers/index.js +41 -0
- package/dist/containers/index.js.map +1 -0
- package/dist/containers/transcode.d.ts +138 -0
- package/dist/containers/transcode.d.ts.map +1 -0
- package/dist/containers/transcode.js +536 -0
- package/dist/containers/transcode.js.map +1 -0
- package/dist/core/AudioData.d.ts +5 -1
- package/dist/core/AudioData.d.ts.map +1 -1
- package/dist/core/AudioData.js +69 -13
- package/dist/core/AudioData.js.map +1 -1
- package/dist/core/EncodedAudioChunk.d.ts +1 -1
- package/dist/core/EncodedAudioChunk.d.ts.map +1 -1
- package/dist/core/EncodedAudioChunk.js +1 -1
- package/dist/core/EncodedAudioChunk.js.map +1 -1
- package/dist/core/EncodedVideoChunk.d.ts.map +1 -1
- package/dist/core/EncodedVideoChunk.js +3 -19
- package/dist/core/EncodedVideoChunk.js.map +1 -1
- package/dist/core/VideoFrame.d.ts +31 -11
- package/dist/core/VideoFrame.d.ts.map +1 -1
- package/dist/core/VideoFrame.js +244 -81
- package/dist/core/VideoFrame.js.map +1 -1
- package/dist/decoders/AudioDecoder.d.ts +14 -12
- package/dist/decoders/AudioDecoder.d.ts.map +1 -1
- package/dist/decoders/AudioDecoder.js +113 -173
- package/dist/decoders/AudioDecoder.js.map +1 -1
- package/dist/decoders/ImageDecoder.d.ts +1 -3
- package/dist/decoders/ImageDecoder.d.ts.map +1 -1
- package/dist/decoders/ImageDecoder.js +39 -192
- package/dist/decoders/ImageDecoder.js.map +1 -1
- package/dist/decoders/VideoDecoder.d.ts +14 -13
- package/dist/decoders/VideoDecoder.d.ts.map +1 -1
- package/dist/decoders/VideoDecoder.js +115 -166
- package/dist/decoders/VideoDecoder.js.map +1 -1
- package/dist/demos/demo-1080p-transcode.d.ts +8 -0
- package/dist/demos/demo-1080p-transcode.d.ts.map +1 -0
- package/dist/demos/demo-1080p-transcode.js +188 -0
- package/dist/demos/demo-1080p-transcode.js.map +1 -0
- package/dist/demos/demo-containers.d.ts +7 -0
- package/dist/demos/demo-containers.d.ts.map +1 -0
- package/dist/demos/demo-containers.js +140 -0
- package/dist/demos/demo-containers.js.map +1 -0
- package/dist/demos/demo-conversion.js +2 -2
- package/dist/demos/demo-conversion.js.map +1 -1
- package/dist/demos/demo-four-corners.d.ts +6 -0
- package/dist/demos/demo-four-corners.d.ts.map +1 -0
- package/dist/demos/demo-four-corners.js +218 -0
- package/dist/demos/demo-four-corners.js.map +1 -0
- package/dist/demos/demo-hwaccel-conversion.js +1 -1
- package/dist/demos/demo-hwaccel-conversion.js.map +1 -1
- package/dist/demos/demo-hwaccel.js +1 -1
- package/dist/demos/demo-hwaccel.js.map +1 -1
- package/dist/demos/demo-samples.js +232 -68
- package/dist/demos/demo-samples.js.map +1 -1
- package/dist/demos/demo-streaming.js +6 -6
- package/dist/demos/demo-streaming.js.map +1 -1
- package/dist/encoders/AudioEncoder.d.ts +14 -13
- package/dist/encoders/AudioEncoder.d.ts.map +1 -1
- package/dist/encoders/AudioEncoder.js +138 -228
- package/dist/encoders/AudioEncoder.js.map +1 -1
- package/dist/encoders/VideoEncoder.d.ts +13 -14
- package/dist/encoders/VideoEncoder.d.ts.map +1 -1
- package/dist/encoders/VideoEncoder.js +92 -161
- package/dist/encoders/VideoEncoder.js.map +1 -1
- package/dist/ffmpeg/index.d.ts +3 -2
- package/dist/ffmpeg/index.d.ts.map +1 -1
- package/dist/ffmpeg/index.js +3 -3
- package/dist/ffmpeg/index.js.map +1 -1
- package/dist/formats/conversions/batch-converter.d.ts +61 -0
- package/dist/formats/conversions/batch-converter.d.ts.map +1 -0
- package/dist/formats/conversions/batch-converter.js +274 -0
- package/dist/formats/conversions/batch-converter.js.map +1 -0
- package/dist/formats/conversions/frame-converter.d.ts +17 -0
- package/dist/formats/conversions/frame-converter.d.ts.map +1 -1
- package/dist/formats/conversions/frame-converter.js +144 -10
- package/dist/formats/conversions/frame-converter.js.map +1 -1
- package/dist/formats/conversions/index.d.ts +2 -0
- package/dist/formats/conversions/index.d.ts.map +1 -1
- package/dist/formats/conversions/index.js +4 -0
- package/dist/formats/conversions/index.js.map +1 -1
- package/dist/hardware/decoder-args.d.ts +2 -7
- package/dist/hardware/decoder-args.d.ts.map +1 -1
- package/dist/hardware/decoder-args.js +2 -32
- package/dist/hardware/decoder-args.js.map +1 -1
- package/dist/hardware/detection.d.ts +1 -1
- package/dist/hardware/detection.d.ts.map +1 -1
- package/dist/hardware/detection.js +54 -27
- package/dist/hardware/detection.js.map +1 -1
- package/dist/hardware/encoder-args.d.ts +2 -11
- package/dist/hardware/encoder-args.d.ts.map +1 -1
- package/dist/hardware/encoder-args.js +2 -71
- package/dist/hardware/encoder-args.js.map +1 -1
- package/dist/hardware/index.d.ts +2 -2
- package/dist/hardware/index.d.ts.map +1 -1
- package/dist/hardware/index.js +2 -2
- package/dist/hardware/index.js.map +1 -1
- package/dist/index.d.ts +5 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/dist/mediabunny/FFmpegAudioDecoder.d.ts +4 -17
- package/dist/mediabunny/FFmpegAudioDecoder.d.ts.map +1 -1
- package/dist/mediabunny/FFmpegAudioDecoder.js +57 -185
- package/dist/mediabunny/FFmpegAudioDecoder.js.map +1 -1
- package/dist/mediabunny/FFmpegAudioEncoder.d.ts +6 -39
- package/dist/mediabunny/FFmpegAudioEncoder.d.ts.map +1 -1
- package/dist/mediabunny/FFmpegAudioEncoder.js +66 -329
- package/dist/mediabunny/FFmpegAudioEncoder.js.map +1 -1
- package/dist/mediabunny/FFmpegVideoDecoder.d.ts +5 -38
- package/dist/mediabunny/FFmpegVideoDecoder.d.ts.map +1 -1
- package/dist/mediabunny/FFmpegVideoDecoder.js +39 -283
- package/dist/mediabunny/FFmpegVideoDecoder.js.map +1 -1
- package/dist/mediabunny/FFmpegVideoEncoder.d.ts +9 -37
- package/dist/mediabunny/FFmpegVideoEncoder.d.ts.map +1 -1
- package/dist/mediabunny/FFmpegVideoEncoder.js +49 -336
- package/dist/mediabunny/FFmpegVideoEncoder.js.map +1 -1
- package/dist/node-av/HardwarePipeline.d.ts +36 -0
- package/dist/node-av/HardwarePipeline.d.ts.map +1 -0
- package/dist/node-av/HardwarePipeline.js +243 -0
- package/dist/node-av/HardwarePipeline.js.map +1 -0
- package/dist/node-av/NodeAvAudioDecoder.d.ts +46 -0
- package/dist/node-av/NodeAvAudioDecoder.d.ts.map +1 -0
- package/dist/node-av/NodeAvAudioDecoder.js +350 -0
- package/dist/node-av/NodeAvAudioDecoder.js.map +1 -0
- package/dist/node-av/NodeAvAudioEncoder.d.ts +50 -0
- package/dist/node-av/NodeAvAudioEncoder.d.ts.map +1 -0
- package/dist/node-av/NodeAvAudioEncoder.js +506 -0
- package/dist/node-av/NodeAvAudioEncoder.js.map +1 -0
- package/dist/node-av/NodeAvImageDecoder.d.ts +114 -0
- package/dist/node-av/NodeAvImageDecoder.d.ts.map +1 -0
- package/dist/node-av/NodeAvImageDecoder.js +406 -0
- package/dist/node-av/NodeAvImageDecoder.js.map +1 -0
- package/dist/node-av/NodeAvVideoDecoder.d.ts +43 -0
- package/dist/node-av/NodeAvVideoDecoder.d.ts.map +1 -0
- package/dist/node-av/NodeAvVideoDecoder.js +365 -0
- package/dist/node-av/NodeAvVideoDecoder.js.map +1 -0
- package/dist/node-av/NodeAvVideoEncoder.d.ts +56 -0
- package/dist/node-av/NodeAvVideoEncoder.d.ts.map +1 -0
- package/dist/node-av/NodeAvVideoEncoder.js +509 -0
- package/dist/node-av/NodeAvVideoEncoder.js.map +1 -0
- package/dist/polyfill.d.ts.map +1 -1
- package/dist/polyfill.js +11 -9
- package/dist/polyfill.js.map +1 -1
- package/dist/utils/aac.d.ts.map +1 -1
- package/dist/utils/aac.js +6 -0
- package/dist/utils/aac.js.map +1 -1
- package/dist/utils/buffer-pool.d.ts +109 -0
- package/dist/utils/buffer-pool.d.ts.map +1 -0
- package/dist/utils/buffer-pool.js +278 -0
- package/dist/utils/buffer-pool.js.map +1 -0
- package/dist/utils/codec-cache.d.ts +65 -0
- package/dist/utils/codec-cache.d.ts.map +1 -0
- package/dist/utils/codec-cache.js +116 -0
- package/dist/utils/codec-cache.js.map +1 -0
- package/dist/utils/errors.d.ts +62 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +84 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/event-target.d.ts +42 -0
- package/dist/utils/event-target.d.ts.map +1 -0
- package/dist/utils/event-target.js +96 -0
- package/dist/utils/event-target.js.map +1 -0
- package/dist/utils/hardware-pool.d.ts +88 -0
- package/dist/utils/hardware-pool.d.ts.map +1 -0
- package/dist/utils/hardware-pool.js +266 -0
- package/dist/utils/hardware-pool.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +12 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/timeout.d.ts +44 -0
- package/dist/utils/timeout.d.ts.map +1 -0
- package/dist/utils/timeout.js +73 -0
- package/dist/utils/timeout.js.map +1 -0
- package/dist/utils/type-guards.d.ts +11 -1
- package/dist/utils/type-guards.d.ts.map +1 -1
- package/dist/utils/type-guards.js.map +1 -1
- package/package.json +20 -34
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* AudioEncoder - Encodes AudioData into EncodedAudioChunks
|
|
3
3
|
* https://developer.mozilla.org/en-US/docs/Web/API/AudioEncoder
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { WebCodecsEventTarget } from '../utils/event-target.js';
|
|
6
6
|
import { AudioData } from '../core/AudioData.js';
|
|
7
7
|
import { EncodedAudioChunk } from '../core/EncodedAudioChunk.js';
|
|
8
|
+
type EventHandler = ((event: Event) => void) | null;
|
|
8
9
|
export type CodecState = 'unconfigured' | 'configured' | 'closed';
|
|
9
10
|
export interface AudioEncoderConfig {
|
|
10
11
|
codec: string;
|
|
@@ -31,24 +32,28 @@ export interface AudioEncoderSupport {
|
|
|
31
32
|
supported: boolean;
|
|
32
33
|
config: AudioEncoderConfig;
|
|
33
34
|
}
|
|
34
|
-
export declare class AudioEncoder extends
|
|
35
|
+
export declare class AudioEncoder extends WebCodecsEventTarget {
|
|
35
36
|
private _state;
|
|
36
37
|
private _encodeQueueSize;
|
|
37
38
|
private _config;
|
|
38
39
|
private _outputCallback;
|
|
39
40
|
private _errorCallback;
|
|
40
|
-
private
|
|
41
|
+
private _encoder;
|
|
41
42
|
private _frameCount;
|
|
42
43
|
private _firstChunk;
|
|
43
|
-
private _accumulatedData;
|
|
44
44
|
private _ffmpegCodec;
|
|
45
45
|
private _bitstreamFormat;
|
|
46
46
|
private _codecDescription;
|
|
47
|
+
private _ondequeue;
|
|
47
48
|
constructor(init: AudioEncoderInit);
|
|
48
49
|
get state(): CodecState;
|
|
49
50
|
get encodeQueueSize(): number;
|
|
50
|
-
|
|
51
|
+
/** Event handler called when encodeQueueSize decreases */
|
|
52
|
+
get ondequeue(): EventHandler;
|
|
53
|
+
set ondequeue(handler: EventHandler);
|
|
51
54
|
private _safeErrorCallback;
|
|
55
|
+
/** Fire the dequeue event (both EventTarget and ondequeue handler) */
|
|
56
|
+
private _fireDequeueEvent;
|
|
52
57
|
private _safeOutputCallback;
|
|
53
58
|
static isConfigSupported(config: AudioEncoderConfig): Promise<AudioEncoderSupport>;
|
|
54
59
|
configure(config: AudioEncoderConfig): void;
|
|
@@ -56,14 +61,10 @@ export declare class AudioEncoder extends EventEmitter {
|
|
|
56
61
|
flush(timeout?: number): Promise<void>;
|
|
57
62
|
reset(): void;
|
|
58
63
|
close(): void;
|
|
59
|
-
private
|
|
60
|
-
private
|
|
64
|
+
private _startEncoder;
|
|
65
|
+
private _stopEncoder;
|
|
66
|
+
private _handleEncodedFrame;
|
|
61
67
|
private _audioDataToPCM;
|
|
62
|
-
private _parseEncodedFrames;
|
|
63
|
-
private _findFrameEnd;
|
|
64
|
-
private _findADTSFrame;
|
|
65
|
-
private _findMP3Frame;
|
|
66
|
-
private _findOggPage;
|
|
67
|
-
private _emitChunk;
|
|
68
68
|
}
|
|
69
|
+
export {};
|
|
69
70
|
//# sourceMappingURL=AudioEncoder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioEncoder.d.ts","sourceRoot":"","sources":["../../src/encoders/AudioEncoder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"AudioEncoder.d.ts","sourceRoot":"","sources":["../../src/encoders/AudioEncoder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGjE,KAAK,YAAY,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;AAiBpD,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,YAAY,GAAG,QAAQ,CAAC;AAElE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACtC,WAAW,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAClF,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,0BAA0B;IACzC,aAAa,CAAC,EAAE;QACd,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,EAAE,UAAU,CAAC;KAC1B,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAKD,qBAAa,YAAa,SAAQ,oBAAoB;IACpD,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,eAAe,CAA4E;IACnG,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,iBAAiB,CAA2B;IACpD,OAAO,CAAC,UAAU,CAA6B;gBAEnC,IAAI,EAAE,gBAAgB;IAclC,IAAI,KAAK,IAAI,UAAU,CAAwB;IAC/C,IAAI,eAAe,IAAI,MAAM,CAAkC;IAE/D,0DAA0D;IAC1D,IAAI,SAAS,IAAI,YAAY,CAA4B;IACzD,IAAI,SAAS,CAAC,OAAO,EAAE,YAAY,EAAgC;IAEnE,OAAO,CAAC,kBAAkB;IAQ1B,sEAAsE;IACtE,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,mBAAmB;WAQd,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAWxF,SAAS,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAwC3C,MAAM,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAqDvB,KAAK,CAAC,OAAO,GAAE,MAA8B,GAAG,OAAO,CAAC,IAAI,CAAC;IAoDnE,KAAK,IAAI,IAAI;IAcb,KAAK,IAAI,IAAI;IAUb,OAAO,CAAC,aAAa;IA8BrB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,mBAAmB;IAqD3B,OAAO,CAAC,eAAe;CAoCxB"}
|
|
@@ -2,29 +2,31 @@
|
|
|
2
2
|
* AudioEncoder - Encodes AudioData into EncodedAudioChunks
|
|
3
3
|
* https://developer.mozilla.org/en-US/docs/Web/API/AudioEncoder
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import { spawn } from 'child_process';
|
|
5
|
+
import { WebCodecsEventTarget } from '../utils/event-target.js';
|
|
7
6
|
import { AudioData } from '../core/AudioData.js';
|
|
8
7
|
import { EncodedAudioChunk } from '../core/EncodedAudioChunk.js';
|
|
9
8
|
import { DOMException } from '../types/index.js';
|
|
10
|
-
import {
|
|
11
|
-
import { getAudioEncoderCodec, getAudioEncoderFormat, getAudioFrameSize, AUDIO_ENCODER_CODEC_MAP, } from '../ffmpeg/audio-codecs.js';
|
|
9
|
+
import { getAudioEncoderCodec, getAudioFrameSize, AUDIO_ENCODER_CODEC_MAP, } from '../codec-utils/audio-codecs.js';
|
|
12
10
|
import { buildAudioSpecificConfig, stripAdtsHeader } from '../utils/aac.js';
|
|
13
|
-
|
|
11
|
+
import { NodeAvAudioEncoder } from '../node-av/NodeAvAudioEncoder.js';
|
|
12
|
+
import { validateNonEmptyString, validatePositiveInteger, validateRequired, } from '../utils/validation.js';
|
|
13
|
+
import { getCodecBase } from '../utils/codec-cache.js';
|
|
14
|
+
import { encodingError, wrapAsWebCodecsError } from '../utils/errors.js';
|
|
14
15
|
const DEFAULT_FLUSH_TIMEOUT = 30000;
|
|
15
|
-
|
|
16
|
+
const MAX_QUEUE_SIZE = 100; // Prevent unbounded memory growth
|
|
17
|
+
export class AudioEncoder extends WebCodecsEventTarget {
|
|
16
18
|
_state = 'unconfigured';
|
|
17
19
|
_encodeQueueSize = 0;
|
|
18
20
|
_config = null;
|
|
19
21
|
_outputCallback;
|
|
20
22
|
_errorCallback;
|
|
21
|
-
|
|
23
|
+
_encoder = null;
|
|
22
24
|
_frameCount = 0;
|
|
23
25
|
_firstChunk = true;
|
|
24
|
-
_accumulatedData = Buffer.alloc(0);
|
|
25
26
|
_ffmpegCodec = '';
|
|
26
27
|
_bitstreamFormat = 'adts';
|
|
27
28
|
_codecDescription = null;
|
|
29
|
+
_ondequeue = null;
|
|
28
30
|
constructor(init) {
|
|
29
31
|
super();
|
|
30
32
|
if (!init || typeof init.output !== 'function') {
|
|
@@ -38,9 +40,9 @@ export class AudioEncoder extends EventEmitter {
|
|
|
38
40
|
}
|
|
39
41
|
get state() { return this._state; }
|
|
40
42
|
get encodeQueueSize() { return this._encodeQueueSize; }
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
43
|
+
/** Event handler called when encodeQueueSize decreases */
|
|
44
|
+
get ondequeue() { return this._ondequeue; }
|
|
45
|
+
set ondequeue(handler) { this._ondequeue = handler; }
|
|
44
46
|
_safeErrorCallback(error) {
|
|
45
47
|
try {
|
|
46
48
|
this._errorCallback(error);
|
|
@@ -49,19 +51,31 @@ export class AudioEncoder extends EventEmitter {
|
|
|
49
51
|
this.emit('callbackError', error);
|
|
50
52
|
}
|
|
51
53
|
}
|
|
54
|
+
/** Fire the dequeue event (both EventTarget and ondequeue handler) */
|
|
55
|
+
_fireDequeueEvent() {
|
|
56
|
+
this.emit('dequeue');
|
|
57
|
+
if (this._ondequeue) {
|
|
58
|
+
try {
|
|
59
|
+
this._ondequeue(new Event('dequeue'));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Ignore errors in user handler per spec
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
52
66
|
_safeOutputCallback(chunk, metadata) {
|
|
53
67
|
try {
|
|
54
68
|
this._outputCallback(chunk, metadata);
|
|
55
69
|
}
|
|
56
70
|
catch (err) {
|
|
57
|
-
this._safeErrorCallback(err
|
|
71
|
+
this._safeErrorCallback(wrapAsWebCodecsError(err, 'EncodingError'));
|
|
58
72
|
}
|
|
59
73
|
}
|
|
60
74
|
static async isConfigSupported(config) {
|
|
61
75
|
if (!config.codec || !config.sampleRate || !config.numberOfChannels) {
|
|
62
76
|
return { supported: false, config };
|
|
63
77
|
}
|
|
64
|
-
const codecBase = config.codec
|
|
78
|
+
const codecBase = getCodecBase(config.codec);
|
|
65
79
|
const supported = codecBase in AUDIO_ENCODER_CODEC_MAP || config.codec in AUDIO_ENCODER_CODEC_MAP;
|
|
66
80
|
return { supported, config };
|
|
67
81
|
}
|
|
@@ -69,18 +83,13 @@ export class AudioEncoder extends EventEmitter {
|
|
|
69
83
|
if (this._state === 'closed') {
|
|
70
84
|
throw new DOMException('Encoder is closed', 'InvalidStateError');
|
|
71
85
|
}
|
|
72
|
-
|
|
86
|
+
validateRequired(config, 'config');
|
|
87
|
+
if (typeof config !== 'object') {
|
|
73
88
|
throw new TypeError('config must be an object');
|
|
74
89
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (typeof config.sampleRate !== 'number' || config.sampleRate <= 0 || !Number.isInteger(config.sampleRate)) {
|
|
79
|
-
throw new TypeError('sampleRate must be a positive integer');
|
|
80
|
-
}
|
|
81
|
-
if (typeof config.numberOfChannels !== 'number' || config.numberOfChannels <= 0 || !Number.isInteger(config.numberOfChannels)) {
|
|
82
|
-
throw new TypeError('numberOfChannels must be a positive integer');
|
|
83
|
-
}
|
|
90
|
+
validateNonEmptyString(config.codec, 'codec');
|
|
91
|
+
validatePositiveInteger(config.sampleRate, 'sampleRate');
|
|
92
|
+
validatePositiveInteger(config.numberOfChannels, 'numberOfChannels');
|
|
84
93
|
if (config.bitrate !== undefined && (typeof config.bitrate !== 'number' || config.bitrate <= 0)) {
|
|
85
94
|
throw new TypeError('bitrate must be a positive number');
|
|
86
95
|
}
|
|
@@ -94,18 +103,14 @@ export class AudioEncoder extends EventEmitter {
|
|
|
94
103
|
if (!ffmpegCodec) {
|
|
95
104
|
throw new DOMException(`Codec '${config.codec}' is not supported`, 'NotSupportedError');
|
|
96
105
|
}
|
|
97
|
-
|
|
98
|
-
this._process.kill();
|
|
99
|
-
this._process = null;
|
|
100
|
-
}
|
|
106
|
+
this._stopEncoder();
|
|
101
107
|
this._config = { ...config };
|
|
102
108
|
this._state = 'configured';
|
|
103
109
|
this._frameCount = 0;
|
|
104
110
|
this._firstChunk = true;
|
|
105
|
-
this._accumulatedData = Buffer.alloc(0);
|
|
106
111
|
this._bitstreamFormat = config.format ?? 'adts';
|
|
107
112
|
this._codecDescription = null;
|
|
108
|
-
this.
|
|
113
|
+
this._startEncoder();
|
|
109
114
|
}
|
|
110
115
|
encode(data) {
|
|
111
116
|
if (this._state !== 'configured') {
|
|
@@ -114,18 +119,39 @@ export class AudioEncoder extends EventEmitter {
|
|
|
114
119
|
if (!(data instanceof AudioData)) {
|
|
115
120
|
throw new TypeError('data must be an AudioData');
|
|
116
121
|
}
|
|
117
|
-
if (!this.
|
|
118
|
-
this._safeErrorCallback(
|
|
122
|
+
if (!this._encoder?.isHealthy) {
|
|
123
|
+
this._safeErrorCallback(encodingError('Encoder is not healthy'));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Check queue saturation to prevent unbounded memory growth
|
|
127
|
+
if (this._encodeQueueSize >= MAX_QUEUE_SIZE) {
|
|
128
|
+
this._safeErrorCallback(new DOMException(`Encoder queue saturated (${MAX_QUEUE_SIZE} frames pending). Wait for dequeue events before encoding more frames.`, 'QuotaExceededError'));
|
|
119
129
|
return;
|
|
120
130
|
}
|
|
121
131
|
this._encodeQueueSize++;
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
132
|
+
const nativeFrame = data._native ?? null;
|
|
133
|
+
let writeSuccess = false;
|
|
134
|
+
if (nativeFrame && typeof this._encoder.writeFrame === 'function') {
|
|
135
|
+
const clone = typeof nativeFrame.clone === 'function' ? nativeFrame.clone() : null;
|
|
136
|
+
const frameForEncode = clone ?? nativeFrame;
|
|
137
|
+
try {
|
|
138
|
+
writeSuccess = this._encoder.writeFrame(frameForEncode, true);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
writeSuccess = false;
|
|
142
|
+
}
|
|
143
|
+
if (!writeSuccess) {
|
|
144
|
+
const pcmData = this._audioDataToPCM(data);
|
|
145
|
+
writeSuccess = this._encoder.write(pcmData);
|
|
146
|
+
}
|
|
125
147
|
}
|
|
126
|
-
|
|
148
|
+
else {
|
|
149
|
+
const pcmData = this._audioDataToPCM(data);
|
|
150
|
+
writeSuccess = this._encoder.write(pcmData);
|
|
151
|
+
}
|
|
152
|
+
if (!writeSuccess) {
|
|
127
153
|
this._encodeQueueSize = Math.max(0, this._encodeQueueSize - 1);
|
|
128
|
-
this._safeErrorCallback(
|
|
154
|
+
this._safeErrorCallback(encodingError('Failed to write audio data to encoder'));
|
|
129
155
|
return;
|
|
130
156
|
}
|
|
131
157
|
this._frameCount += data.numberOfFrames;
|
|
@@ -135,7 +161,7 @@ export class AudioEncoder extends EventEmitter {
|
|
|
135
161
|
throw new DOMException('Encoder is not configured', 'InvalidStateError');
|
|
136
162
|
}
|
|
137
163
|
return new Promise((resolve, reject) => {
|
|
138
|
-
if (!this.
|
|
164
|
+
if (!this._encoder) {
|
|
139
165
|
resolve();
|
|
140
166
|
return;
|
|
141
167
|
}
|
|
@@ -155,10 +181,9 @@ export class AudioEncoder extends EventEmitter {
|
|
|
155
181
|
this._encodeQueueSize = 0;
|
|
156
182
|
this._frameCount = 0;
|
|
157
183
|
this._firstChunk = true;
|
|
158
|
-
this.
|
|
159
|
-
this._process = null;
|
|
184
|
+
this._encoder = null;
|
|
160
185
|
if (this._config) {
|
|
161
|
-
this.
|
|
186
|
+
this._startEncoder();
|
|
162
187
|
}
|
|
163
188
|
resolve();
|
|
164
189
|
};
|
|
@@ -172,227 +197,83 @@ export class AudioEncoder extends EventEmitter {
|
|
|
172
197
|
timeoutId = setTimeout(() => {
|
|
173
198
|
doReject(new DOMException('Flush operation timed out', 'TimeoutError'));
|
|
174
199
|
}, timeout);
|
|
175
|
-
this.
|
|
176
|
-
this.
|
|
177
|
-
this.
|
|
200
|
+
this._encoder.once('close', doResolve);
|
|
201
|
+
this._encoder.once('error', doReject);
|
|
202
|
+
this._encoder.end();
|
|
178
203
|
});
|
|
179
204
|
}
|
|
180
205
|
reset() {
|
|
181
206
|
if (this._state === 'closed') {
|
|
182
207
|
throw new DOMException('Encoder is closed', 'InvalidStateError');
|
|
183
208
|
}
|
|
184
|
-
this.
|
|
209
|
+
this._stopEncoder();
|
|
185
210
|
this._state = 'unconfigured';
|
|
186
211
|
this._config = null;
|
|
187
212
|
this._encodeQueueSize = 0;
|
|
188
213
|
this._frameCount = 0;
|
|
189
214
|
this._firstChunk = true;
|
|
190
|
-
this._accumulatedData = Buffer.alloc(0);
|
|
191
215
|
this._codecDescription = null;
|
|
192
216
|
}
|
|
193
217
|
close() {
|
|
194
218
|
if (this._state === 'closed')
|
|
195
219
|
return;
|
|
196
|
-
this.
|
|
220
|
+
this._stopEncoder();
|
|
197
221
|
this._state = 'closed';
|
|
198
222
|
this._config = null;
|
|
199
223
|
this._encodeQueueSize = 0;
|
|
200
224
|
this._codecDescription = null;
|
|
201
225
|
}
|
|
202
|
-
|
|
226
|
+
_startEncoder() {
|
|
203
227
|
if (!this._config)
|
|
204
228
|
return;
|
|
205
|
-
// Codec was already validated in configure(), so we can safely use a fallback here
|
|
206
229
|
this._ffmpegCodec = getAudioEncoderCodec(this._config.codec) || 'aac';
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
];
|
|
216
|
-
if (this._config.bitrate) {
|
|
217
|
-
args.push('-b:a', String(this._config.bitrate));
|
|
218
|
-
}
|
|
219
|
-
const isRealtime = this._config.latencyMode === 'realtime';
|
|
220
|
-
if (this._ffmpegCodec === 'libopus') {
|
|
221
|
-
args.push('-application', isRealtime ? 'voip' : 'audio');
|
|
222
|
-
if (isRealtime) {
|
|
223
|
-
args.push('-frame_duration', '10');
|
|
224
|
-
}
|
|
225
|
-
if (this._config.sampleRate !== 48000) {
|
|
226
|
-
args.push('-ar', '48000');
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
else if (this._ffmpegCodec === 'aac') {
|
|
230
|
-
if (isRealtime) {
|
|
231
|
-
args.push('-profile:a', 'aac_low');
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
args.push('-f', format, 'pipe:1');
|
|
235
|
-
this._process = spawn('ffmpeg', args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
236
|
-
this._process.stdout?.on('data', (data) => {
|
|
237
|
-
this._accumulatedData = Buffer.concat([this._accumulatedData, data]);
|
|
238
|
-
this._parseEncodedFrames();
|
|
230
|
+
this._encoder = new NodeAvAudioEncoder();
|
|
231
|
+
this._encoder.startEncoder({
|
|
232
|
+
codec: this._config.codec,
|
|
233
|
+
sampleRate: this._config.sampleRate,
|
|
234
|
+
numberOfChannels: this._config.numberOfChannels,
|
|
235
|
+
bitrate: this._config.bitrate,
|
|
236
|
+
bitrateMode: this._config.bitrateMode,
|
|
237
|
+
latencyMode: this._config.latencyMode,
|
|
239
238
|
});
|
|
240
|
-
this.
|
|
241
|
-
|
|
242
|
-
if (!msg.includes('Discarding ID3')) {
|
|
243
|
-
logger.warn('FFmpeg stderr', { message: msg });
|
|
244
|
-
}
|
|
239
|
+
this._encoder.on('encodedFrame', (frame) => {
|
|
240
|
+
this._handleEncodedFrame(frame);
|
|
245
241
|
});
|
|
246
|
-
this.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
242
|
+
this._encoder.on('frameAccepted', () => {
|
|
243
|
+
// Frame has started processing - decrement queue and fire dequeue event
|
|
244
|
+
this._encodeQueueSize = Math.max(0, this._encodeQueueSize - 1);
|
|
245
|
+
this._fireDequeueEvent();
|
|
246
|
+
});
|
|
247
|
+
this._encoder.on('error', (err) => {
|
|
248
|
+
this._safeErrorCallback(err);
|
|
251
249
|
});
|
|
252
|
-
this._process.stdin?.on('error', () => { });
|
|
253
|
-
}
|
|
254
|
-
_stopFFmpeg() {
|
|
255
|
-
if (this._process) {
|
|
256
|
-
this._process.kill('SIGTERM');
|
|
257
|
-
this._process = null;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
_audioDataToPCM(data) {
|
|
261
|
-
const numFrames = data.numberOfFrames;
|
|
262
|
-
const numChannels = data.numberOfChannels;
|
|
263
|
-
const bufferSize = numFrames * numChannels * 4;
|
|
264
|
-
const buffer = Buffer.alloc(bufferSize);
|
|
265
|
-
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
266
|
-
const isPlanar = data.format.endsWith('-planar');
|
|
267
|
-
const tempBuffer = new Float32Array(numFrames);
|
|
268
|
-
if (isPlanar) {
|
|
269
|
-
for (let ch = 0; ch < numChannels; ch++) {
|
|
270
|
-
data.copyTo(new Uint8Array(tempBuffer.buffer), {
|
|
271
|
-
planeIndex: ch,
|
|
272
|
-
format: 'f32-planar',
|
|
273
|
-
});
|
|
274
|
-
for (let frame = 0; frame < numFrames; frame++) {
|
|
275
|
-
const offset = (frame * numChannels + ch) * 4;
|
|
276
|
-
view.setFloat32(offset, tempBuffer[frame], true);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
const srcBuffer = new Uint8Array(bufferSize);
|
|
282
|
-
data.copyTo(srcBuffer, { planeIndex: 0, format: 'f32' });
|
|
283
|
-
buffer.set(srcBuffer);
|
|
284
|
-
}
|
|
285
|
-
return buffer;
|
|
286
|
-
}
|
|
287
|
-
_parseEncodedFrames() {
|
|
288
|
-
const minChunkSize = 64;
|
|
289
|
-
while (this._accumulatedData.length >= minChunkSize) {
|
|
290
|
-
let frameEnd = this._findFrameEnd();
|
|
291
|
-
if (frameEnd > 0) {
|
|
292
|
-
const frameData = Buffer.from(this._accumulatedData.subarray(0, frameEnd));
|
|
293
|
-
this._accumulatedData = this._accumulatedData.subarray(frameEnd);
|
|
294
|
-
this._emitChunk(frameData, 'key');
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
break;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
_findFrameEnd() {
|
|
302
|
-
if (this._ffmpegCodec === 'aac') {
|
|
303
|
-
return this._findADTSFrame();
|
|
304
|
-
}
|
|
305
|
-
else if (this._ffmpegCodec === 'libmp3lame') {
|
|
306
|
-
return this._findMP3Frame();
|
|
307
|
-
}
|
|
308
|
-
else if (this._ffmpegCodec === 'libopus' || this._ffmpegCodec === 'libvorbis') {
|
|
309
|
-
return this._findOggPage();
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
return Math.min(this._accumulatedData.length, 4096);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
_findADTSFrame() {
|
|
316
|
-
if (this._accumulatedData.length < 7)
|
|
317
|
-
return 0;
|
|
318
|
-
if ((this._accumulatedData[0] !== 0xFF) || ((this._accumulatedData[1] & 0xF0) !== 0xF0)) {
|
|
319
|
-
for (let i = 1; i < this._accumulatedData.length - 1; i++) {
|
|
320
|
-
if (this._accumulatedData[i] === 0xFF && (this._accumulatedData[i + 1] & 0xF0) === 0xF0) {
|
|
321
|
-
this._accumulatedData = this._accumulatedData.subarray(i);
|
|
322
|
-
return 0;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return 0;
|
|
326
|
-
}
|
|
327
|
-
const frameLength = ((this._accumulatedData[3] & 0x03) << 11) |
|
|
328
|
-
(this._accumulatedData[4] << 3) |
|
|
329
|
-
((this._accumulatedData[5] & 0xE0) >> 5);
|
|
330
|
-
if (frameLength > this._accumulatedData.length)
|
|
331
|
-
return 0;
|
|
332
|
-
return frameLength;
|
|
333
250
|
}
|
|
334
|
-
|
|
335
|
-
if (this.
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
for (let i = 1; i < this._accumulatedData.length - 1; i++) {
|
|
339
|
-
if (this._accumulatedData[i] === 0xFF && (this._accumulatedData[i + 1] & 0xE0) === 0xE0) {
|
|
340
|
-
this._accumulatedData = this._accumulatedData.subarray(i);
|
|
341
|
-
return 0;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return 0;
|
|
251
|
+
_stopEncoder() {
|
|
252
|
+
if (this._encoder) {
|
|
253
|
+
this._encoder.kill();
|
|
254
|
+
this._encoder = null;
|
|
345
255
|
}
|
|
346
|
-
const header = this._accumulatedData.readUInt32BE(0);
|
|
347
|
-
const bitrateIndex = (header >> 12) & 0x0F;
|
|
348
|
-
const samplingRateIndex = (header >> 10) & 0x03;
|
|
349
|
-
const padding = (header >> 9) & 0x01;
|
|
350
|
-
const bitrates = [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0];
|
|
351
|
-
const sampleRates = [44100, 48000, 32000, 0];
|
|
352
|
-
const bitrate = bitrates[bitrateIndex] * 1000;
|
|
353
|
-
const sampleRate = sampleRates[samplingRateIndex];
|
|
354
|
-
if (bitrate === 0 || sampleRate === 0)
|
|
355
|
-
return 0;
|
|
356
|
-
const frameSize = Math.floor((144 * bitrate) / sampleRate) + padding;
|
|
357
|
-
if (frameSize > this._accumulatedData.length)
|
|
358
|
-
return 0;
|
|
359
|
-
return frameSize;
|
|
360
256
|
}
|
|
361
|
-
|
|
362
|
-
if (this.
|
|
363
|
-
return 0;
|
|
364
|
-
if (this._accumulatedData.toString('ascii', 0, 4) !== 'OggS') {
|
|
365
|
-
for (let i = 1; i < this._accumulatedData.length - 3; i++) {
|
|
366
|
-
if (this._accumulatedData.toString('ascii', i, i + 4) === 'OggS') {
|
|
367
|
-
this._accumulatedData = this._accumulatedData.subarray(i);
|
|
368
|
-
return 0;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
return this._accumulatedData.length;
|
|
372
|
-
}
|
|
373
|
-
const numSegments = this._accumulatedData[26];
|
|
374
|
-
if (this._accumulatedData.length < 27 + numSegments)
|
|
375
|
-
return 0;
|
|
376
|
-
let pageSize = 27 + numSegments;
|
|
377
|
-
for (let i = 0; i < numSegments; i++) {
|
|
378
|
-
pageSize += this._accumulatedData[27 + i];
|
|
379
|
-
}
|
|
380
|
-
if (pageSize > this._accumulatedData.length)
|
|
381
|
-
return 0;
|
|
382
|
-
return pageSize;
|
|
383
|
-
}
|
|
384
|
-
_emitChunk(data, type) {
|
|
385
|
-
if (!this._config || data.length === 0)
|
|
257
|
+
_handleEncodedFrame(frame) {
|
|
258
|
+
if (!this._config)
|
|
386
259
|
return;
|
|
387
260
|
const samplesPerFrame = getAudioFrameSize(this._ffmpegCodec) || 1024;
|
|
388
|
-
|
|
261
|
+
// Use timestamp from backend (in samples) converted to microseconds
|
|
262
|
+
// Clamp to non-negative (AAC encoder has priming delay causing negative initial timestamps)
|
|
263
|
+
const timestamp = Math.max(0, (frame.timestamp * 1_000_000) / this._config.sampleRate);
|
|
389
264
|
const duration = (samplesPerFrame * 1_000_000) / this._config.sampleRate;
|
|
390
|
-
let payload = data;
|
|
391
|
-
const codecBase = this._config.codec
|
|
265
|
+
let payload = frame.data;
|
|
266
|
+
const codecBase = getCodecBase(this._config.codec);
|
|
392
267
|
const isAac = codecBase === 'mp4a' || codecBase === 'aac';
|
|
268
|
+
// Use description from backend if provided (AAC, FLAC, Vorbis, etc.)
|
|
269
|
+
// For AAC, the backend provides proper AudioSpecificConfig with correct channelConfiguration
|
|
270
|
+
if (frame.description && !this._codecDescription) {
|
|
271
|
+
this._codecDescription = new Uint8Array(frame.description);
|
|
272
|
+
}
|
|
393
273
|
if (this._bitstreamFormat === 'aac' && isAac) {
|
|
394
|
-
const stripped = stripAdtsHeader(new Uint8Array(data));
|
|
274
|
+
const stripped = stripAdtsHeader(new Uint8Array(frame.data));
|
|
395
275
|
payload = Buffer.from(stripped);
|
|
276
|
+
// Only build AudioSpecificConfig if backend didn't provide one
|
|
396
277
|
if (!this._codecDescription) {
|
|
397
278
|
this._codecDescription = buildAudioSpecificConfig({
|
|
398
279
|
samplingRate: this._config.sampleRate,
|
|
@@ -401,13 +282,11 @@ export class AudioEncoder extends EventEmitter {
|
|
|
401
282
|
}
|
|
402
283
|
}
|
|
403
284
|
const chunk = new EncodedAudioChunk({
|
|
404
|
-
type,
|
|
285
|
+
type: frame.keyFrame ? 'key' : 'delta',
|
|
405
286
|
timestamp,
|
|
406
287
|
duration,
|
|
407
288
|
data: new Uint8Array(payload),
|
|
408
289
|
});
|
|
409
|
-
this._encodeQueueSize = Math.max(0, this._encodeQueueSize - 1);
|
|
410
|
-
this.emit('dequeue');
|
|
411
290
|
const metadata = this._firstChunk
|
|
412
291
|
? {
|
|
413
292
|
decoderConfig: {
|
|
@@ -421,5 +300,36 @@ export class AudioEncoder extends EventEmitter {
|
|
|
421
300
|
this._firstChunk = false;
|
|
422
301
|
this._safeOutputCallback(chunk, metadata);
|
|
423
302
|
}
|
|
303
|
+
_audioDataToPCM(data) {
|
|
304
|
+
const numFrames = data.numberOfFrames;
|
|
305
|
+
const numChannels = data.numberOfChannels;
|
|
306
|
+
const format = data.format;
|
|
307
|
+
if (!format) {
|
|
308
|
+
throw encodingError('Cannot convert closed AudioData to PCM');
|
|
309
|
+
}
|
|
310
|
+
const bufferSize = numFrames * numChannels * 4;
|
|
311
|
+
const buffer = Buffer.alloc(bufferSize);
|
|
312
|
+
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
313
|
+
const isPlanar = format.endsWith('-planar');
|
|
314
|
+
const tempBuffer = new Float32Array(numFrames);
|
|
315
|
+
if (isPlanar) {
|
|
316
|
+
for (let ch = 0; ch < numChannels; ch++) {
|
|
317
|
+
data.copyTo(new Uint8Array(tempBuffer.buffer), {
|
|
318
|
+
planeIndex: ch,
|
|
319
|
+
format: 'f32-planar',
|
|
320
|
+
});
|
|
321
|
+
for (let frame = 0; frame < numFrames; frame++) {
|
|
322
|
+
const offset = (frame * numChannels + ch) * 4;
|
|
323
|
+
view.setFloat32(offset, tempBuffer[frame], true);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
const srcBuffer = new Uint8Array(bufferSize);
|
|
329
|
+
data.copyTo(srcBuffer, { planeIndex: 0, format: 'f32' });
|
|
330
|
+
buffer.set(srcBuffer);
|
|
331
|
+
}
|
|
332
|
+
return buffer;
|
|
333
|
+
}
|
|
424
334
|
}
|
|
425
335
|
//# sourceMappingURL=AudioEncoder.js.map
|