Merge branch 'main' into hls

This commit is contained in:
Vanilagy
2026-03-04 17:33:16 +01:00
54 changed files with 2007 additions and 291 deletions
+2 -1
View File
@@ -6,7 +6,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { aacChannelMap, aacFrequencyTable, AudioCodec } from '../codec';
import { aacChannelMap, aacFrequencyTable } from '../../shared/aac-misc';
import { AudioCodec } from '../codec';
import { Demuxer } from '../demuxer';
import {
ID3_V2_HEADER_SIZE,
-47
View File
@@ -1,47 +0,0 @@
/*!
* Copyright (c) 2026-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/.
*/
import { AacAudioSpecificConfig } from '../codec';
import { Bitstream } from '../misc';
export type AdtsHeaderTemplate = {
header: Uint8Array;
bitstream: Bitstream;
};
export const buildAdtsHeaderTemplate = (config: AacAudioSpecificConfig): AdtsHeaderTemplate => {
const header = new Uint8Array(7);
const bitstream = new Bitstream(header);
const { objectType, frequencyIndex, channelConfiguration } = config;
const profile = objectType - 1;
bitstream.writeBits(12, 0b1111_11111111); // Syncword
bitstream.writeBits(1, 0); // MPEG Version
bitstream.writeBits(2, 0); // Layer
bitstream.writeBits(1, 1); // Protection absence
bitstream.writeBits(2, profile); // Profile
bitstream.writeBits(4, frequencyIndex); // MPEG-4 Sampling Frequency Index
bitstream.writeBits(1, 0); // Private bit
bitstream.writeBits(3, channelConfiguration); // MPEG-4 Channel Configuration
bitstream.writeBits(1, 0); // Originality
bitstream.writeBits(1, 0); // Home
bitstream.writeBits(1, 0); // Copyright ID bit
bitstream.writeBits(1, 0); // Copyright ID start
bitstream.skipBits(13); // Frame length (to be filled per packet)
bitstream.writeBits(11, 0x7ff); // Buffer fullness
bitstream.writeBits(2, 0); // Number of AAC frames minus 1
// Omit CRC check
return { header, bitstream };
};
export const writeAdtsFrameLength = (bitstream: Bitstream, frameLength: number) => {
bitstream.pos = 30;
bitstream.writeBits(13, frameLength);
};
+4 -3
View File
@@ -6,16 +6,17 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { parseAacAudioSpecificConfig, validateAudioChunkMetadata } from '../codec';
import { buildAdtsHeaderTemplate, parseAacAudioSpecificConfig, writeAdtsFrameLength } from '../../shared/aac-misc';
import { Bitstream } from '../../shared/bitstream';
import { validateAudioChunkMetadata } from '../codec';
import { Id3V2Writer } from '../id3';
import { metadataTagsAreEmpty } from '../metadata';
import { assert, Bitstream, toUint8Array } from '../misc';
import { assert, toUint8Array } from '../misc';
import { Muxer } from '../muxer';
import { Output, OutputAudioTrack } from '../output';
import { AdtsOutputFormat } from '../output-format';
import { EncodedPacket } from '../packet';
import { Writer } from '../writer';
import { buildAdtsHeaderTemplate, writeAdtsFrameLength } from './adts-misc';
export class AdtsMuxer extends Muxer {
private format: AdtsOutputFormat;
+1 -1
View File
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { Bitstream } from '../misc';
import { Bitstream } from '../../shared/bitstream';
import { FileSlice, readBytes } from '../reader';
export const MIN_ADTS_FRAME_HEADER_SIZE = 7;
+1 -1
View File
@@ -11,7 +11,6 @@ import {
assert,
assertNever,
base64ToBytes,
Bitstream,
bytesToBase64,
keyValueIterator,
getUint24,
@@ -30,6 +29,7 @@ import {
import { PacketType } from './packet';
import { MetadataTags } from './metadata';
import { AC3_SAMPLE_RATES, EAC3_REDUCED_SAMPLE_RATES } from '../shared/ac3-misc';
import { Bitstream } from '../shared/bitstream';
// References for AVC/HEVC code:
// ISO 14496-15
+1 -101
View File
@@ -6,6 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { parseAacAudioSpecificConfig } from '../shared/aac-misc';
import {
Av1CodecInfo,
AvcDecoderConfigurationRecord,
@@ -13,7 +14,6 @@ import {
Vp9CodecInfo,
} from './codec-data';
import {
Bitstream,
COLOR_PRIMARIES_MAP,
MATRIX_COEFFICIENTS_MAP,
TRANSFER_CHARACTERISTICS_MAP,
@@ -590,106 +590,6 @@ export const extractAudioCodecString = (trackInfo: {
throw new TypeError(`Unhandled codec '${codec}'.`);
};
export type AacAudioSpecificConfig = {
objectType: number;
frequencyIndex: number;
sampleRate: number | null;
channelConfiguration: number;
numberOfChannels: number | null;
};
export const aacFrequencyTable = [
96000, 88200, 64000, 48000, 44100, 32000,
24000, 22050, 16000, 12000, 11025, 8000, 7350,
];
export const aacChannelMap = [-1, 1, 2, 3, 4, 5, 6, 8];
export const parseAacAudioSpecificConfig = (bytes: Uint8Array | null): AacAudioSpecificConfig => {
if (!bytes || bytes.byteLength < 2) {
throw new TypeError('AAC description must be at least 2 bytes long.');
}
const bitstream = new Bitstream(bytes);
let objectType = bitstream.readBits(5);
if (objectType === 31) {
objectType = 32 + bitstream.readBits(6);
}
const frequencyIndex = bitstream.readBits(4);
let sampleRate: number | null = null;
if (frequencyIndex === 15) {
sampleRate = bitstream.readBits(24);
} else {
if (frequencyIndex < aacFrequencyTable.length) {
sampleRate = aacFrequencyTable[frequencyIndex]!;
}
}
const channelConfiguration = bitstream.readBits(4);
let numberOfChannels: number | null = null;
if (channelConfiguration >= 1 && channelConfiguration <= 7) {
numberOfChannels = aacChannelMap[channelConfiguration]!;
}
return {
objectType,
frequencyIndex,
sampleRate,
channelConfiguration,
numberOfChannels,
};
};
export const buildAacAudioSpecificConfig = (config: {
objectType: number;
sampleRate: number;
numberOfChannels: number;
}) => {
let frequencyIndex = aacFrequencyTable.indexOf(config.sampleRate);
let customSampleRate: number | null = null;
if (frequencyIndex === -1) {
frequencyIndex = 15;
customSampleRate = config.sampleRate;
}
const channelConfiguration = aacChannelMap.indexOf(config.numberOfChannels);
if (channelConfiguration === -1) {
throw new TypeError(`Unsupported number of channels: ${config.numberOfChannels}`);
}
let bitCount = 5 + 4 + 4;
if (config.objectType >= 32) {
bitCount += 6;
}
if (frequencyIndex === 15) {
bitCount += 24;
}
const byteCount = Math.ceil(bitCount / 8);
const bytes = new Uint8Array(byteCount);
const bitstream = new Bitstream(bytes);
if (config.objectType < 32) {
bitstream.writeBits(5, config.objectType);
} else {
bitstream.writeBits(5, 31);
bitstream.writeBits(6, config.objectType - 32);
}
bitstream.writeBits(4, frequencyIndex);
if (frequencyIndex === 15) {
bitstream.writeBits(24, customSampleRate!);
}
bitstream.writeBits(4, channelConfiguration);
return bytes;
};
export const OPUS_SAMPLE_RATE = 48_000;
const PCM_CODEC_REGEX = /^pcm-([usf])(\d+)+(be)?$/;
+6
View File
@@ -774,6 +774,12 @@ export class Conversion {
);
}
if (codecs.includes('aac')) {
elements.push(
'\nThe @mediabunny/aac-encoder extension package provides support for encoding AAC.',
);
}
if (codecs.includes('ac3') || codecs.includes('eac3')) {
elements.push(
'\nThe @mediabunny/ac3 extension package provides support'
+1 -1
View File
@@ -15,7 +15,6 @@ import {
assert,
AsyncMutex,
binarySearchLessOrEqual,
Bitstream,
textDecoder,
UNDETERMINED_LANGUAGE,
} from '../misc';
@@ -37,6 +36,7 @@ import {
readSampleRate,
getSampleRateOrUncommon,
} from './flac-misc';
import { Bitstream } from '../../shared/bitstream';
type FlacAudioInfo = {
numberOfChannels: number;
+2 -1
View File
@@ -6,7 +6,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { assert, assertNever, Bitstream } from '../misc';
import { Bitstream } from '../../shared/bitstream';
import { assert, assertNever } from '../misc';
import { FileSlice, readBytes, readU16Be, readU8 } from '../reader';
type BlockSizeOrUncommon = number | 'uncommon-u16' | 'uncommon-u8';
+1 -1
View File
@@ -10,7 +10,6 @@ import { validateAudioChunkMetadata } from '../codec';
import { createVorbisComments, FlacBlockType } from '../codec-data';
import {
assert,
Bitstream,
textEncoder,
toDataView,
toUint8Array,
@@ -27,6 +26,7 @@ import {
getBlockSizeOrUncommon,
readCodedNumber,
} from './flac-misc';
import { Bitstream } from '../../shared/bitstream';
const FLAC_HEADER = /* #__PURE__ */ new Uint8Array([0x66, 0x4c, 0x61, 0x43]); // 'fLaC'
const STREAMINFO_SIZE = 38;
+22 -7
View File
@@ -20,7 +20,6 @@ import {
UNDETERMINED_LANGUAGE,
assertNever,
keyValueIterator,
Bitstream,
} from '../misc';
import {
AudioCodec,
@@ -46,6 +45,7 @@ import {
} from './isobmff-muxer';
import { parseAc3SyncFrame, parseEac3SyncFrame, parseOpusIdentificationHeader } from '../codec-data';
import { MetadataTags, RichImageData } from '../metadata';
import { Bitstream } from '../../shared/bitstream';
export class IsobmffBoxWriter {
private helper = new Uint8Array(8);
@@ -726,9 +726,10 @@ export const soundSampleDescription = (
) => {
let version = 0;
let contents: NestedNumberArray;
let sampleSizeInBits = 16;
if ((PCM_AUDIO_CODECS as readonly AudioCodec[]).includes(trackData.track.source._codec)) {
const isPcmCodec = (PCM_AUDIO_CODECS as readonly AudioCodec[]).includes(trackData.track.source._codec);
if (isPcmCodec) {
const codec = trackData.track.source._codec as PcmAudioCodec;
const { sampleSize } = parsePcmCodec(codec);
sampleSizeInBits = 8 * sampleSize;
@@ -738,6 +739,10 @@ export const soundSampleDescription = (
}
}
if (trackData.muxer.isQuickTime) {
version = 1;
}
if (version === 0) {
contents = [
Array(6).fill(0), // Reserved
@@ -753,6 +758,8 @@ export const soundSampleDescription = (
u16(0), // Sample rate (lower)
];
} else {
const compressionId = isPcmCodec ? 0 : -2;
contents = [
Array(6).fill(0), // Reserved
u16(1), // Data reference index
@@ -761,13 +768,21 @@ export const soundSampleDescription = (
u32(0), // Vendor
u16(trackData.info.numberOfChannels), // Number of channels
u16(Math.min(sampleSizeInBits, 16)), // Sample size (bits)
u16(0), // Compression ID
i16(compressionId), // Compression ID
u16(0), // Packet size
u16(trackData.info.sampleRate < 2 ** 16 ? trackData.info.sampleRate : 0), // Sample rate (upper)
u16(0), // Sample rate (lower)
u32(1), // Samples per packet (must be 1 for uncompressed formats)
u32(sampleSizeInBits / 8), // Bytes per packet
u32(trackData.info.numberOfChannels * sampleSizeInBits / 8), // Bytes per frame
isPcmCodec
? [
u32(1), // Samples per packet (must be 1 for uncompressed formats)
u32(sampleSizeInBits / 8), // Bytes per packet
u32(trackData.info.numberOfChannels * sampleSizeInBits / 8), // Bytes per frame
]
: [
u32(0), // Samples per packet (don't bother, still works with 0)
u32(0), // Bytes per packet (variable)
u32(0), // Bytes per frame (variable)
],
u32(2), // Bytes per sample (constant in FFmpeg)
];
}
+2 -2
View File
@@ -6,6 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { parseAacAudioSpecificConfig } from '../../shared/aac-misc';
import {
AacCodecInfo,
AudioCodec,
@@ -13,7 +14,6 @@ import {
extractVideoCodecString,
MediaCodec,
OPUS_SAMPLE_RATE,
parseAacAudioSpecificConfig,
parsePcmCodec,
PCM_AUDIO_CODECS,
PcmAudioCodec,
@@ -47,7 +47,6 @@ import {
assert,
binarySearchExact,
binarySearchLessOrEqual,
Bitstream,
COLOR_PRIMARIES_MAP_INVERSE,
findLastIndex,
isIso639Dash2LanguageCode,
@@ -92,6 +91,7 @@ import {
} from '../reader';
import { DEFAULT_TRACK_DISPOSITION, MetadataTags, RichImageData, TrackDisposition } from '../metadata';
import { AC3_SAMPLE_RATES } from '../../shared/ac3-misc';
import { Bitstream } from '../../shared/bitstream';
type InternalTrack = {
id: number;
+1 -3
View File
@@ -13,10 +13,8 @@ import { BufferTargetWriter, Writer } from '../writer';
import { assert, computeRationalApproximation, last, promiseWithResolvers, Rational, simplifyRational } from '../misc';
import { IsobmffOutputFormatOptions, IsobmffOutputFormat, MovOutputFormat } from '../output-format';
import { inlineTimestampRegex, SubtitleConfig, SubtitleCue, SubtitleMetadata } from '../subtitles';
import { aacChannelMap, aacFrequencyTable, buildAacAudioSpecificConfig } from '../../shared/aac-misc';
import {
aacChannelMap,
aacFrequencyTable,
buildAacAudioSpecificConfig,
parsePcmCodec,
PCM_AUDIO_CODECS,
PcmAudioCodec,
+2 -4
View File
@@ -6,8 +6,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { Bitstream } from '../../shared/bitstream';
import {
Bitstream,
COLOR_PRIMARIES_MAP,
MATRIX_COEFFICIENTS_MAP,
TRANSFER_CHARACTERISTICS_MAP,
@@ -49,10 +49,8 @@ import {
inlineTimestampRegex,
parseSubtitleTimestamp,
} from '../subtitles';
import { aacChannelMap, aacFrequencyTable, buildAacAudioSpecificConfig } from '../../shared/aac-misc';
import {
aacChannelMap,
aacFrequencyTable,
buildAacAudioSpecificConfig,
OPUS_SAMPLE_RATE,
PCM_AUDIO_CODECS,
PcmAudioCodec,
+1 -2
View File
@@ -6,11 +6,10 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { buildAacAudioSpecificConfig, parseAacAudioSpecificConfig } from '../shared/aac-misc';
import {
AUDIO_CODECS,
AudioCodec,
buildAacAudioSpecificConfig,
parseAacAudioSpecificConfig,
parsePcmCodec,
PCM_AUDIO_CODECS,
PcmAudioCodec,
+2 -79
View File
@@ -6,6 +6,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { Bitstream } from '../shared/bitstream';
export function assert(x: unknown): asserts x {
if (!x) {
throw new Error('Assertion failed.');
@@ -39,85 +41,6 @@ export const isU32 = (value: number) => {
return value >= 0 && value < 2 ** 32;
};
export class Bitstream {
/** Current offset in bits. */
pos = 0;
constructor(public bytes: Uint8Array) {}
seekToByte(byteOffset: number) {
this.pos = 8 * byteOffset;
}
private readBit() {
const byteIndex = Math.floor(this.pos / 8);
const byte = this.bytes[byteIndex] ?? 0;
const bitIndex = 0b111 - (this.pos & 0b111);
const bit = (byte & (1 << bitIndex)) >> bitIndex;
this.pos++;
return bit;
}
readBits(n: number) {
if (n === 1) {
return this.readBit();
}
let result = 0;
for (let i = 0; i < n; i++) {
result <<= 1;
result |= this.readBit();
}
return result;
}
writeBits(n: number, value: number) {
const end = this.pos + n;
for (let i = this.pos; i < end; i++) {
const byteIndex = Math.floor(i / 8);
let byte = this.bytes[byteIndex]!;
const bitIndex = 0b111 - (i & 0b111);
byte &= ~(1 << bitIndex);
byte |= ((value & (1 << (end - i - 1))) >> (end - i - 1)) << bitIndex;
this.bytes[byteIndex] = byte;
}
this.pos = end;
};
readAlignedByte() {
// Ensure we're byte-aligned
if (this.pos % 8 !== 0) {
throw new Error('Bitstream is not byte-aligned.');
}
const byteIndex = this.pos / 8;
const byte = this.bytes[byteIndex] ?? 0;
this.pos += 8;
return byte;
}
skipBits(n: number) {
this.pos += n;
}
getBitsLeft() {
return this.bytes.length * 8 - this.pos;
}
clone() {
const clone = new Bitstream(this.bytes);
clone.pos = this.pos;
return clone;
}
}
/** Reads an exponential-Golomb universal code from a Bitstream. */
export const readExpGolomb = (bitstream: Bitstream) => {
let leadingZeroBits = 0;
+2 -3
View File
@@ -8,10 +8,9 @@
import { SAMPLES_PER_AAC_FRAME } from '../adts/adts-demuxer';
import { MAX_ADTS_FRAME_HEADER_SIZE, readAdtsFrameHeader } from '../adts/adts-reader';
import { aacChannelMap, aacFrequencyTable } from '../../shared/aac-misc';
import {
aacChannelMap,
AacCodecInfo,
aacFrequencyTable,
AudioCodec,
extractAudioCodecString,
extractVideoCodecString,
@@ -55,7 +54,6 @@ import {
assert,
binarySearchExact,
binarySearchLessOrEqual,
Bitstream,
COLOR_PRIMARIES_MAP_INVERSE,
findLastIndex,
floorToMultiple,
@@ -72,6 +70,7 @@ import { EncodedPacket, PacketType, PLACEHOLDER_DATA } from '../packet';
import { FileSlice, readBytes, Reader, readU16Be, readU32Be, readU8 } from '../reader';
import { buildMpegTsMimeType, MpegTsStreamType, TIMESCALE, TS_PACKET_SIZE } from './mpeg-ts-misc';
import { AC3_SAMPLE_RATES } from '../../shared/ac3-misc';
import { Bitstream } from '../../shared/bitstream';
// Resources:
// ISO/IEC 13818-1
+4 -3
View File
@@ -6,8 +6,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { parseAacAudioSpecificConfig, validateAudioChunkMetadata, validateVideoChunkMetadata } from '../codec';
import { buildAdtsHeaderTemplate, writeAdtsFrameLength } from '../adts/adts-misc';
import { buildAdtsHeaderTemplate, parseAacAudioSpecificConfig, writeAdtsFrameLength } from '../../shared/aac-misc';
import { validateAudioChunkMetadata, validateVideoChunkMetadata } from '../codec';
import {
AC3_REGISTRATION_DESCRIPTOR,
AvcDecoderConfigurationRecord,
@@ -23,7 +23,8 @@ import {
iterateNalUnitsInAnnexB,
iterateNalUnitsInLengthPrefixed,
} from '../codec-data';
import { assert, Bitstream, promiseWithResolvers, setUint24, toDataView, toUint8Array } from '../misc';
import { Bitstream } from '../../shared/bitstream';
import { assert, promiseWithResolvers, setUint24, toDataView, toUint8Array } from '../misc';
import { Muxer } from '../muxer';
import { Output, OutputAudioTrack, OutputTrack, OutputVideoTrack } from '../output';
import { MpegTsOutputFormat } from '../output-format';