mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-03 13:53:53 +02:00
Remove sample correlation logic in decoder pipeline
This commit is contained in:
@@ -11,6 +11,7 @@ export interface InputTrackBacking {
|
|||||||
getFirstTimestamp(): Promise<number>;
|
getFirstTimestamp(): Promise<number>;
|
||||||
computeDuration(): Promise<number>;
|
computeDuration(): Promise<number>;
|
||||||
getLanguageCode(): Promise<string>;
|
getLanguageCode(): Promise<string>;
|
||||||
|
getTimeResolution(): Promise<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
@@ -52,6 +53,10 @@ export abstract class InputTrack {
|
|||||||
getLanguageCode() {
|
getLanguageCode() {
|
||||||
return this._backing.getLanguageCode();
|
return this._backing.getLanguageCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTimeResolution() {
|
||||||
|
return this._backing.getTimeResolution();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InputVideoTrackBacking extends InputTrackBacking {
|
export interface InputVideoTrackBacking extends InputTrackBacking {
|
||||||
|
|||||||
@@ -1743,6 +1743,10 @@ abstract class IsobmffTrackBacking<
|
|||||||
return firstSample?.timestamp ?? 0;
|
return firstSample?.timestamp ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTimeResolution() {
|
||||||
|
return this.internalTrack.timescale;
|
||||||
|
}
|
||||||
|
|
||||||
abstract createSample(
|
abstract createSample(
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
byteLength: number,
|
byteLength: number,
|
||||||
|
|||||||
@@ -998,6 +998,10 @@ abstract class MatroskaTrackBacking<
|
|||||||
return firstSample?.timestamp ?? 0;
|
return firstSample?.timestamp ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTimeResolution() {
|
||||||
|
return this.internalTrack.segment.timestampFactor;
|
||||||
|
}
|
||||||
|
|
||||||
abstract createSample(
|
abstract createSample(
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
byteLength: number,
|
byteLength: number,
|
||||||
|
|||||||
+29
-47
@@ -4,7 +4,6 @@ import { InputAudioTrack, InputVideoTrack } from './input-track';
|
|||||||
import {
|
import {
|
||||||
AnyIterable,
|
AnyIterable,
|
||||||
assert,
|
assert,
|
||||||
binarySearchLessOrEqual,
|
|
||||||
getInt24,
|
getInt24,
|
||||||
getUint24,
|
getUint24,
|
||||||
mapAsyncGenerator,
|
mapAsyncGenerator,
|
||||||
@@ -140,9 +139,8 @@ export abstract class BaseSampleSink<Sample extends EncodedVideoSample | Encoded
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WrappedMediaFrame<T extends VideoFrame | AudioData, S extends EncodedVideoSample | EncodedAudioSample> = {
|
export type WrappedMediaFrame<T extends VideoFrame | AudioData> = {
|
||||||
frame: T;
|
frame: T;
|
||||||
sample: S;
|
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
};
|
};
|
||||||
@@ -150,7 +148,7 @@ export type WrappedMediaFrame<T extends VideoFrame | AudioData, S extends Encode
|
|||||||
abstract class DecoderWrapper<
|
abstract class DecoderWrapper<
|
||||||
Sample extends EncodedVideoSample | EncodedAudioSample,
|
Sample extends EncodedVideoSample | EncodedAudioSample,
|
||||||
MediaFrame extends VideoFrame | AudioData,
|
MediaFrame extends VideoFrame | AudioData,
|
||||||
WrappedFrame extends WrappedMediaFrame<MediaFrame, Sample> = WrappedMediaFrame<MediaFrame, Sample>,
|
WrappedFrame extends WrappedMediaFrame<MediaFrame> = WrappedMediaFrame<MediaFrame>,
|
||||||
> {
|
> {
|
||||||
constructor(
|
constructor(
|
||||||
public onFrame: (frame: WrappedFrame) => unknown,
|
public onFrame: (frame: WrappedFrame) => unknown,
|
||||||
@@ -168,7 +166,7 @@ export abstract class BaseMediaFrameSink<
|
|||||||
Sample extends EncodedVideoSample | EncodedAudioSample,
|
Sample extends EncodedVideoSample | EncodedAudioSample,
|
||||||
MediaFrame extends VideoFrame | AudioData,
|
MediaFrame extends VideoFrame | AudioData,
|
||||||
/** @internal */
|
/** @internal */
|
||||||
WrappedFrame extends WrappedMediaFrame<MediaFrame, Sample> = WrappedMediaFrame<MediaFrame, Sample>,
|
WrappedFrame extends WrappedMediaFrame<MediaFrame> = WrappedMediaFrame<MediaFrame>,
|
||||||
> {
|
> {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
abstract _createDecoder(
|
abstract _createDecoder(
|
||||||
@@ -361,7 +359,7 @@ export abstract class BaseMediaFrameSink<
|
|||||||
): AsyncGenerator<WrappedFrame | null, void, unknown> {
|
): AsyncGenerator<WrappedFrame | null, void, unknown> {
|
||||||
validateAnyIterable(timestamps);
|
validateAnyIterable(timestamps);
|
||||||
const timestampIterator = toAsyncIterator(timestamps);
|
const timestampIterator = toAsyncIterator(timestamps);
|
||||||
const samplesOfInterest: Sample[] = [];
|
const timestampsOfInterest: number[] = [];
|
||||||
|
|
||||||
const MAX_QUEUE_SIZE = 8;
|
const MAX_QUEUE_SIZE = 8;
|
||||||
const frameQueue: (WrappedFrame | null)[] = [];
|
const frameQueue: (WrappedFrame | null)[] = [];
|
||||||
@@ -395,11 +393,11 @@ export abstract class BaseMediaFrameSink<
|
|||||||
|
|
||||||
let frameUsed = false;
|
let frameUsed = false;
|
||||||
while (
|
while (
|
||||||
samplesOfInterest.length > 0
|
timestampsOfInterest.length > 0
|
||||||
&& samplesOfInterest[0]!.is(wrappedFrame.sample as EncodedVideoSample & EncodedAudioSample)
|
&& wrappedFrame.timestamp - timestampsOfInterest[0]! > -1e-10 // Give it a little epsilon
|
||||||
) {
|
) {
|
||||||
pushToQueue(this._duplicateFrame(wrappedFrame));
|
pushToQueue(this._duplicateFrame(wrappedFrame));
|
||||||
samplesOfInterest.shift();
|
timestampsOfInterest.shift();
|
||||||
frameUsed = true;
|
frameUsed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,7 +443,7 @@ export abstract class BaseMediaFrameSink<
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
samplesOfInterest.push(targetSample);
|
timestampsOfInterest.push(targetSample.timestamp);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
lastKeySample
|
lastKeySample
|
||||||
@@ -454,13 +452,13 @@ export abstract class BaseMediaFrameSink<
|
|||||||
) {
|
) {
|
||||||
assert(lastSample);
|
assert(lastSample);
|
||||||
|
|
||||||
if (targetSample.timestamp === lastSample.timestamp && samplesOfInterest.length === 1) {
|
if (targetSample.timestamp === lastSample.timestamp && timestampsOfInterest.length === 1) {
|
||||||
// Special case: We have a repeat sample, but the frame for that sample has already been
|
// Special case: We have a repeat sample, but the frame for that sample has already been
|
||||||
// decoded. Therefore, we need to push the frame here instead of in the decoder callback.
|
// decoded. Therefore, we need to push the frame here instead of in the decoder callback.
|
||||||
if (lastUsedFrame) {
|
if (lastUsedFrame) {
|
||||||
pushToQueue(this._duplicateFrame(lastUsedFrame));
|
pushToQueue(this._duplicateFrame(lastUsedFrame));
|
||||||
}
|
}
|
||||||
samplesOfInterest.shift();
|
timestampsOfInterest.shift();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lastKeySample = keySample;
|
lastKeySample = keySample;
|
||||||
@@ -581,32 +579,27 @@ export class EncodedVideoSampleSink extends BaseSampleSink<EncodedVideoSample> {
|
|||||||
|
|
||||||
class VideoDecoderWrapper extends DecoderWrapper<EncodedVideoSample, VideoFrame> {
|
class VideoDecoderWrapper extends DecoderWrapper<EncodedVideoSample, VideoFrame> {
|
||||||
decoder: VideoDecoder | null = null;
|
decoder: VideoDecoder | null = null;
|
||||||
pendingSamples: EncodedVideoSample[] = [];
|
|
||||||
|
|
||||||
customDecoder: CustomVideoDecoder | null = null;
|
customDecoder: CustomVideoDecoder | null = null;
|
||||||
lastCustomDecoderPromise = Promise.resolve();
|
lastCustomDecoderPromise = Promise.resolve();
|
||||||
customDecoderQueueSize = 0;
|
customDecoderQueueSize = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
onFrame: (frame: WrappedMediaFrame<VideoFrame, EncodedVideoSample>) => unknown,
|
onFrame: (frame: WrappedMediaFrame<VideoFrame>) => unknown,
|
||||||
onError: (error: DOMException) => unknown,
|
onError: (error: DOMException) => unknown,
|
||||||
codec: VideoCodec,
|
codec: VideoCodec,
|
||||||
decoderConfig: VideoDecoderConfig,
|
decoderConfig: VideoDecoderConfig,
|
||||||
|
timeResolution: number,
|
||||||
) {
|
) {
|
||||||
super(onFrame, onError);
|
super(onFrame, onError);
|
||||||
|
|
||||||
const frameHandler = (frame: VideoFrame) => {
|
const frameHandler = (frame: VideoFrame) => {
|
||||||
const sample = this.pendingSamples.shift();
|
// Round the microsecond timestamps to the time resolution
|
||||||
assert(sample);
|
const timestamp = Math.round(frame.timestamp / 1e6 * timeResolution) / timeResolution;
|
||||||
|
const duration = Math.round((frame.duration ?? 0) / 1e6 * timeResolution) / timeResolution;
|
||||||
// Let's get these from the sample instead of the frame, as the frame has no innate timing info
|
|
||||||
// (unlike AudioData), so the sample will always be more accurate.
|
|
||||||
const timestamp = sample.timestamp;
|
|
||||||
const duration = sample.duration;
|
|
||||||
|
|
||||||
onFrame({
|
onFrame({
|
||||||
frame,
|
frame,
|
||||||
sample,
|
|
||||||
timestamp,
|
timestamp,
|
||||||
duration,
|
duration,
|
||||||
});
|
});
|
||||||
@@ -634,10 +627,6 @@ class VideoDecoderWrapper extends DecoderWrapper<EncodedVideoSample, VideoFrame>
|
|||||||
}
|
}
|
||||||
|
|
||||||
decode(sample: EncodedVideoSample) {
|
decode(sample: EncodedVideoSample) {
|
||||||
// We know the decoder spits out frames in sorted order, so we need to insert the sample in the right place
|
|
||||||
const insertionIndex = binarySearchLessOrEqual(this.pendingSamples, sample.timestamp, x => x.timestamp);
|
|
||||||
this.pendingSamples.splice(insertionIndex + 1, 0, sample);
|
|
||||||
|
|
||||||
if (this.customDecoder) {
|
if (this.customDecoder) {
|
||||||
this.customDecoderQueueSize++;
|
this.customDecoderQueueSize++;
|
||||||
this.lastCustomDecoderPromise = this.lastCustomDecoderPromise.then(() => {
|
this.lastCustomDecoderPromise = this.lastCustomDecoderPromise.then(() => {
|
||||||
@@ -694,7 +683,7 @@ export class VideoFrameSink extends BaseMediaFrameSink<EncodedVideoSample, Video
|
|||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
async _createDecoder(
|
async _createDecoder(
|
||||||
onFrame: (frame: WrappedMediaFrame<VideoFrame, EncodedVideoSample>) => unknown,
|
onFrame: (frame: WrappedMediaFrame<VideoFrame>) => unknown,
|
||||||
onError: (error: DOMException) => unknown,
|
onError: (error: DOMException) => unknown,
|
||||||
) {
|
) {
|
||||||
if (!(await this._videoTrack.canDecode())) {
|
if (!(await this._videoTrack.canDecode())) {
|
||||||
@@ -706,9 +695,10 @@ export class VideoFrameSink extends BaseMediaFrameSink<EncodedVideoSample, Video
|
|||||||
|
|
||||||
const codec = await this._videoTrack.getCodec();
|
const codec = await this._videoTrack.getCodec();
|
||||||
const decoderConfig = await this._videoTrack.getDecoderConfig();
|
const decoderConfig = await this._videoTrack.getDecoderConfig();
|
||||||
|
const timeResolution = await this._videoTrack.getTimeResolution();
|
||||||
assert(codec && decoderConfig);
|
assert(codec && decoderConfig);
|
||||||
|
|
||||||
return new VideoDecoderWrapper(onFrame, onError, codec, decoderConfig);
|
return new VideoDecoderWrapper(onFrame, onError, codec, decoderConfig, timeResolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
@@ -717,7 +707,7 @@ export class VideoFrameSink extends BaseMediaFrameSink<EncodedVideoSample, Video
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_wrappedFrameToWrappedVideoFrame(frame: WrappedMediaFrame<VideoFrame, EncodedVideoSample>): WrappedVideoFrame {
|
_wrappedFrameToWrappedVideoFrame(frame: WrappedMediaFrame<VideoFrame>): WrappedVideoFrame {
|
||||||
return {
|
return {
|
||||||
frame: frame.frame,
|
frame: frame.frame,
|
||||||
timestamp: frame.timestamp,
|
timestamp: frame.timestamp,
|
||||||
@@ -888,31 +878,27 @@ export class EncodedAudioSampleSink extends BaseSampleSink<EncodedAudioSample> {
|
|||||||
|
|
||||||
class AudioDecoderWrapper extends DecoderWrapper<EncodedAudioSample, AudioData> {
|
class AudioDecoderWrapper extends DecoderWrapper<EncodedAudioSample, AudioData> {
|
||||||
decoder: AudioDecoder | null = null;
|
decoder: AudioDecoder | null = null;
|
||||||
pendingSamples: EncodedAudioSample[] = [];
|
|
||||||
|
|
||||||
customDecoder: CustomAudioDecoder | null = null;
|
customDecoder: CustomAudioDecoder | null = null;
|
||||||
lastCustomDecoderPromise = Promise.resolve();
|
lastCustomDecoderPromise = Promise.resolve();
|
||||||
customDecoderQueueSize = 0;
|
customDecoderQueueSize = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
onData: (data: WrappedMediaFrame<AudioData, EncodedAudioSample>) => unknown,
|
onData: (data: WrappedMediaFrame<AudioData>) => unknown,
|
||||||
onError: (error: DOMException) => unknown,
|
onError: (error: DOMException) => unknown,
|
||||||
codec: AudioCodec,
|
codec: AudioCodec,
|
||||||
decoderConfig: AudioDecoderConfig,
|
decoderConfig: AudioDecoderConfig,
|
||||||
|
timeResolution: number,
|
||||||
) {
|
) {
|
||||||
super(onData, onError);
|
super(onData, onError);
|
||||||
|
|
||||||
const dataHandler = (data: AudioData) => {
|
const dataHandler = (data: AudioData) => {
|
||||||
const sample = this.pendingSamples.shift();
|
// Round the microsecond timestamps to the time resolution
|
||||||
assert(sample);
|
const timestamp = Math.round(data.timestamp / 1e6 * timeResolution) / timeResolution;
|
||||||
|
const duration = Math.round(data.duration / 1e6 * timeResolution) / timeResolution;
|
||||||
// We use the timing information from the data instead of sample as it will be more accurate
|
|
||||||
const timestamp = Math.round(data.timestamp / 1e6 * decoderConfig.sampleRate) / decoderConfig.sampleRate;
|
|
||||||
const duration = Math.round(data.duration / 1e6 * decoderConfig.sampleRate) / decoderConfig.sampleRate;
|
|
||||||
|
|
||||||
onData({
|
onData({
|
||||||
frame: data,
|
frame: data,
|
||||||
sample,
|
|
||||||
timestamp,
|
timestamp,
|
||||||
duration,
|
duration,
|
||||||
});
|
});
|
||||||
@@ -940,10 +926,6 @@ class AudioDecoderWrapper extends DecoderWrapper<EncodedAudioSample, AudioData>
|
|||||||
}
|
}
|
||||||
|
|
||||||
decode(sample: EncodedAudioSample) {
|
decode(sample: EncodedAudioSample) {
|
||||||
// We know the decoder spits out data in sorted order, so we need to insert the sample in the right place
|
|
||||||
const insertionIndex = binarySearchLessOrEqual(this.pendingSamples, sample.timestamp, x => x.timestamp);
|
|
||||||
this.pendingSamples.splice(insertionIndex + 1, 0, sample);
|
|
||||||
|
|
||||||
if (this.customDecoder) {
|
if (this.customDecoder) {
|
||||||
this.customDecoderQueueSize++;
|
this.customDecoderQueueSize++;
|
||||||
this.lastCustomDecoderPromise = this.lastCustomDecoderPromise.then(() => {
|
this.lastCustomDecoderPromise = this.lastCustomDecoderPromise.then(() => {
|
||||||
@@ -993,7 +975,7 @@ class PcmAudioDecoderWrapper extends DecoderWrapper<EncodedAudioSample, AudioDat
|
|||||||
currentTimestamp: number | null = null;
|
currentTimestamp: number | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
onData: (data: WrappedMediaFrame<AudioData, EncodedAudioSample>) => unknown,
|
onData: (data: WrappedMediaFrame<AudioData>) => unknown,
|
||||||
onError: (error: DOMException) => unknown,
|
onError: (error: DOMException) => unknown,
|
||||||
public decoderConfig: AudioDecoderConfig,
|
public decoderConfig: AudioDecoderConfig,
|
||||||
) {
|
) {
|
||||||
@@ -1131,7 +1113,6 @@ class PcmAudioDecoderWrapper extends DecoderWrapper<EncodedAudioSample, AudioDat
|
|||||||
// Since all other decoders are async, we'll make this one behave async as well
|
// Since all other decoders are async, we'll make this one behave async as well
|
||||||
queueMicrotask(() => this.onFrame({
|
queueMicrotask(() => this.onFrame({
|
||||||
frame: audioData,
|
frame: audioData,
|
||||||
sample,
|
|
||||||
timestamp: preciseTimestamp,
|
timestamp: preciseTimestamp,
|
||||||
duration: preciseDuration,
|
duration: preciseDuration,
|
||||||
}));
|
}));
|
||||||
@@ -1170,7 +1151,7 @@ export class AudioDataSink extends BaseMediaFrameSink<EncodedAudioSample, AudioD
|
|||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
async _createDecoder(
|
async _createDecoder(
|
||||||
onData: (data: WrappedMediaFrame<AudioData, EncodedAudioSample>) => unknown,
|
onData: (data: WrappedMediaFrame<AudioData>) => unknown,
|
||||||
onError: (error: DOMException) => unknown,
|
onError: (error: DOMException) => unknown,
|
||||||
) {
|
) {
|
||||||
if (!(await this._audioTrack.canDecode())) {
|
if (!(await this._audioTrack.canDecode())) {
|
||||||
@@ -1187,12 +1168,13 @@ export class AudioDataSink extends BaseMediaFrameSink<EncodedAudioSample, AudioD
|
|||||||
if ((PCM_AUDIO_CODECS as readonly string[]).includes(decoderConfig.codec)) {
|
if ((PCM_AUDIO_CODECS as readonly string[]).includes(decoderConfig.codec)) {
|
||||||
return new PcmAudioDecoderWrapper(onData, onError, decoderConfig);
|
return new PcmAudioDecoderWrapper(onData, onError, decoderConfig);
|
||||||
} else {
|
} else {
|
||||||
return new AudioDecoderWrapper(onData, onError, codec, decoderConfig);
|
const timeResolution = await this._audioTrack.getTimeResolution();
|
||||||
|
return new AudioDecoderWrapper(onData, onError, codec, decoderConfig, timeResolution);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_wrappedFrameToWrappedAudioData(frame: WrappedMediaFrame<AudioData, EncodedAudioSample>): WrappedAudioData {
|
_wrappedFrameToWrappedAudioData(frame: WrappedMediaFrame<AudioData>): WrappedAudioData {
|
||||||
return {
|
return {
|
||||||
data: frame.frame,
|
data: frame.frame,
|
||||||
timestamp: frame.timestamp,
|
timestamp: frame.timestamp,
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import { EncodedAudioSample, PLACEHOLDER_DATA } from '../sample';
|
|||||||
import { FrameHeader, getXingOffset, INFO, XING } from './mp3-misc';
|
import { FrameHeader, getXingOffset, INFO, XING } from './mp3-misc';
|
||||||
import { Mp3Reader } from './mp3-reader';
|
import { Mp3Reader } from './mp3-reader';
|
||||||
|
|
||||||
const AUDIO_SAMPLES_PER_FRAME = 1152;
|
|
||||||
|
|
||||||
type Sample = {
|
type Sample = {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
@@ -46,7 +44,7 @@ export class Mp3Demuxer extends Demuxer {
|
|||||||
this.reader.pos += id3Tag.size;
|
this.reader.pos += id3Tag.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextTimestamp = 0;
|
let nextTimestampInSamples = 0;
|
||||||
|
|
||||||
// Let's read all samples
|
// Let's read all samples
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -71,16 +69,16 @@ export class Mp3Demuxer extends Demuxer {
|
|||||||
this.firstFrameHeader = header;
|
this.firstFrameHeader = header;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sampleDuration = AUDIO_SAMPLES_PER_FRAME / header.sampleRate;
|
const sampleDuration = header.audioSamplesInFrame / header.sampleRate;
|
||||||
const sample: Sample = {
|
const sample: Sample = {
|
||||||
timestamp: nextTimestamp,
|
timestamp: nextTimestampInSamples / header.sampleRate,
|
||||||
duration: sampleDuration,
|
duration: sampleDuration,
|
||||||
dataStart: header.startPos,
|
dataStart: header.startPos,
|
||||||
dataSize: header.totalSize,
|
dataSize: header.totalSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.allSamples.push(sample);
|
this.allSamples.push(sample);
|
||||||
nextTimestamp += sampleDuration;
|
nextTimestampInSamples += header.audioSamplesInFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.firstFrameHeader) {
|
if (!this.firstFrameHeader) {
|
||||||
@@ -121,6 +119,11 @@ class Mp3AudioTrackBacking implements InputAudioTrackBacking {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTimeResolution() {
|
||||||
|
assert(this.demuxer.firstFrameHeader);
|
||||||
|
return this.demuxer.firstFrameHeader.sampleRate / this.demuxer.firstFrameHeader.audioSamplesInFrame;
|
||||||
|
}
|
||||||
|
|
||||||
computeDuration() {
|
computeDuration() {
|
||||||
return this.demuxer.computeDuration();
|
return this.demuxer.computeDuration();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export type FrameHeader = {
|
|||||||
copyright: number;
|
copyright: number;
|
||||||
original: number;
|
original: number;
|
||||||
emphasis: number;
|
emphasis: number;
|
||||||
|
audioSamplesInFrame: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const computeMp3FrameSize = (layer: number, bitrate: number, sampleRate: number, padding: number) => {
|
export const computeMp3FrameSize = (layer: number, bitrate: number, sampleRate: number, padding: number) => {
|
||||||
@@ -114,6 +115,19 @@ export const readFrameHeader = (word: number, reader: { pos: number; fileSize: n
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let audioSamplesInFrame: number;
|
||||||
|
if (mpegVersionId === 3) {
|
||||||
|
audioSamplesInFrame = layer === 3 ? 384 : 1152;
|
||||||
|
} else {
|
||||||
|
if (layer === 3) {
|
||||||
|
audioSamplesInFrame = 384;
|
||||||
|
} else if (layer === 2) {
|
||||||
|
audioSamplesInFrame = 1152;
|
||||||
|
} else {
|
||||||
|
audioSamplesInFrame = 576;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startPos: startPos,
|
startPos: startPos,
|
||||||
totalSize: frameLength,
|
totalSize: frameLength,
|
||||||
@@ -127,5 +141,6 @@ export const readFrameHeader = (word: number, reader: { pos: number; fileSize: n
|
|||||||
copyright,
|
copyright,
|
||||||
original,
|
original,
|
||||||
emphasis,
|
emphasis,
|
||||||
|
audioSamplesInFrame,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -418,6 +418,10 @@ class OggAudioTrackBacking implements InputAudioTrackBacking {
|
|||||||
return this.bitstream.sampleRate;
|
return this.bitstream.sampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTimeResolution() {
|
||||||
|
return this.bitstream.sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
async getCodec() {
|
async getCodec() {
|
||||||
return this.bitstream.codec;
|
return this.bitstream.codec;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,6 +219,11 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
|
|||||||
return this.demuxer.audioInfo.sampleRate;
|
return this.demuxer.audioInfo.sampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTimeResolution() {
|
||||||
|
assert(this.demuxer.audioInfo);
|
||||||
|
return this.demuxer.audioInfo.sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
async getLanguageCode() {
|
async getLanguageCode() {
|
||||||
return UNDETERMINED_LANGUAGE;
|
return UNDETERMINED_LANGUAGE;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user