mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 02:43:48 +02:00
240 lines
8.6 KiB
TypeScript
240 lines
8.6 KiB
TypeScript
/*!
|
|
* Copyright (c) 2026-present, Vanilagy and contributors
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
import { VideoSamplePixelFormat, MediaCodec } from 'mediabunny';
|
|
import * as NodeAv from 'node-av';
|
|
|
|
export const CODEC_TO_CODEC_ID: Partial<Record<MediaCodec, NodeAv.AVCodecID>> = {
|
|
avc: NodeAv.AV_CODEC_ID_H264,
|
|
hevc: NodeAv.AV_CODEC_ID_HEVC,
|
|
vp8: NodeAv.AV_CODEC_ID_VP8,
|
|
vp9: NodeAv.AV_CODEC_ID_VP9,
|
|
av1: NodeAv.AV_CODEC_ID_AV1,
|
|
|
|
aac: NodeAv.AV_CODEC_ID_AAC,
|
|
opus: NodeAv.AV_CODEC_ID_OPUS,
|
|
mp3: NodeAv.AV_CODEC_ID_MP3,
|
|
vorbis: NodeAv.AV_CODEC_ID_VORBIS,
|
|
flac: NodeAv.AV_CODEC_ID_FLAC,
|
|
ac3: NodeAv.AV_CODEC_ID_AC3,
|
|
eac3: NodeAv.AV_CODEC_ID_EAC3,
|
|
};
|
|
|
|
let cachedHardwareContext: NodeAv.HardwareContext | null | undefined = undefined;
|
|
export const getHardwareContext = (): NodeAv.HardwareContext | null => {
|
|
if (cachedHardwareContext === undefined) {
|
|
cachedHardwareContext = NodeAv.HardwareContext.auto();
|
|
}
|
|
return cachedHardwareContext;
|
|
};
|
|
|
|
const hardwareDecoderCodecCache = new Map<NodeAv.AVCodecID, NodeAv.Codec | null>();
|
|
export const getHardwareDecoderCodec = (codecId: NodeAv.AVCodecID): NodeAv.Codec | null => {
|
|
if (!hardwareDecoderCodecCache.has(codecId)) {
|
|
const hw = getHardwareContext();
|
|
hardwareDecoderCodecCache.set(codecId, hw?.getDecoderCodec(codecId) ?? null);
|
|
}
|
|
return hardwareDecoderCodecCache.get(codecId)!;
|
|
};
|
|
|
|
const hardwareEncoderCodecCache = new Map<NodeAv.AVCodecID, NodeAv.Codec | null>();
|
|
export const getHardwareEncoderCodec = (codecId: NodeAv.AVCodecID): NodeAv.Codec | null => {
|
|
if (!hardwareEncoderCodecCache.has(codecId)) {
|
|
const hw = getHardwareContext();
|
|
hardwareEncoderCodecCache.set(codecId, hw?.getEncoderCodec(codecId) ?? null);
|
|
}
|
|
return hardwareEncoderCodecCache.get(codecId)!;
|
|
};
|
|
|
|
export const mapColorPrimaries = (primaries: string) => {
|
|
switch (primaries) {
|
|
case 'bt709': return NodeAv.AVCOL_PRI_BT709;
|
|
case 'bt470bg': return NodeAv.AVCOL_PRI_BT470BG;
|
|
case 'smpte170m': return NodeAv.AVCOL_PRI_SMPTE170M;
|
|
case 'bt2020': return NodeAv.AVCOL_PRI_BT2020;
|
|
case 'smpte432': return NodeAv.AVCOL_PRI_SMPTE432;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
export const unmapColorPrimaries = (primaries: number) => {
|
|
switch (primaries) {
|
|
case NodeAv.AVCOL_PRI_BT709: return 'bt709';
|
|
case NodeAv.AVCOL_PRI_BT470BG: return 'bt470bg';
|
|
case NodeAv.AVCOL_PRI_SMPTE170M: return 'smpte170m';
|
|
case NodeAv.AVCOL_PRI_BT2020: return 'bt2020';
|
|
case NodeAv.AVCOL_PRI_SMPTE432: return 'smpte432';
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
export const mapTransferCharacteristics = (transfer: string) => {
|
|
switch (transfer) {
|
|
case 'bt709': return NodeAv.AVCOL_TRC_BT709;
|
|
case 'smpte170m': return NodeAv.AVCOL_TRC_SMPTE170M;
|
|
case 'iec61966-2-1': return NodeAv.AVCOL_TRC_IEC61966_2_1;
|
|
case 'linear': return NodeAv.AVCOL_TRC_LINEAR;
|
|
case 'pq': return NodeAv.AVCOL_TRC_SMPTE2084;
|
|
case 'hlg': return NodeAv.AVCOL_TRC_ARIB_STD_B67;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
export const unmapTransferCharacteristics = (transfer: number) => {
|
|
switch (transfer) {
|
|
case NodeAv.AVCOL_TRC_BT709: return 'bt709';
|
|
case NodeAv.AVCOL_TRC_SMPTE170M: return 'smpte170m';
|
|
case NodeAv.AVCOL_TRC_IEC61966_2_1: return 'iec61966-2-1';
|
|
case NodeAv.AVCOL_TRC_LINEAR: return 'linear';
|
|
case NodeAv.AVCOL_TRC_SMPTE2084: return 'pq';
|
|
case NodeAv.AVCOL_TRC_ARIB_STD_B67: return 'hlg';
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
export const mapMatrixCoefficients = (matrix: string) => {
|
|
switch (matrix) {
|
|
case 'rgb': return NodeAv.AVCOL_SPC_RGB;
|
|
case 'bt709': return NodeAv.AVCOL_SPC_BT709;
|
|
case 'bt470bg': return NodeAv.AVCOL_SPC_BT470BG;
|
|
case 'smpte170m': return NodeAv.AVCOL_SPC_SMPTE170M;
|
|
case 'bt2020-ncl': return NodeAv.AVCOL_SPC_BT2020_NCL;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
export const unmapMatrixCoefficients = (matrix: number) => {
|
|
switch (matrix) {
|
|
case NodeAv.AVCOL_SPC_RGB: return 'rgb';
|
|
case NodeAv.AVCOL_SPC_BT709: return 'bt709';
|
|
case NodeAv.AVCOL_SPC_BT470BG: return 'bt470bg';
|
|
case NodeAv.AVCOL_SPC_SMPTE170M: return 'smpte170m';
|
|
case NodeAv.AVCOL_SPC_BT2020_NCL: return 'bt2020-ncl';
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
export const toPixelFormat = (ffmpegPixelFormat: NodeAv.AVPixelFormat): VideoSamplePixelFormat | null => {
|
|
switch (ffmpegPixelFormat) {
|
|
case NodeAv.AV_PIX_FMT_YUV420P: return 'I420';
|
|
// "deprecated in favor of AV_PIX_FMT_YUV420P and setting color_range"
|
|
case NodeAv.AV_PIX_FMT_YUVJ420P: return 'I420';
|
|
case NodeAv.AV_PIX_FMT_YUV420P10LE: return 'I420P10';
|
|
case NodeAv.AV_PIX_FMT_YUV420P12LE: return 'I420P12';
|
|
case NodeAv.AV_PIX_FMT_YUVA420P: return 'I420A';
|
|
case NodeAv.AV_PIX_FMT_YUVA420P10LE: return 'I420AP10';
|
|
|
|
case NodeAv.AV_PIX_FMT_YUV422P: return 'I422';
|
|
// "deprecated in favor of AV_PIX_FMT_YUV422P and setting color_range"
|
|
case NodeAv.AV_PIX_FMT_YUVJ422P: return 'I422';
|
|
case NodeAv.AV_PIX_FMT_YUV422P10LE: return 'I422P10';
|
|
case NodeAv.AV_PIX_FMT_YUV422P12LE: return 'I422P12';
|
|
case NodeAv.AV_PIX_FMT_YUVA422P: return 'I422A';
|
|
case NodeAv.AV_PIX_FMT_YUVA422P10LE: return 'I422AP10';
|
|
case NodeAv.AV_PIX_FMT_YUVA422P12LE: return 'I422AP12';
|
|
|
|
case NodeAv.AV_PIX_FMT_YUV444P: return 'I444';
|
|
// "deprecated in favor of AV_PIX_FMT_YUV444P and setting color_range"
|
|
case NodeAv.AV_PIX_FMT_YUVJ444P: return 'I444';
|
|
case NodeAv.AV_PIX_FMT_YUV444P10LE: return 'I444P10';
|
|
case NodeAv.AV_PIX_FMT_YUV444P12LE: return 'I444P12';
|
|
case NodeAv.AV_PIX_FMT_YUVA444P: return 'I444A';
|
|
case NodeAv.AV_PIX_FMT_YUVA444P10LE: return 'I444AP10';
|
|
case NodeAv.AV_PIX_FMT_YUVA444P12LE: return 'I444AP12';
|
|
|
|
case NodeAv.AV_PIX_FMT_NV12: return 'NV12';
|
|
|
|
case NodeAv.AV_PIX_FMT_RGBA: return 'RGBA';
|
|
case NodeAv.AV_PIX_FMT_RGB0: return 'RGBX';
|
|
case NodeAv.AV_PIX_FMT_BGRA: return 'BGRA';
|
|
case NodeAv.AV_PIX_FMT_BGR0: return 'BGRX';
|
|
|
|
default: return null;
|
|
}
|
|
};
|
|
|
|
export const fromPixelFormat = (pixelFormat: VideoSamplePixelFormat) => {
|
|
switch (pixelFormat) {
|
|
case 'I420': return NodeAv.AV_PIX_FMT_YUV420P;
|
|
case 'I420P10': return NodeAv.AV_PIX_FMT_YUV420P10LE;
|
|
case 'I420P12': return NodeAv.AV_PIX_FMT_YUV420P12LE;
|
|
case 'I420A': return NodeAv.AV_PIX_FMT_YUVA420P;
|
|
case 'I420AP10': return NodeAv.AV_PIX_FMT_YUVA420P10LE;
|
|
|
|
case 'I422': return NodeAv.AV_PIX_FMT_YUV422P;
|
|
case 'I422P10': return NodeAv.AV_PIX_FMT_YUV422P10LE;
|
|
case 'I422P12': return NodeAv.AV_PIX_FMT_YUV422P12LE;
|
|
case 'I422A': return NodeAv.AV_PIX_FMT_YUVA422P;
|
|
case 'I422AP10': return NodeAv.AV_PIX_FMT_YUVA422P10LE;
|
|
case 'I422AP12': return NodeAv.AV_PIX_FMT_YUVA422P12LE;
|
|
|
|
case 'I444': return NodeAv.AV_PIX_FMT_YUV444P;
|
|
case 'I444P10': return NodeAv.AV_PIX_FMT_YUV444P10LE;
|
|
case 'I444P12': return NodeAv.AV_PIX_FMT_YUV444P12LE;
|
|
case 'I444A': return NodeAv.AV_PIX_FMT_YUVA444P;
|
|
case 'I444AP10': return NodeAv.AV_PIX_FMT_YUVA444P10LE;
|
|
case 'I444AP12': return NodeAv.AV_PIX_FMT_YUVA444P12LE;
|
|
|
|
case 'NV12': return NodeAv.AV_PIX_FMT_NV12;
|
|
|
|
case 'RGBA': return NodeAv.AV_PIX_FMT_RGBA;
|
|
case 'RGBX': return NodeAv.AV_PIX_FMT_RGB0;
|
|
case 'BGRA': return NodeAv.AV_PIX_FMT_BGRA;
|
|
case 'BGRX': return NodeAv.AV_PIX_FMT_BGR0;
|
|
|
|
default: return NodeAv.AV_PIX_FMT_NONE;
|
|
}
|
|
};
|
|
|
|
export const toAudioSampleFormat = (ffmpegSampleFormat: NodeAv.AVSampleFormat): AudioSampleFormat | null => {
|
|
switch (ffmpegSampleFormat) {
|
|
case NodeAv.AV_SAMPLE_FMT_U8: return 'u8';
|
|
case NodeAv.AV_SAMPLE_FMT_S16: return 's16';
|
|
case NodeAv.AV_SAMPLE_FMT_S32: return 's32';
|
|
case NodeAv.AV_SAMPLE_FMT_FLT: return 'f32';
|
|
case NodeAv.AV_SAMPLE_FMT_U8P: return 'u8-planar';
|
|
case NodeAv.AV_SAMPLE_FMT_S16P: return 's16-planar';
|
|
case NodeAv.AV_SAMPLE_FMT_S32P: return 's32-planar';
|
|
case NodeAv.AV_SAMPLE_FMT_FLTP: return 'f32-planar';
|
|
|
|
default: return null;
|
|
}
|
|
};
|
|
|
|
export const fromAudioSampleFormat = (sampleFormat: AudioSampleFormat): NodeAv.AVSampleFormat => {
|
|
switch (sampleFormat) {
|
|
case 'u8': return NodeAv.AV_SAMPLE_FMT_U8;
|
|
case 's16': return NodeAv.AV_SAMPLE_FMT_S16;
|
|
case 's32': return NodeAv.AV_SAMPLE_FMT_S32;
|
|
case 'f32': return NodeAv.AV_SAMPLE_FMT_FLT;
|
|
case 'u8-planar': return NodeAv.AV_SAMPLE_FMT_U8P;
|
|
case 's16-planar': return NodeAv.AV_SAMPLE_FMT_S16P;
|
|
case 's32-planar': return NodeAv.AV_SAMPLE_FMT_S32P;
|
|
case 'f32-planar': return NodeAv.AV_SAMPLE_FMT_FLTP;
|
|
|
|
default: return NodeAv.AV_SAMPLE_FMT_NONE;
|
|
}
|
|
};
|
|
|
|
export const getChannelLayout = (numChannels: number): NodeAv.ChannelLayout => {
|
|
switch (numChannels) {
|
|
case 1: return NodeAv.AV_CHANNEL_LAYOUT_MONO;
|
|
case 2: return NodeAv.AV_CHANNEL_LAYOUT_STEREO;
|
|
case 4: return NodeAv.AV_CHANNEL_LAYOUT_QUAD;
|
|
case 6: return NodeAv.AV_CHANNEL_LAYOUT_5POINT1_BACK;
|
|
case 8: return NodeAv.AV_CHANNEL_LAYOUT_7POINT1;
|
|
default: return { nbChannels: numChannels, order: NodeAv.AV_CHANNEL_ORDER_UNSPEC, mask: 0n };
|
|
}
|
|
};
|