mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 10:53:50 +02:00
Add fullCodecString and rename other config field
This commit is contained in:
+3
-2
@@ -42,7 +42,7 @@
|
|||||||
format = new Metamuxer.Mp4OutputFormat({ fastStart: 'fragmented' }); // new Metamuxer.MkvOutputFormat();// new Metamuxer.Mp4OutputFormat({ fastStart: false });
|
format = new Metamuxer.Mp4OutputFormat({ fastStart: 'fragmented' }); // new Metamuxer.MkvOutputFormat();// new Metamuxer.Mp4OutputFormat({ fastStart: false });
|
||||||
format = new Metamuxer.OggOutputFormat();
|
format = new Metamuxer.OggOutputFormat();
|
||||||
format = new Metamuxer.Mp4OutputFormat({ fastStart: 'fragmented', minimumFragmentDuration: 2 });
|
format = new Metamuxer.Mp4OutputFormat({ fastStart: 'fragmented', minimumFragmentDuration: 2 });
|
||||||
format = new Metamuxer.WebMOutputFormat({ minimumClusterDuration: 2 });
|
format = new Metamuxer.MkvOutputFormat({ minimumClusterDuration: 2 });
|
||||||
let target = new Metamuxer.BufferTarget();
|
let target = new Metamuxer.BufferTarget();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -87,7 +87,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
let videoSource = new Metamuxer.CanvasSource(canvas, {
|
let videoSource = new Metamuxer.CanvasSource(canvas, {
|
||||||
codec: 'av1',
|
codec: 'avc',
|
||||||
|
fullCodecString: 'avc1.42001f',
|
||||||
bitrate: 1e6
|
bitrate: 1e6
|
||||||
});
|
});
|
||||||
let audioSource = new Metamuxer.AudioBufferSource({
|
let audioSource = new Metamuxer.AudioBufferSource({
|
||||||
|
|||||||
+68
-7
@@ -1058,6 +1058,7 @@ export const parseOpusTocByte = (packet: Uint8Array) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PCM_CODEC_REGEX = /^pcm-([usf])(\d+)+(be)?$/;
|
const PCM_CODEC_REGEX = /^pcm-([usf])(\d+)+(be)?$/;
|
||||||
|
|
||||||
export const parsePcmCodec = (codec: PcmAudioCodec) => {
|
export const parsePcmCodec = (codec: PcmAudioCodec) => {
|
||||||
assert(PCM_AUDIO_CODECS.includes(codec));
|
assert(PCM_AUDIO_CODECS.includes(codec));
|
||||||
|
|
||||||
@@ -1086,6 +1087,52 @@ export const parsePcmCodec = (codec: PcmAudioCodec) => {
|
|||||||
return { dataType, sampleSize, littleEndian, silentValue };
|
return { dataType, sampleSize, littleEndian, silentValue };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const inferCodecFromCodecString = (codecString: string): MediaCodec | null => {
|
||||||
|
// Video codecs
|
||||||
|
if (codecString.startsWith('avc1') || codecString.startsWith('avc3')) {
|
||||||
|
return 'avc';
|
||||||
|
} else if (codecString.startsWith('hev1') || codecString.startsWith('hvc1')) {
|
||||||
|
return 'hevc';
|
||||||
|
} else if (codecString === 'vp8') {
|
||||||
|
return 'vp8';
|
||||||
|
} else if (codecString.startsWith('vp09')) {
|
||||||
|
return 'vp9';
|
||||||
|
} else if (codecString.startsWith('av01')) {
|
||||||
|
return 'av1';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audio codecs
|
||||||
|
if (codecString.startsWith('mp4a.40') || codecString === 'mp4a.67') {
|
||||||
|
return 'aac';
|
||||||
|
} else if (
|
||||||
|
codecString === 'mp3'
|
||||||
|
|| codecString === 'mp4a.69'
|
||||||
|
|| codecString === 'mp4a.6B'
|
||||||
|
|| codecString === 'mp4a.6b'
|
||||||
|
) {
|
||||||
|
return 'mp3';
|
||||||
|
} else if (codecString === 'opus') {
|
||||||
|
return 'opus';
|
||||||
|
} else if (codecString === 'vorbis') {
|
||||||
|
return 'vorbis';
|
||||||
|
} else if (codecString === 'flac') {
|
||||||
|
return 'flac';
|
||||||
|
} else if (codecString === 'ulaw') {
|
||||||
|
return 'ulaw';
|
||||||
|
} else if (codecString === 'alaw') {
|
||||||
|
return 'alaw';
|
||||||
|
} else if (PCM_CODEC_REGEX.test(codecString)) {
|
||||||
|
return codecString as PcmAudioCodec;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtitle codecs
|
||||||
|
if (codecString === 'webvtt') {
|
||||||
|
return 'webvtt';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export const getVideoEncoderConfigExtension = (codec: VideoCodec) => {
|
export const getVideoEncoderConfigExtension = (codec: VideoCodec) => {
|
||||||
if (codec === 'avc') {
|
if (codec === 'avc') {
|
||||||
return {
|
return {
|
||||||
@@ -1423,7 +1470,13 @@ export const validateAudioChunkMetadata = (metadata: EncodedAudioChunkMetadata |
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AAC-specific validation
|
// AAC-specific validation
|
||||||
if (metadata.decoderConfig.codec.startsWith('mp4a')) {
|
if (
|
||||||
|
metadata.decoderConfig.codec.startsWith('mp4a')
|
||||||
|
// These three refer to MP3:
|
||||||
|
&& metadata.decoderConfig.codec !== 'mp4a.69'
|
||||||
|
&& metadata.decoderConfig.codec !== 'mp4a.6B'
|
||||||
|
&& metadata.decoderConfig.codec !== 'mp4a.6b'
|
||||||
|
) {
|
||||||
const validStrings = ['mp4a.40.2', 'mp4a.40.02', 'mp4a.40.5', 'mp4a.40.05', 'mp4a.40.29', 'mp4a.67'];
|
const validStrings = ['mp4a.40.2', 'mp4a.40.02', 'mp4a.40.5', 'mp4a.40.05', 'mp4a.40.29', 'mp4a.67'];
|
||||||
if (!validStrings.includes(metadata.decoderConfig.codec)) {
|
if (!validStrings.includes(metadata.decoderConfig.codec)) {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
@@ -1441,14 +1494,22 @@ export const validateAudioChunkMetadata = (metadata: EncodedAudioChunkMetadata |
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MP3-specific validation
|
// MP3-specific validation
|
||||||
if (metadata.decoderConfig.codec === 'mp3') {
|
if (metadata.decoderConfig.codec.startsWith('mp3') || metadata.decoderConfig.codec.startsWith('mp4a')) {
|
||||||
if (metadata.decoderConfig.codec !== 'mp3') {
|
if (
|
||||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for MP3 must be "mp3".');
|
metadata.decoderConfig.codec !== 'mp3'
|
||||||
|
&& metadata.decoderConfig.codec !== 'mp4a.69'
|
||||||
|
&& metadata.decoderConfig.codec !== 'mp4a.6B'
|
||||||
|
&& metadata.decoderConfig.codec !== 'mp4a.6b'
|
||||||
|
) {
|
||||||
|
throw new TypeError(
|
||||||
|
'Audio chunk metadata decoder configuration codec string for MP3 must be "mp3", "mp4a.69" or'
|
||||||
|
+ ' "mp4a.6B".',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opus-specific validation
|
// Opus-specific validation
|
||||||
if (metadata.decoderConfig.codec === 'opus') {
|
if (metadata.decoderConfig.codec.startsWith('opus')) {
|
||||||
if (metadata.decoderConfig.codec !== 'opus') {
|
if (metadata.decoderConfig.codec !== 'opus') {
|
||||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for Opus must be "opus".');
|
throw new TypeError('Audio chunk metadata decoder configuration codec string for Opus must be "opus".');
|
||||||
}
|
}
|
||||||
@@ -1463,7 +1524,7 @@ export const validateAudioChunkMetadata = (metadata: EncodedAudioChunkMetadata |
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Vorbis-specific validation
|
// Vorbis-specific validation
|
||||||
if (metadata.decoderConfig.codec === 'vorbis') {
|
if (metadata.decoderConfig.codec.startsWith('vorbis')) {
|
||||||
if (metadata.decoderConfig.codec !== 'vorbis') {
|
if (metadata.decoderConfig.codec !== 'vorbis') {
|
||||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for Vorbis must be "vorbis".');
|
throw new TypeError('Audio chunk metadata decoder configuration codec string for Vorbis must be "vorbis".');
|
||||||
}
|
}
|
||||||
@@ -1477,7 +1538,7 @@ export const validateAudioChunkMetadata = (metadata: EncodedAudioChunkMetadata |
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FLAC-specific validation
|
// FLAC-specific validation
|
||||||
if (metadata.decoderConfig.codec === 'flac') {
|
if (metadata.decoderConfig.codec.startsWith('flac')) {
|
||||||
if (metadata.decoderConfig.codec !== 'flac') {
|
if (metadata.decoderConfig.codec !== 'flac') {
|
||||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for FLAC must be "flac".');
|
throw new TypeError('Audio chunk metadata decoder configuration codec string for FLAC must be "flac".');
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-10
@@ -5,6 +5,7 @@ import {
|
|||||||
buildVideoCodecString,
|
buildVideoCodecString,
|
||||||
getAudioEncoderConfigExtension,
|
getAudioEncoderConfigExtension,
|
||||||
getVideoEncoderConfigExtension,
|
getVideoEncoderConfigExtension,
|
||||||
|
inferCodecFromCodecString,
|
||||||
parsePcmCodec,
|
parsePcmCodec,
|
||||||
PCM_AUDIO_CODECS,
|
PCM_AUDIO_CODECS,
|
||||||
PcmAudioCodec,
|
PcmAudioCodec,
|
||||||
@@ -186,10 +187,16 @@ export type VideoEncodingConfig = {
|
|||||||
* all the same key frame interval.
|
* all the same key frame interval.
|
||||||
*/
|
*/
|
||||||
keyFrameInterval?: number;
|
keyFrameInterval?: number;
|
||||||
|
/**
|
||||||
|
* The full codec string as specified in the WebCodecs API Codec Registry. When not specified, a fitting codec
|
||||||
|
* string will be constructed automatically by the library.
|
||||||
|
*/
|
||||||
|
fullCodecString?: string;
|
||||||
|
|
||||||
/** Called for each successfully encoded packet. Both the packet and the encoding metadata are passed. */
|
/** Called for each successfully encoded packet. Both the packet and the encoding metadata are passed. */
|
||||||
onEncodedPacket?: (packet: EncodedPacket, meta: EncodedVideoChunkMetadata | undefined) => unknown;
|
onEncodedPacket?: (packet: EncodedPacket, meta: EncodedVideoChunkMetadata | undefined) => unknown;
|
||||||
/** Called when an error occurs during encoding. */
|
/** Called when an error occurs within VideoEncoder. */
|
||||||
onEncodingError?: (error: Error) => unknown;
|
onEncoderError?: (error: Error) => unknown;
|
||||||
/** Called when the internal encoder config, as used by the WebCodecs API, is created. */
|
/** Called when the internal encoder config, as used by the WebCodecs API, is created. */
|
||||||
onEncoderConfig?: (config: VideoEncoderConfig) => unknown;
|
onEncoderConfig?: (config: VideoEncoderConfig) => unknown;
|
||||||
};
|
};
|
||||||
@@ -213,12 +220,24 @@ const validateVideoEncodingConfig = (config: VideoEncodingConfig) => {
|
|||||||
) {
|
) {
|
||||||
throw new TypeError('config.keyFrameInterval, when provided, must be a non-negative number.');
|
throw new TypeError('config.keyFrameInterval, when provided, must be a non-negative number.');
|
||||||
}
|
}
|
||||||
|
if (config.fullCodecString !== undefined && typeof config.fullCodecString !== 'string') {
|
||||||
|
throw new TypeError('config.fullCodecString, when provided, must be a string.');
|
||||||
|
}
|
||||||
|
if (config.fullCodecString !== undefined && inferCodecFromCodecString(config.fullCodecString) !== config.codec) {
|
||||||
|
throw new TypeError(
|
||||||
|
`config.fullCodecString, when provided, must be a string that matches the specified codec`
|
||||||
|
+ ` (${config.codec}).`,
|
||||||
|
);
|
||||||
|
}
|
||||||
if (config.onEncodedPacket !== undefined && typeof config.onEncodedPacket !== 'function') {
|
if (config.onEncodedPacket !== undefined && typeof config.onEncodedPacket !== 'function') {
|
||||||
throw new TypeError('config.onEncodedChunk, when provided, must be a function.');
|
throw new TypeError('config.onEncodedChunk, when provided, must be a function.');
|
||||||
}
|
}
|
||||||
if (config.onEncodingError !== undefined && typeof config.onEncodingError !== 'function') {
|
if (config.onEncoderError !== undefined && typeof config.onEncoderError !== 'function') {
|
||||||
throw new TypeError('config.onEncodingError, when provided, must be a function.');
|
throw new TypeError('config.onEncodingError, when provided, must be a function.');
|
||||||
}
|
}
|
||||||
|
if (config.onEncoderConfig !== undefined && typeof config.onEncoderConfig !== 'function') {
|
||||||
|
throw new TypeError('config.onEncoderConfig, when provided, must be a function.');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class VideoEncoderWrapper {
|
class VideoEncoderWrapper {
|
||||||
@@ -326,7 +345,7 @@ class VideoEncoderWrapper {
|
|||||||
: this.encodingConfig.bitrate;
|
: this.encodingConfig.bitrate;
|
||||||
|
|
||||||
const encoderConfig: VideoEncoderConfig = {
|
const encoderConfig: VideoEncoderConfig = {
|
||||||
codec: buildVideoCodecString(
|
codec: this.encodingConfig.fullCodecString ?? buildVideoCodecString(
|
||||||
this.encodingConfig.codec,
|
this.encodingConfig.codec,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@@ -377,7 +396,7 @@ class VideoEncoderWrapper {
|
|||||||
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
||||||
void this.muxer!.addEncodedVideoPacket(this.source._connectedTrack!, packet, meta);
|
void this.muxer!.addEncodedVideoPacket(this.source._connectedTrack!, packet, meta);
|
||||||
},
|
},
|
||||||
error: this.encodingConfig.onEncodingError ?? (error => console.error('VideoEncoder error:', error)),
|
error: this.encodingConfig.onEncoderError ?? (error => console.error('VideoEncoder error:', error)),
|
||||||
});
|
});
|
||||||
this.encoder.configure(encoderConfig);
|
this.encoder.configure(encoderConfig);
|
||||||
}
|
}
|
||||||
@@ -633,10 +652,16 @@ export type AudioEncodingConfig = {
|
|||||||
* be provided. Required for compressed audio codecs, unused for PCM codecs.
|
* be provided. Required for compressed audio codecs, unused for PCM codecs.
|
||||||
*/
|
*/
|
||||||
bitrate?: number | Quality;
|
bitrate?: number | Quality;
|
||||||
|
/**
|
||||||
|
* The full codec string as specified in the WebCodecs API Codec Registry. When not specified, a fitting codec
|
||||||
|
* string will be constructed automatically by the library.
|
||||||
|
*/
|
||||||
|
fullCodecString?: string;
|
||||||
|
|
||||||
/** Called for each successfully encoded packet. Both the packet and the encoding metadata are passed. */
|
/** Called for each successfully encoded packet. Both the packet and the encoding metadata are passed. */
|
||||||
onEncodedPacket?: (packet: EncodedPacket, meta: EncodedAudioChunkMetadata | undefined) => unknown;
|
onEncodedPacket?: (packet: EncodedPacket, meta: EncodedAudioChunkMetadata | undefined) => unknown;
|
||||||
/** Called when an error occurs during encoding. */
|
/** Called when an error occurs within AudioEncoder. */
|
||||||
onEncodingError?: (error: Error) => unknown;
|
onEncoderError?: (error: Error) => unknown;
|
||||||
/** Called when the internal encoder config, as used by the WebCodecs API, is created. */
|
/** Called when the internal encoder config, as used by the WebCodecs API, is created. */
|
||||||
onEncoderConfig?: (config: AudioEncoderConfig) => unknown;
|
onEncoderConfig?: (config: AudioEncoderConfig) => unknown;
|
||||||
};
|
};
|
||||||
@@ -661,9 +686,24 @@ const validateAudioEncodingConfig = (config: AudioEncodingConfig) => {
|
|||||||
) {
|
) {
|
||||||
throw new TypeError('config.bitrate, when provided, must be a positive integer or a quality.');
|
throw new TypeError('config.bitrate, when provided, must be a positive integer or a quality.');
|
||||||
}
|
}
|
||||||
if (config.onEncodingError !== undefined && typeof config.onEncodingError !== 'function') {
|
if (config.fullCodecString !== undefined && typeof config.fullCodecString !== 'string') {
|
||||||
|
throw new TypeError('config.fullCodecString, when provided, must be a string.');
|
||||||
|
}
|
||||||
|
if (config.fullCodecString !== undefined && inferCodecFromCodecString(config.fullCodecString) !== config.codec) {
|
||||||
|
throw new TypeError(
|
||||||
|
`config.fullCodecString, when provided, must be a string that matches the specified codec`
|
||||||
|
+ ` (${config.codec}).`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (config.onEncodedPacket !== undefined && typeof config.onEncodedPacket !== 'function') {
|
||||||
|
throw new TypeError('config.onEncodedChunk, when provided, must be a function.');
|
||||||
|
}
|
||||||
|
if (config.onEncoderError !== undefined && typeof config.onEncoderError !== 'function') {
|
||||||
throw new TypeError('config.onEncodingError, when provided, must be a function.');
|
throw new TypeError('config.onEncodingError, when provided, must be a function.');
|
||||||
}
|
}
|
||||||
|
if (config.onEncoderConfig !== undefined && typeof config.onEncoderConfig !== 'function') {
|
||||||
|
throw new TypeError('config.onEncoderConfig, when provided, must be a function.');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class AudioEncoderWrapper {
|
class AudioEncoderWrapper {
|
||||||
@@ -837,7 +877,7 @@ class AudioEncoderWrapper {
|
|||||||
: this.encodingConfig.bitrate;
|
: this.encodingConfig.bitrate;
|
||||||
|
|
||||||
const encoderConfig: AudioEncoderConfig = {
|
const encoderConfig: AudioEncoderConfig = {
|
||||||
codec: buildAudioCodecString(
|
codec: this.encodingConfig.fullCodecString ?? buildAudioCodecString(
|
||||||
this.encodingConfig.codec,
|
this.encodingConfig.codec,
|
||||||
numberOfChannels,
|
numberOfChannels,
|
||||||
sampleRate,
|
sampleRate,
|
||||||
@@ -887,7 +927,7 @@ class AudioEncoderWrapper {
|
|||||||
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
||||||
void this.muxer!.addEncodedAudioPacket(this.source._connectedTrack!, packet, meta);
|
void this.muxer!.addEncodedAudioPacket(this.source._connectedTrack!, packet, meta);
|
||||||
},
|
},
|
||||||
error: this.encodingConfig.onEncodingError ?? (error => console.error('AudioEncoder error:', error)),
|
error: this.encodingConfig.onEncoderError ?? (error => console.error('AudioEncoder error:', error)),
|
||||||
});
|
});
|
||||||
this.encoder.configure(encoderConfig);
|
this.encoder.configure(encoderConfig);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user