mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-03 13:53:53 +02:00
CodecConfig -> EncodingConfig
This commit is contained in:
+2
-2
@@ -9,8 +9,8 @@ export {
|
|||||||
WebMOutputFormatOptions,
|
WebMOutputFormatOptions,
|
||||||
} from './output-format';
|
} from './output-format';
|
||||||
export {
|
export {
|
||||||
VideoCodecConfig,
|
VideoEncodingConfig,
|
||||||
AudioCodecConfig,
|
AudioEncodingConfig,
|
||||||
MediaSource,
|
MediaSource,
|
||||||
VideoSource,
|
VideoSource,
|
||||||
EncodedVideoChunkSource,
|
EncodedVideoChunkSource,
|
||||||
|
|||||||
+43
-43
@@ -117,7 +117,7 @@ export class EncodedVideoChunkSource extends VideoSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type VideoCodecConfig = {
|
export type VideoEncodingConfig = {
|
||||||
codec: VideoCodec;
|
codec: VideoCodec;
|
||||||
bitrate: number;
|
bitrate: number;
|
||||||
latencyMode?: VideoEncoderConfig['latencyMode'];
|
latencyMode?: VideoEncoderConfig['latencyMode'];
|
||||||
@@ -126,9 +126,9 @@ export type VideoCodecConfig = {
|
|||||||
onEncodingError?: (error: Error) => unknown;
|
onEncodingError?: (error: Error) => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateVideoCodecConfig = (config: VideoCodecConfig) => {
|
const validateVideoEncodingConfig = (config: VideoEncodingConfig) => {
|
||||||
if (!config || typeof config !== 'object') {
|
if (!config || typeof config !== 'object') {
|
||||||
throw new TypeError('Codec config must be an object.');
|
throw new TypeError('Encoding config must be an object.');
|
||||||
}
|
}
|
||||||
if (!VIDEO_CODECS.includes(config.codec)) {
|
if (!VIDEO_CODECS.includes(config.codec)) {
|
||||||
throw new TypeError(`Invalid video codec '${config.codec}'. Must be one of: ${VIDEO_CODECS.join(', ')}.`);
|
throw new TypeError(`Invalid video codec '${config.codec}'. Must be one of: ${VIDEO_CODECS.join(', ')}.`);
|
||||||
@@ -160,8 +160,8 @@ class VideoEncoderWrapper {
|
|||||||
private lastWidth: number | null = null;
|
private lastWidth: number | null = null;
|
||||||
private lastHeight: number | null = null;
|
private lastHeight: number | null = null;
|
||||||
|
|
||||||
constructor(private source: VideoSource, private codecConfig: VideoCodecConfig) {
|
constructor(private source: VideoSource, private encodingConfig: VideoEncodingConfig) {
|
||||||
validateVideoCodecConfig(codecConfig);
|
validateVideoEncodingConfig(encodingConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
async digest(videoFrame: VideoFrame, encodeOptions?: VideoEncoderEncodeOptions) {
|
async digest(videoFrame: VideoFrame, encodeOptions?: VideoEncoderEncodeOptions) {
|
||||||
@@ -183,7 +183,7 @@ class VideoEncoderWrapper {
|
|||||||
this.ensureEncoder(videoFrame);
|
this.ensureEncoder(videoFrame);
|
||||||
assert(this.encoder);
|
assert(this.encoder);
|
||||||
|
|
||||||
const keyFrameInterval = this.codecConfig.keyFrameInterval ?? 5;
|
const keyFrameInterval = this.encodingConfig.keyFrameInterval ?? 5;
|
||||||
const multipleOfKeyFrameInterval = Math.floor((videoFrame.timestamp / 1e6) / keyFrameInterval);
|
const multipleOfKeyFrameInterval = Math.floor((videoFrame.timestamp / 1e6) / keyFrameInterval);
|
||||||
|
|
||||||
// Ensure a key frame every KEY_FRAME_INTERVAL seconds. It is important that all video tracks follow the same
|
// Ensure a key frame every KEY_FRAME_INTERVAL seconds. It is important that all video tracks follow the same
|
||||||
@@ -211,25 +211,25 @@ class VideoEncoderWrapper {
|
|||||||
|
|
||||||
this.encoder = new VideoEncoder({
|
this.encoder = new VideoEncoder({
|
||||||
output: (chunk, meta) => {
|
output: (chunk, meta) => {
|
||||||
this.codecConfig.onEncodedChunk?.(chunk, meta);
|
this.encodingConfig.onEncodedChunk?.(chunk, meta);
|
||||||
void this.muxer!.addEncodedVideoChunk(this.source._connectedTrack!, chunk, meta);
|
void this.muxer!.addEncodedVideoChunk(this.source._connectedTrack!, chunk, meta);
|
||||||
},
|
},
|
||||||
error: this.codecConfig.onEncodingError ?? (error => console.error('VideoEncoder error:', error)),
|
error: this.encodingConfig.onEncodingError ?? (error => console.error('VideoEncoder error:', error)),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.encoder.configure({
|
this.encoder.configure({
|
||||||
codec: buildVideoCodecString(
|
codec: buildVideoCodecString(
|
||||||
this.codecConfig.codec,
|
this.encodingConfig.codec,
|
||||||
videoFrame.codedWidth,
|
videoFrame.codedWidth,
|
||||||
videoFrame.codedHeight,
|
videoFrame.codedHeight,
|
||||||
this.codecConfig.bitrate,
|
this.encodingConfig.bitrate,
|
||||||
),
|
),
|
||||||
width: videoFrame.codedWidth,
|
width: videoFrame.codedWidth,
|
||||||
height: videoFrame.codedHeight,
|
height: videoFrame.codedHeight,
|
||||||
bitrate: this.codecConfig.bitrate,
|
bitrate: this.encodingConfig.bitrate,
|
||||||
framerate: this.source._connectedTrack?.metadata.frameRate,
|
framerate: this.source._connectedTrack?.metadata.frameRate,
|
||||||
latencyMode: this.codecConfig.latencyMode,
|
latencyMode: this.encodingConfig.latencyMode,
|
||||||
...getVideoEncoderConfigExtension(this.codecConfig.codec),
|
...getVideoEncoderConfigExtension(this.encodingConfig.codec),
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(this.source._connectedTrack);
|
assert(this.source._connectedTrack);
|
||||||
@@ -249,9 +249,9 @@ export class VideoFrameSource extends VideoSource {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
private _encoder: VideoEncoderWrapper;
|
private _encoder: VideoEncoderWrapper;
|
||||||
|
|
||||||
constructor(codecConfig: VideoCodecConfig) {
|
constructor(encodingConfig: VideoEncodingConfig) {
|
||||||
super(codecConfig.codec);
|
super(encodingConfig.codec);
|
||||||
this._encoder = new VideoEncoderWrapper(this, codecConfig);
|
this._encoder = new VideoEncoderWrapper(this, encodingConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
digest(videoFrame: VideoFrame, encodeOptions?: VideoEncoderEncodeOptions) {
|
digest(videoFrame: VideoFrame, encodeOptions?: VideoEncoderEncodeOptions) {
|
||||||
@@ -275,13 +275,13 @@ export class CanvasSource extends VideoSource {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
private _canvas: HTMLCanvasElement | OffscreenCanvas;
|
private _canvas: HTMLCanvasElement | OffscreenCanvas;
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement | OffscreenCanvas, codecConfig: VideoCodecConfig) {
|
constructor(canvas: HTMLCanvasElement | OffscreenCanvas, encodingConfig: VideoEncodingConfig) {
|
||||||
if (!(canvas instanceof HTMLCanvasElement)) {
|
if (!(canvas instanceof HTMLCanvasElement)) {
|
||||||
throw new TypeError('canvas must be an HTMLCanvasElement.');
|
throw new TypeError('canvas must be an HTMLCanvasElement.');
|
||||||
}
|
}
|
||||||
|
|
||||||
super(codecConfig.codec);
|
super(encodingConfig.codec);
|
||||||
this._encoder = new VideoEncoderWrapper(this, codecConfig);
|
this._encoder = new VideoEncoderWrapper(this, encodingConfig);
|
||||||
this._canvas = canvas;
|
this._canvas = canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,18 +323,18 @@ export class MediaStreamVideoTrackSource extends VideoSource {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
override _offsetTimestamps = true;
|
override _offsetTimestamps = true;
|
||||||
|
|
||||||
constructor(track: MediaStreamVideoTrack, codecConfig: VideoCodecConfig) {
|
constructor(track: MediaStreamVideoTrack, encodingConfig: VideoEncodingConfig) {
|
||||||
if (!(track instanceof MediaStreamTrack) || track.kind !== 'video') {
|
if (!(track instanceof MediaStreamTrack) || track.kind !== 'video') {
|
||||||
throw new TypeError('track must be a video MediaStreamTrack.');
|
throw new TypeError('track must be a video MediaStreamTrack.');
|
||||||
}
|
}
|
||||||
|
|
||||||
codecConfig = {
|
encodingConfig = {
|
||||||
...codecConfig,
|
...encodingConfig,
|
||||||
latencyMode: 'realtime',
|
latencyMode: 'realtime',
|
||||||
};
|
};
|
||||||
|
|
||||||
super(codecConfig.codec);
|
super(encodingConfig.codec);
|
||||||
this._encoder = new VideoEncoderWrapper(this, codecConfig);
|
this._encoder = new VideoEncoderWrapper(this, encodingConfig);
|
||||||
this._track = track;
|
this._track = track;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,16 +407,16 @@ export class EncodedAudioChunkSource extends AudioSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/** @public */
|
/** @public */
|
||||||
export type AudioCodecConfig = {
|
export type AudioEncodingConfig = {
|
||||||
codec: AudioCodec;
|
codec: AudioCodec;
|
||||||
bitrate: number;
|
bitrate: number;
|
||||||
onEncodedChunk?: (chunk: EncodedAudioChunk, meta: EncodedAudioChunkMetadata | undefined) => unknown;
|
onEncodedChunk?: (chunk: EncodedAudioChunk, meta: EncodedAudioChunkMetadata | undefined) => unknown;
|
||||||
onEncodingError?: (error: Error) => unknown;
|
onEncodingError?: (error: Error) => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateAudioCodecConfig = (config: AudioCodecConfig) => {
|
const validateAudioEncodingConfig = (config: AudioEncodingConfig) => {
|
||||||
if (!config || typeof config !== 'object') {
|
if (!config || typeof config !== 'object') {
|
||||||
throw new TypeError('Codec config must be an object.');
|
throw new TypeError('Encoding config must be an object.');
|
||||||
}
|
}
|
||||||
if (!AUDIO_CODECS.includes(config.codec)) {
|
if (!AUDIO_CODECS.includes(config.codec)) {
|
||||||
throw new TypeError(`Invalid audio codec '${config.codec}'. Must be one of: ${AUDIO_CODECS.join(', ')}.`);
|
throw new TypeError(`Invalid audio codec '${config.codec}'. Must be one of: ${AUDIO_CODECS.join(', ')}.`);
|
||||||
@@ -435,8 +435,8 @@ class AudioEncoderWrapper {
|
|||||||
private lastNumberOfChannels: number | null = null;
|
private lastNumberOfChannels: number | null = null;
|
||||||
private lastSampleRate: number | null = null;
|
private lastSampleRate: number | null = null;
|
||||||
|
|
||||||
constructor(private source: AudioSource, private codecConfig: AudioCodecConfig) {
|
constructor(private source: AudioSource, private encodingConfig: AudioEncodingConfig) {
|
||||||
validateAudioCodecConfig(codecConfig);
|
validateAudioEncodingConfig(encodingConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
async digest(audioData: AudioData) {
|
async digest(audioData: AudioData) {
|
||||||
@@ -478,18 +478,18 @@ class AudioEncoderWrapper {
|
|||||||
|
|
||||||
this.encoder = new AudioEncoder({
|
this.encoder = new AudioEncoder({
|
||||||
output: (chunk, meta) => {
|
output: (chunk, meta) => {
|
||||||
this.codecConfig.onEncodedChunk?.(chunk, meta);
|
this.encodingConfig.onEncodedChunk?.(chunk, meta);
|
||||||
void this.muxer!.addEncodedAudioChunk(this.source._connectedTrack!, chunk, meta);
|
void this.muxer!.addEncodedAudioChunk(this.source._connectedTrack!, chunk, meta);
|
||||||
},
|
},
|
||||||
error: this.codecConfig.onEncodingError ?? (error => console.error('AudioEncoder error:', error)),
|
error: this.encodingConfig.onEncodingError ?? (error => console.error('AudioEncoder error:', error)),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.encoder.configure({
|
this.encoder.configure({
|
||||||
codec: buildAudioCodecString(this.codecConfig.codec, audioData.numberOfChannels, audioData.sampleRate),
|
codec: buildAudioCodecString(this.encodingConfig.codec, audioData.numberOfChannels, audioData.sampleRate),
|
||||||
numberOfChannels: audioData.numberOfChannels,
|
numberOfChannels: audioData.numberOfChannels,
|
||||||
sampleRate: audioData.sampleRate,
|
sampleRate: audioData.sampleRate,
|
||||||
bitrate: this.codecConfig.bitrate,
|
bitrate: this.encodingConfig.bitrate,
|
||||||
...getAudioEncoderConfigExtension(this.codecConfig.codec),
|
...getAudioEncoderConfigExtension(this.encodingConfig.codec),
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(this.source._connectedTrack);
|
assert(this.source._connectedTrack);
|
||||||
@@ -509,9 +509,9 @@ export class AudioDataSource extends AudioSource {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
private _encoder: AudioEncoderWrapper;
|
private _encoder: AudioEncoderWrapper;
|
||||||
|
|
||||||
constructor(codecConfig: AudioCodecConfig) {
|
constructor(encodingConfig: AudioEncodingConfig) {
|
||||||
super(codecConfig.codec);
|
super(encodingConfig.codec);
|
||||||
this._encoder = new AudioEncoderWrapper(this, codecConfig);
|
this._encoder = new AudioEncoderWrapper(this, encodingConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
digest(audioData: AudioData) {
|
digest(audioData: AudioData) {
|
||||||
@@ -535,9 +535,9 @@ export class AudioBufferSource extends AudioSource {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
private _accumulatedFrameCount = 0;
|
private _accumulatedFrameCount = 0;
|
||||||
|
|
||||||
constructor(codecConfig: AudioCodecConfig) {
|
constructor(encodingConfig: AudioEncodingConfig) {
|
||||||
super(codecConfig.codec);
|
super(encodingConfig.codec);
|
||||||
this._encoder = new AudioEncoderWrapper(this, codecConfig);
|
this._encoder = new AudioEncoderWrapper(this, encodingConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
digest(audioBuffer: AudioBuffer) {
|
digest(audioBuffer: AudioBuffer) {
|
||||||
@@ -608,13 +608,13 @@ export class MediaStreamAudioTrackSource extends AudioSource {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
override _offsetTimestamps = true;
|
override _offsetTimestamps = true;
|
||||||
|
|
||||||
constructor(track: MediaStreamAudioTrack, codecConfig: AudioCodecConfig) {
|
constructor(track: MediaStreamAudioTrack, encodingConfig: AudioEncodingConfig) {
|
||||||
if (!(track instanceof MediaStreamTrack) || track.kind !== 'audio') {
|
if (!(track instanceof MediaStreamTrack) || track.kind !== 'audio') {
|
||||||
throw new TypeError('track must be an audio MediaStreamTrack.');
|
throw new TypeError('track must be an audio MediaStreamTrack.');
|
||||||
}
|
}
|
||||||
|
|
||||||
super(codecConfig.codec);
|
super(encodingConfig.codec);
|
||||||
this._encoder = new AudioEncoderWrapper(this, codecConfig);
|
this._encoder = new AudioEncoderWrapper(this, encodingConfig);
|
||||||
this._track = track;
|
this._track = track;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user