Replace ArrayBufferTarget with BufferTarget, now spitting out Uint8Array and using .resize

This commit is contained in:
Vanilagy
2025-01-05 14:24:06 +01:00
parent ee81e1b674
commit 37820f1d4c
6 changed files with 63 additions and 40 deletions
+3 -3
View File
@@ -44,7 +44,7 @@
//console.log(await videoTrack.computeSampleStats()); //console.log(await videoTrack.computeSampleStats());
//return; //return;
const target = new Metamuxer.ArrayBufferTarget(); const target = new Metamuxer.BufferTarget();
const output = new Metamuxer.Output({ const output = new Metamuxer.Output({
format: new Metamuxer.MovOutputFormat({ fastStart: undefined }), format: new Metamuxer.MovOutputFormat({ fastStart: undefined }),
target target
@@ -57,7 +57,7 @@
const decoderConfig = await audioTrack.getDecoderConfig(); const decoderConfig = await audioTrack.getDecoderConfig();
const sampleSource = new Metamuxer.EncodedAudioSampleSource(await audioTrack.getCodec()); 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()); const videoSampleSource = new Metamuxer.EncodedVideoSampleSource(await videoTrack.getCodec());
output.addAudioTrack(audioDataSource ?? sampleSource); output.addAudioTrack(audioDataSource ?? sampleSource);
output.addVideoTrack(videoSampleSource); output.addVideoTrack(videoSampleSource);
@@ -94,7 +94,7 @@
a.click(); a.click();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
download(new Blob([target.buffer]), 'converted.mov'); //download(new Blob([target.buffer]), 'converted.mov');
document.body.textContent = performance.now() - start; document.body.textContent = performance.now() - start;
+3 -9
View File
@@ -390,10 +390,8 @@ export const buildAudioCodecString = (codec: AudioCodec, numberOfChannels: numbe
return 'vorbis'; return 'vorbis';
} else if (codec === 'flac') { } else if (codec === 'flac') {
return 'flac'; return 'flac';
} else if (codec === 'ulaw') { } else if ((PCM_CODECS as readonly string[]).includes(codec)) {
return 'ulaw'; return codec;
} else if (codec === 'alaw') {
return 'alaw';
} }
throw new TypeError(`Unhandled codec '${codec}'.`); throw new TypeError(`Unhandled codec '${codec}'.`);
@@ -427,12 +425,8 @@ export const extractAudioCodecString = (trackInfo: {
return 'vorbis'; return 'vorbis';
} else if (codec === 'flac') { } else if (codec === 'flac') {
return 'flac'; return 'flac';
} else if (codec?.startsWith('pcm-')) { } else if (codec && (PCM_CODECS as readonly string[]).includes(codec)) {
return codec; return codec;
} else if (codec === 'ulaw') {
return 'ulaw';
} else if (codec === 'alaw') {
return 'alaw';
} }
throw new TypeError(`Unhandled codec '${codec}'.`); throw new TypeError(`Unhandled codec '${codec}'.`);
+1 -1
View File
@@ -44,7 +44,7 @@ export {
getEncodableAudioCodecs, getEncodableAudioCodecs,
getEncodableSubtitleCodecs, getEncodableSubtitleCodecs,
} from './codec'; } 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 { Rotation, TransformationMatrix, AnyIterable } from './misc';
export { Source, BufferSource, BlobSource, UrlSource } from './source'; export { Source, BufferSource, BlobSource, UrlSource } from './source';
export { export {
+4 -4
View File
@@ -1,11 +1,10 @@
import { Box, free, ftyp, IsobmffBoxWriter, mdat, mfra, moof, moov, vtta, vttc, vtte } from './isobmff-boxes'; import { Box, free, ftyp, IsobmffBoxWriter, mdat, mfra, moof, moov, vtta, vttc, vtte } from './isobmff-boxes';
import { Muxer } from '../muxer'; import { Muxer } from '../muxer';
import { Output, OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from '../output'; import { Output, OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from '../output';
import { ArrayBufferTargetWriter, Writer } from '../writer'; import { BufferTargetWriter, Writer } from '../writer';
import { assert, last } from '../misc'; import { assert, last } from '../misc';
import { IsobmffOutputFormatOptions, IsobmffOutputFormat, MovOutputFormat } from '../output-format'; import { IsobmffOutputFormatOptions, IsobmffOutputFormat, MovOutputFormat } from '../output-format';
import { inlineTimestampRegex, SubtitleConfig, SubtitleCue, SubtitleMetadata } from '../subtitles'; import { inlineTimestampRegex, SubtitleConfig, SubtitleCue, SubtitleMetadata } from '../subtitles';
import { ArrayBufferTarget } from '../target';
import { import {
parsePcmCodec, parsePcmCodec,
PCM_CODECS, PCM_CODECS,
@@ -15,6 +14,7 @@ import {
validateVideoChunkMetadata, validateVideoChunkMetadata,
} from '../codec'; } from '../codec';
import { EncodedAudioSample, EncodedVideoSample } from '../sample'; import { EncodedAudioSample, EncodedVideoSample } from '../sample';
import { BufferTarget } from '../target';
export const GLOBAL_TIMESCALE = 1000; export const GLOBAL_TIMESCALE = 1000;
const TIMESTAMP_OFFSET = 2_082_844_800; // Seconds between Jan 1 1904 and Jan 1 1970 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 isMov: boolean;
private fastStart: NonNullable<IsobmffOutputFormatOptions['fastStart']>; private fastStart: NonNullable<IsobmffOutputFormatOptions['fastStart']>;
private auxTarget = new ArrayBufferTarget(); private auxTarget = new BufferTarget();
private auxWriter = this.auxTarget._createWriter(); private auxWriter = this.auxTarget._createWriter();
private auxBoxWriter = new IsobmffBoxWriter(this.auxWriter); 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 // If the fastStart option isn't defined, enable in-memory fast start if the target is an ArrayBuffer, as the
// memory usage remains identical // 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; this.fastStart = format._options.fastStart ?? fastStartDefault;
if (this.fastStart === 'in-memory' || this.fastStart === 'fragmented') { if (this.fastStart === 'in-memory' || this.fastStart === 'fragmented') {
+4 -5
View File
@@ -1,4 +1,4 @@
import { ArrayBufferTargetWriter, ChunkedStreamTargetWriter, StreamTargetWriter, Writer } from './writer'; import { BufferTargetWriter, ChunkedStreamTargetWriter, StreamTargetWriter, Writer } from './writer';
import { Output } from './output'; import { Output } from './output';
/** @public */ /** @public */
@@ -11,13 +11,12 @@ export abstract class Target {
} }
/** @public */ /** @public */
// TODO: Switch to Uint8ArrayTarget for efficiency? export class BufferTarget extends Target {
export class ArrayBufferTarget extends Target { buffer: Uint8Array | null = null;
buffer: ArrayBuffer | null = null;
/** @internal */ /** @internal */
_createWriter() { _createWriter() {
return new ArrayBufferTargetWriter(this); return new BufferTargetWriter(this);
} }
} }
+48 -18
View File
@@ -1,4 +1,4 @@
import { ArrayBufferTarget, StreamTarget, StreamTargetChunk } from './target'; import { BufferTarget, StreamTarget, StreamTargetChunk } from './target';
import { assert } from './misc'; import { assert } from './misc';
export abstract class Writer { export abstract class Writer {
@@ -21,21 +21,36 @@ export abstract class Writer {
abstract close(): Promise<void>; abstract close(): Promise<void>;
} }
/** const ARRAY_BUFFER_INITIAL_SIZE = 2 ** 16;
* Writes to an ArrayBufferTarget. Maintains a growable internal buffer during the muxing process, which will then be const ARRAY_BUFFER_MAX_SIZE = 2 ** 32;
* 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;
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(); super();
this.target = target; 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) { private ensureSize(size: number) {
@@ -44,12 +59,27 @@ export class ArrayBufferTargetWriter extends Writer {
if (newLength === this.buffer.byteLength) return; if (newLength === this.buffer.byteLength) return;
const newBuffer = new ArrayBuffer(newLength); if (newLength > ARRAY_BUFFER_MAX_SIZE) {
const newBytes = new Uint8Array(newBuffer); throw new Error(
newBytes.set(this.bytes, 0); `ArrayBuffer exceeded maximum size of ${ARRAY_BUFFER_MAX_SIZE} bytes. Please consider using another`
+ ` target.`,
);
}
this.buffer = newBuffer; if (this.supportsResize) {
this.bytes = newBytes; // 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) { write(data: Uint8Array) {
@@ -73,7 +103,7 @@ export class ArrayBufferTargetWriter extends Writer {
async finalize() { async finalize() {
this.ensureSize(this.pos); 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() {} async close() {}