From 022b157eeb941f7028884fcd1add9ceecbc5d138 Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Thu, 2 Jan 2025 19:28:43 +0100 Subject: [PATCH] Turn AudioBuffers into AudioData in a chunked fashion --- .gitignore | 3 +- dev/index.html | 4 ++- src/isobmff/isobmff-demuxer.ts | 2 +- src/media-source.ts | 59 ++++++++++++++++++++++------------ todo.txt | 10 ++++++ 5 files changed, 53 insertions(+), 25 deletions(-) create mode 100644 todo.txt diff --git a/.gitignore b/.gitignore index d090bb7..57d3105 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /node_modules /build -/dist -todo.txt \ No newline at end of file +/dist \ No newline at end of file diff --git a/dev/index.html b/dev/index.html index f4aeb59..69808ee 100644 --- a/dev/index.html +++ b/dev/index.html @@ -40,6 +40,7 @@ format = new Metamuxer.Mp4OutputFormat({ fastStart: false }); // new Metamuxer.MkvOutputFormat();// new Metamuxer.Mp4OutputFormat({ fastStart: false }); let target = new Metamuxer.ArrayBufferTarget(); + /* target = new Metamuxer.StreamTarget(new WritableStream({ async write(chunk) { console.log(chunk) @@ -47,6 +48,7 @@ await new Promise(resolve => setTimeout(resolve, 250)); } }), { chunked: true }); + */ let output = new Metamuxer.Output({ format, target }); @@ -170,5 +172,5 @@ Testing... <00:17.350>One... <00:18.125>Two... await output.finalize(); console.log(target); - //download(new Blob([target.buffer]), 'test.mp4'); + download(new Blob([target.buffer]), 'test.mp4'); \ No newline at end of file diff --git a/src/isobmff/isobmff-demuxer.ts b/src/isobmff/isobmff-demuxer.ts index 640e5a6..3a781b1 100644 --- a/src/isobmff/isobmff-demuxer.ts +++ b/src/isobmff/isobmff-demuxer.ts @@ -1076,7 +1076,7 @@ export class IsobmffDemuxer extends Demuxer { const startPos = this.isobmffReader.pos; - while (true) { + while (this.isobmffReader.pos < boxEndPos) { const flagAndType = this.isobmffReader.readU8(); const metadataBlockLength = this.isobmffReader.readU24(); const type = flagAndType & BLOCK_TYPE_MASK; diff --git a/src/media-source.ts b/src/media-source.ts index a6d34d5..ae72bf8 100644 --- a/src/media-source.ts +++ b/src/media-source.ts @@ -517,32 +517,49 @@ export class AudioBufferSource extends AudioSource { throw new TypeError('audioBuffer must be an AudioBuffer.'); } + const MAX_FLOAT_COUNT = 64 * 1024 * 1024; + const numberOfChannels = audioBuffer.numberOfChannels; const sampleRate = audioBuffer.sampleRate; - const numberOfFrames = audioBuffer.length; + const totalFrames = audioBuffer.length; + const maxFramesPerChunk = Math.floor(MAX_FLOAT_COUNT / numberOfChannels); - // Create a planar F32 array containing all channels - const data = new Float32Array(numberOfChannels * numberOfFrames); - for (let channel = 0; channel < numberOfChannels; channel++) { - const channelData = audioBuffer.getChannelData(channel); - data.set(channelData, channel * numberOfFrames); + let currentRelativeFrame = 0; + let remainingFrames = totalFrames; + + const promises: Promise[] = []; + + // Create AudioData in a chunked fashion so we don't create huge Float32Arrays + while (remainingFrames > 0) { + const framesToCopy = Math.min(maxFramesPerChunk, remainingFrames); + const chunkData = new Float32Array(numberOfChannels * framesToCopy); + + for (let channel = 0; channel < numberOfChannels; channel++) { + audioBuffer.copyFromChannel( + chunkData.subarray(channel * framesToCopy, channel * framesToCopy + framesToCopy), + channel, + currentRelativeFrame, + ); + } + + const audioData = new AudioData({ + format: 'f32-planar', + sampleRate, + numberOfFrames: framesToCopy, + numberOfChannels, + timestamp: (1e6 * (this._accumulatedFrameCount + currentRelativeFrame)) / sampleRate, + data: chunkData, + }); + + promises.push(this._encoder.digest(audioData)); + audioData.close(); + + currentRelativeFrame += framesToCopy; + remainingFrames -= framesToCopy; } - const audioData = new AudioData({ - format: 'f32-planar', - sampleRate, - numberOfFrames, - numberOfChannels, - timestamp: Math.round(1e6 * this._accumulatedFrameCount / sampleRate), - data: data, - }); - - const promise = this._encoder.digest(audioData); - audioData.close(); - - this._accumulatedFrameCount += numberOfFrames; - - return promise; + this._accumulatedFrameCount += totalFrames; + return Promise.all(promises); } /** @internal */ diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..54f50c9 --- /dev/null +++ b/todo.txt @@ -0,0 +1,10 @@ +- Manual key frame passing +- Progress reporting +- Test tree shaking +- Throw if edit list detected +- Audio codec string regex validation +- Add the new audio codecs to muxers +- Mov muxer!!! Only mov can hold PCM audio, MP4 cannot +- Metadata methods for computing average fps and bitrate +- close method on Output to cancel it (and dispose the encoders). Perhaps abort is a better title +- lower default key frame interval (3 secs is better I think?) and make it outside-configurable \ No newline at end of file