mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 02:43:48 +02:00
feat(codec): add detection support for AC-3, E-AC-3 audio codecs (#275)
* ac3/eac3 detection * fix tests * review pass + follow codebase comments style * Add ac3/eac3 to docs * WIP mpeg-2 * Set registration_descriptor * add support for ac3 system b in mpeg-ts * Update docs with ac3 in .ts support * fix formatting * Remove default to fix linter * Add eac3 to mpeg-ts * fix formatting * Fix failing test * Refactor AC3/EAC3 code a little --------- Co-authored-by: Vanilagy <[email protected]>
This commit is contained in:
@@ -16,7 +16,7 @@ test('MPEG-TS output format', async () => {
|
||||
expect(format.mimeType).toBe('video/MP2T');
|
||||
expect(format.fileExtension).toBe('.ts');
|
||||
expect(format.supportsVideoRotationMetadata).toBe(false);
|
||||
expect(format.getSupportedCodecs()).toEqual(['avc', 'hevc', 'aac', 'mp3']);
|
||||
expect(format.getSupportedCodecs()).toEqual(['avc', 'hevc', 'aac', 'mp3', 'ac3', 'eac3']);
|
||||
expect(format.getSupportedTrackCounts()).toEqual({
|
||||
video: { min: 0, max: 16 },
|
||||
audio: { min: 0, max: 32 },
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { expect, test } from 'vitest';
|
||||
import { Input } from '../../src/input.js';
|
||||
import { BufferSource, FilePathSource } from '../../src/source.js';
|
||||
import path from 'node:path';
|
||||
import { ALL_FORMATS } from '../../src/input-format.js';
|
||||
import { Output } from '../../src/output.js';
|
||||
import { MpegTsOutputFormat } from '../../src/output-format.js';
|
||||
import { BufferTarget } from '../../src/target.js';
|
||||
import { Conversion } from '../../src/conversion.js';
|
||||
import { AC3_REGISTRATION_DESCRIPTOR, EAC3_REGISTRATION_DESCRIPTOR } from '../../src/codec-data.js';
|
||||
|
||||
const __dirname = new URL('.', import.meta.url).pathname;
|
||||
|
||||
test('reads AC-3 from MP4', async () => {
|
||||
using input = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '..', 'public/ac3.mp4')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTrack = (await input.getPrimaryAudioTrack())!;
|
||||
const decoderConfig = (await audioTrack.getDecoderConfig())!;
|
||||
|
||||
expect(audioTrack.codec).toBe('ac3');
|
||||
expect(decoderConfig.description).toBeUndefined();
|
||||
});
|
||||
|
||||
test('reads E-AC-3 from MP4', async () => {
|
||||
using input = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '..', 'public/eac3.mp4')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTrack = (await input.getPrimaryAudioTrack())!;
|
||||
const decoderConfig = (await audioTrack.getDecoderConfig())!;
|
||||
|
||||
expect(audioTrack.codec).toBe('eac3');
|
||||
expect(decoderConfig.description).toBeUndefined();
|
||||
});
|
||||
|
||||
test('reads and writes AC-3 in MPEG-TS', async () => {
|
||||
using originalInput = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '..', 'public/ac3.ts')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const originalAudioTrack = (await originalInput.getPrimaryAudioTrack())!;
|
||||
|
||||
expect(originalAudioTrack.codec).toBe('ac3');
|
||||
expect(originalAudioTrack.internalCodecId).toBe(0x81);
|
||||
|
||||
const output = new Output({
|
||||
format: new MpegTsOutputFormat(),
|
||||
target: new BufferTarget(),
|
||||
});
|
||||
|
||||
const conversion = await Conversion.init({ input: originalInput, output });
|
||||
await conversion.execute();
|
||||
|
||||
using newInput = new Input({
|
||||
source: new BufferSource(output.target.buffer!),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const newAudioTrack = (await newInput.getPrimaryAudioTrack())!;
|
||||
|
||||
expect(newAudioTrack.codec).toBe('ac3');
|
||||
expect(newAudioTrack.internalCodecId).toBe(0x81);
|
||||
expect(newAudioTrack.numberOfChannels).toBe(originalAudioTrack.numberOfChannels);
|
||||
expect(newAudioTrack.sampleRate).toBe(originalAudioTrack.sampleRate);
|
||||
|
||||
// Verify registration_descriptor is present
|
||||
const buffer = new Uint8Array(output.target.buffer!);
|
||||
let found = false;
|
||||
for (let i = 0; i < buffer.length - AC3_REGISTRATION_DESCRIPTOR.length; i++) {
|
||||
if (AC3_REGISTRATION_DESCRIPTOR.every((byte, j) => buffer[i + j] === byte)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect(found).toBe(true);
|
||||
});
|
||||
|
||||
test('reads and writes E-AC-3 in MPEG-TS', async () => {
|
||||
using originalInput = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '..', 'public/eac3.ts')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const originalAudioTrack = (await originalInput.getPrimaryAudioTrack())!;
|
||||
|
||||
expect(originalAudioTrack.codec).toBe('eac3');
|
||||
expect(originalAudioTrack.internalCodecId).toBe(0x87);
|
||||
|
||||
const output = new Output({
|
||||
format: new MpegTsOutputFormat(),
|
||||
target: new BufferTarget(),
|
||||
});
|
||||
|
||||
const conversion = await Conversion.init({ input: originalInput, output });
|
||||
await conversion.execute();
|
||||
|
||||
using newInput = new Input({
|
||||
source: new BufferSource(output.target.buffer!),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const newAudioTrack = (await newInput.getPrimaryAudioTrack())!;
|
||||
|
||||
expect(newAudioTrack.codec).toBe('eac3');
|
||||
expect(newAudioTrack.internalCodecId).toBe(0x87);
|
||||
expect(newAudioTrack.numberOfChannels).toBe(originalAudioTrack.numberOfChannels);
|
||||
expect(newAudioTrack.sampleRate).toBe(originalAudioTrack.sampleRate);
|
||||
|
||||
// Verify registration_descriptor is present
|
||||
const buffer = new Uint8Array(output.target.buffer!);
|
||||
let found = false;
|
||||
for (let i = 0; i < buffer.length - EAC3_REGISTRATION_DESCRIPTOR.length; i++) {
|
||||
if (EAC3_REGISTRATION_DESCRIPTOR.every((byte, j) => buffer[i + j] === byte)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect(found).toBe(true);
|
||||
});
|
||||
@@ -9,6 +9,7 @@ import { assert } from '../../src/misc.js';
|
||||
import { EncodedPacketSink } from '../../src/media-sink.js';
|
||||
import { EncodedPacket } from '../../src/packet.js';
|
||||
import { MpegTsDemuxer } from '../../src/mpeg-ts/mpeg-ts-demuxer.js';
|
||||
import { MpegTsStreamType } from '../../src/mpeg-ts/mpeg-ts-misc.js';
|
||||
|
||||
const __dirname = new URL('.', import.meta.url).pathname;
|
||||
|
||||
@@ -612,6 +613,146 @@ test('MPEG-TS with MP3 audio', async () => {
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('MPEG-TS with AC-3 audio (System A)', async () => {
|
||||
using input = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '../public/ac3.ts')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTrack = await input.getPrimaryAudioTrack();
|
||||
assert(audioTrack);
|
||||
|
||||
expect(audioTrack.codec).toBe('ac3');
|
||||
expect(audioTrack.internalCodecId).toBe(MpegTsStreamType.AC3_SYSTEM_A);
|
||||
|
||||
const audioDecoderConfig = await audioTrack.getDecoderConfig();
|
||||
expect(audioDecoderConfig).toEqual({
|
||||
codec: 'ac-3',
|
||||
numberOfChannels: 6,
|
||||
sampleRate: 48000,
|
||||
});
|
||||
|
||||
const sink = new EncodedPacketSink(audioTrack);
|
||||
|
||||
const firstPacket = await sink.getFirstPacket();
|
||||
assert(firstPacket);
|
||||
|
||||
let count = 0;
|
||||
for await (const packet of sink.packets()) {
|
||||
expect(packet.data[0]).toBe(0x0b);
|
||||
expect(packet.data[1]).toBe(0x77);
|
||||
expect(packet.type).toBe('key');
|
||||
count++;
|
||||
}
|
||||
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('MPEG-TS with AC-3 audio (System B)', async () => {
|
||||
using input = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '../public/ac3-system-b.ts')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTrack = await input.getPrimaryAudioTrack();
|
||||
assert(audioTrack);
|
||||
|
||||
expect(audioTrack.codec).toBe('ac3');
|
||||
expect(audioTrack.internalCodecId).toBe(MpegTsStreamType.PRIVATE_DATA);
|
||||
|
||||
const audioDecoderConfig = await audioTrack.getDecoderConfig();
|
||||
expect(audioDecoderConfig).toEqual({
|
||||
codec: 'ac-3',
|
||||
numberOfChannels: 6,
|
||||
sampleRate: 48000,
|
||||
});
|
||||
|
||||
const sink = new EncodedPacketSink(audioTrack);
|
||||
|
||||
const firstPacket = await sink.getFirstPacket();
|
||||
assert(firstPacket);
|
||||
|
||||
let count = 0;
|
||||
for await (const packet of sink.packets()) {
|
||||
expect(packet.data[0]).toBe(0x0b);
|
||||
expect(packet.data[1]).toBe(0x77);
|
||||
expect(packet.type).toBe('key');
|
||||
count++;
|
||||
}
|
||||
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('MPEG-TS with E-AC-3 audio (System A)', async () => {
|
||||
using input = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '../public/eac3.ts')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTrack = await input.getPrimaryAudioTrack();
|
||||
assert(audioTrack);
|
||||
|
||||
expect(audioTrack.codec).toBe('eac3');
|
||||
expect(audioTrack.internalCodecId).toBe(MpegTsStreamType.EAC3_SYSTEM_A);
|
||||
|
||||
const audioDecoderConfig = await audioTrack.getDecoderConfig();
|
||||
expect(audioDecoderConfig).toEqual({
|
||||
codec: 'ec-3',
|
||||
numberOfChannels: 6,
|
||||
sampleRate: 48000,
|
||||
});
|
||||
|
||||
const sink = new EncodedPacketSink(audioTrack);
|
||||
|
||||
const firstPacket = await sink.getFirstPacket();
|
||||
assert(firstPacket);
|
||||
|
||||
let count = 0;
|
||||
for await (const packet of sink.packets()) {
|
||||
expect(packet.data[0]).toBe(0x0b);
|
||||
expect(packet.data[1]).toBe(0x77);
|
||||
expect(packet.type).toBe('key');
|
||||
count++;
|
||||
}
|
||||
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('MPEG-TS with E-AC-3 audio (System B)', async () => {
|
||||
using input = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '../public/eac3-system-b.ts')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTrack = await input.getPrimaryAudioTrack();
|
||||
assert(audioTrack);
|
||||
|
||||
expect(audioTrack.codec).toBe('eac3');
|
||||
expect(audioTrack.internalCodecId).toBe(MpegTsStreamType.PRIVATE_DATA);
|
||||
|
||||
const audioDecoderConfig = await audioTrack.getDecoderConfig();
|
||||
expect(audioDecoderConfig).toEqual({
|
||||
codec: 'ec-3',
|
||||
numberOfChannels: 6,
|
||||
sampleRate: 48000,
|
||||
});
|
||||
|
||||
const sink = new EncodedPacketSink(audioTrack);
|
||||
|
||||
const firstPacket = await sink.getFirstPacket();
|
||||
assert(firstPacket);
|
||||
|
||||
let count = 0;
|
||||
for await (const packet of sink.packets()) {
|
||||
expect(packet.data[0]).toBe(0x0b);
|
||||
expect(packet.data[1]).toBe(0x77);
|
||||
expect(packet.type).toBe('key');
|
||||
count++;
|
||||
}
|
||||
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('MPEG-TS partial reading', async () => {
|
||||
const fullPath = path.join(__dirname, '../public/193039199_mp4_h264_aac_fhd_7.ts');
|
||||
const buffer = await fs.promises.readFile(fullPath);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user