从HTML画布捕获图像的最快方法

时间:2020-04-12 23:25:19

标签: javascript performance animation html5-canvas html5-video

这是我的代码,用于从Canvas播放的视频中捕获图像:

    let drawImage = function(time) {
        prevCtx.drawImage(videoPlayer, 0, 0, w, h);
        requestAnimationFrame(drawImage);
    }
    requestAnimationFrame(drawImage);

    let currIndex = 0;
    setInterval(function () {
        if(currIndex === 30) {
            currIndex = 0;
            console.log("Finishing video...");
            videoWorker.postMessage({action : "finish"});
        } else {
            console.log("Adding frame...");
            // w/o this `toDataURL` this loop runs at 30 cycle / second
            // so that means, this is the hot-spot and needs optimization:
            const base64img = preview.toDataURL(mimeType, 0.9);
            videoWorker.postMessage({ action: "addFrame", data: base64img});
            currIndex++;
        }
    }, 1000 / 30)

目标是每30帧(应该在1秒钟出现一次),它会触发转码添加的帧。

这里的问题是preview.toDataURL(mimeType, 0.9);加了至少1秒,没有它,日志显示currIndex === 30每秒钟被触发。能够捕获至少约30 FPS图像的最佳方法是什么。 从HTML Canvas捕获图像的最快方法是什么,它不会成为实时视频转码过程的瓶颈?

1 个答案:

答案 0 :(得分:1)

您可能应该修改项目,因为将整个视频保存为静止图像会立即耗尽大多数设备的内存。而是看一下MediaStreamsMediaRecorder API,它们能够实时进行转码和压缩。您可以通过其captureStream()方法从画布请求MediaStream。


最快的方法是将ImageBitmap发送到您的Worker线程,这些线程确实可以从画布(像素缓冲区的简单副本)生成,并且可以传输到您的工作脚本,您应该可以在此处将其绘制在OffscreenCanvas上。

主要缺点:目前仅支持最新的Chrome和Firefox(通过webgl),并且无法进行多填充...

main.js

else {
  console.log("Adding frame...");
  const bitmap = await createImageBitmap(preview);
  videoWorker.postMessage({ action: "addFrame", data: bitmap }, [bitmap]);
  currIndex++;
}

worker.js

const canvas = new OffscreenCanvas(width,height);
const ctx = canvas.getContext('2d'); // Chrome only
onmessage = async (evt) => {
  // ...
  ctx.drawImage( evt.data.data, 0, 0 );
  const image = await canvas.convertToBlob();
  storeImage(image);
};

另一种选择是传输ImageData数据。它不具有ImageBitmap的速度,但仍然具有不停止带有压缩部分的主线程的优点,并且由于可以传输它,因此发送给Worker的消息也不占用大量计算资源。
如果您走这条路,您可能想使用来自工作线程的pako(使用PNG图像使用的压缩算法)之类的数据进行压缩。

main.js

else {
  console.log("Adding frame...");
  const img_data = prevCtx.getImageData(0,0,width,height);
  videoWorker.postMessage({ action: "addFrame", data: img_data }, [img_data.data]);
  currIndex++;
}

worker.js

onmessage = (evt) => {
 // ...
 const image = pako.deflate(evt.data.data); // compress to store
 storeImage(image);
};
相关问题