mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-28 19:33:51 +02:00
105 lines
3.1 KiB
TypeScript
105 lines
3.1 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 { AudioSample, AudioSampleResource } from 'mediabunny';
|
|
import * as NodeAv from 'node-av';
|
|
import { fromAudioSampleFormat, getChannelLayout, toAudioSampleFormat } from './misc';
|
|
import { assert, toUint8Array } from '../../../src/misc';
|
|
|
|
/**
|
|
* A custom `AudioSampleResource` backed by NodeAV's
|
|
* [`Frame`](https://seydx.github.io/node-av/api/lib/classes/Frame.html), which in turn is backed by FFmpeg's
|
|
* [`AVFrame`](https://ffmpeg.org/doxygen/2.7/structAVFrame.html). You can use this resource to create `AudioSample`
|
|
* instances that are directly backed by FFmpeg's `AVFrame` without data having to be copied.
|
|
*
|
|
* When passed, the `Frame` is now owned by resource, meaning it takes care of closing the frame later. If you want to
|
|
* keep a copy for your own use, clone the frame first.
|
|
*
|
|
* @group \@mediabunny/server
|
|
* @public
|
|
*/
|
|
export class AvFrameAudioSampleResource extends AudioSampleResource {
|
|
/** @internal */
|
|
_frame: NodeAv.Frame | null;
|
|
|
|
/**
|
|
* The NodeAV [`Frame`](https://seydx.github.io/node-av/api/lib/classes/Frame.html) instance backing this resource.
|
|
* Access throws if the resource has already been closed.
|
|
*/
|
|
get frame() {
|
|
if (!this._frame) {
|
|
throw new Error('AvFrameAudioSampleResource has been closed.');
|
|
}
|
|
|
|
return this._frame;
|
|
}
|
|
|
|
constructor(frame: NodeAv.Frame) {
|
|
super();
|
|
|
|
if (!(frame instanceof NodeAv.Frame)) {
|
|
throw new TypeError('frame must be a NodeAv.Frame.');
|
|
}
|
|
if (frame.getMediaType() !== NodeAv.AVMEDIA_TYPE_AUDIO) {
|
|
throw new Error('AvFrameAudioSampleResource must be initialized with an audio frame.');
|
|
}
|
|
|
|
this._frame = frame;
|
|
}
|
|
|
|
getFormat(): AudioSampleFormat {
|
|
const result = toAudioSampleFormat(this.frame.format as NodeAv.AVSampleFormat);
|
|
if (result === null) {
|
|
const name = NodeAv.avGetSampleFmtName(this.frame.format as NodeAv.AVSampleFormat);
|
|
throw new TypeError(`Unsupported audio sample format: ${name} (${this.frame.format})`);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
getSampleRate(): number {
|
|
return this.frame.sampleRate;
|
|
}
|
|
|
|
getNumberOfChannels(): number {
|
|
return this.frame.channels;
|
|
}
|
|
|
|
getNumberOfFrames(): number {
|
|
return this.frame.nbSamples;
|
|
}
|
|
|
|
getTimestamp(): number {
|
|
return Number(this.frame.pts) / this.frame.timeBase.den;
|
|
}
|
|
|
|
close(): void {
|
|
this.frame.free();
|
|
this._frame = null;
|
|
}
|
|
|
|
getDataPlane(planeIndex: number): Uint8Array {
|
|
assert(this.frame.data && planeIndex < this.frame.data.length);
|
|
return toUint8Array(this.frame.data[planeIndex]!);
|
|
}
|
|
}
|
|
|
|
export const copyAudioSampleToAvFrame = (sample: AudioSample, frame: NodeAv.Frame) => {
|
|
frame.format = fromAudioSampleFormat(sample.format);
|
|
frame.nbSamples = sample.numberOfFrames;
|
|
frame.sampleRate = sample.sampleRate;
|
|
frame.channelLayout = getChannelLayout(sample.numberOfChannels);
|
|
|
|
frame.allocBuffer();
|
|
assert(frame.data);
|
|
|
|
for (let i = 0; i < frame.data.length; i++) {
|
|
sample.copyTo(frame.data[i]!, { planeIndex: i });
|
|
}
|
|
};
|