From 37820f1d4c5f508fd47880df6fd99470a48b8590 Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Sun, 5 Jan 2025 14:24:06 +0100 Subject: [PATCH] Replace ArrayBufferTarget with BufferTarget, now spitting out Uint8Array and using .resize --- dev/demux.html | 6 ++-- src/codec.ts | 12 ++----- src/index.ts | 2 +- src/isobmff/isobmff-muxer.ts | 8 ++--- src/target.ts | 9 +++-- src/writer.ts | 66 ++++++++++++++++++++++++++---------- 6 files changed, 63 insertions(+), 40 deletions(-) diff --git a/dev/demux.html b/dev/demux.html index 8e00dc2..509d08e 100644 --- a/dev/demux.html +++ b/dev/demux.html @@ -44,7 +44,7 @@ //console.log(await videoTrack.computeSampleStats()); //return; - const target = new Metamuxer.ArrayBufferTarget(); + const target = new Metamuxer.BufferTarget(); const output = new Metamuxer.Output({ format: new Metamuxer.MovOutputFormat({ fastStart: undefined }), target @@ -57,7 +57,7 @@ const decoderConfig = await audioTrack.getDecoderConfig(); const sampleSource = new Metamuxer.EncodedAudioSampleSource(await audioTrack.getCodec()); - const audioDataSource = new Metamuxer.AudioDataSource({ codec: 'ulaw' }); + const audioDataSource = new Metamuxer.AudioDataSource({ codec: 'aac', bitrate: 128e3 }); const videoSampleSource = new Metamuxer.EncodedVideoSampleSource(await videoTrack.getCodec()); output.addAudioTrack(audioDataSource ?? sampleSource); output.addVideoTrack(videoSampleSource); @@ -94,7 +94,7 @@ a.click(); URL.revokeObjectURL(url); } - download(new Blob([target.buffer]), 'converted.mov'); + //download(new Blob([target.buffer]), 'converted.mov'); document.body.textContent = performance.now() - start; diff --git a/src/codec.ts b/src/codec.ts index a91abb3..f33d1fc 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -390,10 +390,8 @@ export const buildAudioCodecString = (codec: AudioCodec, numberOfChannels: numbe return 'vorbis'; } else if (codec === 'flac') { return 'flac'; - } else if (codec === 'ulaw') { - return 'ulaw'; - } else if (codec === 'alaw') { - return 'alaw'; + } else if ((PCM_CODECS as readonly string[]).includes(codec)) { + return codec; } throw new TypeError(`Unhandled codec '${codec}'.`); @@ -427,12 +425,8 @@ export const extractAudioCodecString = (trackInfo: { return 'vorbis'; } else if (codec === 'flac') { return 'flac'; - } else if (codec?.startsWith('pcm-')) { + } else if (codec && (PCM_CODECS as readonly string[]).includes(codec)) { return codec; - } else if (codec === 'ulaw') { - return 'ulaw'; - } else if (codec === 'alaw') { - return 'alaw'; } throw new TypeError(`Unhandled codec '${codec}'.`); diff --git a/src/index.ts b/src/index.ts index 857fe05..db91b06 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,7 +44,7 @@ export { getEncodableAudioCodecs, getEncodableSubtitleCodecs, } from './codec'; -export { Target, ArrayBufferTarget, StreamTarget, StreamTargetChunk, StreamTargetOptions } from './target'; +export { Target, BufferTarget, StreamTarget, StreamTargetChunk, StreamTargetOptions } from './target'; export { Rotation, TransformationMatrix, AnyIterable } from './misc'; export { Source, BufferSource, BlobSource, UrlSource } from './source'; export { diff --git a/src/isobmff/isobmff-muxer.ts b/src/isobmff/isobmff-muxer.ts index 74d7c6b..9589964 100644 --- a/src/isobmff/isobmff-muxer.ts +++ b/src/isobmff/isobmff-muxer.ts @@ -1,11 +1,10 @@ import { Box, free, ftyp, IsobmffBoxWriter, mdat, mfra, moof, moov, vtta, vttc, vtte } from './isobmff-boxes'; import { Muxer } from '../muxer'; import { Output, OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from '../output'; -import { ArrayBufferTargetWriter, Writer } from '../writer'; +import { BufferTargetWriter, Writer } from '../writer'; import { assert, last } from '../misc'; import { IsobmffOutputFormatOptions, IsobmffOutputFormat, MovOutputFormat } from '../output-format'; import { inlineTimestampRegex, SubtitleConfig, SubtitleCue, SubtitleMetadata } from '../subtitles'; -import { ArrayBufferTarget } from '../target'; import { parsePcmCodec, PCM_CODECS, @@ -15,6 +14,7 @@ import { validateVideoChunkMetadata, } from '../codec'; import { EncodedAudioSample, EncodedVideoSample } from '../sample'; +import { BufferTarget } from '../target'; export const GLOBAL_TIMESCALE = 1000; const TIMESTAMP_OFFSET = 2_082_844_800; // Seconds between Jan 1 1904 and Jan 1 1970 @@ -102,7 +102,7 @@ export class IsobmffMuxer extends Muxer { private isMov: boolean; private fastStart: NonNullable; - private auxTarget = new ArrayBufferTarget(); + private auxTarget = new BufferTarget(); private auxWriter = this.auxTarget._createWriter(); private auxBoxWriter = new IsobmffBoxWriter(this.auxWriter); @@ -126,7 +126,7 @@ export class IsobmffMuxer extends Muxer { // If the fastStart option isn't defined, enable in-memory fast start if the target is an ArrayBuffer, as the // memory usage remains identical - const fastStartDefault = this.writer instanceof ArrayBufferTargetWriter ? 'in-memory' : false; + const fastStartDefault = this.writer instanceof BufferTargetWriter ? 'in-memory' : false; this.fastStart = format._options.fastStart ?? fastStartDefault; if (this.fastStart === 'in-memory' || this.fastStart === 'fragmented') { diff --git a/src/target.ts b/src/target.ts index 2d69844..743a889 100644 --- a/src/target.ts +++ b/src/target.ts @@ -1,4 +1,4 @@ -import { ArrayBufferTargetWriter, ChunkedStreamTargetWriter, StreamTargetWriter, Writer } from './writer'; +import { BufferTargetWriter, ChunkedStreamTargetWriter, StreamTargetWriter, Writer } from './writer'; import { Output } from './output'; /** @public */ @@ -11,13 +11,12 @@ export abstract class Target { } /** @public */ -// TODO: Switch to Uint8ArrayTarget for efficiency? -export class ArrayBufferTarget extends Target { - buffer: ArrayBuffer | null = null; +export class BufferTarget extends Target { + buffer: Uint8Array | null = null; /** @internal */ _createWriter() { - return new ArrayBufferTargetWriter(this); + return new BufferTargetWriter(this); } } diff --git a/src/writer.ts b/src/writer.ts index 2fcaf62..361e469 100644 --- a/src/writer.ts +++ b/src/writer.ts @@ -1,4 +1,4 @@ -import { ArrayBufferTarget, StreamTarget, StreamTargetChunk } from './target'; +import { BufferTarget, StreamTarget, StreamTargetChunk } from './target'; import { assert } from './misc'; export abstract class Writer { @@ -21,21 +21,36 @@ export abstract class Writer { abstract close(): Promise; } -/** - * Writes to an ArrayBufferTarget. Maintains a growable internal buffer during the muxing process, which will then be - * written to the ArrayBufferTarget once the muxing finishes. - */ -export class ArrayBufferTargetWriter extends Writer { - private pos = 0; - private target: ArrayBufferTarget; - private buffer = new ArrayBuffer(2 ** 16); - private bytes = new Uint8Array(this.buffer); - private maxPos = 0; +const ARRAY_BUFFER_INITIAL_SIZE = 2 ** 16; +const ARRAY_BUFFER_MAX_SIZE = 2 ** 32; - constructor(target: ArrayBufferTarget) { +export class BufferTargetWriter extends Writer { + private pos = 0; + private target: BufferTarget; + private buffer: ArrayBuffer; + private bytes: Uint8Array; + private maxPos = 0; + private supportsResize: boolean; + + constructor(target: BufferTarget) { super(); this.target = target; + + this.supportsResize = 'resize' in new ArrayBuffer(0); + if (this.supportsResize) { + try { + // @ts-expect-error Don't want to bump "lib" in tsconfig + this.buffer = new ArrayBuffer(ARRAY_BUFFER_INITIAL_SIZE, { maxByteLength: ARRAY_BUFFER_MAX_SIZE }); + } catch { + this.buffer = new ArrayBuffer(ARRAY_BUFFER_INITIAL_SIZE); + this.supportsResize = false; + } + } else { + this.buffer = new ArrayBuffer(ARRAY_BUFFER_INITIAL_SIZE); + } + + this.bytes = new Uint8Array(this.buffer); } private ensureSize(size: number) { @@ -44,12 +59,27 @@ export class ArrayBufferTargetWriter extends Writer { if (newLength === this.buffer.byteLength) return; - const newBuffer = new ArrayBuffer(newLength); - const newBytes = new Uint8Array(newBuffer); - newBytes.set(this.bytes, 0); + if (newLength > ARRAY_BUFFER_MAX_SIZE) { + throw new Error( + `ArrayBuffer exceeded maximum size of ${ARRAY_BUFFER_MAX_SIZE} bytes. Please consider using another` + + ` target.`, + ); + } - this.buffer = newBuffer; - this.bytes = newBytes; + if (this.supportsResize) { + // Use resize if it exists + // @ts-expect-error Don't want to bump "lib" in tsconfig + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + this.buffer.resize(newLength); + // The Uint8Array scales automatically + } else { + const newBuffer = new ArrayBuffer(newLength); + const newBytes = new Uint8Array(newBuffer); + newBytes.set(this.bytes, 0); + + this.buffer = newBuffer; + this.bytes = newBytes; + } } write(data: Uint8Array) { @@ -73,7 +103,7 @@ export class ArrayBufferTargetWriter extends Writer { async finalize() { this.ensureSize(this.pos); - this.target.buffer = this.buffer.slice(0, Math.max(this.maxPos, this.pos)); + this.target.buffer = this.bytes.subarray(0, Math.max(this.maxPos, this.pos)); } async close() {}