Fix some bugs & polish edge cases

This commit is contained in:
Vanilagy
2026-05-11 23:50:06 +02:00
parent d8c8606341
commit 81531cc510
8 changed files with 92 additions and 28 deletions
+6 -5
View File
@@ -1,7 +1,7 @@
import { AudioCodec, AudioSample, CustomAudioDecoder, EncodedPacket, MaybePromise } from 'mediabunny';
import * as NodeAv from 'node-av';
import { CODEC_TO_CODEC_ID, getChannelLayout } from './misc';
import { assert, roundToDivisor, toUint8Array } from '../../../src/misc';
import { assert, toUint8Array } from '../../../src/misc';
import { NodeAvFrameAudioSampleResource } from './audio-sample';
export class NodeAvAudioDecoder extends CustomAudioDecoder {
@@ -39,6 +39,7 @@ export class NodeAvAudioDecoder extends CustomAudioDecoder {
codecContext.sampleRate = this.config.sampleRate;
codecContext.channelLayout = getChannelLayout(this.config.numberOfChannels);
codecContext.timeBase = new NodeAv.Rational(1, this.config.sampleRate);
codecContext.codecType = NodeAv.AVMEDIA_TYPE_AUDIO;
codecContext.codecId = codecId;
codecContext.extraData = this.config.description
@@ -54,10 +55,10 @@ export class NodeAvAudioDecoder extends CustomAudioDecoder {
async decode(packet: EncodedPacket): Promise<void> {
this.packet.isKeyframe = packet.type === 'key';
this.packet.data = Buffer.from(packet.data);
this.packet.timeBase = { num: 1, den: 1e6 };
this.packet.pts = BigInt(packet.microsecondTimestamp);
this.packet.timeBase = { num: 1, den: this.config.sampleRate };
this.packet.pts = BigInt(Math.round(packet.timestamp * this.config.sampleRate));
this.packet.dts = NodeAv.AV_NOPTS_VALUE;
this.packet.duration = BigInt(packet.microsecondDuration);
this.packet.duration = BigInt(Math.round(packet.duration * this.config.sampleRate));
const ret = await this.codecContext.sendPacket(this.packet);
NodeAv.FFmpegError.throwIfError(ret, 'Send packet');
@@ -75,7 +76,7 @@ export class NodeAvAudioDecoder extends CustomAudioDecoder {
receiveFrame(ret: number) {
NodeAv.FFmpegError.throwIfError(ret, 'Receive frame');
const timestamp = roundToDivisor(Number(this.frame.pts) / 1e6, this.config.sampleRate);
const timestamp = Number(this.frame.pts) / this.config.sampleRate;
this.onSample(new AudioSample(new NodeAvFrameAudioSampleResource(this.frame, timestamp)));
}
+6 -3
View File
@@ -124,8 +124,7 @@ export class NodeAvAudioEncoder extends CustomAudioEncoder {
this.frame.nbSamples = audioSample.numberOfFrames;
this.frame.sampleRate = audioSample.sampleRate;
this.frame.channelLayout = getChannelLayout(audioSample.numberOfChannels);
this.frame.pts = BigInt(Math.round(audioSample.timestamp * audioSample.sampleRate));
this.frame.duration = BigInt(audioSample.numberOfFrames);
this.frame.duration = BigInt(Math.round(audioSample.duration * this.config.sampleRate));
this.frame.allocBuffer();
assert(this.frame.data);
@@ -135,6 +134,9 @@ export class NodeAvAudioEncoder extends CustomAudioEncoder {
}
}
this.frame.pts = BigInt(Math.round(audioSample.timestamp * this.config.sampleRate));
this.frame.timeBase = new NodeAv.Rational(1, this.config.sampleRate);
const key = `${this.frame.sampleRate}:${this.frame.channels}:${this.frame.format}`;
if (this.inputParametersKey !== null && this.inputParametersKey !== key) {
throw new Error(
@@ -314,8 +316,8 @@ export class NodeAvAudioEncoder extends CustomAudioEncoder {
duration,
);
this.onPacket(packet, metadata);
this.packetEmitted = true;
this.onPacket(packet, metadata);
}
async flush(): Promise<void> {
@@ -350,6 +352,7 @@ export class NodeAvAudioEncoder extends CustomAudioEncoder {
this.packetEmitted = false;
this.firstExpectedTimestamp = null;
this.outputTimestampOffset = 0;
this.adtsHeaderTemplate = null;
this.resampler?.free();
this.resampler = null;
+23 -5
View File
@@ -1,4 +1,12 @@
import { CustomVideoEncoder, MaybePromise, QUALITY_MEDIUM, VideoCodec, VideoSample, EncodedPacket } from 'mediabunny';
import {
CustomVideoEncoder,
MaybePromise,
QUALITY_MEDIUM,
VideoCodec,
VideoSample,
EncodedPacket,
EncodedPacketSideData,
} from 'mediabunny';
import * as NodeAv from 'node-av';
import {
CODEC_TO_CODEC_ID,
@@ -25,7 +33,6 @@ 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;
@@ -180,7 +187,6 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
}
this.lastBuffer = await copyVideoSampleToAvFrame(videoSample, this.frame, this.lastBuffer);
this.frame.keyFrame = options?.keyFrame ? 1 : 0;
}
let frameToEncode = this.frame;
@@ -225,6 +231,18 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
frameToEncode = this.dstFrame;
}
frameToEncode.pts = BigInt(videoSample.microsecondTimestamp);
frameToEncode.duration = BigInt(videoSample.microsecondDuration);
frameToEncode.timeBase = new NodeAv.Rational(1, 1e6);
// Let's just set both for good measure
frameToEncode.pictType = options?.keyFrame
? NodeAv.AV_PICTURE_TYPE_I
: NodeAv.AV_PICTURE_TYPE_NONE;
frameToEncode.keyFrame = options?.keyFrame
? 1
: 0;
const preciseTimingIndex = binarySearchLessOrEqual(
this.preciseTimings,
videoSample.microsecondTimestamp,
@@ -503,8 +521,8 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
};
}
this.onPacket(packet, metadata);
this.packetEmitted = true;
this.onPacket(packet, metadata);
}
async flush(): Promise<void> {
@@ -525,7 +543,7 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
this.codecContext.freeContext();
this.codecContext = null;
// The codec is done now and can't be reused. Any subsequence encode call will first need to recreate a
// The codec is done now and can't be reused. Any subsequent encode call will first need to recreate a
// codec context.
}
+7 -7
View File
@@ -1,7 +1,11 @@
import assert from 'assert';
import { VideoSamplePixelFormat } from 'mediabunny';
import { VideoSampleColorSpace } from 'mediabunny';
import { VideoSampleResource } from 'mediabunny';
import {
VideoSamplePixelFormat,
VideoSampleResource,
VideoSampleColorSpace,
SetRequired,
VideoSampleInit,
} from 'mediabunny';
import * as NodeAv from 'node-av';
import { MaybePromise, toUint8Array } from '../../../src/misc';
import { VideoSampleTransformationDescription, VideoDataPlane, VideoSample } from '../../../src/sample';
@@ -15,8 +19,6 @@ import {
mapMatrixCoefficients,
mapTransferCharacteristics,
} from './misc';
import { SetRequired } from 'mediabunny';
import { VideoSampleInit } from 'mediabunny';
const JPEG_RANGE_PIX_FORMATS = new Set([
NodeAv.AV_PIX_FMT_YUVJ411P,
@@ -140,8 +142,6 @@ export const copyVideoSampleToAvFrame = async (sample: VideoSample, frame: NodeA
sample.pixelAspectRatio.num,
sample.pixelAspectRatio.den,
);
frame.pts = BigInt(sample.microsecondTimestamp);
frame.duration = BigInt(sample.microsecondDuration);
frame.colorPrimaries = mapColorPrimaries(sample.colorSpace.primaries ?? 'unknown')
?? NodeAv.AVCOL_PRI_UNSPECIFIED;
frame.colorSpace = mapMatrixCoefficients(sample.colorSpace.matrix ?? 'unknown')