@mediabunny/server 1.45.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/LICENSE +373 -0
- package/README.md +276 -0
- package/dist/bundles/mediabunny-server.cjs +3361 -0
- package/dist/bundles/mediabunny-server.min.cjs +10 -0
- package/dist/bundles/mediabunny-server.min.mjs +9 -0
- package/dist/bundles/mediabunny-server.mjs +3334 -0
- package/dist/mediabunny-server.d.ts +105 -0
- package/dist/modules/src/audio-decoder.d.ts +21 -0
- package/dist/modules/src/audio-decoder.d.ts.map +1 -0
- package/dist/modules/src/audio-decoder.js +132 -0
- package/dist/modules/src/audio-encoder.d.ts +35 -0
- package/dist/modules/src/audio-encoder.d.ts.map +1 -0
- package/dist/modules/src/audio-encoder.js +329 -0
- package/dist/modules/src/audio-sample.d.ts +38 -0
- package/dist/modules/src/audio-sample.d.ts.map +1 -0
- package/dist/modules/src/audio-sample.js +119 -0
- package/dist/modules/src/index.d.ts +39 -0
- package/dist/modules/src/index.d.ts.map +1 -0
- package/dist/modules/src/index.js +132 -0
- package/dist/modules/src/misc.d.ts +26 -0
- package/dist/modules/src/misc.d.ts.map +1 -0
- package/dist/modules/src/misc.js +255 -0
- package/dist/modules/src/video-decoder.d.ts +30 -0
- package/dist/modules/src/video-decoder.d.ts.map +1 -0
- package/dist/modules/src/video-decoder.js +214 -0
- package/dist/modules/src/video-encoder.d.ts +35 -0
- package/dist/modules/src/video-encoder.d.ts.map +1 -0
- package/dist/modules/src/video-encoder.js +474 -0
- package/dist/modules/src/video-sample.d.ts +45 -0
- package/dist/modules/src/video-sample.d.ts.map +1 -0
- package/dist/modules/src/video-sample.js +276 -0
- package/dist/modules/tsconfig.tsbuildinfo +1 -0
- package/package.json +59 -0
- package/src/audio-decoder.ts +120 -0
- package/src/audio-encoder.ts +396 -0
- package/src/audio-sample.ts +101 -0
- package/src/index.ts +104 -0
- package/src/misc.ts +242 -0
- package/src/video-decoder.ts +224 -0
- package/src/video-encoder.ts +568 -0
- package/src/video-sample.ts +313 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { AudioSample } from 'mediabunny';
|
|
2
|
+
import { AudioSampleResource } from 'mediabunny';
|
|
3
|
+
import { MaybePromise } from 'mediabunny';
|
|
4
|
+
import * as NodeAv from 'node-av';
|
|
5
|
+
import { SetRequired } from 'mediabunny';
|
|
6
|
+
import { VideoDataPlane } from 'mediabunny';
|
|
7
|
+
import { VideoSample } from 'mediabunny';
|
|
8
|
+
import { VideoSampleColorSpace } from 'mediabunny';
|
|
9
|
+
import { VideoSampleInit } from 'mediabunny';
|
|
10
|
+
import { VideoSamplePixelFormat } from 'mediabunny';
|
|
11
|
+
import { VideoSampleResource } from 'mediabunny';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A custom `AudioSampleResource` backed by NodeAV's
|
|
15
|
+
* [`Frame`](https://seydx.github.io/node-av/api/lib/classes/Frame.html), which in turn is backed by FFmpeg's
|
|
16
|
+
* [`AVFrame`](https://ffmpeg.org/doxygen/2.7/structAVFrame.html). You can use this resource to create `AudioSample`
|
|
17
|
+
* instances that are directly backed by FFmpeg's `AVFrame` without data having to be copied.
|
|
18
|
+
*
|
|
19
|
+
* When passed, the `Frame` is now owned by resource, meaning it takes care of closing the frame later. If you want to
|
|
20
|
+
* keep a copy for your own use, clone the frame first.
|
|
21
|
+
*
|
|
22
|
+
* @group \@mediabunny/server
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
export declare class AvFrameAudioSampleResource extends AudioSampleResource {
|
|
26
|
+
/**
|
|
27
|
+
* The NodeAV [`Frame`](https://seydx.github.io/node-av/api/lib/classes/Frame.html) instance backing this resource.
|
|
28
|
+
* Access throws if the resource has already been closed.
|
|
29
|
+
*/
|
|
30
|
+
get frame(): NodeAv.Frame;
|
|
31
|
+
constructor(frame: NodeAv.Frame);
|
|
32
|
+
getFormat(): AudioSampleFormat;
|
|
33
|
+
getSampleRate(): number;
|
|
34
|
+
getNumberOfChannels(): number;
|
|
35
|
+
getNumberOfFrames(): number;
|
|
36
|
+
getTimestamp(): number;
|
|
37
|
+
close(): void;
|
|
38
|
+
getDataPlane(planeIndex: number): Uint8Array;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A custom `VideoSampleResource` backed by NodeAV's
|
|
43
|
+
* [`Frame`](https://seydx.github.io/node-av/api/lib/classes/Frame.html), which in turn is backed by FFmpeg's
|
|
44
|
+
* [`AVFrame`](https://ffmpeg.org/doxygen/2.7/structAVFrame.html). You can use this resource to create `VideoSample`
|
|
45
|
+
* instances that are directly backed by FFmpeg's `AVFrame` without data having to be copied. Since `AVFrame`s can
|
|
46
|
+
* themselves be backed by data on the GPU, this enables zero-copy hardware-accelerated decode and encode paths.
|
|
47
|
+
*
|
|
48
|
+
* When using Electron, you can directly create `Frame` instances without the data having to leave the GPU. For more,
|
|
49
|
+
* see [NodeAV's docs](https://seydx.github.io/node-av/api/lib/classes/Frame.html).
|
|
50
|
+
*
|
|
51
|
+
* When passed, the `Frame` is now owned by resource, meaning it takes care of closing the frame later. If you want to
|
|
52
|
+
* keep a copy for your own use, clone the frame first.
|
|
53
|
+
*
|
|
54
|
+
* @group \@mediabunny/server
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
export declare class AvFrameVideoSampleResource extends VideoSampleResource {
|
|
58
|
+
/**
|
|
59
|
+
* The NodeAV [`Frame`](https://seydx.github.io/node-av/api/lib/classes/Frame.html) instance backing this resource.
|
|
60
|
+
* Access throws if the resource has already been closed.
|
|
61
|
+
*/
|
|
62
|
+
get frame(): NodeAv.Frame;
|
|
63
|
+
constructor(frame: NodeAv.Frame);
|
|
64
|
+
getFormat(): VideoSamplePixelFormat | null;
|
|
65
|
+
getCodedWidth(): number;
|
|
66
|
+
getCodedHeight(): number;
|
|
67
|
+
getSquarePixelWidth(): number;
|
|
68
|
+
getSquarePixelHeight(): number;
|
|
69
|
+
getColorSpace(): VideoSampleColorSpace;
|
|
70
|
+
close(): void;
|
|
71
|
+
getDataPlanes(): MaybePromise<VideoDataPlane[]>;
|
|
72
|
+
toRgbSample(init: SetRequired<VideoSampleInit, 'timestamp'>, colorSpace: PredefinedColorSpace): Promise<VideoSample>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Registers video and audio decoders and encoders for all codecs, using FFmpeg's libavcodec under the hood.
|
|
77
|
+
* Additionally, a custom `VideoSample` transformer based on libavfilter is registered to enable resizing, rotation and
|
|
78
|
+
* cropping of video frames.
|
|
79
|
+
*
|
|
80
|
+
* Make sure to call this function before interacting with Mediabunny.
|
|
81
|
+
*
|
|
82
|
+
* The decoders and encoders will automatically detect hardware acceleration support for each codec and platform and
|
|
83
|
+
* make use of it if applicable.
|
|
84
|
+
*
|
|
85
|
+
* @group \@mediabunny/server
|
|
86
|
+
* @public
|
|
87
|
+
*/
|
|
88
|
+
export declare const registerMediabunnyServer: () => void;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Copies a `VideoSample` or `AudioSample` into the given NodeAV
|
|
92
|
+
* [`Frame`](https://seydx.github.io/node-av/api/lib/classes/Frame.html), setting up the frame's format, media type,
|
|
93
|
+
* timing and data. When the sample is already backed by an `AVFrame` (via `AvFrameVideoSampleResource` or
|
|
94
|
+
* `AvFrameAudioSampleResource`), the frame is ref'd instead of copied for zero-copy reuse.
|
|
95
|
+
*
|
|
96
|
+
* For video samples, the frame's time base is always set to 1/1000000 (microsecond accuracy). For audio samples, the
|
|
97
|
+
* frame's time base is always set to 1/sampleRate.
|
|
98
|
+
*
|
|
99
|
+
* @group \@mediabunny/server
|
|
100
|
+
* @public
|
|
101
|
+
*/
|
|
102
|
+
export declare const toAvFrame: (sample: VideoSample | AudioSample, frame: NodeAv.Frame) => Promise<void>;
|
|
103
|
+
|
|
104
|
+
export { }
|
|
105
|
+
export as namespace MediabunnyServer;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026-present, Vanilagy and contributors
|
|
3
|
+
*
|
|
4
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
import { AudioCodec, CustomAudioDecoder, EncodedPacket, type MaybePromise } from 'mediabunny';
|
|
9
|
+
import * as NodeAv from 'node-av';
|
|
10
|
+
export declare class NodeAvAudioDecoder extends CustomAudioDecoder {
|
|
11
|
+
frame: NodeAv.Frame;
|
|
12
|
+
packet: NodeAv.Packet;
|
|
13
|
+
codecContext: NodeAv.CodecContext;
|
|
14
|
+
static supports(codec: AudioCodec, config: AudioDecoderConfig): boolean;
|
|
15
|
+
init(): Promise<void>;
|
|
16
|
+
decode(packet: EncodedPacket): Promise<void>;
|
|
17
|
+
receiveFrame(ret: number): void;
|
|
18
|
+
flush(): Promise<void>;
|
|
19
|
+
close(): MaybePromise<void>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=audio-decoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-decoder.d.ts","sourceRoot":"","sources":["../../../src/audio-decoder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAe,kBAAkB,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAC3G,OAAO,KAAK,MAAM,MAAM,SAAS,CAAC;AAKlC,qBAAa,kBAAmB,SAAQ,kBAAkB;IACzD,KAAK,EAAG,MAAM,CAAC,KAAK,CAAC;IACrB,MAAM,EAAG,MAAM,CAAC,MAAM,CAAC;IACvB,YAAY,EAAG,MAAM,CAAC,YAAY,CAAC;WAGnB,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO;IAU1E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgCrB,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBlD,YAAY,CAAC,GAAG,EAAE,MAAM;IAYlB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB5B,KAAK,IAAI,YAAY,CAAC,IAAI,CAAC;CAK3B"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* Copyright (c) 2026-present, Vanilagy and contributors
|
|
4
|
+
*
|
|
5
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
6
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
7
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.NodeAvAudioDecoder = void 0;
|
|
44
|
+
const mediabunny_1 = require("mediabunny");
|
|
45
|
+
const NodeAv = __importStar(require("node-av"));
|
|
46
|
+
const misc_1 = require("./misc");
|
|
47
|
+
const misc_2 = require("../../../src/misc");
|
|
48
|
+
const audio_sample_1 = require("./audio-sample");
|
|
49
|
+
class NodeAvAudioDecoder extends mediabunny_1.CustomAudioDecoder {
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
51
|
+
static supports(codec, config) {
|
|
52
|
+
return codec === 'aac'
|
|
53
|
+
|| codec === 'opus'
|
|
54
|
+
|| codec === 'mp3'
|
|
55
|
+
|| codec === 'vorbis'
|
|
56
|
+
|| codec === 'flac'
|
|
57
|
+
|| codec === 'ac3'
|
|
58
|
+
|| codec === 'eac3';
|
|
59
|
+
}
|
|
60
|
+
async init() {
|
|
61
|
+
this.frame = new NodeAv.Frame();
|
|
62
|
+
this.frame.alloc();
|
|
63
|
+
this.packet = new NodeAv.Packet();
|
|
64
|
+
this.packet.alloc();
|
|
65
|
+
const codecId = misc_1.CODEC_TO_CODEC_ID[this.codec];
|
|
66
|
+
(0, misc_2.assert)(codecId !== undefined);
|
|
67
|
+
const codec = NodeAv.Codec.findDecoder(codecId);
|
|
68
|
+
if (codec === null) {
|
|
69
|
+
throw new Error(`Unable to obtain libav codec for '${this.codec}'.`);
|
|
70
|
+
}
|
|
71
|
+
const codecContext = new NodeAv.CodecContext();
|
|
72
|
+
codecContext.allocContext3(codec);
|
|
73
|
+
codecContext.sampleRate = this.config.sampleRate;
|
|
74
|
+
codecContext.channelLayout = (0, misc_1.getChannelLayout)(this.config.numberOfChannels);
|
|
75
|
+
codecContext.timeBase = new NodeAv.Rational(1, this.config.sampleRate);
|
|
76
|
+
codecContext.codecType = NodeAv.AVMEDIA_TYPE_AUDIO;
|
|
77
|
+
codecContext.codecId = codecId;
|
|
78
|
+
codecContext.extraData = this.config.description
|
|
79
|
+
? Buffer.from((0, misc_2.toUint8Array)(this.config.description))
|
|
80
|
+
: null;
|
|
81
|
+
const ret = await codecContext.open2();
|
|
82
|
+
NodeAv.FFmpegError.throwIfError(ret, 'Open codec context');
|
|
83
|
+
this.codecContext = codecContext;
|
|
84
|
+
}
|
|
85
|
+
async decode(packet) {
|
|
86
|
+
this.packet.isKeyframe = packet.type === 'key';
|
|
87
|
+
this.packet.data = Buffer.from(packet.data);
|
|
88
|
+
this.packet.timeBase = { num: 1, den: this.config.sampleRate };
|
|
89
|
+
this.packet.pts = BigInt(Math.round(packet.timestamp * this.config.sampleRate));
|
|
90
|
+
this.packet.dts = NodeAv.AV_NOPTS_VALUE;
|
|
91
|
+
this.packet.duration = BigInt(Math.round(packet.duration * this.config.sampleRate));
|
|
92
|
+
const ret = await this.codecContext.sendPacket(this.packet);
|
|
93
|
+
NodeAv.FFmpegError.throwIfError(ret, 'Send packet');
|
|
94
|
+
while (true) {
|
|
95
|
+
const receiveRet = await this.codecContext.receiveFrame(this.frame);
|
|
96
|
+
if (receiveRet === NodeAv.AVERROR_EAGAIN || receiveRet === NodeAv.AVERROR_EOF) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
this.receiveFrame(receiveRet);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
receiveFrame(ret) {
|
|
103
|
+
NodeAv.FFmpegError.throwIfError(ret, 'Receive frame');
|
|
104
|
+
const clone = this.frame.clone();
|
|
105
|
+
if (!clone) {
|
|
106
|
+
throw new Error('Allocation failure during frame clone.');
|
|
107
|
+
}
|
|
108
|
+
clone.timeBase = new NodeAv.Rational(1, this.config.sampleRate);
|
|
109
|
+
this.onSample(new mediabunny_1.AudioSample(new audio_sample_1.AvFrameAudioSampleResource(clone)));
|
|
110
|
+
}
|
|
111
|
+
async flush() {
|
|
112
|
+
// Send null packet to signal flush
|
|
113
|
+
const ret = await this.codecContext.sendPacket(null);
|
|
114
|
+
NodeAv.FFmpegError.throwIfError(ret, 'Flush decoder');
|
|
115
|
+
// Keep receiving frames until no more are available
|
|
116
|
+
while (true) {
|
|
117
|
+
const receiveRet = await this.codecContext.receiveFrame(this.frame);
|
|
118
|
+
if (receiveRet === NodeAv.AVERROR_EAGAIN || receiveRet === NodeAv.AVERROR_EOF) {
|
|
119
|
+
// No more frames available
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
this.receiveFrame(receiveRet);
|
|
123
|
+
}
|
|
124
|
+
this.codecContext.flushBuffers();
|
|
125
|
+
}
|
|
126
|
+
close() {
|
|
127
|
+
this.codecContext.freeContext();
|
|
128
|
+
this.frame.free();
|
|
129
|
+
this.packet.free();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.NodeAvAudioDecoder = NodeAvAudioDecoder;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026-present, Vanilagy and contributors
|
|
3
|
+
*
|
|
4
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
import { AudioCodec, AudioSample, CustomAudioEncoder, type MaybePromise } from 'mediabunny';
|
|
9
|
+
import * as NodeAv from 'node-av';
|
|
10
|
+
import { AdtsHeaderTemplate } from '../../../shared/aac-misc';
|
|
11
|
+
export declare class NodeAvAudioEncoder extends CustomAudioEncoder {
|
|
12
|
+
frame: NodeAv.Frame;
|
|
13
|
+
packet: NodeAv.Packet;
|
|
14
|
+
avCodec: NodeAv.Codec;
|
|
15
|
+
codecContext: NodeAv.CodecContext | null;
|
|
16
|
+
firstExpectedTimestamp: number | null;
|
|
17
|
+
outputTimestampOffset: number;
|
|
18
|
+
resampler: NodeAv.SoftwareResampleContext | null;
|
|
19
|
+
inputParametersKey: string | null;
|
|
20
|
+
resamplerInputSampleRate: number | null;
|
|
21
|
+
nextResamplerPts: bigint | null;
|
|
22
|
+
dstFrame: NodeAv.Frame | null;
|
|
23
|
+
packetEmitted: boolean;
|
|
24
|
+
adtsHeaderTemplate: AdtsHeaderTemplate | null;
|
|
25
|
+
static supports(codec: AudioCodec, config: AudioEncoderConfig): boolean;
|
|
26
|
+
init(): Promise<void>;
|
|
27
|
+
createCodecContext(): Promise<void>;
|
|
28
|
+
encode(audioSample: AudioSample): Promise<void>;
|
|
29
|
+
pullResampledFrames(): Promise<void>;
|
|
30
|
+
sendFrameAndReceivePackets(frame: NodeAv.Frame | null): Promise<void>;
|
|
31
|
+
receivePacket(ret: number): void;
|
|
32
|
+
flush(): Promise<void>;
|
|
33
|
+
close(): MaybePromise<void>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=audio-encoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-encoder.d.ts","sourceRoot":"","sources":["../../../src/audio-encoder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACN,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,KAAK,YAAY,EAGjB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,MAAM,MAAM,SAAS,CAAC;AAIlC,OAAO,EACN,kBAAkB,EAGlB,MAAM,0BAA0B,CAAC;AAUlC,qBAAa,kBAAmB,SAAQ,kBAAkB;IACzD,KAAK,EAAG,MAAM,CAAC,KAAK,CAAC;IACrB,MAAM,EAAG,MAAM,CAAC,MAAM,CAAC;IACvB,OAAO,EAAG,MAAM,CAAC,KAAK,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,IAAI,CAAQ;IAChD,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC7C,qBAAqB,SAAK;IAE1B,SAAS,EAAE,MAAM,CAAC,uBAAuB,GAAG,IAAI,CAAQ;IACxD,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAQ;IACzC,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC/C,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAQ;IACvC,QAAQ,EAAE,MAAM,CAAC,KAAK,GAAG,IAAI,CAAQ;IACrC,aAAa,UAAS;IACtB,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAQ;WAErC,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO;IA2B1E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBrB,kBAAkB;IA+BlB,MAAM,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA6E/C,mBAAmB;IAuBnB,0BAA0B,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,GAAG,IAAI;IAgB3D,aAAa,CAAC,GAAG,EAAE,MAAM;IAsGnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA2C5B,KAAK,IAAI,YAAY,CAAC,IAAI,CAAC;CAO3B"}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* Copyright (c) 2026-present, Vanilagy and contributors
|
|
4
|
+
*
|
|
5
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
6
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
7
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.NodeAvAudioEncoder = void 0;
|
|
44
|
+
const mediabunny_1 = require("mediabunny");
|
|
45
|
+
const NodeAv = __importStar(require("node-av"));
|
|
46
|
+
const misc_1 = require("./misc");
|
|
47
|
+
const misc_2 = require("../../../src/misc");
|
|
48
|
+
const audio_sample_1 = require("./audio-sample");
|
|
49
|
+
const aac_misc_1 = require("../../../shared/aac-misc");
|
|
50
|
+
const AAC_SAMPLE_RATES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
|
51
|
+
const OPUS_SAMPLE_RATES = [8000, 12000, 16000, 24000, 48000];
|
|
52
|
+
const MP3_SAMPLE_RATES = [8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000];
|
|
53
|
+
const AC3_SAMPLE_RATES = [32000, 44100, 48000];
|
|
54
|
+
const FRAME_SIZE_FALLBACK = 1024; // Just 'cause
|
|
55
|
+
class NodeAvAudioEncoder extends mediabunny_1.CustomAudioEncoder {
|
|
56
|
+
constructor() {
|
|
57
|
+
super(...arguments);
|
|
58
|
+
this.codecContext = null;
|
|
59
|
+
this.firstExpectedTimestamp = null;
|
|
60
|
+
this.outputTimestampOffset = 0;
|
|
61
|
+
this.resampler = null;
|
|
62
|
+
this.inputParametersKey = null;
|
|
63
|
+
this.resamplerInputSampleRate = null;
|
|
64
|
+
this.nextResamplerPts = null;
|
|
65
|
+
this.dstFrame = null;
|
|
66
|
+
this.packetEmitted = false;
|
|
67
|
+
this.adtsHeaderTemplate = null;
|
|
68
|
+
}
|
|
69
|
+
static supports(codec, config) {
|
|
70
|
+
const { numberOfChannels, sampleRate } = config;
|
|
71
|
+
return (codec === 'aac' && numberOfChannels >= 1 && numberOfChannels <= 48
|
|
72
|
+
&& AAC_SAMPLE_RATES.includes(sampleRate)) || (codec === 'opus' && numberOfChannels >= 1 && numberOfChannels <= 255
|
|
73
|
+
&& OPUS_SAMPLE_RATES.includes(sampleRate)) || (codec === 'mp3' && numberOfChannels >= 1 && numberOfChannels <= 2
|
|
74
|
+
&& MP3_SAMPLE_RATES.includes(sampleRate)) || (codec === 'vorbis' && numberOfChannels >= 1 && numberOfChannels <= 255
|
|
75
|
+
&& sampleRate <= 200000) || (codec === 'flac' && numberOfChannels >= 1 && numberOfChannels <= 8
|
|
76
|
+
&& sampleRate <= 655350) || (codec === 'ac3' && numberOfChannels >= 1 && numberOfChannels <= 6
|
|
77
|
+
&& AC3_SAMPLE_RATES.includes(sampleRate)) || (codec === 'eac3' && numberOfChannels >= 1 && numberOfChannels <= 16
|
|
78
|
+
&& AC3_SAMPLE_RATES.includes(sampleRate));
|
|
79
|
+
}
|
|
80
|
+
async init() {
|
|
81
|
+
this.frame = new NodeAv.Frame();
|
|
82
|
+
this.frame.alloc();
|
|
83
|
+
this.packet = new NodeAv.Packet();
|
|
84
|
+
this.packet.alloc();
|
|
85
|
+
const codecId = misc_1.CODEC_TO_CODEC_ID[this.codec];
|
|
86
|
+
(0, misc_2.assert)(codecId !== undefined);
|
|
87
|
+
const codec = NodeAv.Codec.findEncoder(codecId);
|
|
88
|
+
if (!codec) {
|
|
89
|
+
throw new Error(`Unable to obtain libav codec for '${this.codec}'.`);
|
|
90
|
+
}
|
|
91
|
+
this.avCodec = codec;
|
|
92
|
+
await this.createCodecContext();
|
|
93
|
+
}
|
|
94
|
+
async createCodecContext() {
|
|
95
|
+
(0, misc_2.assert)(this.codecContext === null);
|
|
96
|
+
const codecContext = new NodeAv.CodecContext();
|
|
97
|
+
codecContext.allocContext3(this.avCodec);
|
|
98
|
+
let sampleFormat = NodeAv.AV_SAMPLE_FMT_FLTP;
|
|
99
|
+
if (this.avCodec.sampleFormats && !this.avCodec.sampleFormats.includes(NodeAv.AV_SAMPLE_FMT_FLTP)) {
|
|
100
|
+
// Use a format that's supported
|
|
101
|
+
sampleFormat = this.avCodec.sampleFormats[0];
|
|
102
|
+
}
|
|
103
|
+
codecContext.sampleRate = this.config.sampleRate;
|
|
104
|
+
codecContext.channelLayout = (0, misc_1.getChannelLayout)(this.config.numberOfChannels);
|
|
105
|
+
codecContext.codecType = NodeAv.AVMEDIA_TYPE_AUDIO;
|
|
106
|
+
codecContext.codecId = misc_1.CODEC_TO_CODEC_ID[this.codec];
|
|
107
|
+
codecContext.sampleFormat = sampleFormat;
|
|
108
|
+
codecContext.timeBase = new NodeAv.Rational(1, this.config.sampleRate);
|
|
109
|
+
codecContext.bitRate = BigInt(this.config.bitrate ?? mediabunny_1.QUALITY_MEDIUM._toAudioBitrate(this.codec) ?? 0);
|
|
110
|
+
if (this.config.bitrateMode === 'constant') {
|
|
111
|
+
codecContext.rcMinRate = codecContext.bitRate;
|
|
112
|
+
codecContext.rcMaxRate = codecContext.bitRate;
|
|
113
|
+
}
|
|
114
|
+
const ret = await codecContext.open2();
|
|
115
|
+
NodeAv.FFmpegError.throwIfError(ret, 'Open codec context');
|
|
116
|
+
this.codecContext = codecContext;
|
|
117
|
+
}
|
|
118
|
+
async encode(audioSample) {
|
|
119
|
+
if (this.codecContext === null) {
|
|
120
|
+
await this.createCodecContext();
|
|
121
|
+
(0, misc_2.assert)(this.codecContext);
|
|
122
|
+
}
|
|
123
|
+
this.firstExpectedTimestamp ??= audioSample.timestamp;
|
|
124
|
+
if (audioSample._data instanceof audio_sample_1.AvFrameAudioSampleResource) {
|
|
125
|
+
this.frame.ref(audioSample._data.frame);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
(0, audio_sample_1.copyAudioSampleToAvFrame)(audioSample, this.frame);
|
|
129
|
+
}
|
|
130
|
+
this.frame.pts = BigInt(Math.round(audioSample.timestamp * this.config.sampleRate));
|
|
131
|
+
this.frame.duration = BigInt(Math.round(audioSample.duration * this.config.sampleRate));
|
|
132
|
+
this.frame.timeBase = new NodeAv.Rational(1, this.config.sampleRate);
|
|
133
|
+
const key = `${this.frame.sampleRate}:${this.frame.channels}:${this.frame.format}`;
|
|
134
|
+
if (this.inputParametersKey !== null && this.inputParametersKey !== key) {
|
|
135
|
+
throw new Error('Input audio parameters changed. For this audio encoder, you cannot change the input audio'
|
|
136
|
+
+ ' parameters over time.');
|
|
137
|
+
}
|
|
138
|
+
this.inputParametersKey = key;
|
|
139
|
+
// We need the resampler when:
|
|
140
|
+
// 1. Format conversion is needed (sample format, sample rate, or channel count differs)
|
|
141
|
+
// 2. The codec requires fixed frame sizes
|
|
142
|
+
const requiresResampler = this.codecContext.frameSize > 0
|
|
143
|
+
|| this.codecContext.sampleFormat !== this.frame.format
|
|
144
|
+
|| this.codecContext.sampleRate !== this.frame.sampleRate
|
|
145
|
+
|| this.codecContext.channels !== this.frame.channels;
|
|
146
|
+
if (requiresResampler) {
|
|
147
|
+
if (!this.resampler) {
|
|
148
|
+
this.resampler = new NodeAv.SoftwareResampleContext();
|
|
149
|
+
this.resamplerInputSampleRate = this.frame.sampleRate;
|
|
150
|
+
const outLayout = (0, misc_1.getChannelLayout)(this.codecContext.channels);
|
|
151
|
+
const inLayout = (0, misc_1.getChannelLayout)(this.frame.channels);
|
|
152
|
+
const ret = this.resampler.allocSetOpts2(outLayout, this.codecContext.sampleFormat, this.codecContext.sampleRate, inLayout, this.frame.format, this.frame.sampleRate);
|
|
153
|
+
NodeAv.FFmpegError.throwIfError(ret, 'allocSetOpts2');
|
|
154
|
+
const ret2 = this.resampler.init();
|
|
155
|
+
NodeAv.FFmpegError.throwIfError(ret2, 'init');
|
|
156
|
+
this.dstFrame = new NodeAv.Frame();
|
|
157
|
+
this.dstFrame.alloc();
|
|
158
|
+
this.dstFrame.channelLayout = outLayout;
|
|
159
|
+
this.dstFrame.sampleRate = this.codecContext.sampleRate;
|
|
160
|
+
this.dstFrame.format = this.codecContext.sampleFormat;
|
|
161
|
+
this.dstFrame.nbSamples = this.codecContext.frameSize || FRAME_SIZE_FALLBACK;
|
|
162
|
+
this.dstFrame.duration = BigInt(this.dstFrame.nbSamples);
|
|
163
|
+
this.dstFrame.allocBuffer();
|
|
164
|
+
this.nextResamplerPts = this.frame.pts;
|
|
165
|
+
}
|
|
166
|
+
const inputBuffers = this.frame.data;
|
|
167
|
+
if (!inputBuffers) {
|
|
168
|
+
throw new DOMException('Frame has no data', 'EncodingError');
|
|
169
|
+
}
|
|
170
|
+
await this.resampler.convert(null, 0, inputBuffers, this.frame.nbSamples);
|
|
171
|
+
await this.pullResampledFrames();
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
await this.sendFrameAndReceivePackets(this.frame);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async pullResampledFrames() {
|
|
178
|
+
(0, misc_2.assert)(this.codecContext);
|
|
179
|
+
(0, misc_2.assert)(this.resampler);
|
|
180
|
+
(0, misc_2.assert)(this.dstFrame);
|
|
181
|
+
(0, misc_2.assert)(this.nextResamplerPts !== null);
|
|
182
|
+
const frameSize = this.codecContext.frameSize || FRAME_SIZE_FALLBACK;
|
|
183
|
+
while (true) {
|
|
184
|
+
const available = this.resampler.getOutSamples(0);
|
|
185
|
+
if (available < frameSize) {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
await this.resampler.convert(this.dstFrame.data, frameSize, null, 0);
|
|
189
|
+
this.dstFrame.pts = this.nextResamplerPts;
|
|
190
|
+
await this.sendFrameAndReceivePackets(this.dstFrame);
|
|
191
|
+
this.nextResamplerPts += BigInt(frameSize);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async sendFrameAndReceivePackets(frame) {
|
|
195
|
+
(0, misc_2.assert)(this.codecContext);
|
|
196
|
+
const ret = await this.codecContext.sendFrame(frame);
|
|
197
|
+
NodeAv.FFmpegError.throwIfError(ret, 'Send frame');
|
|
198
|
+
while (true) {
|
|
199
|
+
const receiveRet = await this.codecContext.receivePacket(this.packet);
|
|
200
|
+
if (receiveRet === NodeAv.AVERROR_EAGAIN || receiveRet === NodeAv.AVERROR_EOF) {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
this.receivePacket(receiveRet);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
receivePacket(ret) {
|
|
207
|
+
(0, misc_2.assert)(this.codecContext);
|
|
208
|
+
(0, misc_2.assert)(this.firstExpectedTimestamp !== null);
|
|
209
|
+
NodeAv.FFmpegError.throwIfError(ret, 'Receive packet');
|
|
210
|
+
if (!this.packet.data) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
let timestamp = Number(this.packet.pts) / this.codecContext.sampleRate;
|
|
214
|
+
const duration = Number(this.packet.duration) / this.codecContext.sampleRate;
|
|
215
|
+
let data = this.packet.data;
|
|
216
|
+
let metadata;
|
|
217
|
+
if (this.packetEmitted) {
|
|
218
|
+
metadata = {};
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
// To compensate for any negative timestamp things that FFmpeg might do. It does these for a reason, to
|
|
222
|
+
// indicate encoder delay, but the notion of this is not yet supported in Mediabunny. Yes, this technically
|
|
223
|
+
// introduces audio sync drift.
|
|
224
|
+
this.outputTimestampOffset = Math.max(this.firstExpectedTimestamp - timestamp, 0);
|
|
225
|
+
const codecString = this.config.codec;
|
|
226
|
+
let description = this.codecContext.extraData
|
|
227
|
+
? (0, misc_2.toUint8Array)(this.codecContext.extraData)
|
|
228
|
+
: undefined;
|
|
229
|
+
if (this.codec === 'aac') {
|
|
230
|
+
if (!description) {
|
|
231
|
+
throw new Error('Extradata expected for AAC.');
|
|
232
|
+
}
|
|
233
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
234
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
235
|
+
const isAdts = this.config.aac?.format === 'adts';
|
|
236
|
+
if (isAdts) {
|
|
237
|
+
const parsedConfig = (0, aac_misc_1.parseAacAudioSpecificConfig)(description);
|
|
238
|
+
this.adtsHeaderTemplate = (0, aac_misc_1.buildAdtsHeaderTemplate)(parsedConfig);
|
|
239
|
+
description = undefined; // Not used with 'adts' format
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else if (this.codec === 'opus') {
|
|
243
|
+
if (!description) {
|
|
244
|
+
// Technically not required by the WebCodecs/Mediabunny Codec Registry, but we strive to be better
|
|
245
|
+
throw new Error('Extradata expected for Opus.');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (this.codec === 'vorbis') {
|
|
249
|
+
if (!description) {
|
|
250
|
+
throw new Error('Extradata expected for Vorbis.');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else if (this.codec === 'flac') {
|
|
254
|
+
if (!description) {
|
|
255
|
+
throw new Error('Extradata expected for FLAC.');
|
|
256
|
+
}
|
|
257
|
+
// FFmpeg uses the STREAMINFO block as the extradata, but WebCodecs wants a different format:
|
|
258
|
+
// 1. The bytes 0x66 0x4C 0x61 0x43 ("fLaC" in ASCII)
|
|
259
|
+
// 2. A metadata block (called the STREAMINFO block) as described in section 7 of [FLAC]
|
|
260
|
+
// 3. Other optional metadata blocks (not included here, because, well, they're optional)
|
|
261
|
+
description = new Uint8Array([
|
|
262
|
+
0x66, 0x4c, 0x61, 0x43, // 'fLaC'
|
|
263
|
+
128, 0, 0, description.byteLength,
|
|
264
|
+
...description,
|
|
265
|
+
]);
|
|
266
|
+
}
|
|
267
|
+
metadata = {
|
|
268
|
+
decoderConfig: {
|
|
269
|
+
codec: codecString,
|
|
270
|
+
sampleRate: this.codecContext.sampleRate,
|
|
271
|
+
numberOfChannels: this.codecContext.channels,
|
|
272
|
+
description,
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
if (this.adtsHeaderTemplate) {
|
|
277
|
+
const frameLength = data.byteLength + this.adtsHeaderTemplate.header.byteLength;
|
|
278
|
+
this.adtsHeaderTemplate.bitstream.pos = 30;
|
|
279
|
+
this.adtsHeaderTemplate.bitstream.writeBits(13, frameLength);
|
|
280
|
+
const final = new Uint8Array(this.adtsHeaderTemplate.header.byteLength + data.byteLength);
|
|
281
|
+
final.set(this.adtsHeaderTemplate.header, 0);
|
|
282
|
+
final.set(data, this.adtsHeaderTemplate.header.byteLength);
|
|
283
|
+
data = final;
|
|
284
|
+
}
|
|
285
|
+
timestamp += this.outputTimestampOffset;
|
|
286
|
+
const packet = new mediabunny_1.EncodedPacket(data, 'key', timestamp, duration);
|
|
287
|
+
this.packetEmitted = true;
|
|
288
|
+
this.onPacket(packet, metadata);
|
|
289
|
+
}
|
|
290
|
+
async flush() {
|
|
291
|
+
if (!this.codecContext) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
outer: if (this.resampler) {
|
|
295
|
+
(0, misc_2.assert)(this.resamplerInputSampleRate !== null);
|
|
296
|
+
const currentOutSamples = this.resampler.getOutSamples(0);
|
|
297
|
+
if (currentOutSamples === 0) {
|
|
298
|
+
break outer; // Clean cut-off point
|
|
299
|
+
}
|
|
300
|
+
const frameSize = this.codecContext.frameSize || FRAME_SIZE_FALLBACK;
|
|
301
|
+
(0, misc_2.assert)(currentOutSamples < frameSize); // Because if it's more, it would've already been retrieved
|
|
302
|
+
const inputSamplesNeeded = Math.ceil(((frameSize - currentOutSamples) / this.codecContext.sampleRate) * this.resamplerInputSampleRate);
|
|
303
|
+
this.resampler.injectSilence(inputSamplesNeeded);
|
|
304
|
+
await this.pullResampledFrames();
|
|
305
|
+
}
|
|
306
|
+
await this.sendFrameAndReceivePackets(null);
|
|
307
|
+
this.codecContext.freeContext();
|
|
308
|
+
this.codecContext = null;
|
|
309
|
+
this.packetEmitted = false;
|
|
310
|
+
this.firstExpectedTimestamp = null;
|
|
311
|
+
this.outputTimestampOffset = 0;
|
|
312
|
+
this.adtsHeaderTemplate = null;
|
|
313
|
+
this.resampler?.free();
|
|
314
|
+
this.resampler = null;
|
|
315
|
+
this.inputParametersKey = null;
|
|
316
|
+
this.resamplerInputSampleRate = null;
|
|
317
|
+
this.nextResamplerPts = null;
|
|
318
|
+
this.dstFrame?.free();
|
|
319
|
+
this.dstFrame = null;
|
|
320
|
+
}
|
|
321
|
+
close() {
|
|
322
|
+
this.codecContext?.freeContext();
|
|
323
|
+
this.frame.free();
|
|
324
|
+
this.packet.free();
|
|
325
|
+
this.dstFrame?.free();
|
|
326
|
+
this.resampler?.free();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
exports.NodeAvAudioEncoder = NodeAvAudioEncoder;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026-present, Vanilagy and contributors
|
|
3
|
+
*
|
|
4
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
import { AudioSample, AudioSampleResource } from 'mediabunny';
|
|
9
|
+
import * as NodeAv from 'node-av';
|
|
10
|
+
/**
|
|
11
|
+
* A custom `AudioSampleResource` backed by NodeAV's
|
|
12
|
+
* [`Frame`](https://seydx.github.io/node-av/api/lib/classes/Frame.html), which in turn is backed by FFmpeg's
|
|
13
|
+
* [`AVFrame`](https://ffmpeg.org/doxygen/2.7/structAVFrame.html). You can use this resource to create `AudioSample`
|
|
14
|
+
* instances that are directly backed by FFmpeg's `AVFrame` without data having to be copied.
|
|
15
|
+
*
|
|
16
|
+
* When passed, the `Frame` is now owned by resource, meaning it takes care of closing the frame later. If you want to
|
|
17
|
+
* keep a copy for your own use, clone the frame first.
|
|
18
|
+
*
|
|
19
|
+
* @group \@mediabunny/server
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
export declare class AvFrameAudioSampleResource extends AudioSampleResource {
|
|
23
|
+
/**
|
|
24
|
+
* The NodeAV [`Frame`](https://seydx.github.io/node-av/api/lib/classes/Frame.html) instance backing this resource.
|
|
25
|
+
* Access throws if the resource has already been closed.
|
|
26
|
+
*/
|
|
27
|
+
get frame(): NodeAv.Frame;
|
|
28
|
+
constructor(frame: NodeAv.Frame);
|
|
29
|
+
getFormat(): AudioSampleFormat;
|
|
30
|
+
getSampleRate(): number;
|
|
31
|
+
getNumberOfChannels(): number;
|
|
32
|
+
getNumberOfFrames(): number;
|
|
33
|
+
getTimestamp(): number;
|
|
34
|
+
close(): void;
|
|
35
|
+
getDataPlane(planeIndex: number): Uint8Array;
|
|
36
|
+
}
|
|
37
|
+
export declare const copyAudioSampleToAvFrame: (sample: AudioSample, frame: NodeAv.Frame) => void;
|
|
38
|
+
//# sourceMappingURL=audio-sample.d.ts.map
|