mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-03 13:53:53 +02:00
Make Matroska block decoding lazy
This commit is contained in:
+7
-5
@@ -54,7 +54,8 @@
|
|||||||
source
|
source
|
||||||
}),
|
}),
|
||||||
output,
|
output,
|
||||||
audio: {
|
audio: (_, n) => ({
|
||||||
|
discard: n > 1,
|
||||||
//codec: 'pcm-s16',
|
//codec: 'pcm-s16',
|
||||||
//sampleRate: 16000,
|
//sampleRate: 16000,
|
||||||
//numberOfChannels: 1,
|
//numberOfChannels: 1,
|
||||||
@@ -65,7 +66,7 @@
|
|||||||
//sampleRate: 4000
|
//sampleRate: 4000
|
||||||
//discard: true
|
//discard: true
|
||||||
//forceTranscode: true,
|
//forceTranscode: true,
|
||||||
},
|
}),
|
||||||
/*
|
/*
|
||||||
video: {
|
video: {
|
||||||
discard: true,
|
discard: true,
|
||||||
@@ -91,6 +92,7 @@
|
|||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
video: () => ({
|
video: () => ({
|
||||||
|
discard: true,
|
||||||
//discard: true,
|
//discard: true,
|
||||||
//crop: {
|
//crop: {
|
||||||
// left: 0,
|
// left: 0,
|
||||||
@@ -124,7 +126,7 @@
|
|||||||
//width: 200,
|
//width: 200,
|
||||||
//height: 100,
|
//height: 100,
|
||||||
}),
|
}),
|
||||||
tags: {
|
tags: {} ?? {
|
||||||
title: 'Bigggy',
|
title: 'Bigggy',
|
||||||
artist: 'Buck Bunny',
|
artist: 'Buck Bunny',
|
||||||
images: [{
|
images: [{
|
||||||
@@ -142,8 +144,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
trim: {
|
trim: {
|
||||||
start: 10,
|
//start: 10,
|
||||||
end: 20
|
//end: 20
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(conversion);
|
console.log(conversion);
|
||||||
|
|||||||
Generated
+6
-6
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.16.0",
|
"version": "1.16.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.16.0",
|
"version": "1.16.1",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@@ -7749,9 +7749,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mediabunny": {
|
"node_modules/mediabunny": {
|
||||||
"version": "1.15.2",
|
"version": "1.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.15.2.tgz",
|
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.16.0.tgz",
|
||||||
"integrity": "sha512-OvIuSK10wNdz+7Dd4bmeq/IWkYG0bkm33FQb29vmyDztpr47ru8XSxYansa3EHLy6lLEm4rUADUENOcmrnN0jg==",
|
"integrity": "sha512-JXNR8MVo4bMHN4WMLSBjCUMBAxPjEJKtfAqBJI7gJayMOMeLp00K3WE5CH5uLSyhk0yCr26bGNjIEBF0/pQjZA==",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
@@ -12242,7 +12242,7 @@
|
|||||||
},
|
},
|
||||||
"packages/mp3-encoder": {
|
"packages/mp3-encoder": {
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"version": "1.16.0",
|
"version": "1.16.1",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/emscripten": "^1.40.1"
|
"@types/emscripten": "^1.40.1"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.16.0",
|
"version": "1.16.1",
|
||||||
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.16.0",
|
"version": "1.16.1",
|
||||||
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
||||||
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||||
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||||
|
|||||||
+2
-1
@@ -12,6 +12,7 @@ import {
|
|||||||
assert,
|
assert,
|
||||||
assertNever,
|
assertNever,
|
||||||
Bitstream,
|
Bitstream,
|
||||||
|
getUint24,
|
||||||
last,
|
last,
|
||||||
readExpGolomb,
|
readExpGolomb,
|
||||||
readSignedExpGolomb,
|
readSignedExpGolomb,
|
||||||
@@ -116,7 +117,7 @@ const findNalUnitsInLengthPrefixed = (packetData: Uint8Array, lengthSize: 1 | 2
|
|||||||
} else if (lengthSize === 2) {
|
} else if (lengthSize === 2) {
|
||||||
nalUnitLength = dataView.getUint16(offset, false);
|
nalUnitLength = dataView.getUint16(offset, false);
|
||||||
} else if (lengthSize === 3) {
|
} else if (lengthSize === 3) {
|
||||||
nalUnitLength = (dataView.getUint16(offset, false) << 8) + dataView.getUint8(offset + 2);
|
nalUnitLength = getUint24(dataView, offset, false);
|
||||||
} else if (lengthSize === 4) {
|
} else if (lengthSize === 4) {
|
||||||
nalUnitLength = dataView.getUint32(offset, false);
|
nalUnitLength = dataView.getUint32(offset, false);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ type Cluster = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ClusterTrackData = {
|
type ClusterTrackData = {
|
||||||
track: InternalTrack | null;
|
track: InternalTrack;
|
||||||
startTimestamp: number;
|
startTimestamp: number;
|
||||||
endTimestamp: number;
|
endTimestamp: number;
|
||||||
firstKeyFrameTimestamp: number | null;
|
firstKeyFrameTimestamp: number | null;
|
||||||
@@ -141,6 +141,7 @@ type ClusterBlock = {
|
|||||||
referencedTimestamps: number[];
|
referencedTimestamps: number[];
|
||||||
data: Uint8Array;
|
data: Uint8Array;
|
||||||
lacing: BlockLacing;
|
lacing: BlockLacing;
|
||||||
|
decoded: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CuePoint = {
|
type CuePoint = {
|
||||||
@@ -667,7 +668,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
const nextEntry = trackData.presentationTimestamps[i + 1]!;
|
const nextEntry = trackData.presentationTimestamps[i + 1]!;
|
||||||
currentBlock.duration = nextEntry.timestamp - currentBlock.timestamp;
|
currentBlock.duration = nextEntry.timestamp - currentBlock.timestamp;
|
||||||
} else if (currentBlock.duration === 0) {
|
} else if (currentBlock.duration === 0) {
|
||||||
if (track?.defaultDuration != null) {
|
if (track.defaultDuration != null) {
|
||||||
if (currentBlock.lacing === BlockLacing.None) {
|
if (currentBlock.lacing === BlockLacing.None) {
|
||||||
currentBlock.duration = track.defaultDuration;
|
currentBlock.duration = track.defaultDuration;
|
||||||
} else {
|
} else {
|
||||||
@@ -695,13 +696,11 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
trackData.startTimestamp = firstBlock.timestamp;
|
trackData.startTimestamp = firstBlock.timestamp;
|
||||||
trackData.endTimestamp = lastBlock.timestamp + lastBlock.duration;
|
trackData.endTimestamp = lastBlock.timestamp + lastBlock.duration;
|
||||||
|
|
||||||
if (track) {
|
insertSorted(track.clusters, cluster, x => x.elementStartPos);
|
||||||
insertSorted(track.clusters, cluster, x => x.elementStartPos);
|
|
||||||
|
|
||||||
const hasKeyFrame = trackData.firstKeyFrameTimestamp !== null;
|
const hasKeyFrame = trackData.firstKeyFrameTimestamp !== null;
|
||||||
if (hasKeyFrame) {
|
if (hasKeyFrame) {
|
||||||
insertSorted(track.clustersWithKeyFrame, cluster, x => x.elementStartPos);
|
insertSorted(track.clustersWithKeyFrame, cluster, x => x.elementStartPos);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,8 +713,13 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
getTrackDataInCluster(cluster: Cluster, trackNumber: number) {
|
getTrackDataInCluster(cluster: Cluster, trackNumber: number) {
|
||||||
let trackData = cluster.trackData.get(trackNumber);
|
let trackData = cluster.trackData.get(trackNumber);
|
||||||
if (!trackData) {
|
if (!trackData) {
|
||||||
|
const track = cluster.segment.tracks.find(x => x.id === trackNumber);
|
||||||
|
if (!track) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
trackData = {
|
trackData = {
|
||||||
track: cluster.segment.tracks.find(x => x.id === trackNumber) ?? null,
|
track,
|
||||||
startTimestamp: 0,
|
startTimestamp: 0,
|
||||||
endTimestamp: 0,
|
endTimestamp: 0,
|
||||||
firstKeyFrameTimestamp: null,
|
firstKeyFrameTimestamp: null,
|
||||||
@@ -728,7 +732,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
return trackData;
|
return trackData;
|
||||||
}
|
}
|
||||||
|
|
||||||
expandLacedBlocks(blocks: ClusterBlock[], track: InternalTrack | null) {
|
expandLacedBlocks(blocks: ClusterBlock[], track: InternalTrack) {
|
||||||
// https://www.matroska.org/technical/notes.html#block-lacing
|
// https://www.matroska.org/technical/notes.html#block-lacing
|
||||||
|
|
||||||
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
|
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
|
||||||
@@ -737,6 +741,12 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode the block data if it hasn't been decoded yet (needed for lacing expansion)
|
||||||
|
if (!originalBlock.decoded) {
|
||||||
|
originalBlock.data = this.decodeBlockData(track, originalBlock.data);
|
||||||
|
originalBlock.decoded = true;
|
||||||
|
}
|
||||||
|
|
||||||
const slice = FileSlice.tempFromBytes(originalBlock.data);
|
const slice = FileSlice.tempFromBytes(originalBlock.data);
|
||||||
|
|
||||||
const frameSizes: number[] = [];
|
const frameSizes: number[] = [];
|
||||||
@@ -819,7 +829,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
const frameSize = frameSizes[i]!;
|
const frameSize = frameSizes[i]!;
|
||||||
const frameData = readBytes(slice, frameSize);
|
const frameData = readBytes(slice, frameSize);
|
||||||
|
|
||||||
const blockDuration = originalBlock.duration || (frameCount * (track?.defaultDuration ?? 0));
|
const blockDuration = originalBlock.duration || (frameCount * (track.defaultDuration ?? 0));
|
||||||
|
|
||||||
// Distribute timestamps evenly across the block duration
|
// Distribute timestamps evenly across the block duration
|
||||||
const frameTimestamp = originalBlock.timestamp + (blockDuration * i / frameCount);
|
const frameTimestamp = originalBlock.timestamp + (blockDuration * i / frameCount);
|
||||||
@@ -832,6 +842,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
referencedTimestamps: originalBlock.referencedTimestamps,
|
referencedTimestamps: originalBlock.referencedTimestamps,
|
||||||
data: frameData,
|
data: frameData,
|
||||||
lacing: BlockLacing.None,
|
lacing: BlockLacing.None,
|
||||||
|
decoded: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1331,18 +1342,17 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
const trackNumber = readVarInt(slice);
|
const trackNumber = readVarInt(slice);
|
||||||
if (trackNumber === null) break;
|
if (trackNumber === null) break;
|
||||||
|
|
||||||
|
const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
|
||||||
|
if (!trackData) break; // Not a track we care about
|
||||||
|
|
||||||
const relativeTimestamp = readI16Be(slice);
|
const relativeTimestamp = readI16Be(slice);
|
||||||
|
|
||||||
const flags = readU8(slice);
|
const flags = readU8(slice);
|
||||||
const isKeyFrame = !!(flags & 0x80);
|
const isKeyFrame = !!(flags & 0x80);
|
||||||
const lacing = (flags >> 1) & 0x3 as BlockLacing; // If the block is laced, we'll expand it later
|
const lacing = (flags >> 1) & 0x3 as BlockLacing; // If the block is laced, we'll expand it later
|
||||||
|
|
||||||
const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
|
const blockData = readBytes(slice, size - (slice.filePos - dataStartPos));
|
||||||
let blockData = readBytes(slice, size - (slice.filePos - dataStartPos));
|
const hasDecodingInstructions = trackData.track.decodingInstructions.length > 0;
|
||||||
|
|
||||||
if (trackData.track) {
|
|
||||||
blockData = this.decodeBlockData(trackData.track, blockData);
|
|
||||||
}
|
|
||||||
|
|
||||||
trackData.blocks.push({
|
trackData.blocks.push({
|
||||||
timestamp: relativeTimestamp, // We'll add the cluster's timestamp to this later
|
timestamp: relativeTimestamp, // We'll add the cluster's timestamp to this later
|
||||||
@@ -1351,6 +1361,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
referencedTimestamps: [],
|
referencedTimestamps: [],
|
||||||
data: blockData,
|
data: blockData,
|
||||||
lacing,
|
lacing,
|
||||||
|
decoded: !hasDecodingInstructions,
|
||||||
});
|
});
|
||||||
}; break;
|
}; break;
|
||||||
|
|
||||||
@@ -1374,17 +1385,16 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
const trackNumber = readVarInt(slice);
|
const trackNumber = readVarInt(slice);
|
||||||
if (trackNumber === null) break;
|
if (trackNumber === null) break;
|
||||||
|
|
||||||
|
const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
|
||||||
|
if (!trackData) break;
|
||||||
|
|
||||||
const relativeTimestamp = readI16Be(slice);
|
const relativeTimestamp = readI16Be(slice);
|
||||||
|
|
||||||
const flags = readU8(slice);
|
const flags = readU8(slice);
|
||||||
const lacing = (flags >> 1) & 0x3 as BlockLacing; // If the block is laced, we'll expand it later
|
const lacing = (flags >> 1) & 0x3 as BlockLacing; // If the block is laced, we'll expand it later
|
||||||
|
|
||||||
const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
|
const blockData = readBytes(slice, size - (slice.filePos - dataStartPos));
|
||||||
let blockData = readBytes(slice, size - (slice.filePos - dataStartPos));
|
const hasDecodingInstructions = trackData.track.decodingInstructions.length > 0;
|
||||||
|
|
||||||
if (trackData.track) {
|
|
||||||
blockData = this.decodeBlockData(trackData.track, blockData);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentBlock = {
|
this.currentBlock = {
|
||||||
timestamp: relativeTimestamp, // We'll add the cluster's timestamp to this later
|
timestamp: relativeTimestamp, // We'll add the cluster's timestamp to this later
|
||||||
@@ -1393,6 +1403,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
referencedTimestamps: [],
|
referencedTimestamps: [],
|
||||||
data: blockData,
|
data: blockData,
|
||||||
lacing,
|
lacing,
|
||||||
|
decoded: !hasDecodingInstructions,
|
||||||
};
|
};
|
||||||
trackData.blocks.push(this.currentBlock);
|
trackData.blocks.push(this.currentBlock);
|
||||||
}; break;
|
}; break;
|
||||||
@@ -1600,24 +1611,26 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
decodeBlockData(track: InternalTrack, rawData: Uint8Array) {
|
decodeBlockData(track: InternalTrack, rawData: Uint8Array) {
|
||||||
|
assert(track.decodingInstructions.length > 0); // This method shouldn't be called otherwise
|
||||||
|
|
||||||
let currentData = rawData;
|
let currentData = rawData;
|
||||||
|
|
||||||
// In the vast number of cases there are exactly zero decoding instructions
|
for (const instruction of track.decodingInstructions) {
|
||||||
for (let i = 0; i < track.decodingInstructions.length; i++) {
|
|
||||||
const instruction = track.decodingInstructions[i]!;
|
|
||||||
assert(instruction.data);
|
assert(instruction.data);
|
||||||
|
|
||||||
switch (instruction.data.type) {
|
switch (instruction.data.type) {
|
||||||
case 'decompress': {
|
case 'decompress': {
|
||||||
switch (instruction.data.algorithm) {
|
switch (instruction.data.algorithm) {
|
||||||
case ContentCompAlgo.HeaderStripping: {
|
case ContentCompAlgo.HeaderStripping: {
|
||||||
const prefix = instruction.data.settings ?? new Uint8Array(0);
|
if (instruction.data.settings && instruction.data.settings.length > 0) {
|
||||||
const newData = new Uint8Array(prefix.length + currentData.length);
|
const prefix = instruction.data.settings;
|
||||||
|
const newData = new Uint8Array(prefix.length + currentData.length);
|
||||||
|
|
||||||
newData.set(prefix, 0);
|
newData.set(prefix, 0);
|
||||||
newData.set(currentData, prefix.length);
|
newData.set(currentData, prefix.length);
|
||||||
|
|
||||||
currentData = newData;
|
currentData = newData;
|
||||||
|
}
|
||||||
}; break;
|
}; break;
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
@@ -1959,6 +1972,12 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
|
|||||||
const block = trackData.blocks[blockIndex];
|
const block = trackData.blocks[blockIndex];
|
||||||
assert(block);
|
assert(block);
|
||||||
|
|
||||||
|
// Perform lazy decoding if needed
|
||||||
|
if (!block.decoded) {
|
||||||
|
block.data = this.internalTrack.demuxer.decodeBlockData(this.internalTrack, block.data);
|
||||||
|
block.decoded = true;
|
||||||
|
}
|
||||||
|
|
||||||
const data = options.metadataOnly ? PLACEHOLDER_DATA : block.data;
|
const data = options.metadataOnly ? PLACEHOLDER_DATA : block.data;
|
||||||
const timestamp = block.timestamp / this.internalTrack.segment.timestampFactor;
|
const timestamp = block.timestamp / this.internalTrack.segment.timestampFactor;
|
||||||
const duration = block.duration / this.internalTrack.segment.timestampFactor;
|
const duration = block.duration / this.internalTrack.segment.timestampFactor;
|
||||||
|
|||||||
+5
-4
@@ -6,7 +6,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert, clamp, MaybePromise, toDataView } from './misc';
|
import { assert, clamp, getUint24, MaybePromise, toDataView } from './misc';
|
||||||
import { Source } from './source';
|
import { Source } from './source';
|
||||||
|
|
||||||
export class Reader {
|
export class Reader {
|
||||||
@@ -157,9 +157,10 @@ export const readU16Be = (slice: FileSlice) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const readU24Be = (slice: FileSlice) => {
|
export const readU24Be = (slice: FileSlice) => {
|
||||||
const high = readU16Be(slice);
|
const value = getUint24(slice.view, slice.bufferPos, false);
|
||||||
const low = readU8(slice);
|
slice.bufferPos += 3;
|
||||||
return high * 0x100 + low;
|
|
||||||
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const readI16Be = (slice: FileSlice) => {
|
export const readI16Be = (slice: FileSlice) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user