From 05881ff1f5892ee4b02ea717040b378ae254b03c Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Mon, 16 Jun 2025 10:49:55 +0200 Subject: [PATCH] Add support for PCM codecs in MP4, add 64-bit float PCM support --- dev/convert.html | 5 +- docs/guide/supported-formats-and-codecs.md | 29 +++-- src/codec.ts | 4 +- src/isobmff/isobmff-boxes.ts | 131 +++++++++++++++------ src/isobmff/isobmff-demuxer.ts | 76 +++++++++++- src/isobmff/isobmff-misc.ts | 4 +- src/isobmff/isobmff-muxer.ts | 13 +- src/matroska/ebml.ts | 1 + src/matroska/matroska-demuxer.ts | 2 + src/media-sink.ts | 24 +++- src/media-source.ts | 16 ++- src/output-format.ts | 13 +- 12 files changed, 254 insertions(+), 64 deletions(-) diff --git a/dev/convert.html b/dev/convert.html index 1d3f215..a92d43c 100644 --- a/dev/convert.html +++ b/dev/convert.html @@ -21,7 +21,7 @@ chunked: true, chunkSize: 2**20 }); - const outputFormat = new Metamuxer.OggOutputFormat(); + const outputFormat = new Metamuxer.MovOutputFormat(); const button = document.createElement('button'); button.textContent = 'Cancel'; @@ -38,6 +38,7 @@ target }), audio: { + codec: 'pcm-f64' //numberOfChannels: 1, //sampleRate: 4000 //discard: true @@ -85,7 +86,7 @@ }, trim: { start: 0, - end: 3 + end: 10 }, }); console.log(conversion); diff --git a/docs/guide/supported-formats-and-codecs.md b/docs/guide/supported-formats-and-codecs.md index 011e38c..3b6f55b 100644 --- a/docs/guide/supported-formats-and-codecs.md +++ b/docs/guide/supported-formats-and-codecs.md @@ -47,6 +47,8 @@ Mediakit ships with built-in decoders and encoders for all audio PCM codecs, mea - `'pcm-s32be'` - 32-bit big-endian signed PCM - `'pcm-f32'` - 32-bit little-endian float PCM - `'pcm-f32be'` - 32-bit big-endian float PCM +- `'pcm-f64'` - 64-bit little-endian float PCM +- `'pcm-f64be'` - 64-bit big-endian float PCM - `'ulaw'` - μ-law PCM - `'alaw'` - A-law PCM @@ -58,7 +60,7 @@ Mediakit ships with built-in decoders and encoders for all audio PCM codecs, mea Not all codecs can be used with all containers. The following table specifies the supported codec-container combinations: -| | .mp4[^1] | .mov | .mkv | .webm[^2] | .ogg | .mp3 | .wav | +| | .mp4 | .mov | .mkv | .webm[^1] | .ogg | .mp3 | .wav | |:--------------:|:--------:|:-----:|:-----:|:---------:|:-----:|:-----:|:-----:| | `'avc'` | ✓ | ✓ | ✓ | | | | | | `'hevc'` | ✓ | ✓ | ✓ | | | | | @@ -72,22 +74,23 @@ Not all codecs can be used with all containers. The following table specifies th | `'flac'` | ✓ | ✓ | ✓ | | | | | | `'pcm-u8'` | | ✓ | ✓ | | | | ✓ | | `'pcm-s8'` | | ✓ | | | | | | -| `'pcm-s16'` | | ✓ | ✓ | | | | ✓ | -| `'pcm-s16be'` | | ✓ | ✓ | | | | | -| `'pcm-s24'` | | ✓ | ✓ | | | | ✓ | -| `'pcm-s24be'` | | ✓ | ✓ | | | | | -| `'pcm-s32'` | | ✓ | ✓ | | | | ✓ | -| `'pcm-s32be'` | | ✓ | ✓ | | | | | -| `'pcm-f32'` | | ✓ | ✓ | | | | ✓ | -| `'pcm-f32be'` | | ✓ | | | | | | +| `'pcm-s16'` | ✓ | ✓ | ✓ | | | | ✓ | +| `'pcm-s16be'` | ✓ | ✓ | ✓ | | | | | +| `'pcm-s24'` | ✓ | ✓ | ✓ | | | | ✓ | +| `'pcm-s24be'` | ✓ | ✓ | ✓ | | | | | +| `'pcm-s32'` | ✓ | ✓ | ✓ | | | | ✓ | +| `'pcm-s32be'` | ✓ | ✓ | ✓ | | | | | +| `'pcm-f32'` | ✓ | ✓ | ✓ | | | | ✓ | +| `'pcm-f32be'` | ✓ | ✓ | | | | | | +| `'pcm-f64'` | ✓ | ✓ | ✓ | | | | | +| `'pcm-f64be'` | ✓ | ✓ | | | | | | | `'ulaw'` | | ✓ | | | | | ✓ | | `'alaw'` | | ✓ | | | | | ✓ | -| `'webvtt'`[^3] | (✓) | | (✓) | (✓) | | | | +| `'webvtt'`[^2] | (✓) | | (✓) | (✓) | | | | -[^1]: PCM audio codecs are not supported by MP4. However, if PCM audio is included in an MP4 nonetheless, this library would still be able to read it. -[^2]: WebM only supports a small subset of the codecs supported by Matroska. However, this library can technically read all codecs from a WebM that are supported by Matroska. -[^3]: WebVTT can only be written, not read. +[^1]: WebM only supports a small subset of the codecs supported by Matroska. However, this library can technically read all codecs from a WebM that are supported by Matroska. +[^2]: WebVTT can only be written, not read. ## Querying codec encodability diff --git a/src/codec.ts b/src/codec.ts index a275500..fd2e9af 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -43,6 +43,8 @@ export const PCM_AUDIO_CODECS = [ 'pcm-s32be', 'pcm-f32', 'pcm-f32be', + 'pcm-f64', + 'pcm-f64be', 'pcm-u8', 'pcm-s8', 'ulaw', @@ -626,7 +628,7 @@ export const parsePcmCodec = (codec: PcmAudioCodec) => { dataType = 'float'; } - const sampleSize = (Number(match[2]) / 8) as 1 | 2 | 3 | 4; + const sampleSize = (Number(match[2]) / 8) as 1 | 2 | 3 | 4 | 8; const littleEndian = match[3] !== 'be'; const silentValue = codec === 'pcm-u8' ? 2 ** 7 : 0; diff --git a/src/isobmff/isobmff-boxes.ts b/src/isobmff/isobmff-boxes.ts index 1a6a2c5..7fc519d 100644 --- a/src/isobmff/isobmff-boxes.ts +++ b/src/isobmff/isobmff-boxes.ts @@ -274,7 +274,7 @@ export const fullBox = ( * reader understands. */ export const ftyp = (details: { - isMov: boolean; + isQuickTime: boolean; holdsAvc: boolean; fragmented: boolean; }) => { @@ -284,7 +284,7 @@ export const ftyp = (details: { const minorVersion = 0x200; - if (details.isMov) { + if (details.isQuickTime) { return box('ftyp', [ ascii('qt '), // Major brand u32(minorVersion), // Minor version @@ -558,7 +558,7 @@ export const stsd = (trackData: IsobmffTrackData) => { trackData, ); } else if (trackData.type === 'audio') { - const boxName = AUDIO_CODEC_TO_BOX_NAME[trackData.track.source._codec]; + const boxName = audioCodecToBoxName(trackData.track.source._codec, trackData.muxer.isQuickTime); assert(boxName); sampleDescription = soundSampleDescription( @@ -731,7 +731,7 @@ export const soundSampleDescription = ( } return box(compressionType, contents, [ - AUDIO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec]?.(trackData) ?? null, + audioCodecToConfigurationBox(trackData.track.source._codec, trackData.muxer.isQuickTime)?.(trackData) ?? null, ]); }; @@ -801,7 +801,7 @@ export const wave = (trackData: IsobmffAudioTrackData) => { export const frma = (trackData: IsobmffAudioTrackData) => { return box('frma', [ - ascii(AUDIO_CODEC_TO_BOX_NAME[trackData.track.source._codec]!), + ascii(audioCodecToBoxName(trackData.track.source._codec, trackData.muxer.isQuickTime)), ]); }; @@ -868,6 +868,17 @@ export const dfLa = (trackData: IsobmffAudioTrackData) => { ]); }; +/** PCM Configuration Box, ISO/IEC 23003-5. */ +const pcmC = (trackData: IsobmffAudioTrackData) => { + const { littleEndian, sampleSize } = parsePcmCodec(trackData.track.source._codec as PcmAudioCodec); + const formatFlags = +littleEndian; + + return fullBox('pcmC', 0, 0, [ + u8(formatFlags), + u8(8 * sampleSize), + ]); +}; + export const subtitleSampleDescription = ( compressionType: string, trackData: IsobmffSubtitleTrackData, @@ -1252,40 +1263,86 @@ const VIDEO_CODEC_TO_CONFIGURATION_BOX: Record> = { - 'aac': 'mp4a', - 'mp3': 'mp4a', - 'opus': 'Opus', - 'vorbis': 'mp4a', - 'flac': 'fLaC', - 'ulaw': 'ulaw', - 'alaw': 'alaw', - 'pcm-u8': 'raw ', - 'pcm-s8': 'sowt', - 'pcm-s16': 'sowt', - 'pcm-s16be': 'twos', - 'pcm-s24': 'in24', - 'pcm-s24be': 'in24', - 'pcm-s32': 'in32', - 'pcm-s32be': 'in32', - 'pcm-f32': 'fl32', - 'pcm-f32be': 'fl32', +const audioCodecToBoxName = (codec: AudioCodec, isQuickTime: boolean): string => { + switch (codec) { + case 'aac': return 'mp4a'; + case 'mp3': return 'mp4a'; + case 'opus': return 'Opus'; + case 'vorbis': return 'mp4a'; + case 'flac': return 'fLaC'; + case 'ulaw': return 'ulaw'; + case 'alaw': return 'alaw'; + case 'pcm-u8': return 'raw '; + case 'pcm-s8': return 'sowt'; + } + + // Logic diverges here + if (isQuickTime) { + switch (codec) { + case 'pcm-s16': return 'sowt'; + case 'pcm-s16be': return 'twos'; + case 'pcm-s24': return 'in24'; + case 'pcm-s24be': return 'in24'; + case 'pcm-s32': return 'in32'; + case 'pcm-s32be': return 'in32'; + case 'pcm-f32': return 'fl32'; + case 'pcm-f32be': return 'fl32'; + case 'pcm-f64': return 'fl64'; + case 'pcm-f64be': return 'fl64'; + } + } else { + switch (codec) { + case 'pcm-s16': return 'ipcm'; + case 'pcm-s16be': return 'ipcm'; + case 'pcm-s24': return 'ipcm'; + case 'pcm-s24be': return 'ipcm'; + case 'pcm-s32': return 'ipcm'; + case 'pcm-s32be': return 'ipcm'; + case 'pcm-f32': return 'fpcm'; + case 'pcm-f32be': return 'fpcm'; + case 'pcm-f64': return 'fpcm'; + case 'pcm-f64be': return 'fpcm'; + } + } }; -const AUDIO_CODEC_TO_CONFIGURATION_BOX: Partial< - Record Box | null> -> = { - 'aac': esds, - 'mp3': esds, - 'opus': dOps, - 'vorbis': esds, - 'flac': dfLa, - 'pcm-s24': wave, - 'pcm-s24be': wave, - 'pcm-s32': wave, - 'pcm-s32be': wave, - 'pcm-f32': wave, - 'pcm-f32be': wave, +const audioCodecToConfigurationBox = (codec: AudioCodec, isQuickTime: boolean) => { + switch (codec) { + case 'aac': return esds; + case 'mp3': return esds; + case 'opus': return dOps; + case 'vorbis': return esds; + case 'flac': return dfLa; + } + + // Logic diverges here + if (isQuickTime) { + switch (codec) { + case 'pcm-s24': return wave; + case 'pcm-s24be': return wave; + case 'pcm-s32': return wave; + case 'pcm-s32be': return wave; + case 'pcm-f32': return wave; + case 'pcm-f32be': return wave; + case 'pcm-f64': return wave; + case 'pcm-f64be': return wave; + } + } else { + switch (codec) { + case 'pcm-s16': return pcmC; + case 'pcm-s16be': return pcmC; + case 'pcm-s24': return pcmC; + case 'pcm-s24be': return pcmC; + case 'pcm-s32': return pcmC; + case 'pcm-s32be': return pcmC; + case 'pcm-f32': return pcmC; + case 'pcm-f32be': return pcmC; + case 'pcm-f64': return pcmC; + case 'pcm-f64be': return pcmC; + } + } + + return null; }; const SUBTITLE_CODEC_TO_BOX_NAME: Record = { diff --git a/src/isobmff/isobmff-demuxer.ts b/src/isobmff/isobmff-demuxer.ts index 8effbad..9a627c5 100644 --- a/src/isobmff/isobmff-demuxer.ts +++ b/src/isobmff/isobmff-demuxer.ts @@ -227,7 +227,7 @@ export class IsobmffDemuxer extends Demuxer { const codecStrings = await Promise.all(this.tracks.map(x => x.inputTrack!.getCodecParameterString())); return buildIsobmffMimeType({ - isQuicktime: this.isQuickTime, + isQuickTime: this.isQuickTime, hasVideo: this.tracks.some(x => x.info?.type === 'video'), hasAudio: this.tracks.some(x => x.info?.type === 'audio'), codecStrings: codecStrings.filter(Boolean) as string[], @@ -800,6 +800,7 @@ export class IsobmffDemuxer extends Demuxer { const entries = this.metadataReader.readU32(); for (let i = 0; i < entries; i++) { + const startPos = this.metadataReader.pos; const sampleBoxInfo = this.metadataReader.readBoxHeader(); const lowercaseBoxName = sampleBoxInfo.name.toLowerCase(); @@ -840,7 +841,10 @@ export class IsobmffDemuxer extends Demuxer { || lowercaseBoxName === 'in24' || lowercaseBoxName === 'in32' || lowercaseBoxName === 'fl32' + || lowercaseBoxName === 'fl64' || lowercaseBoxName === 'lpcm' + || lowercaseBoxName === 'ipcm' // ISO/IEC 23003-5 + || lowercaseBoxName === 'fpcm' // " ) { // It's PCM // developer.apple.com/documentation/quicktime-file-format/sound_sample_descriptions/ @@ -923,6 +927,7 @@ export class IsobmffDemuxer extends Demuxer { track.info.numberOfChannels = channelCount; track.info.sampleRate = sampleRate; + // PCM codec assignments if (lowercaseBoxName === 'twos') { if (sampleSize === 8) { track.info.codec = 'pcm-s8'; @@ -947,6 +952,12 @@ export class IsobmffDemuxer extends Demuxer { track.info.codec = 'pcm-s32be'; } else if (lowercaseBoxName === 'fl32') { track.info.codec = 'pcm-f32be'; + } else if (lowercaseBoxName === 'fl64') { + track.info.codec = 'pcm-f64be'; + } else if (lowercaseBoxName === 'ipcm') { + track.info.codec = 'pcm-s16be'; // Placeholder, will be adjusted by the pcmC box + } else if (lowercaseBoxName === 'fpcm') { + track.info.codec = 'pcm-f32be'; // Placeholder, will be adjusted by the pcmC box } this.readContiguousBoxes(startPos + sampleBoxInfo.totalSize - this.metadataReader.pos); @@ -1147,10 +1158,73 @@ export class IsobmffDemuxer extends Demuxer { track.info.codec = 'pcm-s32'; } else if (track.info.codec === 'pcm-f32be') { track.info.codec = 'pcm-f32'; + } else if (track.info.codec === 'pcm-f64be') { + track.info.codec = 'pcm-f64'; } } }; break; + case 'pcmC': { + const track = this.currentTrack; + assert(track && track.info?.type === 'audio'); + + this.metadataReader.pos += 1 + 3; // Version + flags + + // ISO/IEC 23003-5 + + const formatFlags = this.metadataReader.readU8(); + const isLittleEndian = Boolean(formatFlags & 0x01); + const pcmSampleSize = this.metadataReader.readU8(); + + if (track.info.codec === 'pcm-s16be') { + // ipcm + + if (isLittleEndian) { + if (pcmSampleSize === 16) { + track.info.codec = 'pcm-s16'; + } else if (pcmSampleSize === 24) { + track.info.codec = 'pcm-s24'; + } else if (pcmSampleSize === 32) { + track.info.codec = 'pcm-s32'; + } else { + throw new Error(`Invalid ipcm sample size ${pcmSampleSize}.`); + } + } else { + if (pcmSampleSize === 16) { + track.info.codec = 'pcm-s16be'; + } else if (pcmSampleSize === 24) { + track.info.codec = 'pcm-s24be'; + } else if (pcmSampleSize === 32) { + track.info.codec = 'pcm-s32be'; + } else { + throw new Error(`Invalid ipcm sample size ${pcmSampleSize}.`); + } + } + } else if (track.info.codec === 'pcm-f32be') { + // fpcm + + if (isLittleEndian) { + if (pcmSampleSize === 32) { + track.info.codec = 'pcm-f32'; + } else if (pcmSampleSize === 64) { + track.info.codec = 'pcm-f64'; + } else { + throw new Error(`Invalid fpcm sample size ${pcmSampleSize}.`); + } + } else { + if (pcmSampleSize === 32) { + track.info.codec = 'pcm-f32be'; + } else if (pcmSampleSize === 64) { + track.info.codec = 'pcm-f64be'; + } else { + throw new Error(`Invalid fpcm sample size ${pcmSampleSize}.`); + } + } + } + + break; + }; + case 'dOps': { // Used for Opus audio const track = this.currentTrack; assert(track && track.info?.type === 'audio'); diff --git a/src/isobmff/isobmff-misc.ts b/src/isobmff/isobmff-misc.ts index 804932d..bf0ea8b 100644 --- a/src/isobmff/isobmff-misc.ts +++ b/src/isobmff/isobmff-misc.ts @@ -1,5 +1,5 @@ export const buildIsobmffMimeType = (info: { - isQuicktime: boolean; + isQuickTime: boolean; hasVideo: boolean; hasAudio: boolean; codecStrings: string[]; @@ -10,7 +10,7 @@ export const buildIsobmffMimeType = (info: { ? 'audio/' : 'application/'; - let string = base + (info.isQuicktime ? 'quicktime' : 'mp4'); + let string = base + (info.isQuickTime ? 'quicktime' : 'mp4'); if (info.codecStrings.length > 0) { const uniqueCodecMimeTypes = [...new Set(info.codecStrings)]; diff --git a/src/isobmff/isobmff-muxer.ts b/src/isobmff/isobmff-muxer.ts index 8294e54..9e9def7 100644 --- a/src/isobmff/isobmff-muxer.ts +++ b/src/isobmff/isobmff-muxer.ts @@ -48,6 +48,7 @@ type Chunk = { }; export type IsobmffTrackData = { + muxer: IsobmffMuxer; timescale: number; samples: Sample[]; sampleQueue: Sample[]; // For fragmented files @@ -116,10 +117,11 @@ export class IsobmffMuxer extends Muxer { private format: IsobmffOutputFormat; private writer: Writer; private boxWriter: IsobmffBoxWriter; - private isMov: boolean; private fastStart: NonNullable; private isFragmented: boolean; + isQuickTime: boolean; + private auxTarget = new BufferTarget(); private auxWriter = this.auxTarget._createWriter(); private auxBoxWriter = new IsobmffBoxWriter(this.auxWriter); @@ -144,7 +146,7 @@ export class IsobmffMuxer extends Muxer { this.writer = output._writer; this.boxWriter = new IsobmffBoxWriter(this.writer); - this.isMov = format instanceof MovOutputFormat; + this.isQuickTime = format instanceof MovOutputFormat; // If the fastStart option isn't defined, enable in-memory fast start if the target is an ArrayBuffer, as the // memory usage remains identical @@ -171,7 +173,7 @@ export class IsobmffMuxer extends Muxer { } this.boxWriter.writeBox(ftyp({ - isMov: this.isMov, + isQuickTime: this.isQuickTime, holdsAvc: holdsAvc, fragmented: this.isFragmented, })); @@ -227,7 +229,7 @@ export class IsobmffMuxer extends Muxer { }); return buildIsobmffMimeType({ - isQuicktime: this.isMov, + isQuickTime: this.isQuickTime, hasVideo: this.trackDatas.some(x => x.type === 'video'), hasAudio: this.trackDatas.some(x => x.type === 'audio'), codecStrings, @@ -291,6 +293,7 @@ export class IsobmffMuxer extends Muxer { const timescale = computeRationalApproximation(1 / (track.metadata.frameRate ?? 57600), 1e6).denominator; const newTrackData: IsobmffVideoTrackData = { + muxer: this, track, type: 'video', info: { @@ -334,6 +337,7 @@ export class IsobmffMuxer extends Muxer { assert(meta.decoderConfig); const newTrackData: IsobmffAudioTrackData = { + muxer: this, track, type: 'audio', info: { @@ -379,6 +383,7 @@ export class IsobmffMuxer extends Muxer { assert(meta.config); const newTrackData: IsobmffSubtitleTrackData = { + muxer: this, track, type: 'subtitle', info: { diff --git a/src/matroska/ebml.ts b/src/matroska/ebml.ts index 290e1ab..2ed9be0 100644 --- a/src/matroska/ebml.ts +++ b/src/matroska/ebml.ts @@ -575,6 +575,7 @@ export const CODEC_STRING_MAP: Partial> = { 'pcm-s32': 'A_PCM/INT/LIT', 'pcm-s32be': 'A_PCM/INT/BIG', 'pcm-f32': 'A_PCM/FLOAT/IEEE', + 'pcm-f64': 'A_PCM/FLOAT/IEEE', 'webvtt': 'S_TEXT/WEBVTT', }; diff --git a/src/matroska/matroska-demuxer.ts b/src/matroska/matroska-demuxer.ts index d60d373..64d9d9d 100644 --- a/src/matroska/matroska-demuxer.ts +++ b/src/matroska/matroska-demuxer.ts @@ -707,6 +707,8 @@ export class MatroskaDemuxer extends Demuxer { } else if (this.currentTrack.codecId === 'A_PCM/FLOAT/IEEE') { if (this.currentTrack.info.bitDepth === 32) { this.currentTrack.info.codec = 'pcm-f32'; + } else if (this.currentTrack.info.bitDepth === 64) { + this.currentTrack.info.codec = 'pcm-f64'; } } diff --git a/src/media-sink.ts b/src/media-sink.ts index d79bcc9..48b8795 100644 --- a/src/media-sink.ts +++ b/src/media-sink.ts @@ -4,6 +4,7 @@ import { InputAudioTrack, InputTrack, InputVideoTrack } from './input-track'; import { AnyIterable, assert, + assertNever, binarySearchLessOrEqual, CallSerializer, getInt24, @@ -1222,7 +1223,7 @@ class AudioDecoderWrapper extends DecoderWrapper { class PcmAudioDecoderWrapper extends DecoderWrapper { codec: PcmAudioCodec; - inputSampleSize: 1 | 2 | 3 | 4; + inputSampleSize: 1 | 2 | 3 | 4 | 8; readInputValue: (view: DataView, byteOffset: number) => number; outputSampleSize: 1 | 2 | 4; @@ -1289,6 +1290,17 @@ class PcmAudioDecoderWrapper extends DecoderWrapper { assert(false); } }; break; + case 8: { + if (dataType === 'float') { + this.readInputValue = (view, byteOffset) => view.getFloat64(byteOffset, littleEndian); + } else { + assert(false); + } + }; break; + default: { + assertNever(sampleSize); + assert(false); + }; } switch (sampleSize) { @@ -1327,6 +1339,16 @@ class PcmAudioDecoderWrapper extends DecoderWrapper { this.writeOutputValue = (view, byteOffset, value) => view.setInt32(byteOffset, value, true); } }; break; + case 8: { + this.outputSampleSize = 4; + + this.outputFormat = 'f32'; + this.writeOutputValue = (view, byteOffset, value) => view.setFloat32(byteOffset, value, true); + }; break; + default: { + assertNever(sampleSize); + assert(false); + }; }; } diff --git a/src/media-source.ts b/src/media-source.ts index 70d7a4c..94c2bb9 100644 --- a/src/media-source.ts +++ b/src/media-source.ts @@ -16,7 +16,7 @@ import { VideoCodec, } from './codec'; import { OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from './output'; -import { assert, CallSerializer, clamp, setInt24, setUint24 } from './misc'; +import { assert, assertNever, CallSerializer, clamp, setInt24, setUint24 } from './misc'; import { Muxer } from './muxer'; import { SubtitleParser } from './subtitles'; import { toAlaw, toUlaw } from './pcm'; @@ -1083,7 +1083,19 @@ class AudioEncoderWrapper { } else { assert(false); } - } + }; break; + case 8: { + if (dataType === 'float') { + this.writeOutputValue = (view, byteOffset, value) => + view.setFloat64(byteOffset, value, littleEndian); + } else { + assert(false); + } + }; break; + default: { + assertNever(sampleSize); + assert(false); + }; } } diff --git a/src/output-format.ts b/src/output-format.ts index ec202d8..eabd49c 100644 --- a/src/output-format.ts +++ b/src/output-format.ts @@ -237,6 +237,17 @@ export class Mp4OutputFormat extends IsobmffOutputFormat { return [ ...VIDEO_CODECS, ...NON_PCM_AUDIO_CODECS, + // These are supported via ISO/IEC 23003-5 + 'pcm-s16', + 'pcm-s16be', + 'pcm-s24', + 'pcm-s24be', + 'pcm-s32', + 'pcm-s32be', + 'pcm-f32', + 'pcm-f32be', + 'pcm-f64', + 'pcm-f64be', ...SUBTITLE_CODECS, ]; } @@ -398,7 +409,7 @@ export class MkvOutputFormat extends OutputFormat { return [ ...VIDEO_CODECS, ...NON_PCM_AUDIO_CODECS, - ...PCM_AUDIO_CODECS.filter(codec => !['pcm-s8', 'pcm-f32be', 'ulaw', 'alaw'].includes(codec)), + ...PCM_AUDIO_CODECS.filter(codec => !['pcm-s8', 'pcm-f32be', 'pcm-f64be', 'ulaw', 'alaw'].includes(codec)), ...SUBTITLE_CODECS, ]; }