mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 19:03:46 +02:00
Create @mediabunny/mp3-encoder, restructure and extend build system
This commit is contained in:
+32
-4
@@ -138,9 +138,23 @@ export const customAudioEncoders: typeof CustomAudioEncoder[] = [];
|
||||
*/
|
||||
export const registerDecoder = (decoder: typeof CustomVideoDecoder | typeof CustomAudioDecoder) => {
|
||||
if (decoder.prototype instanceof CustomVideoDecoder) {
|
||||
customVideoDecoders.push(decoder as typeof CustomVideoDecoder);
|
||||
const casted = decoder as typeof CustomVideoDecoder;
|
||||
|
||||
if (customVideoDecoders.includes(casted)) {
|
||||
console.warn('Video decoder already registered.');
|
||||
return;
|
||||
}
|
||||
|
||||
customVideoDecoders.push(casted);
|
||||
} else if (decoder.prototype instanceof CustomAudioDecoder) {
|
||||
customAudioDecoders.push(decoder as typeof CustomAudioDecoder);
|
||||
const casted = decoder as typeof CustomAudioDecoder;
|
||||
|
||||
if (customAudioDecoders.includes(casted)) {
|
||||
console.warn('Audio decoder already registered.');
|
||||
return;
|
||||
}
|
||||
|
||||
customAudioDecoders.push(casted);
|
||||
} else {
|
||||
throw new TypeError('Decoder must be a CustomVideoDecoder or CustomAudioDecoder.');
|
||||
}
|
||||
@@ -153,9 +167,23 @@ export const registerDecoder = (decoder: typeof CustomVideoDecoder | typeof Cust
|
||||
*/
|
||||
export const registerEncoder = (encoder: typeof CustomVideoEncoder | typeof CustomAudioEncoder) => {
|
||||
if (encoder.prototype instanceof CustomVideoEncoder) {
|
||||
customVideoEncoders.push(encoder as typeof CustomVideoEncoder);
|
||||
const casted = encoder as typeof CustomVideoEncoder;
|
||||
|
||||
if (customVideoEncoders.includes(casted)) {
|
||||
console.warn('Video encoder already registered.');
|
||||
return;
|
||||
}
|
||||
|
||||
customVideoEncoders.push(casted);
|
||||
} else if (encoder.prototype instanceof CustomAudioEncoder) {
|
||||
customAudioEncoders.push(encoder as typeof CustomAudioEncoder);
|
||||
const casted = encoder as typeof CustomAudioEncoder;
|
||||
|
||||
if (customAudioEncoders.includes(casted)) {
|
||||
console.warn('Audio encoder already registered.');
|
||||
return;
|
||||
}
|
||||
|
||||
customAudioEncoders.push(casted);
|
||||
} else {
|
||||
throw new TypeError('Encoder must be a CustomVideoEncoder or CustomAudioEncoder.');
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import { IsobmffReader } from './isobmff/isobmff-reader';
|
||||
import { EBMLId, EBMLReader } from './matroska/ebml';
|
||||
import { MatroskaDemuxer } from './matroska/matroska-demuxer';
|
||||
import { Mp3Demuxer } from './mp3/mp3-demuxer';
|
||||
import { FRAME_HEADER_SIZE } from './mp3/mp3-misc';
|
||||
import { FRAME_HEADER_SIZE } from '../shared/mp3-misc';
|
||||
import { Mp3Reader } from './mp3/mp3-reader';
|
||||
import { OggDemuxer } from './ogg/ogg-demuxer';
|
||||
import { OggReader } from './ogg/ogg-reader';
|
||||
|
||||
+1
-3
@@ -1527,9 +1527,7 @@ type MediaStreamTrackProcessorControllerMessage = {
|
||||
const mediaStreamTrackProcessorWorkerCode = () => {
|
||||
const sendMessage = (message: MediaStreamTrackProcessorWorkerMessage, transfer?: Transferable[]) => {
|
||||
if (transfer) {
|
||||
// The error is bullshit, it's using the wrong postMessage
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
||||
self.postMessage(message, transfer as any);
|
||||
self.postMessage(message, { transfer });
|
||||
} else {
|
||||
self.postMessage(message);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
|
||||
import { PacketRetrievalOptions } from '../media-sink';
|
||||
import { assert, binarySearchExact, binarySearchLessOrEqual, last, UNDETERMINED_LANGUAGE } from '../misc';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||
import { FrameHeader, getXingOffset, INFO, XING } from './mp3-misc';
|
||||
import { FrameHeader, getXingOffset, INFO, XING } from '../../shared/mp3-misc';
|
||||
import { Mp3Reader } from './mp3-reader';
|
||||
|
||||
type Sample = {
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
/*!
|
||||
* Copyright (c) 2025-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
export const FRAME_HEADER_SIZE = 4;
|
||||
|
||||
// These are in kbps:
|
||||
export const MPEG_V1_BITRATES: Record<number, number[]> = {
|
||||
// Layer 3
|
||||
1: [-1, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1],
|
||||
// Layer 2
|
||||
2: [-1, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1],
|
||||
// Layer 1
|
||||
3: [-1, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1],
|
||||
};
|
||||
export const MPEG_V2_BITRATES: Record<number, number[]> = {
|
||||
// Layer 3
|
||||
1: [-1, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1],
|
||||
// Layer 2
|
||||
2: [-1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1],
|
||||
// Layer 1
|
||||
3: [-1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1],
|
||||
};
|
||||
export const SAMPLING_RATES: Record<number, number[]> = {
|
||||
// MPEG Version 2.5
|
||||
0: [11025, 12000, 8000, -1],
|
||||
// MPEG Version 2 (ISO/IEC 13818-3)
|
||||
2: [22050, 24000, 16000, -1],
|
||||
// MPEG Version 1 (ISO/IEC 11172-3)
|
||||
3: [44100, 48000, 32000, -1],
|
||||
};
|
||||
|
||||
/** 'Xing' */
|
||||
export const XING = 0x58696e67;
|
||||
/** 'Info' */
|
||||
export const INFO = 0x496e666f;
|
||||
|
||||
export type FrameHeader = {
|
||||
startPos: number;
|
||||
totalSize: number;
|
||||
mpegVersionId: number;
|
||||
layer: number;
|
||||
bitrate: number;
|
||||
frequencyIndex: number;
|
||||
sampleRate: number;
|
||||
channel: number;
|
||||
modeExtension: number;
|
||||
copyright: number;
|
||||
original: number;
|
||||
emphasis: number;
|
||||
audioSamplesInFrame: number;
|
||||
};
|
||||
|
||||
export const computeMp3FrameSize = (layer: number, bitrate: number, sampleRate: number, padding: number) => {
|
||||
if (layer === 3) {
|
||||
// Layer 1
|
||||
return Math.floor((12 * bitrate / sampleRate + padding) * 4);
|
||||
} else {
|
||||
return Math.floor((144 * bitrate / sampleRate) + padding);
|
||||
}
|
||||
};
|
||||
|
||||
export const getXingOffset = (mpegVersionId: number, channel: number) => {
|
||||
return mpegVersionId === 3
|
||||
? (channel === 3 ? 21 : 36)
|
||||
: (channel === 3 ? 13 : 21);
|
||||
};
|
||||
|
||||
export const readFrameHeader = (word: number, reader: { pos: number; fileSize: number | null }): FrameHeader | null => {
|
||||
const startPos = reader.pos;
|
||||
|
||||
const firstByte = word >>> 24;
|
||||
const secondByte = (word >>> 16) & 0xff;
|
||||
const thirdByte = (word >>> 8) & 0xff;
|
||||
const fourthByte = word & 0xff;
|
||||
|
||||
if (firstByte !== 0xff && secondByte !== 0xff && thirdByte !== 0xff && fourthByte !== 0xff) {
|
||||
reader.pos += 4;
|
||||
return null;
|
||||
}
|
||||
|
||||
reader.pos += 1;
|
||||
|
||||
if (firstByte !== 0xff) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((secondByte & 0xe0) !== 0xe0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mpegVersionId = (secondByte >> 3) & 0x3;
|
||||
const layer = (secondByte >> 1) & 0x3;
|
||||
|
||||
const bitrateIndex = (thirdByte >> 4) & 0xf;
|
||||
const frequencyIndex = (thirdByte >> 2) & 0x3;
|
||||
const padding = (thirdByte >> 1) & 0x1;
|
||||
|
||||
const channel = (fourthByte >> 6) & 0x3;
|
||||
const modeExtension = (fourthByte >> 4) & 0x3;
|
||||
const copyright = (fourthByte >> 3) & 0x1;
|
||||
const original = (fourthByte >> 2) & 0x1;
|
||||
const emphasis = fourthByte & 0x3;
|
||||
|
||||
const kilobitRate = mpegVersionId === 3
|
||||
? MPEG_V1_BITRATES[layer]?.[bitrateIndex]
|
||||
: MPEG_V2_BITRATES[layer]?.[bitrateIndex];
|
||||
if (!kilobitRate || kilobitRate === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bitrate = kilobitRate * 1000;
|
||||
|
||||
const sampleRate = SAMPLING_RATES[mpegVersionId]?.[frequencyIndex];
|
||||
if (!sampleRate || sampleRate === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const frameLength = computeMp3FrameSize(layer, bitrate, sampleRate, padding);
|
||||
|
||||
if (reader.fileSize !== null && reader.fileSize - startPos < frameLength) {
|
||||
// The frame doesn't fit into the rest of the file
|
||||
return null;
|
||||
}
|
||||
|
||||
let audioSamplesInFrame: number;
|
||||
if (mpegVersionId === 3) {
|
||||
audioSamplesInFrame = layer === 3 ? 384 : 1152;
|
||||
} else {
|
||||
if (layer === 3) {
|
||||
audioSamplesInFrame = 384;
|
||||
} else if (layer === 2) {
|
||||
audioSamplesInFrame = 1152;
|
||||
} else {
|
||||
audioSamplesInFrame = 576;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
startPos: startPos,
|
||||
totalSize: frameLength,
|
||||
mpegVersionId,
|
||||
layer,
|
||||
bitrate,
|
||||
frequencyIndex,
|
||||
sampleRate,
|
||||
channel,
|
||||
modeExtension,
|
||||
copyright,
|
||||
original,
|
||||
emphasis,
|
||||
audioSamplesInFrame,
|
||||
};
|
||||
};
|
||||
@@ -12,7 +12,7 @@ import { Output, OutputAudioTrack } from '../output';
|
||||
import { Mp3OutputFormat } from '../output-format';
|
||||
import { EncodedPacket } from '../packet';
|
||||
import { Writer } from '../writer';
|
||||
import { getXingOffset, INFO, readFrameHeader, XING } from './mp3-misc';
|
||||
import { getXingOffset, INFO, readFrameHeader, XING } from '../../shared/mp3-misc';
|
||||
import { Mp3Writer, XingFrameData } from './mp3-writer';
|
||||
|
||||
export class Mp3Muxer extends Muxer {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { assert } from '../misc';
|
||||
import { Reader } from '../reader';
|
||||
import { FRAME_HEADER_SIZE, FrameHeader, readFrameHeader } from './mp3-misc';
|
||||
import { FRAME_HEADER_SIZE, FrameHeader, readFrameHeader } from '../../shared/mp3-misc';
|
||||
|
||||
export class Mp3Reader {
|
||||
pos = 0;
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
MPEG_V2_BITRATES,
|
||||
SAMPLING_RATES,
|
||||
XING,
|
||||
} from './mp3-misc';
|
||||
} from '../../shared/mp3-misc';
|
||||
|
||||
export type XingFrameData = {
|
||||
mpegVersionId: number;
|
||||
|
||||
+5
-2
@@ -2,12 +2,15 @@
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../dist/modules",
|
||||
"rootDir": "..",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"stripInternal": true,
|
||||
"noEmit": false
|
||||
"noEmit": false,
|
||||
"composite": true
|
||||
},
|
||||
"include": [
|
||||
"**/*"
|
||||
"**/*",
|
||||
"../shared/**/*"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user