@mediabunny/aac-encoder 1.35.1
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 +156 -0
- package/dist/bundles/mediabunny-aac-encoder.js +3510 -0
- package/dist/bundles/mediabunny-aac-encoder.min.js +3070 -0
- package/dist/bundles/mediabunny-aac-encoder.min.mjs +3069 -0
- package/dist/bundles/mediabunny-aac-encoder.mjs +3469 -0
- package/dist/mediabunny-aac-encoder.d.ts +22 -0
- package/dist/modules/build/aac.d.ts +3 -0
- package/dist/modules/build/aac.d.ts.map +1 -0
- package/dist/modules/build/aac.js +0 -0
- package/dist/modules/src/encode.worker.d.ts +9 -0
- package/dist/modules/src/encode.worker.d.ts.map +1 -0
- package/dist/modules/src/encode.worker.js +172 -0
- package/dist/modules/src/encoder.d.ts +27 -0
- package/dist/modules/src/encoder.d.ts.map +1 -0
- package/dist/modules/src/encoder.js +241 -0
- package/dist/modules/src/index.d.ts +9 -0
- package/dist/modules/src/index.d.ts.map +1 -0
- package/dist/modules/src/index.js +20 -0
- package/dist/modules/src/shared.d.ts +64 -0
- package/dist/modules/src/shared.d.ts.map +1 -0
- package/dist/modules/src/shared.js +9 -0
- package/dist/modules/tsconfig.tsbuildinfo +1 -0
- package/package.json +55 -0
- package/src/bridge.c +190 -0
- package/src/encode.worker.ts +204 -0
- package/src/encoder.ts +306 -0
- package/src/index.ts +20 -0
- package/src/shared.ts +66 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers the AAC encoder, which Mediabunny will then use automatically when applicable. Make sure to call this
|
|
3
|
+
* function before starting any encoding task.
|
|
4
|
+
*
|
|
5
|
+
* Preferably, wrap the call in a condition to avoid overriding any native AAC encoder:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { canEncodeAudio } from 'mediabunny';
|
|
9
|
+
* import { registerAacEncoder } from '@mediabunny/aac-encoder';
|
|
10
|
+
*
|
|
11
|
+
* if (!(await canEncodeAudio('aac'))) {
|
|
12
|
+
* registerAacEncoder();
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @group \@mediabunny/aac-encoder
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export declare const registerAacEncoder: () => void;
|
|
20
|
+
|
|
21
|
+
export { }
|
|
22
|
+
export as namespace MediabunnyAacEncoder;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aac.d.ts","sourceRoot":"","sources":["../../../build/aac.js"],"names":[],"mappings":";AAAA,qDACkB"}
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=encode.worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encode.worker.d.ts","sourceRoot":"","sources":["../../../src/encode.worker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,172 @@
|
|
|
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 __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
const aac_1 = __importDefault(require("../build/aac"));
|
|
14
|
+
let module;
|
|
15
|
+
let modulePromise = null;
|
|
16
|
+
let initEncoderFn;
|
|
17
|
+
let getEncoderFrameSize;
|
|
18
|
+
let getEncoderExtradata;
|
|
19
|
+
let getEncoderExtradataSize;
|
|
20
|
+
let getEncodeInputPtr;
|
|
21
|
+
let sendFrameFn;
|
|
22
|
+
let receivePacketFn;
|
|
23
|
+
let flushEncoderStartFn;
|
|
24
|
+
let resetEncoderFn;
|
|
25
|
+
let getEncodedData;
|
|
26
|
+
let getEncodedPts;
|
|
27
|
+
let getEncodedDuration;
|
|
28
|
+
let closeEncoderFn;
|
|
29
|
+
const ensureModule = async () => {
|
|
30
|
+
if (!module) {
|
|
31
|
+
if (modulePromise) {
|
|
32
|
+
return modulePromise;
|
|
33
|
+
}
|
|
34
|
+
modulePromise = (0, aac_1.default)();
|
|
35
|
+
module = await modulePromise;
|
|
36
|
+
modulePromise = null;
|
|
37
|
+
initEncoderFn = module.cwrap('init_encoder', 'number', ['number', 'number', 'number']);
|
|
38
|
+
getEncoderFrameSize = module.cwrap('get_encoder_frame_size', 'number', ['number']);
|
|
39
|
+
getEncoderExtradata = module.cwrap('get_encoder_extradata', 'number', ['number']);
|
|
40
|
+
getEncoderExtradataSize = module.cwrap('get_encoder_extradata_size', 'number', ['number']);
|
|
41
|
+
getEncodeInputPtr = module.cwrap('get_encode_input_ptr', 'number', ['number', 'number']);
|
|
42
|
+
sendFrameFn = module.cwrap('send_frame', 'number', ['number', 'number']);
|
|
43
|
+
receivePacketFn = module.cwrap('receive_packet', 'number', ['number']);
|
|
44
|
+
flushEncoderStartFn = module.cwrap('flush_encoder_start', null, ['number']);
|
|
45
|
+
resetEncoderFn = module.cwrap('reset_encoder', null, ['number']);
|
|
46
|
+
getEncodedData = module.cwrap('get_encoded_data', 'number', ['number']);
|
|
47
|
+
getEncodedPts = module.cwrap('get_encoded_pts', 'number', ['number']);
|
|
48
|
+
getEncodedDuration = module.cwrap('get_encoded_duration', 'number', ['number']);
|
|
49
|
+
closeEncoderFn = module.cwrap('close_encoder', null, ['number']);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const initEncoder = async (numberOfChannels, sampleRate, bitrate) => {
|
|
53
|
+
await ensureModule();
|
|
54
|
+
const ctx = initEncoderFn(numberOfChannels, sampleRate, bitrate);
|
|
55
|
+
if (ctx === 0) {
|
|
56
|
+
throw new Error('Failed to initialize AAC encoder.');
|
|
57
|
+
}
|
|
58
|
+
const frameSize = getEncoderFrameSize(ctx);
|
|
59
|
+
const extradataPtr = getEncoderExtradata(ctx);
|
|
60
|
+
const extradataSize = getEncoderExtradataSize(ctx);
|
|
61
|
+
const extradata = module.HEAPU8.slice(extradataPtr, extradataPtr + extradataSize).buffer;
|
|
62
|
+
return { ctx, frameSize, extradata };
|
|
63
|
+
};
|
|
64
|
+
const drainPackets = (ctx) => {
|
|
65
|
+
const packets = [];
|
|
66
|
+
let size;
|
|
67
|
+
while ((size = receivePacketFn(ctx)) > 0) {
|
|
68
|
+
const ptr = getEncodedData(ctx);
|
|
69
|
+
const encodedData = module.HEAPU8.slice(ptr, ptr + size).buffer;
|
|
70
|
+
const pts = getEncodedPts(ctx);
|
|
71
|
+
const duration = getEncodedDuration(ctx);
|
|
72
|
+
packets.push({ encodedData, pts, duration });
|
|
73
|
+
}
|
|
74
|
+
return packets;
|
|
75
|
+
};
|
|
76
|
+
const encode = (ctx, audioData, timestamp) => {
|
|
77
|
+
const audioBytes = new Uint8Array(audioData);
|
|
78
|
+
const inputPtr = getEncodeInputPtr(ctx, audioBytes.length);
|
|
79
|
+
if (inputPtr === 0) {
|
|
80
|
+
throw new Error('Failed to allocate encoder input buffer.');
|
|
81
|
+
}
|
|
82
|
+
module.HEAPU8.set(audioBytes, inputPtr);
|
|
83
|
+
const ret = sendFrameFn(ctx, timestamp);
|
|
84
|
+
if (ret < 0) {
|
|
85
|
+
throw new Error(`Encode failed with error code ${ret}.`);
|
|
86
|
+
}
|
|
87
|
+
return drainPackets(ctx);
|
|
88
|
+
};
|
|
89
|
+
const onMessage = (data) => {
|
|
90
|
+
const { id, command } = data;
|
|
91
|
+
const handleCommand = async () => {
|
|
92
|
+
try {
|
|
93
|
+
let result;
|
|
94
|
+
const transferables = [];
|
|
95
|
+
switch (command.type) {
|
|
96
|
+
case 'init':
|
|
97
|
+
{
|
|
98
|
+
const { ctx, frameSize, extradata } = await initEncoder(command.data.numberOfChannels, command.data.sampleRate, command.data.bitrate);
|
|
99
|
+
result = { type: command.type, ctx, frameSize, extradata };
|
|
100
|
+
transferables.push(extradata);
|
|
101
|
+
}
|
|
102
|
+
;
|
|
103
|
+
break;
|
|
104
|
+
case 'encode':
|
|
105
|
+
{
|
|
106
|
+
const packets = encode(command.data.ctx, command.data.audioData, command.data.timestamp);
|
|
107
|
+
for (const p of packets) {
|
|
108
|
+
transferables.push(p.encodedData);
|
|
109
|
+
}
|
|
110
|
+
result = { type: command.type, packets };
|
|
111
|
+
}
|
|
112
|
+
;
|
|
113
|
+
break;
|
|
114
|
+
case 'flush':
|
|
115
|
+
{
|
|
116
|
+
flushEncoderStartFn(command.data.ctx);
|
|
117
|
+
const packets = drainPackets(command.data.ctx);
|
|
118
|
+
for (const p of packets) {
|
|
119
|
+
transferables.push(p.encodedData);
|
|
120
|
+
}
|
|
121
|
+
resetEncoderFn(command.data.ctx);
|
|
122
|
+
result = { type: command.type, packets };
|
|
123
|
+
}
|
|
124
|
+
;
|
|
125
|
+
break;
|
|
126
|
+
case 'close':
|
|
127
|
+
{
|
|
128
|
+
closeEncoderFn(command.data.ctx);
|
|
129
|
+
result = { type: command.type };
|
|
130
|
+
}
|
|
131
|
+
;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
const response = {
|
|
135
|
+
id,
|
|
136
|
+
success: true,
|
|
137
|
+
data: result,
|
|
138
|
+
};
|
|
139
|
+
sendMessage(response, transferables);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
const response = {
|
|
143
|
+
id,
|
|
144
|
+
success: false,
|
|
145
|
+
error,
|
|
146
|
+
};
|
|
147
|
+
sendMessage(response);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
void handleCommand();
|
|
151
|
+
};
|
|
152
|
+
const sendMessage = (data, transferables) => {
|
|
153
|
+
if (parentPort) {
|
|
154
|
+
parentPort.postMessage(data, transferables ?? []);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
self.postMessage(data, { transfer: transferables ?? [] });
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
let parentPort = null;
|
|
161
|
+
if (typeof self === 'undefined') {
|
|
162
|
+
const workerModule = 'worker_threads';
|
|
163
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
164
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-member-access
|
|
165
|
+
parentPort = require(workerModule).parentPort;
|
|
166
|
+
}
|
|
167
|
+
if (parentPort) {
|
|
168
|
+
parentPort.on('message', onMessage);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
self.addEventListener('message', event => onMessage(event.data));
|
|
172
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* Registers the AAC encoder, which Mediabunny will then use automatically when applicable. Make sure to call this
|
|
10
|
+
* function before starting any encoding task.
|
|
11
|
+
*
|
|
12
|
+
* Preferably, wrap the call in a condition to avoid overriding any native AAC encoder:
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { canEncodeAudio } from 'mediabunny';
|
|
16
|
+
* import { registerAacEncoder } from '@mediabunny/aac-encoder';
|
|
17
|
+
*
|
|
18
|
+
* if (!(await canEncodeAudio('aac'))) {
|
|
19
|
+
* registerAacEncoder();
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @group \@mediabunny/aac-encoder
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
export declare const registerAacEncoder: () => void;
|
|
27
|
+
//# sourceMappingURL=encoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encoder.d.ts","sourceRoot":"","sources":["../../../src/encoder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiRH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,kBAAkB,YAE9B,CAAC"}
|
|
@@ -0,0 +1,241 @@
|
|
|
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 __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.registerAacEncoder = void 0;
|
|
14
|
+
const aac_misc_1 = require("../../../shared/aac-misc");
|
|
15
|
+
const mediabunny_1 = require("mediabunny");
|
|
16
|
+
// @ts-expect-error An esbuild plugin handles this, TypeScript doesn't need to understand
|
|
17
|
+
const encode_worker_1 = __importDefault(require("./encode.worker"));
|
|
18
|
+
const AAC_SAMPLE_RATES = [
|
|
19
|
+
96000, 88200, 64000, 48000, 44100, 32000,
|
|
20
|
+
24000, 22050, 16000, 12000, 11025, 8000, 7350,
|
|
21
|
+
];
|
|
22
|
+
class AacEncoder extends mediabunny_1.CustomAudioEncoder {
|
|
23
|
+
constructor() {
|
|
24
|
+
super(...arguments);
|
|
25
|
+
this.worker = null;
|
|
26
|
+
this.nextMessageId = 0;
|
|
27
|
+
this.pendingMessages = new Map();
|
|
28
|
+
this.ctx = 0;
|
|
29
|
+
this.encoderFrameSize = 0;
|
|
30
|
+
this.sampleRate = 0;
|
|
31
|
+
this.numberOfChannels = 0;
|
|
32
|
+
this.chunkMetadata = {};
|
|
33
|
+
this.useAdts = false;
|
|
34
|
+
this.adtsHeaderTemplate = null;
|
|
35
|
+
this.description = null;
|
|
36
|
+
// Accumulate interleaved f32 samples until we have a full frame
|
|
37
|
+
this.pendingBuffer = new Float32Array(2 ** 16);
|
|
38
|
+
this.pendingFrames = 0;
|
|
39
|
+
this.nextSampleTimestampInSamples = null;
|
|
40
|
+
this.nextPacketTimestampInSamples = null;
|
|
41
|
+
}
|
|
42
|
+
static supports(codec, config) {
|
|
43
|
+
return codec === 'aac'
|
|
44
|
+
&& config.numberOfChannels >= 1
|
|
45
|
+
&& config.numberOfChannels <= 8
|
|
46
|
+
&& AAC_SAMPLE_RATES.includes(config.sampleRate)
|
|
47
|
+
&& config.bitrate !== undefined;
|
|
48
|
+
}
|
|
49
|
+
async init() {
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
51
|
+
this.worker = (await (0, encode_worker_1.default)());
|
|
52
|
+
const onMessage = (data) => {
|
|
53
|
+
const pending = this.pendingMessages.get(data.id);
|
|
54
|
+
assert(pending !== undefined);
|
|
55
|
+
this.pendingMessages.delete(data.id);
|
|
56
|
+
if (data.success) {
|
|
57
|
+
pending.resolve(data.data);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
pending.reject(data.error);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
if (this.worker.addEventListener) {
|
|
64
|
+
this.worker.addEventListener('message', event => onMessage(event.data));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const nodeWorker = this.worker;
|
|
68
|
+
nodeWorker.on('message', onMessage);
|
|
69
|
+
}
|
|
70
|
+
assert(this.config.bitrate !== undefined);
|
|
71
|
+
this.sampleRate = this.config.sampleRate;
|
|
72
|
+
this.numberOfChannels = this.config.numberOfChannels;
|
|
73
|
+
const result = await this.sendCommand({
|
|
74
|
+
type: 'init',
|
|
75
|
+
data: {
|
|
76
|
+
numberOfChannels: this.config.numberOfChannels,
|
|
77
|
+
sampleRate: this.config.sampleRate,
|
|
78
|
+
bitrate: this.config.bitrate,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
this.ctx = result.ctx;
|
|
82
|
+
this.encoderFrameSize = result.frameSize;
|
|
83
|
+
// The ffmpeg encoder provides an AudioSpecificConfig as extradata after init
|
|
84
|
+
const description = new Uint8Array(result.extradata);
|
|
85
|
+
const aacConfig = this.config.aac;
|
|
86
|
+
this.useAdts = aacConfig?.format === 'adts';
|
|
87
|
+
if (this.useAdts) {
|
|
88
|
+
const audioSpecificConfig = (0, aac_misc_1.parseAacAudioSpecificConfig)(description);
|
|
89
|
+
this.adtsHeaderTemplate = (0, aac_misc_1.buildAdtsHeaderTemplate)(audioSpecificConfig);
|
|
90
|
+
}
|
|
91
|
+
this.description = this.useAdts ? null : description;
|
|
92
|
+
this.resetInternalState();
|
|
93
|
+
}
|
|
94
|
+
resetInternalState() {
|
|
95
|
+
this.pendingFrames = 0;
|
|
96
|
+
this.nextSampleTimestampInSamples = null;
|
|
97
|
+
this.nextPacketTimestampInSamples = null;
|
|
98
|
+
this.chunkMetadata = {
|
|
99
|
+
decoderConfig: {
|
|
100
|
+
codec: 'mp4a.40.2',
|
|
101
|
+
numberOfChannels: this.config.numberOfChannels,
|
|
102
|
+
sampleRate: this.config.sampleRate,
|
|
103
|
+
...(this.description ? { description: this.description } : {}),
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async encode(audioSample) {
|
|
108
|
+
if (this.nextSampleTimestampInSamples === null) {
|
|
109
|
+
this.nextSampleTimestampInSamples = Math.round(audioSample.timestamp * this.sampleRate);
|
|
110
|
+
this.nextPacketTimestampInSamples = this.nextSampleTimestampInSamples;
|
|
111
|
+
}
|
|
112
|
+
const channels = this.numberOfChannels;
|
|
113
|
+
const incomingFrames = audioSample.numberOfFrames;
|
|
114
|
+
// Extract interleaved f32 data
|
|
115
|
+
const totalBytes = audioSample.allocationSize({ format: 'f32', planeIndex: 0 });
|
|
116
|
+
const audioBytes = new Uint8Array(totalBytes);
|
|
117
|
+
audioSample.copyTo(audioBytes, { format: 'f32', planeIndex: 0 });
|
|
118
|
+
const incomingData = new Float32Array(audioBytes.buffer);
|
|
119
|
+
const requiredSamples = (this.pendingFrames + incomingFrames) * channels;
|
|
120
|
+
if (requiredSamples > this.pendingBuffer.length) {
|
|
121
|
+
let newSize = this.pendingBuffer.length;
|
|
122
|
+
while (newSize < requiredSamples) {
|
|
123
|
+
newSize *= 2;
|
|
124
|
+
}
|
|
125
|
+
const newBuffer = new Float32Array(newSize);
|
|
126
|
+
newBuffer.set(this.pendingBuffer.subarray(0, this.pendingFrames * channels));
|
|
127
|
+
this.pendingBuffer = newBuffer;
|
|
128
|
+
}
|
|
129
|
+
this.pendingBuffer.set(incomingData, this.pendingFrames * channels);
|
|
130
|
+
this.pendingFrames += incomingFrames;
|
|
131
|
+
while (this.pendingFrames >= this.encoderFrameSize) {
|
|
132
|
+
await this.encodeOneFrame();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async flush() {
|
|
136
|
+
// Pad remaining samples with silence to fill a full frame
|
|
137
|
+
if (this.pendingFrames > 0) {
|
|
138
|
+
const channels = this.numberOfChannels;
|
|
139
|
+
const frameSize = this.encoderFrameSize;
|
|
140
|
+
const usedSamples = this.pendingFrames * channels;
|
|
141
|
+
const frameSamples = frameSize * channels;
|
|
142
|
+
this.pendingBuffer.fill(0, usedSamples, frameSamples);
|
|
143
|
+
this.pendingFrames = frameSize;
|
|
144
|
+
await this.encodeOneFrame();
|
|
145
|
+
}
|
|
146
|
+
const result = await this.sendCommand({ type: 'flush', data: { ctx: this.ctx } });
|
|
147
|
+
this.emitPackets(result.packets);
|
|
148
|
+
this.resetInternalState();
|
|
149
|
+
}
|
|
150
|
+
close() {
|
|
151
|
+
void this.sendCommand({ type: 'close', data: { ctx: this.ctx } });
|
|
152
|
+
this.worker?.terminate();
|
|
153
|
+
}
|
|
154
|
+
async encodeOneFrame() {
|
|
155
|
+
assert(this.nextSampleTimestampInSamples !== null);
|
|
156
|
+
assert(this.nextPacketTimestampInSamples !== null);
|
|
157
|
+
const channels = this.numberOfChannels;
|
|
158
|
+
const frameSize = this.encoderFrameSize;
|
|
159
|
+
const frameSamples = frameSize * channels;
|
|
160
|
+
const frameData = this.pendingBuffer.slice(0, frameSamples);
|
|
161
|
+
// Shift remaining using copyWithin
|
|
162
|
+
this.pendingFrames -= frameSize;
|
|
163
|
+
if (this.pendingFrames > 0) {
|
|
164
|
+
this.pendingBuffer.copyWithin(0, frameSamples, frameSamples + this.pendingFrames * channels);
|
|
165
|
+
}
|
|
166
|
+
const audioData = frameData.buffer;
|
|
167
|
+
const result = await this.sendCommand({
|
|
168
|
+
type: 'encode',
|
|
169
|
+
data: {
|
|
170
|
+
ctx: this.ctx,
|
|
171
|
+
audioData,
|
|
172
|
+
timestamp: this.nextSampleTimestampInSamples,
|
|
173
|
+
},
|
|
174
|
+
}, [audioData]);
|
|
175
|
+
this.nextSampleTimestampInSamples += frameSize;
|
|
176
|
+
this.emitPackets(result.packets);
|
|
177
|
+
}
|
|
178
|
+
emitPackets(packets) {
|
|
179
|
+
assert(this.nextPacketTimestampInSamples !== null);
|
|
180
|
+
for (const p of packets) {
|
|
181
|
+
let data = new Uint8Array(p.encodedData);
|
|
182
|
+
if (this.useAdts) {
|
|
183
|
+
assert(this.adtsHeaderTemplate !== null);
|
|
184
|
+
const { header, bitstream } = this.adtsHeaderTemplate;
|
|
185
|
+
const frameLength = header.byteLength + data.byteLength;
|
|
186
|
+
(0, aac_misc_1.writeAdtsFrameLength)(bitstream, frameLength);
|
|
187
|
+
const adtsFrame = new Uint8Array(frameLength);
|
|
188
|
+
adtsFrame.set(header, 0);
|
|
189
|
+
adtsFrame.set(data, header.byteLength);
|
|
190
|
+
data = adtsFrame;
|
|
191
|
+
}
|
|
192
|
+
const packet = new mediabunny_1.EncodedPacket(data, 'key', this.nextPacketTimestampInSamples / this.sampleRate, p.duration / this.sampleRate);
|
|
193
|
+
this.nextPacketTimestampInSamples += p.duration;
|
|
194
|
+
this.onPacket(packet, this.chunkMetadata);
|
|
195
|
+
this.chunkMetadata = {};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
sendCommand(command, transferables) {
|
|
199
|
+
return new Promise((resolve, reject) => {
|
|
200
|
+
const id = this.nextMessageId++;
|
|
201
|
+
this.pendingMessages.set(id, {
|
|
202
|
+
resolve: resolve,
|
|
203
|
+
reject,
|
|
204
|
+
});
|
|
205
|
+
assert(this.worker);
|
|
206
|
+
if (transferables) {
|
|
207
|
+
this.worker.postMessage({ id, command }, transferables);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
this.worker.postMessage({ id, command });
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Registers the AAC encoder, which Mediabunny will then use automatically when applicable. Make sure to call this
|
|
217
|
+
* function before starting any encoding task.
|
|
218
|
+
*
|
|
219
|
+
* Preferably, wrap the call in a condition to avoid overriding any native AAC encoder:
|
|
220
|
+
*
|
|
221
|
+
* ```ts
|
|
222
|
+
* import { canEncodeAudio } from 'mediabunny';
|
|
223
|
+
* import { registerAacEncoder } from '@mediabunny/aac-encoder';
|
|
224
|
+
*
|
|
225
|
+
* if (!(await canEncodeAudio('aac'))) {
|
|
226
|
+
* registerAacEncoder();
|
|
227
|
+
* }
|
|
228
|
+
* ```
|
|
229
|
+
*
|
|
230
|
+
* @group \@mediabunny/aac-encoder
|
|
231
|
+
* @public
|
|
232
|
+
*/
|
|
233
|
+
const registerAacEncoder = () => {
|
|
234
|
+
(0, mediabunny_1.registerEncoder)(AacEncoder);
|
|
235
|
+
};
|
|
236
|
+
exports.registerAacEncoder = registerAacEncoder;
|
|
237
|
+
function assert(x) {
|
|
238
|
+
if (!x) {
|
|
239
|
+
throw new Error('Assertion failed.');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
export { registerAacEncoder } from './encoder';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.registerAacEncoder = void 0;
|
|
11
|
+
const AAC_ENCODER_LOADED_SYMBOL = Symbol.for('@mediabunny/aac-encoder loaded');
|
|
12
|
+
if (globalThis[AAC_ENCODER_LOADED_SYMBOL]) {
|
|
13
|
+
console.error('[WARNING]\n@mediabunny/aac-encoder was loaded twice.'
|
|
14
|
+
+ ' This will likely cause the encoder not to work correctly.'
|
|
15
|
+
+ ' Check if multiple dependencies are importing different versions of @mediabunny/aac-encoder,'
|
|
16
|
+
+ ' or if something is being bundled incorrectly.');
|
|
17
|
+
}
|
|
18
|
+
globalThis[AAC_ENCODER_LOADED_SYMBOL] = true;
|
|
19
|
+
var encoder_1 = require("./encoder");
|
|
20
|
+
Object.defineProperty(exports, "registerAacEncoder", { enumerable: true, get: function () { return encoder_1.registerAacEncoder; } });
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
export type WorkerCommand = {
|
|
9
|
+
type: 'init';
|
|
10
|
+
data: {
|
|
11
|
+
numberOfChannels: number;
|
|
12
|
+
sampleRate: number;
|
|
13
|
+
bitrate: number;
|
|
14
|
+
};
|
|
15
|
+
} | {
|
|
16
|
+
type: 'encode';
|
|
17
|
+
data: {
|
|
18
|
+
ctx: number;
|
|
19
|
+
audioData: ArrayBuffer;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
};
|
|
22
|
+
} | {
|
|
23
|
+
type: 'flush';
|
|
24
|
+
data: {
|
|
25
|
+
ctx: number;
|
|
26
|
+
};
|
|
27
|
+
} | {
|
|
28
|
+
type: 'close';
|
|
29
|
+
data: {
|
|
30
|
+
ctx: number;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export type WorkerResponseData = {
|
|
34
|
+
type: 'init';
|
|
35
|
+
ctx: number;
|
|
36
|
+
frameSize: number;
|
|
37
|
+
extradata: ArrayBuffer;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'encode';
|
|
40
|
+
packets: Array<{
|
|
41
|
+
encodedData: ArrayBuffer;
|
|
42
|
+
pts: number;
|
|
43
|
+
duration: number;
|
|
44
|
+
}>;
|
|
45
|
+
} | {
|
|
46
|
+
type: 'flush';
|
|
47
|
+
packets: Array<{
|
|
48
|
+
encodedData: ArrayBuffer;
|
|
49
|
+
pts: number;
|
|
50
|
+
duration: number;
|
|
51
|
+
}>;
|
|
52
|
+
} | {
|
|
53
|
+
type: 'close';
|
|
54
|
+
};
|
|
55
|
+
export type WorkerResponse = {
|
|
56
|
+
id: number;
|
|
57
|
+
} & ({
|
|
58
|
+
success: true;
|
|
59
|
+
data: WorkerResponseData;
|
|
60
|
+
} | {
|
|
61
|
+
success: false;
|
|
62
|
+
error: unknown;
|
|
63
|
+
});
|
|
64
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/shared.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,aAAa,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE;QACL,gBAAgB,EAAE,MAAM,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;KAChB,CAAC;CACF,GAAG;IACH,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,EAAE,WAAW,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC;CACF,GAAG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;KACZ,CAAC;CACF,GAAG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;KACZ,CAAC;CACF,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,WAAW,CAAC;CACvB,GAAG;IACH,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,KAAK,CAAC;QACd,WAAW,EAAE,WAAW,CAAC;QACzB,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;CACH,GAAG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,KAAK,CAAC;QACd,WAAW,EAAE,WAAW,CAAC;QACzB,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;CACH,GAAG;IACH,IAAI,EAAE,OAAO,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;CACX,GAAG,CAAC;IACJ,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,kBAAkB,CAAC;CACzB,GAAG;IACH,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CACf,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|