Add reusable Opus identification header parsing function

This commit is contained in:
Vanilagy
2025-02-07 20:14:01 +01:00
parent 50442691a5
commit b4d4c71916
4 changed files with 44 additions and 21 deletions
+24
View File
@@ -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
const OPUS_FRAME_DURATION_TABLE = [
480, 960, 1920, 2880,
+10 -11
View File
@@ -16,6 +16,7 @@ import {
import {
AudioCodec,
generateAv1CodecConfigurationFromCodecString,
parseOpusIdentificationHeader,
parsePcmCodec,
PCM_AUDIO_CODECS,
PcmAudioCodec,
@@ -824,19 +825,17 @@ export const dOps = (trackData: IsobmffAudioTrackData) => {
if (description) {
assert(description.byteLength >= 18);
const view = ArrayBuffer.isView(description)
? new DataView(description.buffer, description.byteOffset, description.byteLength)
: new DataView(description);
const bytes = toUint8Array(description);
const header = parseOpusIdentificationHeader(bytes);
outputChannelCount = view.getUint8(9);
preSkip = view.getUint16(10, true);
inputSampleRate = view.getUint32(12, true);
outputGain = view.getInt16(16, true);
channelMappingFamily = view.getUint8(18);
outputChannelCount = header.outputChannelCount;
preSkip = header.preSkip;
inputSampleRate = header.inputSampleRate;
outputGain = header.outputGain;
channelMappingFamily = header.channelMappingFamily;
if (channelMappingFamily) {
const bytes = toUint8Array(description);
channelMappingTable = bytes.subarray(19, 19 + 2 + outputChannelCount);
if (header.channelMappingTable) {
channelMappingTable = header.channelMappingTable;
}
}
+5 -5
View File
@@ -34,6 +34,7 @@ import {
PCM_AUDIO_CODECS,
PcmAudioCodec,
generateAv1CodecConfigurationFromCodecString,
parseOpusIdentificationHeader,
parsePcmCodec,
validateAudioChunkMetadata,
validateSubtitleMetadata,
@@ -228,12 +229,11 @@ export class MatroskaMuxer extends Muxer {
const description = trackData.info.decoderConfig.description;
if (description) {
const view = ArrayBuffer.isView(description)
? new DataView(description.buffer, description.byteOffset, description.byteLength)
: new DataView(description);
const bytes = toUint8Array(description);
const header = parseOpusIdentificationHeader(bytes);
// Read the value from the Opus Identification Header
seekPreRollNs = Math.round(1e9 * (view.getUint16(10, true) / 48000));
// Use the preSkip value from the header
seekPreRollNs = Math.round(1e9 * (header.preSkip / 48000));
}
}
+5 -5
View File
@@ -1,4 +1,4 @@
import { AudioCodec, parseOpusTocByte } from '../codec';
import { AudioCodec, parseOpusIdentificationHeader, parseOpusTocByte } from '../codec';
import { Demuxer } from '../demuxer';
import { Input } from '../input';
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
@@ -236,12 +236,12 @@ export class OggDemuxer extends Demuxer {
bitstream.description = firstPacket.data;
bitstream.lastMetadataPacket = secondPacket;
const view = toDataView(firstPacket.data);
bitstream.numberOfChannels = view.getUint8(9);
bitstream.sampleRate = view.getUint32(12, true);
const header = parseOpusIdentificationHeader(firstPacket.data);
bitstream.numberOfChannels = header.outputChannelCount;
bitstream.sampleRate = header.inputSampleRate;
bitstream.opusInfo = {
preSkip: view.getUint16(10, true),
preSkip: header.preSkip,
};
}