如何通过 Webcodecs API 播放无容器/原始 .h264 流?

问题描述 投票:0回答:1

我想使用 Webcodecs API 播放原始 .h264 流并提取特定帧。它保存为 .h264 文件,本质上只是混合到 .mp4 容器格式之前的视频数据。

应该可以使用新的 WebCodecs API 进行播放。我无法提供

avc1.64001e
VideoDecoder 以及格式正确的 EncodedVideoChunk。我怎样才能这样做呢?我知道分辨率、帧速率和帧计数,但如何获取这些信息并从中创建 EncodedVideoChunk?

我有一个可能有千兆字节的大本地文件,我通过以下方式收到:

        const readableStream = this.data.file.stream();
        const reader = readableStream.getReader();

如何将其输入到

VideoDecoder: decode()
? 我知道你可以让
mp4box.js
处理解复用和提供块。但我已经将数据分解了。那么,除了混合并通过
mp4box.js
提供它,然后再次将其分解之外,我该如何克服这一步呢?

typescript webcodecs
1个回答
0
投票

为了轻松解码,可以方便地拆分流分组 SPS + PPS + IDR,这可以通过以下方式完成:

for (let i = 0; i < buffer.length; i++) {
    if (buffer[i] === 0 && buffer[i+1] === 0 && buffer[i+2] === 0 && buffer[i+3] === 1 && (buffer[i+4]&0x1f !== 6) && (buffer[i+4]&0x1f) !== 7) {
        if (i !== start) {
            // process a frame
        }
        start = i;
    }
}

一个简单的解码,具有最少的错误情况管理,设置类型和编解码器设置为虚拟值,使用annexb将让VideoDecoder用帧内容覆盖值,可以是:

async function process(data) {
    if (decoder.state !== "configured") {
        await decoder.configure({ codec: "avc1.64001e" });  
    }
    const chunk = new EncodedVideoChunk({
        timestamp: (performance.now() + performance.timeOrigin) * 1000,
        type: "key",
        data,
    });
    return decoder.decode(chunk);
}

综合起来:

const decoder = new VideoDecoder({
    output: (frame) => {
        canvas.getContext("2d").drawImage(frame, 0, 0, frame.displayWidth, frame.displayHeight);
        frame.close();
    },
    error: (e) => console.warn(e.message),
})

async function process(data) {
    if (decoder.state !== "configured") {
        const config = { codec: "avc1.4d002a" };
        await decoder.configure(config);  
    }
    const chunk = new EncodedVideoChunk({
        timestamp: (performance.now() + performance.timeOrigin) * 1000,
        type: "key",
        data,
    });
    return decoder.decode(chunk);
}

function load() {
    const file = document.getElementById("file").files[0];
    const stream = file.stream();
    const reader = stream.getReader();
    let buffer = new Uint8Array();
    return reader.read().then(async function processChunk({done, value}) {
        if (done) {
            return process(buffer);
        } else {
            buffer = new Uint8Array([...buffer, ...value]);
            let start = 0;
            for (let i = 0; i < buffer.length; i++) {
                if (buffer[i] === 0 && buffer[i+1] === 0 && buffer[i+2] === 0 && buffer[i+3] === 1 && (buffer[i+4]&0x1f !== 6) && (buffer[i+4]&0x1f) !== 7) {
                    if (i !== start) {
                        const frame = buffer.slice(start, i);
                        await process(frame);
                    }
                    start = i;
                }
            }
            buffer = buffer.slice(start);
            return reader.read().then(processChunk);
        }
    });
}
<input type="file" id="file" onchange="load()" />
<canvas id="canvas"></canvas>

© www.soinside.com 2019 - 2024. All rights reserved.