mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 10:53:50 +02:00
Add support for PCM codecs in MP4, add 64-bit float PCM support
This commit is contained in:
+3
-2
@@ -21,7 +21,7 @@
|
|||||||
chunked: true,
|
chunked: true,
|
||||||
chunkSize: 2**20
|
chunkSize: 2**20
|
||||||
});
|
});
|
||||||
const outputFormat = new Metamuxer.OggOutputFormat();
|
const outputFormat = new Metamuxer.MovOutputFormat();
|
||||||
|
|
||||||
const button = document.createElement('button');
|
const button = document.createElement('button');
|
||||||
button.textContent = 'Cancel';
|
button.textContent = 'Cancel';
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
target
|
target
|
||||||
}),
|
}),
|
||||||
audio: {
|
audio: {
|
||||||
|
codec: 'pcm-f64'
|
||||||
//numberOfChannels: 1,
|
//numberOfChannels: 1,
|
||||||
//sampleRate: 4000
|
//sampleRate: 4000
|
||||||
//discard: true
|
//discard: true
|
||||||
@@ -85,7 +86,7 @@
|
|||||||
},
|
},
|
||||||
trim: {
|
trim: {
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 3
|
end: 10
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(conversion);
|
console.log(conversion);
|
||||||
|
|||||||
@@ -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-s32be'` - 32-bit big-endian signed PCM
|
||||||
- `'pcm-f32'` - 32-bit little-endian float PCM
|
- `'pcm-f32'` - 32-bit little-endian float PCM
|
||||||
- `'pcm-f32be'` - 32-bit big-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
|
- `'ulaw'` - μ-law PCM
|
||||||
- `'alaw'` - A-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:
|
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'` | ✓ | ✓ | ✓ | | | | |
|
| `'avc'` | ✓ | ✓ | ✓ | | | | |
|
||||||
| `'hevc'` | ✓ | ✓ | ✓ | | | | |
|
| `'hevc'` | ✓ | ✓ | ✓ | | | | |
|
||||||
@@ -72,22 +74,23 @@ Not all codecs can be used with all containers. The following table specifies th
|
|||||||
| `'flac'` | ✓ | ✓ | ✓ | | | | |
|
| `'flac'` | ✓ | ✓ | ✓ | | | | |
|
||||||
| `'pcm-u8'` | | ✓ | ✓ | | | | ✓ |
|
| `'pcm-u8'` | | ✓ | ✓ | | | | ✓ |
|
||||||
| `'pcm-s8'` | | ✓ | | | | | |
|
| `'pcm-s8'` | | ✓ | | | | | |
|
||||||
| `'pcm-s16'` | | ✓ | ✓ | | | | ✓ |
|
| `'pcm-s16'` | ✓ | ✓ | ✓ | | | | ✓ |
|
||||||
| `'pcm-s16be'` | | ✓ | ✓ | | | | |
|
| `'pcm-s16be'` | ✓ | ✓ | ✓ | | | | |
|
||||||
| `'pcm-s24'` | | ✓ | ✓ | | | | ✓ |
|
| `'pcm-s24'` | ✓ | ✓ | ✓ | | | | ✓ |
|
||||||
| `'pcm-s24be'` | | ✓ | ✓ | | | | |
|
| `'pcm-s24be'` | ✓ | ✓ | ✓ | | | | |
|
||||||
| `'pcm-s32'` | | ✓ | ✓ | | | | ✓ |
|
| `'pcm-s32'` | ✓ | ✓ | ✓ | | | | ✓ |
|
||||||
| `'pcm-s32be'` | | ✓ | ✓ | | | | |
|
| `'pcm-s32be'` | ✓ | ✓ | ✓ | | | | |
|
||||||
| `'pcm-f32'` | | ✓ | ✓ | | | | ✓ |
|
| `'pcm-f32'` | ✓ | ✓ | ✓ | | | | ✓ |
|
||||||
| `'pcm-f32be'` | | ✓ | | | | | |
|
| `'pcm-f32be'` | ✓ | ✓ | | | | | |
|
||||||
|
| `'pcm-f64'` | ✓ | ✓ | ✓ | | | | |
|
||||||
|
| `'pcm-f64be'` | ✓ | ✓ | | | | | |
|
||||||
| `'ulaw'` | | ✓ | | | | | ✓ |
|
| `'ulaw'` | | ✓ | | | | | ✓ |
|
||||||
| `'alaw'` | | ✓ | | | | | ✓ |
|
| `'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.
|
[^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]: 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.
|
||||||
[^3]: WebVTT can only be written, not read.
|
|
||||||
|
|
||||||
## Querying codec encodability
|
## Querying codec encodability
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -43,6 +43,8 @@ export const PCM_AUDIO_CODECS = [
|
|||||||
'pcm-s32be',
|
'pcm-s32be',
|
||||||
'pcm-f32',
|
'pcm-f32',
|
||||||
'pcm-f32be',
|
'pcm-f32be',
|
||||||
|
'pcm-f64',
|
||||||
|
'pcm-f64be',
|
||||||
'pcm-u8',
|
'pcm-u8',
|
||||||
'pcm-s8',
|
'pcm-s8',
|
||||||
'ulaw',
|
'ulaw',
|
||||||
@@ -626,7 +628,7 @@ export const parsePcmCodec = (codec: PcmAudioCodec) => {
|
|||||||
dataType = 'float';
|
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 littleEndian = match[3] !== 'be';
|
||||||
const silentValue = codec === 'pcm-u8' ? 2 ** 7 : 0;
|
const silentValue = codec === 'pcm-u8' ? 2 ** 7 : 0;
|
||||||
|
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ export const fullBox = (
|
|||||||
* reader understands.
|
* reader understands.
|
||||||
*/
|
*/
|
||||||
export const ftyp = (details: {
|
export const ftyp = (details: {
|
||||||
isMov: boolean;
|
isQuickTime: boolean;
|
||||||
holdsAvc: boolean;
|
holdsAvc: boolean;
|
||||||
fragmented: boolean;
|
fragmented: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
@@ -284,7 +284,7 @@ export const ftyp = (details: {
|
|||||||
|
|
||||||
const minorVersion = 0x200;
|
const minorVersion = 0x200;
|
||||||
|
|
||||||
if (details.isMov) {
|
if (details.isQuickTime) {
|
||||||
return box('ftyp', [
|
return box('ftyp', [
|
||||||
ascii('qt '), // Major brand
|
ascii('qt '), // Major brand
|
||||||
u32(minorVersion), // Minor version
|
u32(minorVersion), // Minor version
|
||||||
@@ -558,7 +558,7 @@ export const stsd = (trackData: IsobmffTrackData) => {
|
|||||||
trackData,
|
trackData,
|
||||||
);
|
);
|
||||||
} else if (trackData.type === 'audio') {
|
} 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);
|
assert(boxName);
|
||||||
|
|
||||||
sampleDescription = soundSampleDescription(
|
sampleDescription = soundSampleDescription(
|
||||||
@@ -731,7 +731,7 @@ export const soundSampleDescription = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return box(compressionType, contents, [
|
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) => {
|
export const frma = (trackData: IsobmffAudioTrackData) => {
|
||||||
return box('frma', [
|
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 = (
|
export const subtitleSampleDescription = (
|
||||||
compressionType: string,
|
compressionType: string,
|
||||||
trackData: IsobmffSubtitleTrackData,
|
trackData: IsobmffSubtitleTrackData,
|
||||||
@@ -1252,40 +1263,86 @@ const VIDEO_CODEC_TO_CONFIGURATION_BOX: Record<VideoCodec, (trackData: IsobmffVi
|
|||||||
av1: av1C,
|
av1: av1C,
|
||||||
};
|
};
|
||||||
|
|
||||||
const AUDIO_CODEC_TO_BOX_NAME: Partial<Record<AudioCodec, string>> = {
|
const audioCodecToBoxName = (codec: AudioCodec, isQuickTime: boolean): string => {
|
||||||
'aac': 'mp4a',
|
switch (codec) {
|
||||||
'mp3': 'mp4a',
|
case 'aac': return 'mp4a';
|
||||||
'opus': 'Opus',
|
case 'mp3': return 'mp4a';
|
||||||
'vorbis': 'mp4a',
|
case 'opus': return 'Opus';
|
||||||
'flac': 'fLaC',
|
case 'vorbis': return 'mp4a';
|
||||||
'ulaw': 'ulaw',
|
case 'flac': return 'fLaC';
|
||||||
'alaw': 'alaw',
|
case 'ulaw': return 'ulaw';
|
||||||
'pcm-u8': 'raw ',
|
case 'alaw': return 'alaw';
|
||||||
'pcm-s8': 'sowt',
|
case 'pcm-u8': return 'raw ';
|
||||||
'pcm-s16': 'sowt',
|
case 'pcm-s8': return 'sowt';
|
||||||
'pcm-s16be': 'twos',
|
}
|
||||||
'pcm-s24': 'in24',
|
|
||||||
'pcm-s24be': 'in24',
|
// Logic diverges here
|
||||||
'pcm-s32': 'in32',
|
if (isQuickTime) {
|
||||||
'pcm-s32be': 'in32',
|
switch (codec) {
|
||||||
'pcm-f32': 'fl32',
|
case 'pcm-s16': return 'sowt';
|
||||||
'pcm-f32be': 'fl32',
|
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<
|
const audioCodecToConfigurationBox = (codec: AudioCodec, isQuickTime: boolean) => {
|
||||||
Record<AudioCodec, (trackData: IsobmffAudioTrackData) => Box | null>
|
switch (codec) {
|
||||||
> = {
|
case 'aac': return esds;
|
||||||
'aac': esds,
|
case 'mp3': return esds;
|
||||||
'mp3': esds,
|
case 'opus': return dOps;
|
||||||
'opus': dOps,
|
case 'vorbis': return esds;
|
||||||
'vorbis': esds,
|
case 'flac': return dfLa;
|
||||||
'flac': dfLa,
|
}
|
||||||
'pcm-s24': wave,
|
|
||||||
'pcm-s24be': wave,
|
// Logic diverges here
|
||||||
'pcm-s32': wave,
|
if (isQuickTime) {
|
||||||
'pcm-s32be': wave,
|
switch (codec) {
|
||||||
'pcm-f32': wave,
|
case 'pcm-s24': return wave;
|
||||||
'pcm-f32be': 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<SubtitleCodec, string> = {
|
const SUBTITLE_CODEC_TO_BOX_NAME: Record<SubtitleCodec, string> = {
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ export class IsobmffDemuxer extends Demuxer {
|
|||||||
const codecStrings = await Promise.all(this.tracks.map(x => x.inputTrack!.getCodecParameterString()));
|
const codecStrings = await Promise.all(this.tracks.map(x => x.inputTrack!.getCodecParameterString()));
|
||||||
|
|
||||||
return buildIsobmffMimeType({
|
return buildIsobmffMimeType({
|
||||||
isQuicktime: this.isQuickTime,
|
isQuickTime: this.isQuickTime,
|
||||||
hasVideo: this.tracks.some(x => x.info?.type === 'video'),
|
hasVideo: this.tracks.some(x => x.info?.type === 'video'),
|
||||||
hasAudio: this.tracks.some(x => x.info?.type === 'audio'),
|
hasAudio: this.tracks.some(x => x.info?.type === 'audio'),
|
||||||
codecStrings: codecStrings.filter(Boolean) as string[],
|
codecStrings: codecStrings.filter(Boolean) as string[],
|
||||||
@@ -800,6 +800,7 @@ export class IsobmffDemuxer extends Demuxer {
|
|||||||
const entries = this.metadataReader.readU32();
|
const entries = this.metadataReader.readU32();
|
||||||
|
|
||||||
for (let i = 0; i < entries; i++) {
|
for (let i = 0; i < entries; i++) {
|
||||||
|
const startPos = this.metadataReader.pos;
|
||||||
const sampleBoxInfo = this.metadataReader.readBoxHeader();
|
const sampleBoxInfo = this.metadataReader.readBoxHeader();
|
||||||
const lowercaseBoxName = sampleBoxInfo.name.toLowerCase();
|
const lowercaseBoxName = sampleBoxInfo.name.toLowerCase();
|
||||||
|
|
||||||
@@ -840,7 +841,10 @@ export class IsobmffDemuxer extends Demuxer {
|
|||||||
|| lowercaseBoxName === 'in24'
|
|| lowercaseBoxName === 'in24'
|
||||||
|| lowercaseBoxName === 'in32'
|
|| lowercaseBoxName === 'in32'
|
||||||
|| lowercaseBoxName === 'fl32'
|
|| lowercaseBoxName === 'fl32'
|
||||||
|
|| lowercaseBoxName === 'fl64'
|
||||||
|| lowercaseBoxName === 'lpcm'
|
|| lowercaseBoxName === 'lpcm'
|
||||||
|
|| lowercaseBoxName === 'ipcm' // ISO/IEC 23003-5
|
||||||
|
|| lowercaseBoxName === 'fpcm' // "
|
||||||
) {
|
) {
|
||||||
// It's PCM
|
// It's PCM
|
||||||
// developer.apple.com/documentation/quicktime-file-format/sound_sample_descriptions/
|
// 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.numberOfChannels = channelCount;
|
||||||
track.info.sampleRate = sampleRate;
|
track.info.sampleRate = sampleRate;
|
||||||
|
|
||||||
|
// PCM codec assignments
|
||||||
if (lowercaseBoxName === 'twos') {
|
if (lowercaseBoxName === 'twos') {
|
||||||
if (sampleSize === 8) {
|
if (sampleSize === 8) {
|
||||||
track.info.codec = 'pcm-s8';
|
track.info.codec = 'pcm-s8';
|
||||||
@@ -947,6 +952,12 @@ export class IsobmffDemuxer extends Demuxer {
|
|||||||
track.info.codec = 'pcm-s32be';
|
track.info.codec = 'pcm-s32be';
|
||||||
} else if (lowercaseBoxName === 'fl32') {
|
} else if (lowercaseBoxName === 'fl32') {
|
||||||
track.info.codec = 'pcm-f32be';
|
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);
|
this.readContiguousBoxes(startPos + sampleBoxInfo.totalSize - this.metadataReader.pos);
|
||||||
@@ -1147,10 +1158,73 @@ export class IsobmffDemuxer extends Demuxer {
|
|||||||
track.info.codec = 'pcm-s32';
|
track.info.codec = 'pcm-s32';
|
||||||
} else if (track.info.codec === 'pcm-f32be') {
|
} else if (track.info.codec === 'pcm-f32be') {
|
||||||
track.info.codec = 'pcm-f32';
|
track.info.codec = 'pcm-f32';
|
||||||
|
} else if (track.info.codec === 'pcm-f64be') {
|
||||||
|
track.info.codec = 'pcm-f64';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}; break;
|
}; 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
|
case 'dOps': { // Used for Opus audio
|
||||||
const track = this.currentTrack;
|
const track = this.currentTrack;
|
||||||
assert(track && track.info?.type === 'audio');
|
assert(track && track.info?.type === 'audio');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export const buildIsobmffMimeType = (info: {
|
export const buildIsobmffMimeType = (info: {
|
||||||
isQuicktime: boolean;
|
isQuickTime: boolean;
|
||||||
hasVideo: boolean;
|
hasVideo: boolean;
|
||||||
hasAudio: boolean;
|
hasAudio: boolean;
|
||||||
codecStrings: string[];
|
codecStrings: string[];
|
||||||
@@ -10,7 +10,7 @@ export const buildIsobmffMimeType = (info: {
|
|||||||
? 'audio/'
|
? 'audio/'
|
||||||
: 'application/';
|
: 'application/';
|
||||||
|
|
||||||
let string = base + (info.isQuicktime ? 'quicktime' : 'mp4');
|
let string = base + (info.isQuickTime ? 'quicktime' : 'mp4');
|
||||||
|
|
||||||
if (info.codecStrings.length > 0) {
|
if (info.codecStrings.length > 0) {
|
||||||
const uniqueCodecMimeTypes = [...new Set(info.codecStrings)];
|
const uniqueCodecMimeTypes = [...new Set(info.codecStrings)];
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ type Chunk = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type IsobmffTrackData = {
|
export type IsobmffTrackData = {
|
||||||
|
muxer: IsobmffMuxer;
|
||||||
timescale: number;
|
timescale: number;
|
||||||
samples: Sample[];
|
samples: Sample[];
|
||||||
sampleQueue: Sample[]; // For fragmented files
|
sampleQueue: Sample[]; // For fragmented files
|
||||||
@@ -116,10 +117,11 @@ export class IsobmffMuxer extends Muxer {
|
|||||||
private format: IsobmffOutputFormat;
|
private format: IsobmffOutputFormat;
|
||||||
private writer: Writer;
|
private writer: Writer;
|
||||||
private boxWriter: IsobmffBoxWriter;
|
private boxWriter: IsobmffBoxWriter;
|
||||||
private isMov: boolean;
|
|
||||||
private fastStart: NonNullable<IsobmffOutputFormatOptions['fastStart']>;
|
private fastStart: NonNullable<IsobmffOutputFormatOptions['fastStart']>;
|
||||||
private isFragmented: boolean;
|
private isFragmented: boolean;
|
||||||
|
|
||||||
|
isQuickTime: boolean;
|
||||||
|
|
||||||
private auxTarget = new BufferTarget();
|
private auxTarget = new BufferTarget();
|
||||||
private auxWriter = this.auxTarget._createWriter();
|
private auxWriter = this.auxTarget._createWriter();
|
||||||
private auxBoxWriter = new IsobmffBoxWriter(this.auxWriter);
|
private auxBoxWriter = new IsobmffBoxWriter(this.auxWriter);
|
||||||
@@ -144,7 +146,7 @@ export class IsobmffMuxer extends Muxer {
|
|||||||
this.writer = output._writer;
|
this.writer = output._writer;
|
||||||
this.boxWriter = new IsobmffBoxWriter(this.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
|
// If the fastStart option isn't defined, enable in-memory fast start if the target is an ArrayBuffer, as the
|
||||||
// memory usage remains identical
|
// memory usage remains identical
|
||||||
@@ -171,7 +173,7 @@ export class IsobmffMuxer extends Muxer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.boxWriter.writeBox(ftyp({
|
this.boxWriter.writeBox(ftyp({
|
||||||
isMov: this.isMov,
|
isQuickTime: this.isQuickTime,
|
||||||
holdsAvc: holdsAvc,
|
holdsAvc: holdsAvc,
|
||||||
fragmented: this.isFragmented,
|
fragmented: this.isFragmented,
|
||||||
}));
|
}));
|
||||||
@@ -227,7 +229,7 @@ export class IsobmffMuxer extends Muxer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return buildIsobmffMimeType({
|
return buildIsobmffMimeType({
|
||||||
isQuicktime: this.isMov,
|
isQuickTime: this.isQuickTime,
|
||||||
hasVideo: this.trackDatas.some(x => x.type === 'video'),
|
hasVideo: this.trackDatas.some(x => x.type === 'video'),
|
||||||
hasAudio: this.trackDatas.some(x => x.type === 'audio'),
|
hasAudio: this.trackDatas.some(x => x.type === 'audio'),
|
||||||
codecStrings,
|
codecStrings,
|
||||||
@@ -291,6 +293,7 @@ export class IsobmffMuxer extends Muxer {
|
|||||||
const timescale = computeRationalApproximation(1 / (track.metadata.frameRate ?? 57600), 1e6).denominator;
|
const timescale = computeRationalApproximation(1 / (track.metadata.frameRate ?? 57600), 1e6).denominator;
|
||||||
|
|
||||||
const newTrackData: IsobmffVideoTrackData = {
|
const newTrackData: IsobmffVideoTrackData = {
|
||||||
|
muxer: this,
|
||||||
track,
|
track,
|
||||||
type: 'video',
|
type: 'video',
|
||||||
info: {
|
info: {
|
||||||
@@ -334,6 +337,7 @@ export class IsobmffMuxer extends Muxer {
|
|||||||
assert(meta.decoderConfig);
|
assert(meta.decoderConfig);
|
||||||
|
|
||||||
const newTrackData: IsobmffAudioTrackData = {
|
const newTrackData: IsobmffAudioTrackData = {
|
||||||
|
muxer: this,
|
||||||
track,
|
track,
|
||||||
type: 'audio',
|
type: 'audio',
|
||||||
info: {
|
info: {
|
||||||
@@ -379,6 +383,7 @@ export class IsobmffMuxer extends Muxer {
|
|||||||
assert(meta.config);
|
assert(meta.config);
|
||||||
|
|
||||||
const newTrackData: IsobmffSubtitleTrackData = {
|
const newTrackData: IsobmffSubtitleTrackData = {
|
||||||
|
muxer: this,
|
||||||
track,
|
track,
|
||||||
type: 'subtitle',
|
type: 'subtitle',
|
||||||
info: {
|
info: {
|
||||||
|
|||||||
@@ -575,6 +575,7 @@ export const CODEC_STRING_MAP: Partial<Record<MediaCodec, string>> = {
|
|||||||
'pcm-s32': 'A_PCM/INT/LIT',
|
'pcm-s32': 'A_PCM/INT/LIT',
|
||||||
'pcm-s32be': 'A_PCM/INT/BIG',
|
'pcm-s32be': 'A_PCM/INT/BIG',
|
||||||
'pcm-f32': 'A_PCM/FLOAT/IEEE',
|
'pcm-f32': 'A_PCM/FLOAT/IEEE',
|
||||||
|
'pcm-f64': 'A_PCM/FLOAT/IEEE',
|
||||||
|
|
||||||
'webvtt': 'S_TEXT/WEBVTT',
|
'webvtt': 'S_TEXT/WEBVTT',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -707,6 +707,8 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
} else if (this.currentTrack.codecId === 'A_PCM/FLOAT/IEEE') {
|
} else if (this.currentTrack.codecId === 'A_PCM/FLOAT/IEEE') {
|
||||||
if (this.currentTrack.info.bitDepth === 32) {
|
if (this.currentTrack.info.bitDepth === 32) {
|
||||||
this.currentTrack.info.codec = 'pcm-f32';
|
this.currentTrack.info.codec = 'pcm-f32';
|
||||||
|
} else if (this.currentTrack.info.bitDepth === 64) {
|
||||||
|
this.currentTrack.info.codec = 'pcm-f64';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
-1
@@ -4,6 +4,7 @@ import { InputAudioTrack, InputTrack, InputVideoTrack } from './input-track';
|
|||||||
import {
|
import {
|
||||||
AnyIterable,
|
AnyIterable,
|
||||||
assert,
|
assert,
|
||||||
|
assertNever,
|
||||||
binarySearchLessOrEqual,
|
binarySearchLessOrEqual,
|
||||||
CallSerializer,
|
CallSerializer,
|
||||||
getInt24,
|
getInt24,
|
||||||
@@ -1222,7 +1223,7 @@ class AudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
|||||||
class PcmAudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
class PcmAudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
||||||
codec: PcmAudioCodec;
|
codec: PcmAudioCodec;
|
||||||
|
|
||||||
inputSampleSize: 1 | 2 | 3 | 4;
|
inputSampleSize: 1 | 2 | 3 | 4 | 8;
|
||||||
readInputValue: (view: DataView, byteOffset: number) => number;
|
readInputValue: (view: DataView, byteOffset: number) => number;
|
||||||
|
|
||||||
outputSampleSize: 1 | 2 | 4;
|
outputSampleSize: 1 | 2 | 4;
|
||||||
@@ -1289,6 +1290,17 @@ class PcmAudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
|||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
}; break;
|
}; 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) {
|
switch (sampleSize) {
|
||||||
@@ -1327,6 +1339,16 @@ class PcmAudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
|||||||
this.writeOutputValue = (view, byteOffset, value) => view.setInt32(byteOffset, value, true);
|
this.writeOutputValue = (view, byteOffset, value) => view.setInt32(byteOffset, value, true);
|
||||||
}
|
}
|
||||||
}; break;
|
}; 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);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-2
@@ -16,7 +16,7 @@ import {
|
|||||||
VideoCodec,
|
VideoCodec,
|
||||||
} from './codec';
|
} from './codec';
|
||||||
import { OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from './output';
|
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 { Muxer } from './muxer';
|
||||||
import { SubtitleParser } from './subtitles';
|
import { SubtitleParser } from './subtitles';
|
||||||
import { toAlaw, toUlaw } from './pcm';
|
import { toAlaw, toUlaw } from './pcm';
|
||||||
@@ -1083,7 +1083,19 @@ class AudioEncoderWrapper {
|
|||||||
} else {
|
} else {
|
||||||
assert(false);
|
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);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -237,6 +237,17 @@ export class Mp4OutputFormat extends IsobmffOutputFormat {
|
|||||||
return [
|
return [
|
||||||
...VIDEO_CODECS,
|
...VIDEO_CODECS,
|
||||||
...NON_PCM_AUDIO_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,
|
...SUBTITLE_CODECS,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -398,7 +409,7 @@ export class MkvOutputFormat extends OutputFormat {
|
|||||||
return [
|
return [
|
||||||
...VIDEO_CODECS,
|
...VIDEO_CODECS,
|
||||||
...NON_PCM_AUDIO_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,
|
...SUBTITLE_CODECS,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user