mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-02 21:33:52 +02:00
Merge main into release for tag v1.17.1
This commit is contained in:
+1
-3
@@ -24,9 +24,7 @@
|
|||||||
chunked: true,
|
chunked: true,
|
||||||
chunkSize: 2**20
|
chunkSize: 2**20
|
||||||
});
|
});
|
||||||
const outputFormat = new Mediabunny.Mp4OutputFormat({
|
const outputFormat = new Mediabunny.Mp3OutputFormat({});
|
||||||
metadataFormat: 'mdta',
|
|
||||||
});
|
|
||||||
|
|
||||||
const button = document.createElement('button');
|
const button = document.createElement('button');
|
||||||
button.textContent = 'Cancel';
|
button.textContent = 'Cancel';
|
||||||
|
|||||||
+12
-1
@@ -14,7 +14,18 @@
|
|||||||
source: new Mediabunny.BlobSource(file),
|
source: new Mediabunny.BlobSource(file),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(await input.getMetadataTags());
|
console.log(await input.computeDuration());
|
||||||
|
return;
|
||||||
|
|
||||||
|
const audioTrack = await input.getPrimaryAudioTrack();
|
||||||
|
const sink = new Mediabunny.EncodedPacketSink(audioTrack);
|
||||||
|
|
||||||
|
for await (const packet of sink.packets()) {
|
||||||
|
console.log(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Done")
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
|||||||
Generated
+6
-6
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.17.0",
|
"version": "1.17.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.17.0",
|
"version": "1.17.1",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@@ -7749,9 +7749,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mediabunny": {
|
"node_modules/mediabunny": {
|
||||||
"version": "1.16.1",
|
"version": "1.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.17.0.tgz",
|
||||||
"integrity": "sha512-BKA5wJaen/3g0nclyykXkS2rELsokcfOYSUfYbSZpDbChqxuITn+bCQhSTtDyMlv3nmK6KoSTo/JhcZLbF9pxQ==",
|
"integrity": "sha512-3IvrYu1Eur6IoLjygMAeVrh+5jkJxsr1RTAc7+e4QYONUt4MrUaorPIl3XtLKQdJLGcxlERrHn2KC50qBZN9mg==",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
@@ -12242,7 +12242,7 @@
|
|||||||
},
|
},
|
||||||
"packages/mp3-encoder": {
|
"packages/mp3-encoder": {
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"version": "1.17.0",
|
"version": "1.17.1",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/emscripten": "^1.40.1"
|
"@types/emscripten": "^1.40.1"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.17.0",
|
"version": "1.17.1",
|
||||||
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.17.0",
|
"version": "1.17.1",
|
||||||
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
||||||
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||||
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ class Mp3Encoder extends CustomAudioEncoder {
|
|||||||
static override supports(codec: AudioCodec, config: AudioDecoderConfig): boolean {
|
static override supports(codec: AudioCodec, config: AudioDecoderConfig): boolean {
|
||||||
return codec === 'mp3'
|
return codec === 'mp3'
|
||||||
&& (config.numberOfChannels === 1 || config.numberOfChannels === 2)
|
&& (config.numberOfChannels === 1 || config.numberOfChannels === 2)
|
||||||
&& Object.values(SAMPLING_RATES).some(x => x.includes(config.sampleRate));
|
&& Object.values(SAMPLING_RATES).some(x =>
|
||||||
|
x === config.sampleRate || (x / 2) === config.sampleRate || (x / 4) === config.sampleRate,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
|||||||
+43
-43
@@ -7,32 +7,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const FRAME_HEADER_SIZE = 4;
|
export const FRAME_HEADER_SIZE = 4;
|
||||||
|
export const SAMPLING_RATES = [44100, 48000, 32000];
|
||||||
|
export const KILOBIT_RATES = [
|
||||||
|
// lowSamplingFrequency === 0
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // layer = 0
|
||||||
|
-1, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1, // layer 1
|
||||||
|
-1, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1, // layer = 2
|
||||||
|
-1, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1, // layer = 3
|
||||||
|
|
||||||
// These are in kbps:
|
// lowSamplingFrequency === 1
|
||||||
export const MPEG_V1_BITRATES: Record<number, number[]> = {
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // layer = 0
|
||||||
// Layer 3
|
-1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1, // layer = 1
|
||||||
1: [-1, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1],
|
-1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1, // layer = 2
|
||||||
// Layer 2
|
-1, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1, // layer = 3
|
||||||
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' */
|
/** 'Xing' */
|
||||||
export const XING = 0x58696e67;
|
export const XING = 0x58696e67;
|
||||||
@@ -54,12 +42,21 @@ export type FrameHeader = {
|
|||||||
audioSamplesInFrame: number;
|
audioSamplesInFrame: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const computeMp3FrameSize = (layer: number, bitrate: number, sampleRate: number, padding: number) => {
|
export const computeMp3FrameSize = (
|
||||||
if (layer === 3) {
|
lowSamplingFrequency: number,
|
||||||
// Layer 1
|
layer: number,
|
||||||
return Math.floor((12 * bitrate / sampleRate + padding) * 4);
|
bitrate: number,
|
||||||
} else {
|
sampleRate: number,
|
||||||
return Math.floor((144 * bitrate / sampleRate) + padding);
|
padding: number,
|
||||||
|
) => {
|
||||||
|
if (layer === 0) {
|
||||||
|
return 0; // Not expected that this is hit
|
||||||
|
} else if (layer === 1) {
|
||||||
|
return Math.round(144 * bitrate / (sampleRate << lowSamplingFrequency)) + padding;
|
||||||
|
} else if (layer === 2) {
|
||||||
|
return Math.round(144 * bitrate / sampleRate) + padding;
|
||||||
|
} else { // layer === 3
|
||||||
|
return (Math.round(12 * bitrate / sampleRate) + padding) * 4;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,11 +90,21 @@ export const readFrameHeader = (word: number, remainingBytes: number | null): {
|
|||||||
return { header: null, bytesAdvanced: 1 };
|
return { header: null, bytesAdvanced: 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let lowSamplingFrequency = 0;
|
||||||
|
let mpeg25 = 0;
|
||||||
|
|
||||||
|
if (secondByte & (1 << 4)) {
|
||||||
|
lowSamplingFrequency = (secondByte & (1 << 3)) ? 0 : 1;
|
||||||
|
} else {
|
||||||
|
lowSamplingFrequency = 1;
|
||||||
|
mpeg25 = 1;
|
||||||
|
}
|
||||||
|
|
||||||
const mpegVersionId = (secondByte >> 3) & 0x3;
|
const mpegVersionId = (secondByte >> 3) & 0x3;
|
||||||
const layer = (secondByte >> 1) & 0x3;
|
const layer = (secondByte >> 1) & 0x3;
|
||||||
|
|
||||||
const bitrateIndex = (thirdByte >> 4) & 0xf;
|
const bitrateIndex = (thirdByte >> 4) & 0xf;
|
||||||
const frequencyIndex = (thirdByte >> 2) & 0x3;
|
const frequencyIndex = ((thirdByte >> 2) & 0x3) % 3;
|
||||||
const padding = (thirdByte >> 1) & 0x1;
|
const padding = (thirdByte >> 1) & 0x1;
|
||||||
|
|
||||||
const channel = (fourthByte >> 6) & 0x3;
|
const channel = (fourthByte >> 6) & 0x3;
|
||||||
@@ -106,21 +113,14 @@ export const readFrameHeader = (word: number, remainingBytes: number | null): {
|
|||||||
const original = (fourthByte >> 2) & 0x1;
|
const original = (fourthByte >> 2) & 0x1;
|
||||||
const emphasis = fourthByte & 0x3;
|
const emphasis = fourthByte & 0x3;
|
||||||
|
|
||||||
const kilobitRate = mpegVersionId === 3
|
const kilobitRate = KILOBIT_RATES[lowSamplingFrequency * 16 * 4 + layer * 16 + bitrateIndex]!;
|
||||||
? MPEG_V1_BITRATES[layer]?.[bitrateIndex]
|
if (kilobitRate === -1) {
|
||||||
: MPEG_V2_BITRATES[layer]?.[bitrateIndex];
|
|
||||||
if (!kilobitRate || kilobitRate === -1) {
|
|
||||||
return { header: null, bytesAdvanced: 1 };
|
return { header: null, bytesAdvanced: 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const bitrate = kilobitRate * 1000;
|
const bitrate = kilobitRate * 1000;
|
||||||
|
const sampleRate = SAMPLING_RATES[frequencyIndex]! >> (lowSamplingFrequency + mpeg25);
|
||||||
const sampleRate = SAMPLING_RATES[mpegVersionId]?.[frequencyIndex];
|
const frameLength = computeMp3FrameSize(lowSamplingFrequency, layer, bitrate, sampleRate, padding);
|
||||||
if (!sampleRate || sampleRate === -1) {
|
|
||||||
return { header: null, bytesAdvanced: 1 };
|
|
||||||
}
|
|
||||||
|
|
||||||
const frameLength = computeMp3FrameSize(layer, bitrate, sampleRate, padding);
|
|
||||||
|
|
||||||
if (remainingBytes !== null && remainingBytes < frameLength) {
|
if (remainingBytes !== null && remainingBytes < frameLength) {
|
||||||
// The frame doesn't fit into the rest of the file
|
// The frame doesn't fit into the rest of the file
|
||||||
|
|||||||
@@ -117,9 +117,16 @@ export class Mp3Demuxer extends Demuxer {
|
|||||||
this.firstFrameHeader = header;
|
this.firstFrameHeader = header;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sampleDuration = header.audioSamplesInFrame / header.sampleRate;
|
if (header.sampleRate !== this.firstFrameHeader.sampleRate) {
|
||||||
|
console.warn(
|
||||||
|
`MP3 changed sample rate mid-file: ${this.firstFrameHeader.sampleRate} Hz to ${header.sampleRate} Hz.`
|
||||||
|
+ ` Might be a bug, so please report this file.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sampleDuration = header.audioSamplesInFrame / this.firstFrameHeader.sampleRate;
|
||||||
const sample: Sample = {
|
const sample: Sample = {
|
||||||
timestamp: this.nextTimestampInSamples / header.sampleRate,
|
timestamp: this.nextTimestampInSamples / this.firstFrameHeader.sampleRate,
|
||||||
duration: sampleDuration,
|
duration: sampleDuration,
|
||||||
dataStart: result.startPos,
|
dataStart: result.startPos,
|
||||||
dataSize: header.totalSize,
|
dataSize: header.totalSize,
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export class Mp3Muxer extends Muxer {
|
|||||||
mpegVersionId: header.mpegVersionId,
|
mpegVersionId: header.mpegVersionId,
|
||||||
layer: header.layer,
|
layer: header.layer,
|
||||||
frequencyIndex: header.frequencyIndex,
|
frequencyIndex: header.frequencyIndex,
|
||||||
|
sampleRate: header.sampleRate,
|
||||||
channel: header.channel,
|
channel: header.channel,
|
||||||
modeExtension: header.modeExtension,
|
modeExtension: header.modeExtension,
|
||||||
copyright: header.copyright,
|
copyright: header.copyright,
|
||||||
|
|||||||
+74
-71
@@ -12,9 +12,7 @@ import {
|
|||||||
computeMp3FrameSize,
|
computeMp3FrameSize,
|
||||||
encodeSynchsafe,
|
encodeSynchsafe,
|
||||||
getXingOffset,
|
getXingOffset,
|
||||||
MPEG_V1_BITRATES,
|
KILOBIT_RATES,
|
||||||
MPEG_V2_BITRATES,
|
|
||||||
SAMPLING_RATES,
|
|
||||||
XING,
|
XING,
|
||||||
} from '../../shared/mp3-misc';
|
} from '../../shared/mp3-misc';
|
||||||
import { Id3V2TextEncoding } from './mp3-reader';
|
import { Id3V2TextEncoding } from './mp3-reader';
|
||||||
@@ -23,6 +21,7 @@ export type XingFrameData = {
|
|||||||
mpegVersionId: number;
|
mpegVersionId: number;
|
||||||
layer: number;
|
layer: number;
|
||||||
frequencyIndex: number;
|
frequencyIndex: number;
|
||||||
|
sampleRate: number;
|
||||||
channel: number;
|
channel: number;
|
||||||
modeExtension: number;
|
modeExtension: number;
|
||||||
copyright: number;
|
copyright: number;
|
||||||
@@ -62,6 +61,78 @@ export class Mp3Writer {
|
|||||||
this.writer.write(this.helper.subarray(0, text.length));
|
this.writer.write(this.helper.subarray(0, text.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeXingFrame(data: XingFrameData) {
|
||||||
|
const startPos = this.writer.getPos();
|
||||||
|
|
||||||
|
const firstByte = 0xff;
|
||||||
|
const secondByte = 0xe0 | (data.mpegVersionId << 3) | (data.layer << 1);
|
||||||
|
|
||||||
|
const lowSamplingFrequency = data.mpegVersionId & 1;
|
||||||
|
|
||||||
|
const padding = 0;
|
||||||
|
const neededBytes = 155;
|
||||||
|
|
||||||
|
let bitrateIndex = -1;
|
||||||
|
const bitrateOffset = lowSamplingFrequency * 16 * 4 + data.layer * 16;
|
||||||
|
|
||||||
|
// Let's find the lowest bitrate for which the frame size is sufficiently large to fit all the data
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
const kbr = KILOBIT_RATES[bitrateOffset + i]!;
|
||||||
|
const size = computeMp3FrameSize(lowSamplingFrequency, data.layer, 1000 * kbr, data.sampleRate, padding);
|
||||||
|
|
||||||
|
if (size >= neededBytes) {
|
||||||
|
bitrateIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bitrateIndex === -1) {
|
||||||
|
throw new Error('No suitable bitrate found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const thirdByte = (bitrateIndex << 4) | (data.frequencyIndex << 2) | padding << 1;
|
||||||
|
const fourthByte = (data.channel << 6)
|
||||||
|
| (data.modeExtension << 4)
|
||||||
|
| (data.copyright << 3)
|
||||||
|
| (data.original << 2)
|
||||||
|
| data.emphasis;
|
||||||
|
|
||||||
|
this.helper[0] = firstByte;
|
||||||
|
this.helper[1] = secondByte;
|
||||||
|
this.helper[2] = thirdByte;
|
||||||
|
this.helper[3] = fourthByte;
|
||||||
|
|
||||||
|
this.writer.write(this.helper.subarray(0, 4));
|
||||||
|
|
||||||
|
const xingOffset = getXingOffset(data.mpegVersionId, data.channel);
|
||||||
|
|
||||||
|
this.writer.seek(startPos + xingOffset);
|
||||||
|
this.writeU32(XING);
|
||||||
|
|
||||||
|
let flags = 0;
|
||||||
|
if (data.frameCount !== null) {
|
||||||
|
flags |= 1;
|
||||||
|
}
|
||||||
|
if (data.fileSize !== null) {
|
||||||
|
flags |= 2;
|
||||||
|
}
|
||||||
|
if (data.toc !== null) {
|
||||||
|
flags |= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.writeU32(flags);
|
||||||
|
|
||||||
|
this.writeU32(data.frameCount ?? 0);
|
||||||
|
this.writeU32(data.fileSize ?? 0);
|
||||||
|
this.writer.write(data.toc ?? new Uint8Array(100));
|
||||||
|
|
||||||
|
const kilobitRate = KILOBIT_RATES[bitrateOffset + bitrateIndex]!;
|
||||||
|
const frameSize = computeMp3FrameSize(
|
||||||
|
lowSamplingFrequency, data.layer, 1000 * kilobitRate, data.sampleRate, padding,
|
||||||
|
);
|
||||||
|
this.writer.seek(startPos + frameSize);
|
||||||
|
}
|
||||||
|
|
||||||
writeSynchsafeU32(value: number) {
|
writeSynchsafeU32(value: number) {
|
||||||
this.writeU32(encodeSynchsafe(value));
|
this.writeU32(encodeSynchsafe(value));
|
||||||
}
|
}
|
||||||
@@ -170,72 +241,4 @@ export class Mp3Writer {
|
|||||||
|
|
||||||
this.writer.write(imageData);
|
this.writer.write(imageData);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeXingFrame(data: XingFrameData) {
|
|
||||||
const startPos = this.writer.getPos();
|
|
||||||
|
|
||||||
const firstByte = 0xff;
|
|
||||||
const secondByte = 0xe0 | (data.mpegVersionId << 3) | (data.layer << 1);
|
|
||||||
|
|
||||||
const bitrateGroup = data.mpegVersionId === 3 ? MPEG_V1_BITRATES : MPEG_V2_BITRATES;
|
|
||||||
const bitrates = bitrateGroup?.[data.layer];
|
|
||||||
if (!bitrates) {
|
|
||||||
throw new Error('Invalid MPEG version and layer combination.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleRate = SAMPLING_RATES[data.mpegVersionId]?.[data.frequencyIndex];
|
|
||||||
if (!sampleRate || sampleRate === -1) {
|
|
||||||
throw new Error('Invalid MPEG version and frequency index combination.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const padding = 0;
|
|
||||||
const neededBytes = 155;
|
|
||||||
|
|
||||||
// Let's find the lowest bitrate for which the frame size is sufficiently large to fit all the data
|
|
||||||
const bitrateIndex = bitrates.findIndex((kbr) => {
|
|
||||||
return computeMp3FrameSize(data.layer, 1000 * kbr, sampleRate, padding) >= neededBytes;
|
|
||||||
});
|
|
||||||
if (bitrateIndex === -1) {
|
|
||||||
throw new Error('No suitable bitrate found.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const thirdByte = (bitrateIndex << 4) | (data.frequencyIndex << 2) | padding << 1;
|
|
||||||
const fourthByte = (data.channel << 6)
|
|
||||||
| (data.modeExtension << 4)
|
|
||||||
| (data.copyright << 3)
|
|
||||||
| (data.original << 2)
|
|
||||||
| data.emphasis;
|
|
||||||
|
|
||||||
this.helper[0] = firstByte;
|
|
||||||
this.helper[1] = secondByte;
|
|
||||||
this.helper[2] = thirdByte;
|
|
||||||
this.helper[3] = fourthByte;
|
|
||||||
|
|
||||||
this.writer.write(this.helper.subarray(0, 4));
|
|
||||||
|
|
||||||
const xingOffset = getXingOffset(data.mpegVersionId, data.channel);
|
|
||||||
|
|
||||||
this.writer.seek(startPos + xingOffset);
|
|
||||||
this.writeU32(XING);
|
|
||||||
|
|
||||||
let flags = 0;
|
|
||||||
if (data.frameCount !== null) {
|
|
||||||
flags |= 1;
|
|
||||||
}
|
|
||||||
if (data.fileSize !== null) {
|
|
||||||
flags |= 2;
|
|
||||||
}
|
|
||||||
if (data.toc !== null) {
|
|
||||||
flags |= 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.writeU32(flags);
|
|
||||||
|
|
||||||
this.writeU32(data.frameCount ?? 0);
|
|
||||||
this.writeU32(data.fileSize ?? 0);
|
|
||||||
this.writer.write(data.toc ?? new Uint8Array(100));
|
|
||||||
|
|
||||||
const frameSize = computeMp3FrameSize(data.layer, 1000 * bitrates[bitrateIndex]!, sampleRate, padding);
|
|
||||||
this.writer.seek(startPos + frameSize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user