You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用WebCodecs API从JPG生成MP4失败:文件为空如何解决

问题:使用WebCodecs API生成MP4视频时文件为空无法播放

我正在研究WebCodecs API,尝试将一组JPG图片生成MP4视频。先从简单场景入手,用单张JPG图片生成MP4,在output回调中保存视频块到文件,但打开时提示“你尝试播放的文件是空文件”。

我的代码如下:

const downloadVideo = (data) => {

       console.log("downlaodVideo data:" + data);
       const link = document.createElement("a");
 
       const file = new Blob([ Uint8Array.from( data ) ] , {type: "application/octet-stream"});
       link.href = URL.createObjectURL(file);
       link.download = "video.h264";
       link.click();
       URL.revokeObjectURL(link.href);
  };

$(document).ready(async function () {


     const videoEncoder = new VideoEncoder(
                {
                async output(chunk, metadata) {
                    console.log("encoder queue:" + videoEncoder.encodeQueueSize);
                    console.log("timestamp:" + chunk.timestamp + " counter:" + counter);
                    console.log("length:" + chunk.byteLength);
                    console.log("video chunk type:" + chunk.type);
                    console.log("duration:" + chunk.duration)
                    console.log(JSON.stringify(metadata));
                    console.log("decoder config:" + metadata.decoderConfig );
                    
                    var myMetadata;
                    var videoBlob;

                    if (metadata.decoderConfig) {
                        //# save the decoder's description/config ...
                        //# is SPS and PPS metadata needed by players to display H.264)
                        console.log("decoder config description:" + metadata.decoderConfig.description);
                        myMetadata = new Uint8Array( metadata.decoderconfig.description );
                    }

                    videoBlob = new ArrayBuffer(chunk.byteLength);
                    chunk.copyTo(videoBlob);

                    //# combine the two arrays (in the shown order of appearance)
                    var outputBytes = [...myMetadata, ...videoBlob];
  

                    downloadVideo(outputBytes);
                },
                error(error) {
                    console.log(output_Bytes);
                },
            }
            );
    

            const encoderConfig = {
                codec: "avc1.42001E",
                avc: { format: 'annexb' },
                height: 480,
                width: 640,
                framerate: 1,
                latencyMode: "realtime",
                bitrate: 2_000_000, // 2 Mbps
            };



            const support = await VideoEncoder.isConfigSupported(encoderConfig);
                
            if (support.supported) {
                console.log("Video Encoder configured!");
                videoEncoder.configure(encoderConfig );
            }
            else {
                console.error("Video codec not supported!");
            }




            $('#create_movie').click( function(ev) {

                const myImage = new Image();
                myImage.src = "samples/pic0.jpg";

                var imageBitmap = null;
                var imageBitmapPromise = null;

                myImage.onload = () => {

                    imgContext.drawImage(myImage,0,0);
                    imageBitmapPromise = createImageBitmap(myImage);

                    imageBitmap = imageBitmapPromise.then( result => {
                        const ms = 1_000_000; // 1µs
                        const fps = 10;
                        return new VideoFrame(result, {
                            timestamp: (ms * 1) / fps,
                            duration: ms / fps
                        },false);
                    }).then(vidFrame => {
                        console.log("imageBitmap:" + vidFrame);
                        console.log("video frame timestamp:" + vidFrame.timestamp);
                        console.log("format:" + vidFrame.format);
                        videoEncoder.encode(vidFrame, { keyFrame: true });

                        vidFrame.close();
                    });

                }


            });

}

我用FFmpeg工具通过命令ffmpeg -i pic%d.jpg -vcodec mpeg4 test.mp4从同一张JPG生成了正常的MP4,对比两者文件结构发现,我生成的文件缺少FFmpeg生成文件中的部分元数据。请问如何正确使用WebCodecs API生成视频文件?


解决方案

你的代码存在几个关键问题,导致生成的文件无法被播放器识别,以下是修正要点和完整代码:

1. 核心问题分析

  • WebCodecs的VideoEncoder仅输出H.264编码的裸帧数据,不是完整的MP4容器。播放器需要容器格式(如MP4)才能识别,裸流无法直接播放。
  • 代码中存在拼写错误:metadata.decoderconfig应该是metadata.decoderConfig(大小写敏感),导致无法正确获取SPS/PPS元数据。
  • 未调用videoEncoder.flush(),编码器可能还有缓存数据未输出,导致文件不完整。
  • 在output回调中立即下载单块数据,无法拼接完整的流数据。

2. 修正步骤

步骤1:收集所有编码数据

不要在output回调中立即下载,而是先把SPS/PPS元数据和所有帧数据收集到数组中,等待编码完成后统一处理。

步骤2:调用flush()确保所有数据输出

在编码完最后一帧后,必须调用videoEncoder.flush(),触发编码器输出剩余缓存数据。

步骤3:将裸H.264流封装为MP4

浏览器原生没有MP4封装API,有两种可行方案:

  • 方案A:收集裸H.264数据后,用本地FFmpeg转封装为MP4(适合测试场景)。
  • 方案B:使用FFmpeg.wasm在浏览器内完成MP4封装(无需本地工具)。

以下先提供方案A的修正代码,确保生成有效裸H.264流,再用FFmpeg转成MP4。

3. 修正后的完整代码

// 收集所有编码数据的数组
let encodedData = [];

const downloadVideo = () => {
    // 将收集到的所有数据合并为Blob
    const blob = new Blob(encodedData, { type: 'video/h264' });
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = 'video_raw.h264';
    link.click();
    URL.revokeObjectURL(link.href);

    // 提示转封装命令
    console.log('请用FFmpeg转封装为MP4: ffmpeg -i video_raw.h264 -c:v copy output.mp4');
};

$(document).ready(async function () {
    const videoEncoder = new VideoEncoder({
        async output(chunk, metadata) {
            console.log("encoder queue:" + videoEncoder.encodeQueueSize);
            console.log("timestamp:" + chunk.timestamp);
            console.log("length:" + chunk.byteLength);
            console.log("video chunk type:" + chunk.type);
            console.log("duration:" + chunk.duration);
            console.log(JSON.stringify(metadata));

            let chunkData = new Uint8Array(chunk.byteLength);
            chunk.copyTo(chunkData);

            // 处理SPS/PPS元数据(修正拼写错误)
            if (metadata.decoderConfig?.description) {
                const spsPps = new Uint8Array(metadata.decoderConfig.description);
                encodedData.push(spsPps);
            }

            // 添加帧数据
            encodedData.push(chunkData);
        },
        error(error) {
            console.error('编码器错误:', error);
        },
    });

    const encoderConfig = {
        codec: "avc1.42001E",
        avc: { format: 'annexb' },
        height: 480,
        width: 640,
        framerate: 1,
        latencyMode: "realtime",
        bitrate: 2_000_000, // 2 Mbps
    };

    const support = await VideoEncoder.isConfigSupported(encoderConfig);
    if (support.supported) {
        console.log("Video Encoder configured!");
        videoEncoder.configure(encoderConfig);
    } else {
        console.error("Video codec not supported!");
    }

    $('#create_movie').click(async function (ev) {
        // 重置收集数组
        encodedData = [];

        const myImage = new Image();
        // 处理跨域(如果图片来自不同域名)
        myImage.crossOrigin = 'anonymous';
        myImage.src = "samples/pic0.jpg";

        myImage.onload = async () => {
            const imageBitmap = await createImageBitmap(myImage);
            const ms = 1_000_000; // 微秒单位
            const fps = 1; // 单帧视频帧率设为1即可
            const vidFrame = new VideoFrame(imageBitmap, {
                timestamp: 0, // 第一帧时间戳设为0
                duration: ms / fps
            });

            console.log("video frame timestamp:" + vidFrame.timestamp);
            console.log("format:" + vidFrame.format);
            
            // 编码关键帧
            videoEncoder.encode(vidFrame, { keyFrame: true });
            vidFrame.close();
            imageBitmap.close();

            // 触发编码器输出所有剩余数据
            await videoEncoder.flush();
            // 所有数据收集完成后下载
            downloadVideo();
        };
    });
});

4. 转封装为MP4

下载得到video_raw.h264后,执行FFmpeg命令完成封装:

ffmpeg -i video_raw.h264 -c:v copy output.mp4

这条命令会将裸H.264流封装到MP4容器中,添加播放器所需的元数据,生成可正常播放的MP4文件。

5. 进阶:浏览器内直接封装MP4

如果需要在浏览器内直接生成MP4,可以使用FFmpeg.wasm库,它允许在浏览器中运行FFmpeg。核心逻辑是将收集到的H.264数据传入FFmpeg.wasm,调用转封装命令生成MP4后再下载。


内容的提问来源于stack exchange,提问作者grandzello

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 04:34:56