diff --git a/dev/demux.html b/dev/demux.html index a64b60c..1fb6443 100644 --- a/dev/demux.html +++ b/dev/demux.html @@ -4,6 +4,27 @@ const fileInput = document.createElement('input'); fileInput.type = 'file'; document.body.append(fileInput); + + const encoder = new AudioEncoder({ + output: console.log, + error: console.error + }); + encoder.configure({ + codec: 'vorbis', + numberOfChannels: 2, + sampleRate: 48000, + bitrate: 128000 + }); + + let audioData = new AudioData({ + format: 'f32', + numberOfChannels: 2, + sampleRate: 48000, + timestamp: 0, + data: new Float32Array(2 * 48000), + numberOfFrames: 48000 + }); + encoder.encode(audioData); fileInput.addEventListener('change', async () => { const file = fileInput.files[0]; @@ -16,7 +37,9 @@ const audioTrack = await input.getPrimaryAudioTrack(); const drain = new Metamuxer.EncodedAudioChunkDrain(audioTrack); - console.log(await drain.getFirstChunk()); + for await (const chunk of drain.chunks()) { + console.log(chunk) + } /* const videoTrack = await input.getPrimaryVideoTrack(); diff --git a/src/isobmff/isobmff-boxes.ts b/src/isobmff/isobmff-boxes.ts index a5e9604..4b81a8e 100644 --- a/src/isobmff/isobmff-boxes.ts +++ b/src/isobmff/isobmff-boxes.ts @@ -751,6 +751,7 @@ export const dOps = (trackData: IsobmffAudioTrackData) => { gain = view.getInt16(14, true); } + // https://www.opus-codec.org/docs/opus_in_isobmff.html return box('dOps', [ u8(0), // Version u8(trackData.info.numberOfChannels), // OutputChannelCount diff --git a/src/isobmff/isobmff-demuxer.ts b/src/isobmff/isobmff-demuxer.ts index e96ba09..4b8adef 100644 --- a/src/isobmff/isobmff-demuxer.ts +++ b/src/isobmff/isobmff-demuxer.ts @@ -1017,6 +1017,56 @@ export class IsobmffDemuxer extends Demuxer { } }; break; + case 'dOps': { // Used for Opus audio + const track = this.currentTrack; + assert(track && track.info?.type === 'audio'); + + // TODO TEMP + /* + const bytes = this.isobmffReader.readBytes(boxInfo.contentSize); + const description2 = new Uint8Array(8 + bytes.byteLength); + const view2 = new DataView(description2.buffer); + view2.setUint32(0, 0x4f707573, false); // 'Opus' + view2.setUint32(4, 0x646f7073, false); // 'dOps' + description2.set(bytes, 8); + track.info.codecDescription = description2; + break; + */ + + this.isobmffReader.pos += 1; // Version + + // https://www.opus-codec.org/docs/opus_in_isobmff.html + const outputChannelCount = this.isobmffReader.readU8(); + const preSkip = this.isobmffReader.readU16(); + const inputSampleRate = this.isobmffReader.readU32(); + const outputGain = this.isobmffReader.readI16(); + const channelMappingFamily = this.isobmffReader.readU8(); + + let channelMappingTable: Uint8Array; + if (channelMappingFamily !== 0) { + channelMappingTable = this.isobmffReader.readBytes(2 + outputChannelCount); + } else { + channelMappingTable = new Uint8Array(0); + } + + // https://datatracker.ietf.org/doc/html/draft-ietf-codec-oggopus-06 + const description = new Uint8Array(8 + 1 + 1 + 2 + 4 + 2 + 1 + channelMappingTable.byteLength); + const view = new DataView(description.buffer); + view.setUint32(0, 0x4f707573, false); // 'Opus' + view.setUint32(4, 0x48656164, false); // 'Head' + view.setUint8(8, 1); // Version + view.setUint8(9, outputChannelCount); + view.setUint16(10, preSkip, true); + view.setUint32(12, inputSampleRate, true); + view.setInt16(16, outputGain, true); + view.setUint8(18, channelMappingFamily); + description.set(channelMappingTable, 19); + + track.info.codecDescription = description; + track.info.numberOfChannels = outputChannelCount; + track.info.sampleRate = inputSampleRate; + }; break; + case 'dfLa': { // Used for FLAC audio const track = this.currentTrack; assert(track && track.info?.type === 'audio'); @@ -1063,10 +1113,8 @@ export class IsobmffDemuxer extends Demuxer { const bytes = this.isobmffReader.readBytes(endPos - startPos); const description = new Uint8Array(4 + bytes.byteLength); - description[0] = 0x66; // f - description[1] = 0x4C; // L - description[2] = 0x61; // a - description[3] = 0x43; // C + const view = new DataView(description.buffer); + view.setUint32(0, 0x664c6143, false); // 'fLaC' description.set(bytes, 4); // Set the codec description to be 'fLaC' + all metadata blocks diff --git a/src/isobmff/isobmff-reader.ts b/src/isobmff/isobmff-reader.ts index b040b12..2a3930e 100644 --- a/src/isobmff/isobmff-reader.ts +++ b/src/isobmff/isobmff-reader.ts @@ -26,6 +26,13 @@ export class IsobmffReader { return view.getUint16(offset, false); } + readI16() { + const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 2); + this.pos += 2; + + return view.getInt16(offset, false); + } + readU24() { const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 3); this.pos += 3; diff --git a/src/media-drain.ts b/src/media-drain.ts index 12e9116..6d90e0b 100644 --- a/src/media-drain.ts +++ b/src/media-drain.ts @@ -882,7 +882,9 @@ class PcmAudioDecoderWrapper extends DecoderWrapper this.onMedia(audioData)); } async flush() {