mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 10:53:50 +02:00
Add CPU path for transparent encoding and decoding, add transparency support to FFmpeg-backed VP9 decoder and encoder
This commit is contained in:
@@ -229,3 +229,6 @@ export const getChannelLayout = (numChannels: number): NodeAv.ChannelLayout => {
|
||||
default: return { nbChannels: numChannels, order: NodeAv.AV_CHANNEL_ORDER_UNSPEC, mask: 0n };
|
||||
}
|
||||
};
|
||||
|
||||
// The value is incorrect in the node-av source code, so we do:
|
||||
export const LIBVPX_VP9 = 'libvpx-vp9' as (NodeAv.FFEncoderCodec & NodeAv.FFDecoderCodec);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { CustomVideoDecoder, VideoCodec, EncodedPacket, VideoSample, MaybePromise, Rational } from 'mediabunny';
|
||||
import * as NodeAv from 'node-av';
|
||||
import { CODEC_TO_CODEC_ID, getHardwareDecoderCodec } from './misc';
|
||||
import { CODEC_TO_CODEC_ID, getHardwareDecoderCodec, LIBVPX_VP9 } from './misc';
|
||||
import { assert, binarySearchLessOrEqual, simplifyRational, toUint8Array } from '../../../src/misc';
|
||||
import { NodeAvFrameVideoSampleResource } from './video-sample';
|
||||
|
||||
export class NodeAvVideoDecoder extends CustomVideoDecoder {
|
||||
frame!: NodeAv.Frame;
|
||||
packet!: NodeAv.Packet;
|
||||
codecContext!: NodeAv.CodecContext;
|
||||
codecContext: NodeAv.CodecContext | null = null;
|
||||
pixelAspectRatio!: Rational;
|
||||
|
||||
// Bookkeeping to restore the original timing information
|
||||
@@ -29,12 +29,18 @@ export class NodeAvVideoDecoder extends CustomVideoDecoder {
|
||||
this.frame.alloc();
|
||||
this.packet = new NodeAv.Packet();
|
||||
this.packet.alloc();
|
||||
}
|
||||
|
||||
async initCodecContext(packet: EncodedPacket) {
|
||||
assert(this.codecContext === null);
|
||||
|
||||
const codecId = CODEC_TO_CODEC_ID[this.codec];
|
||||
assert(codecId !== undefined);
|
||||
|
||||
let codec: NodeAv.Codec | null;
|
||||
if (this.config.hardwareAcceleration !== 'prefer-hardware' || this.codec === 'av1') {
|
||||
if (this.codec === 'vp9' && packet.sideData.alpha) {
|
||||
codec = NodeAv.Codec.findDecoderByName(LIBVPX_VP9) ?? NodeAv.Codec.findDecoder(codecId);
|
||||
} else if (this.config.hardwareAcceleration !== 'prefer-hardware' || this.codec === 'av1') {
|
||||
// https://github.com/opencv/opencv/issues/24430
|
||||
codec = NodeAv.Codec.findDecoder(codecId);
|
||||
} else {
|
||||
@@ -69,6 +75,11 @@ export class NodeAvVideoDecoder extends CustomVideoDecoder {
|
||||
}
|
||||
|
||||
async decode(packet: EncodedPacket) {
|
||||
if (this.codecContext === null) {
|
||||
await this.initCodecContext(packet);
|
||||
assert(this.codecContext);
|
||||
}
|
||||
|
||||
this.packet.isKeyframe = packet.type === 'key';
|
||||
this.packet.data = Buffer.from(packet.data);
|
||||
this.packet.timeBase = { num: 1, den: 1e6 };
|
||||
@@ -76,6 +87,14 @@ export class NodeAvVideoDecoder extends CustomVideoDecoder {
|
||||
this.packet.dts = NodeAv.AV_NOPTS_VALUE;
|
||||
this.packet.duration = BigInt(packet.microsecondDuration);
|
||||
|
||||
if (packet.sideData.alpha) {
|
||||
const matroskaBlockAdditional = Buffer.alloc(8 + packet.sideData.alpha.byteLength);
|
||||
matroskaBlockAdditional[7] = 1; // BlockAddId
|
||||
matroskaBlockAdditional.set(packet.sideData.alpha, 8);
|
||||
|
||||
this.packet.addSideData(NodeAv.AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, matroskaBlockAdditional);
|
||||
}
|
||||
|
||||
const preciseTimingIndex = binarySearchLessOrEqual(
|
||||
this.preciseTimings,
|
||||
packet.microsecondTimestamp,
|
||||
@@ -161,6 +180,10 @@ export class NodeAvVideoDecoder extends CustomVideoDecoder {
|
||||
}
|
||||
|
||||
async flush() {
|
||||
if (!this.codecContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send null packet to signal flush
|
||||
const ret = await this.codecContext.sendPacket(null);
|
||||
NodeAv.FFmpegError.throwIfError(ret, 'Flush decoder');
|
||||
@@ -180,7 +203,7 @@ export class NodeAvVideoDecoder extends CustomVideoDecoder {
|
||||
}
|
||||
|
||||
close(): MaybePromise<void> {
|
||||
this.codecContext.freeContext();
|
||||
this.codecContext?.freeContext();
|
||||
this.frame.free();
|
||||
this.packet.free();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as NodeAv from 'node-av';
|
||||
import {
|
||||
CODEC_TO_CODEC_ID,
|
||||
getHardwareEncoderCodec,
|
||||
LIBVPX_VP9,
|
||||
unmapColorPrimaries,
|
||||
unmapMatrixCoefficients,
|
||||
unmapTransferCharacteristics,
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
} from '../../../src/codec-data';
|
||||
import { extractVideoCodecString } from '../../../src/codec';
|
||||
import { assert, binarySearchLessOrEqual, simplifyRational, toUint8Array } from '../../../src/misc';
|
||||
import { EncodedPacketSideData } from 'mediabunny';
|
||||
|
||||
export class NodeAvVideoEncoder extends CustomVideoEncoder {
|
||||
frame!: NodeAv.Frame;
|
||||
@@ -63,7 +65,9 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
|
||||
assert(codecId !== undefined);
|
||||
|
||||
let codec: NodeAv.Codec | null = null;
|
||||
if (this.config.hardwareAcceleration === 'prefer-software') {
|
||||
if (this.codec === 'vp9' && this.config.alpha === 'keep') {
|
||||
codec = NodeAv.Codec.findEncoderByName(LIBVPX_VP9) ?? NodeAv.Codec.findEncoder(codecId);
|
||||
} else if (this.config.hardwareAcceleration === 'prefer-software') {
|
||||
codec = NodeAv.Codec.findEncoder(codecId);
|
||||
} else {
|
||||
codec = getHardwareEncoderCodec(codecId) ?? NodeAv.Codec.findEncoder(codecId);
|
||||
@@ -85,8 +89,15 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
|
||||
codecContext.allocContext3(this.avCodec);
|
||||
|
||||
let pixelFormat = NodeAv.AV_PIX_FMT_YUV420P;
|
||||
if (this.avCodec.pixelFormats && !this.avCodec.pixelFormats.includes(NodeAv.AV_PIX_FMT_YUV420P)) {
|
||||
pixelFormat = this.avCodec.pixelFormats[0]!;
|
||||
|
||||
if (this.avCodec.pixelFormats) {
|
||||
if (!this.avCodec.pixelFormats.includes(NodeAv.AV_PIX_FMT_YUV420P)) {
|
||||
pixelFormat = this.avCodec.pixelFormats[0]!;
|
||||
}
|
||||
|
||||
if (this.config.alpha === 'keep' && this.avCodec.pixelFormats.includes(NodeAv.AV_PIX_FMT_YUVA420P)) {
|
||||
pixelFormat = NodeAv.AV_PIX_FMT_YUVA420P;
|
||||
}
|
||||
}
|
||||
|
||||
const pixelAspectRatio = simplifyRational({
|
||||
@@ -451,11 +462,20 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
|
||||
throw new Error('Unreachable.');
|
||||
}
|
||||
|
||||
const sideData: EncodedPacketSideData = {};
|
||||
const matroskaBlockAdditional = this.packet.getSideData(NodeAv.AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL);
|
||||
if (matroskaBlockAdditional) {
|
||||
sideData.alpha = toUint8Array(matroskaBlockAdditional).subarray(8); // Skip the BlockAddId
|
||||
}
|
||||
|
||||
const packet = new EncodedPacket(
|
||||
packetData,
|
||||
this.packet.isKeyframe ? 'key' : 'delta',
|
||||
timestamp,
|
||||
duration,
|
||||
undefined,
|
||||
undefined,
|
||||
sideData,
|
||||
);
|
||||
|
||||
if (decoderConfigCodecString !== null) {
|
||||
|
||||
Reference in New Issue
Block a user