mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-03 13:53:53 +02:00
Add reusable Opus identification header parsing function
This commit is contained in:
@@ -967,6 +967,30 @@ export const parseAacAudioSpecificConfig = (bytes: Uint8Array | null) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const parseOpusIdentificationHeader = (bytes: Uint8Array) => {
|
||||||
|
const view = toDataView(bytes);
|
||||||
|
|
||||||
|
const outputChannelCount = view.getUint8(9);
|
||||||
|
const preSkip = view.getUint16(10, true);
|
||||||
|
const inputSampleRate = view.getUint32(12, true);
|
||||||
|
const outputGain = view.getInt16(16, true);
|
||||||
|
const channelMappingFamily = view.getUint8(18);
|
||||||
|
|
||||||
|
let channelMappingTable: Uint8Array | null = null;
|
||||||
|
if (channelMappingFamily) {
|
||||||
|
channelMappingTable = bytes.subarray(19, 19 + 2 + outputChannelCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
outputChannelCount,
|
||||||
|
preSkip,
|
||||||
|
inputSampleRate,
|
||||||
|
outputGain,
|
||||||
|
channelMappingFamily,
|
||||||
|
channelMappingTable,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// From https://datatracker.ietf.org/doc/html/rfc6716, in 48 kHz samples
|
// From https://datatracker.ietf.org/doc/html/rfc6716, in 48 kHz samples
|
||||||
const OPUS_FRAME_DURATION_TABLE = [
|
const OPUS_FRAME_DURATION_TABLE = [
|
||||||
480, 960, 1920, 2880,
|
480, 960, 1920, 2880,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
AudioCodec,
|
AudioCodec,
|
||||||
generateAv1CodecConfigurationFromCodecString,
|
generateAv1CodecConfigurationFromCodecString,
|
||||||
|
parseOpusIdentificationHeader,
|
||||||
parsePcmCodec,
|
parsePcmCodec,
|
||||||
PCM_AUDIO_CODECS,
|
PCM_AUDIO_CODECS,
|
||||||
PcmAudioCodec,
|
PcmAudioCodec,
|
||||||
@@ -824,19 +825,17 @@ export const dOps = (trackData: IsobmffAudioTrackData) => {
|
|||||||
if (description) {
|
if (description) {
|
||||||
assert(description.byteLength >= 18);
|
assert(description.byteLength >= 18);
|
||||||
|
|
||||||
const view = ArrayBuffer.isView(description)
|
|
||||||
? new DataView(description.buffer, description.byteOffset, description.byteLength)
|
|
||||||
: new DataView(description);
|
|
||||||
|
|
||||||
outputChannelCount = view.getUint8(9);
|
|
||||||
preSkip = view.getUint16(10, true);
|
|
||||||
inputSampleRate = view.getUint32(12, true);
|
|
||||||
outputGain = view.getInt16(16, true);
|
|
||||||
channelMappingFamily = view.getUint8(18);
|
|
||||||
|
|
||||||
if (channelMappingFamily) {
|
|
||||||
const bytes = toUint8Array(description);
|
const bytes = toUint8Array(description);
|
||||||
channelMappingTable = bytes.subarray(19, 19 + 2 + outputChannelCount);
|
const header = parseOpusIdentificationHeader(bytes);
|
||||||
|
|
||||||
|
outputChannelCount = header.outputChannelCount;
|
||||||
|
preSkip = header.preSkip;
|
||||||
|
inputSampleRate = header.inputSampleRate;
|
||||||
|
outputGain = header.outputGain;
|
||||||
|
channelMappingFamily = header.channelMappingFamily;
|
||||||
|
|
||||||
|
if (header.channelMappingTable) {
|
||||||
|
channelMappingTable = header.channelMappingTable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
PCM_AUDIO_CODECS,
|
PCM_AUDIO_CODECS,
|
||||||
PcmAudioCodec,
|
PcmAudioCodec,
|
||||||
generateAv1CodecConfigurationFromCodecString,
|
generateAv1CodecConfigurationFromCodecString,
|
||||||
|
parseOpusIdentificationHeader,
|
||||||
parsePcmCodec,
|
parsePcmCodec,
|
||||||
validateAudioChunkMetadata,
|
validateAudioChunkMetadata,
|
||||||
validateSubtitleMetadata,
|
validateSubtitleMetadata,
|
||||||
@@ -228,12 +229,11 @@ export class MatroskaMuxer extends Muxer {
|
|||||||
|
|
||||||
const description = trackData.info.decoderConfig.description;
|
const description = trackData.info.decoderConfig.description;
|
||||||
if (description) {
|
if (description) {
|
||||||
const view = ArrayBuffer.isView(description)
|
const bytes = toUint8Array(description);
|
||||||
? new DataView(description.buffer, description.byteOffset, description.byteLength)
|
const header = parseOpusIdentificationHeader(bytes);
|
||||||
: new DataView(description);
|
|
||||||
|
|
||||||
// Read the value from the Opus Identification Header
|
// Use the preSkip value from the header
|
||||||
seekPreRollNs = Math.round(1e9 * (view.getUint16(10, true) / 48000));
|
seekPreRollNs = Math.round(1e9 * (header.preSkip / 48000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AudioCodec, parseOpusTocByte } from '../codec';
|
import { AudioCodec, parseOpusIdentificationHeader, parseOpusTocByte } from '../codec';
|
||||||
import { Demuxer } from '../demuxer';
|
import { Demuxer } from '../demuxer';
|
||||||
import { Input } from '../input';
|
import { Input } from '../input';
|
||||||
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
|
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
|
||||||
@@ -236,12 +236,12 @@ export class OggDemuxer extends Demuxer {
|
|||||||
bitstream.description = firstPacket.data;
|
bitstream.description = firstPacket.data;
|
||||||
bitstream.lastMetadataPacket = secondPacket;
|
bitstream.lastMetadataPacket = secondPacket;
|
||||||
|
|
||||||
const view = toDataView(firstPacket.data);
|
const header = parseOpusIdentificationHeader(firstPacket.data);
|
||||||
bitstream.numberOfChannels = view.getUint8(9);
|
bitstream.numberOfChannels = header.outputChannelCount;
|
||||||
bitstream.sampleRate = view.getUint32(12, true);
|
bitstream.sampleRate = header.inputSampleRate;
|
||||||
|
|
||||||
bitstream.opusInfo = {
|
bitstream.opusInfo = {
|
||||||
preSkip: view.getUint16(10, true),
|
preSkip: header.preSkip,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user