web/encode: add passthrough as stream option

This commit is contained in:
dumbmoron 2024-08-26 21:08:36 +00:00
parent 64002345b5
commit 034fe42625
No known key found for this signature in database
3 changed files with 70 additions and 31 deletions

View file

@ -24,6 +24,7 @@ export default class EncodeLibAV extends LibAVWrapper {
#encoders?: EncoderPipeline[];
#ostreams?: OutputStream[];
#passthrough?: (BufferStream<Packet> | null)[];
constructor() {
super(LibAV);
@ -106,9 +107,10 @@ export default class EncodeLibAV extends LibAVWrapper {
throw "decoders are already set up";
}
this.#decoders = Array(this.#istreams.length).fill(null);
this.#encoders = Array(this.#istreams.length).fill(null);
this.#ostreams = Array(this.#istreams.length).fill(null);
this.#decoders = Array(this.#istreams.length).fill(null);
this.#encoders = Array(this.#istreams.length).fill(null);
this.#ostreams = Array(this.#istreams.length).fill(null);
this.#passthrough = Array(this.#istreams.length).fill(null);
for (const idx in this.#istreams) {
try {
@ -165,10 +167,10 @@ export default class EncodeLibAV extends LibAVWrapper {
));
}
async configureEncoder(index: number, config: AudioEncoderConfig | VideoEncoderConfig | null) {
async configureEncoder(index: number, config: AudioEncoderConfig | VideoEncoderConfig | "passthrough" | null) {
if (!this.#istreams || !this.#ostreams || !this.#istreams[index])
throw "stream does not exist or streams are not configured"
else if (!this.#encoders)
else if (!this.#encoders || !this.#passthrough)
throw "decoders have not been set up yet";
const { libav, webcodecs } = await this.#get();
@ -176,6 +178,14 @@ export default class EncodeLibAV extends LibAVWrapper {
let configToStream, initEncoder;
if (config === 'passthrough') {
this.#passthrough[index] = new BufferStream();
this.#ostreams[index] = [ stream.codecpar, stream.time_base_num, stream.time_base_den ];
return true;
} else {
this.#passthrough[index] = null;
}
if (this.#encoders[index]) {
await this.#encoders[index].instance.flush();
this.#encoders[index].instance.close();
@ -221,8 +231,14 @@ export default class EncodeLibAV extends LibAVWrapper {
const pipes: RenderingPipeline[] = Array(this.#decoders.length).fill(null);
for (let i = 0; i < this.#encoders.length; ++i) {
if (this.#encoders[i] === null) continue;
if (this.#decoders[i] === null) continue;
if (this.#passthrough && this.#passthrough[i] !== null) {
pipes[i] = {
type: 'passthrough',
output: this.#passthrough[i]!
};
continue;
} else if (this.#encoders[i] === null) continue;
else if (this.#decoders[i] === null) continue;
pipes[i] = {
encoder: this.#encoders[i],
@ -243,9 +259,14 @@ export default class EncodeLibAV extends LibAVWrapper {
if (!this.#istreams) throw "istreams are not set up";
for await (const { index, packet } of this.#demux()) {
if (pipes[index] === null) continue;
if (pipes[index] === null) {
continue;
} else if ('type' in pipes[index] && pipes[index].type === 'passthrough') {
pipes[index].output.push(packet);
continue;
}
const { decoder } = pipes[index];
const { decoder } = pipes[index] as Pipeline;
this.#decodePacket(decoder.instance, packet, this.#istreams[index]);
@ -265,9 +286,14 @@ export default class EncodeLibAV extends LibAVWrapper {
for (const pipe of pipes) {
if (pipe !== null) {
await pipe.decoder.instance.flush();
pipe.decoder.instance.close();
pipe.decoder.output.push(null);
if ('type' in pipe && pipe.type === 'passthrough') {
pipe.output.push(null);
} else {
const _pipe = pipe as Pipeline;
await _pipe.decoder.instance.flush();
_pipe.decoder.instance.close();
_pipe.decoder.output.push(null);
}
}
}
}
@ -320,7 +346,8 @@ export default class EncodeLibAV extends LibAVWrapper {
async #encodeStreams(pipes: RenderingPipeline[]) {
return Promise.all(
pipes
.filter(p => p !== null)
.filter(p => p !== null && !('type' in p && p.type === 'passthrough'))
.map(p => p as Pipeline)
.map(
({ decoder, encoder }) => {
return this.#encodeStream(decoder.output, encoder);
@ -356,14 +383,21 @@ export default class EncodeLibAV extends LibAVWrapper {
const starterPackets = [], readers: ReadableStreamDefaultReader[] = [];
for (let i = 0; i < ostreams.length; ++i) {
if (pipes[i] === null) continue;
readers[i] = pipes[i]!.encoder.output.getReader();
const pipe = pipes[i];
if (pipe === null) continue;
const isPassthrough = 'type' in pipe && pipe.type === 'passthrough';
if (isPassthrough) {
readers[i] = pipe.output.getReader();
} else {
readers[i] = (pipe as Pipeline).encoder.output.getReader();
}
const { done, value } = await readers[i].read();
if (done) throw "this should not happen";
starterPackets.push(
await this.#processChunk(value, ostreams[i], i)
isPassthrough ? value : await this.#processChunk(value, ostreams[i], i)
);
}
@ -398,13 +432,14 @@ export default class EncodeLibAV extends LibAVWrapper {
if (pipe === null) {
return;
}
const isPassthrough = 'type' in pipe && pipe.type === 'passthrough';
while (true) {
const { done, value } = await readers[i].read();
if (done) break;
writePromise = writePromise.then(async () => {
const packet = await this.#processChunk(value, ostreams[i], i);
const packet = isPassthrough ? value : await this.#processChunk(value, ostreams[i], i);
await libav.ff_write_multi(output_ctx, write_pkt, [ packet ]);
});
}

View file

@ -1,5 +1,6 @@
import { BufferStream } from "$lib/buffer-stream";
import type { ProbeResult } from "$lib/libav/probe";
import type { Packet } from "@imput/libav.js-encode-cli";
export type InputFileKind = "video" | "audio";
@ -69,10 +70,15 @@ export type VideoPipeline = {
encoder: VideoEncoderPipeline
}
export type PassthroughPipe = {
type: "passthrough",
output: BufferStream<Packet>
};
export type Pipeline = AudioPipeline | VideoPipeline;
export type DecoderPipeline = AudioDecoderPipeline | VideoDecoderPipeline | null;
export type EncoderPipeline = AudioEncoderPipeline | VideoEncoderPipeline | null;
export type RenderingPipeline = Pipeline | null;
export type RenderingPipeline = PassthroughPipe | Pipeline | null;
export type OutputStream = [number, number, number] | null;
export type ContainerConfiguration = {
formatName: string

View file

@ -28,20 +28,18 @@
if (!stream.supported) continue;
const maybe_codec = Object.values(stream.output).find(a => a.supported && !a.slow);
if (!maybe_codec || !maybe_codec.supported)
throw "could not find valid codec";
if (maybe_codec && maybe_codec.supported) {
const decoderConfig = await ff.streamIndexToConfig(+stream_index);
const config = {
...decoderConfig,
width: 'codedWidth' in decoderConfig ? decoderConfig.codedWidth : undefined,
height: 'codedHeight' in decoderConfig ? decoderConfig.codedHeight : undefined,
};
const decoderConfig = await ff.streamIndexToConfig(+stream_index);
const config = {
...decoderConfig,
width: 'codedWidth' in decoderConfig ? decoderConfig.codedWidth : undefined,
height: 'codedHeight' in decoderConfig ? decoderConfig.codedHeight : undefined,
};
await ff.configureEncoder(+stream_index, {
...config,
codec: maybe_codec.codec
});
await ff.configureEncoder(+stream_index, config);
} else {
await ff.configureEncoder(+stream_index, 'passthrough');
}
}
const blob = new Blob(