从 HTML Canvas 捕获图像的最快方法

Posted

技术标签:

【中文标题】从 HTML Canvas 捕获图像的最快方法【英文标题】:Fastest way to capture image from a HTML Canvas 【发布时间】:2020-07-25 12:24:59 【问题描述】:

这是我从 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 图像的最佳方法是什么。 是什么,它不会成为实时视频转码过程的瓶颈?

【问题讨论】:

.toBlob 怎么样? .captureStream() 看起来也很有前途... 【参考方案1】:

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


最快的可能是将ImageBitmap 发送到您的工作线程,从画布(像素缓冲区的简单副本)生成这些真的很快,并且可以转移到您的工作线程脚本,您应该可以在 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 的消息计算量也不大。 如果您走这条路,您可能希望使用 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);
;

【讨论】:

MedaiRecorder 的问题是它不能用于直播。我的意思是如果我使用它,录制的开始会延迟,录制后你必须停止它,它会以某种方式编码,这也很慢,因此,它不能用于生成 1 秒的视频块。例如,我需要每秒生成一个 1 秒的视频(AVI 或 MP4 任何可用的编解码器......)当我使用 MediaRecorded 时我不能这样做 为什么需要一秒钟的视频?不确定有多少玩家可以播放那些小视频。 为什么需要完整的视频文件?不能直接发几块吗?或者更好的是,只通过 WebRTC 发送 MediaStream。 为了更好地描述我想要实现的目标,打开此techsmith.com/blog/instructional-videos 并在检查开发人员工具以进行网络检查时播放视频,通过网络发送的 TS 视频文件是完整的视频。这是从流媒体服务器录制的视频,但在我的情况下,TS 文件将来自客户端浏览器网络摄像头,以类似方式流式传输到其他客户端。 我也不能在我的项目的这个阶段使用 WebRTC,因为这个阶段需要确保它首先与 WebSockets 一起工作。我已经测试了 Websockets 并且能够逐帧进行实时视频(JPEG 流),问题是它占用了带宽,所以需要先编码。

以上是关于从 HTML Canvas 捕获图像的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

Canvas:使用drawImage捕获流仅捕获流图像的左上角[重复]

HTML5 Canvas - 在屏幕上显示像素颜色数组的最快方式

JavaScript图形实例:Canvas API

提高 JavaScript 性能以呈现图像

如何使用带有扩展名的 canvas2image 将 DIV 保存为图像

从 HTML5 Canvas 上的图像绘制字体