mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-02 21:33:52 +02:00
Allow more manual control over key frames
This commit is contained in:
+16
-9
@@ -106,13 +106,12 @@ export class EncodedVideoChunkSource extends VideoSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const KEY_FRAME_INTERVAL = 5;
|
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type VideoCodecConfig = {
|
export type VideoCodecConfig = {
|
||||||
codec: VideoCodec;
|
codec: VideoCodec;
|
||||||
bitrate: number;
|
bitrate: number;
|
||||||
latencyMode?: VideoEncoderConfig['latencyMode'];
|
latencyMode?: VideoEncoderConfig['latencyMode'];
|
||||||
|
keyFrameInterval?: number;
|
||||||
onEncodingError?: (error: Error) => void;
|
onEncodingError?: (error: Error) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -129,6 +128,12 @@ const validateVideoCodecConfig = (config: VideoCodecConfig) => {
|
|||||||
if (config.latencyMode !== undefined && !['quality', 'realtime'].includes(config.latencyMode)) {
|
if (config.latencyMode !== undefined && !['quality', 'realtime'].includes(config.latencyMode)) {
|
||||||
throw new TypeError('config.latencyMode, when provided, must be \'quality\' or \'realtime\'.');
|
throw new TypeError('config.latencyMode, when provided, must be \'quality\' or \'realtime\'.');
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
config.keyFrameInterval !== undefined
|
||||||
|
&& (!Number.isFinite(config.keyFrameInterval) || config.keyFrameInterval < 0)
|
||||||
|
) {
|
||||||
|
throw new TypeError('config.keyFrameInterval, when provided, must be a non-negative number.');
|
||||||
|
}
|
||||||
if (config.onEncodingError !== undefined && typeof config.onEncodingError !== 'function') {
|
if (config.onEncodingError !== undefined && typeof config.onEncodingError !== 'function') {
|
||||||
throw new TypeError('config.onEncodingError, when provided, must be a function.');
|
throw new TypeError('config.onEncodingError, when provided, must be a function.');
|
||||||
}
|
}
|
||||||
@@ -145,7 +150,7 @@ class VideoEncoderWrapper {
|
|||||||
validateVideoCodecConfig(codecConfig);
|
validateVideoCodecConfig(codecConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
async digest(videoFrame: VideoFrame) {
|
async digest(videoFrame: VideoFrame, encodeOptions?: VideoEncoderEncodeOptions) {
|
||||||
this.source._ensureValidDigest();
|
this.source._ensureValidDigest();
|
||||||
|
|
||||||
// Ensure video frame size remains constant
|
// Ensure video frame size remains constant
|
||||||
@@ -164,13 +169,15 @@ class VideoEncoderWrapper {
|
|||||||
this.ensureEncoder(videoFrame);
|
this.ensureEncoder(videoFrame);
|
||||||
assert(this.encoder);
|
assert(this.encoder);
|
||||||
|
|
||||||
const multipleOfKeyFrameInterval = Math.floor((videoFrame.timestamp / 1e6) / KEY_FRAME_INTERVAL);
|
const keyFrameInterval = this.codecConfig.keyFrameInterval ?? 5;
|
||||||
|
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
|
||||||
// "key frame" rhythm, because aligned key frames are required to start new fragments in ISOBMFF or clusters
|
// "key frame" rhythm, because aligned key frames are required to start new fragments in ISOBMFF or clusters
|
||||||
// in Matroska.
|
// in Matroska.
|
||||||
this.encoder.encode(videoFrame, {
|
this.encoder.encode(videoFrame, {
|
||||||
keyFrame: multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval,
|
...encodeOptions,
|
||||||
|
keyFrame: keyFrameInterval === 0 || multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval;
|
this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval;
|
||||||
@@ -230,12 +237,12 @@ export class VideoFrameSource extends VideoSource {
|
|||||||
this._encoder = new VideoEncoderWrapper(this, codecConfig);
|
this._encoder = new VideoEncoderWrapper(this, codecConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
digest(videoFrame: VideoFrame) {
|
digest(videoFrame: VideoFrame, encodeOptions?: VideoEncoderEncodeOptions) {
|
||||||
if (!(videoFrame instanceof VideoFrame)) {
|
if (!(videoFrame instanceof VideoFrame)) {
|
||||||
throw new TypeError('videoFrame must be a VideoFrame.');
|
throw new TypeError('videoFrame must be a VideoFrame.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._encoder.digest(videoFrame);
|
return this._encoder.digest(videoFrame, encodeOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
@@ -261,7 +268,7 @@ export class CanvasSource extends VideoSource {
|
|||||||
this._canvas = canvas;
|
this._canvas = canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
digest(timestamp: number, duration = 0) {
|
digest(timestamp: number, duration = 0, encodeOptions?: VideoEncoderEncodeOptions) {
|
||||||
if (!Number.isFinite(timestamp) || timestamp < 0) {
|
if (!Number.isFinite(timestamp) || timestamp < 0) {
|
||||||
throw new TypeError('timestamp must be a non-negative number.');
|
throw new TypeError('timestamp must be a non-negative number.');
|
||||||
}
|
}
|
||||||
@@ -275,7 +282,7 @@ export class CanvasSource extends VideoSource {
|
|||||||
alpha: 'discard',
|
alpha: 'discard',
|
||||||
});
|
});
|
||||||
|
|
||||||
const promise = this._encoder.digest(frame);
|
const promise = this._encoder.digest(frame, encodeOptions);
|
||||||
frame.close();
|
frame.close();
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
- Manual key frame passing
|
|
||||||
- Progress reporting
|
- Progress reporting
|
||||||
- Test tree shaking
|
- Test tree shaking
|
||||||
- Throw if edit list detected
|
- Throw if edit list detected
|
||||||
@@ -6,5 +5,4 @@
|
|||||||
- Add the new audio codecs to muxers
|
- Add the new audio codecs to muxers
|
||||||
- Mov muxer!!! Only mov can hold PCM audio, MP4 cannot
|
- Mov muxer!!! Only mov can hold PCM audio, MP4 cannot
|
||||||
- Metadata methods for computing average fps and bitrate
|
- Metadata methods for computing average fps and bitrate
|
||||||
- close method on Output to cancel it (and dispose the encoders). Perhaps abort is a better title
|
- close method on Output to cancel it (and dispose the encoders). Perhaps abort is a better title
|
||||||
- lower default key frame interval (3 secs is better I think?) and make it outside-configurable
|
|
||||||
Reference in New Issue
Block a user