【问题标题】:Reducing CPU usage of navigator.webkitGetUserMedia (Electron: DesktopCapturer)减少 navigator.webkitGetUserMedia (Electron: DesktopCapturer) 的 CPU 使用率
【发布时间】:2016-07-07 10:46:33
【问题描述】:

我使用navigator.webkitGetUserMedia 每秒捕获一次窗口的屏幕截图,方法是将返回的stream 分配给<video> 并将其复制到<canvas> 并将缓冲区保存到文件中。

我的应用程序中的 CPU 使用率一直很高,我已将其精确定位到该区域。

代码

// Initialize the video, canvas, and ctx
var localStream,
  _video = document.querySelector('#video'),
  _canvas = document.querySelector('#canvas'),
  _ctx = _canvas.getContext('2d'),
  sourceName = 'my-window-id';

// Load the stream from navigator.webkitGetUserMedia
navigator.webkitGetUserMedia({
  audio: false,
  video: {
    mandatory: {
      chromeMediaSource: 'desktop',
      chromeMediaSourceId: sourceName,
      minWidth: 1920,
      maxWidth: 1920,
      minHeight: 1080,
      maxHeight: 1080
    }
  }
}, gotStream, getUserMediaError);

function gotStream(stream) {
  // Use the stream in our <video>
  _video.src = window.URL.createObjectURL(stream);

  // Reference the stream locally
  localStream = stream;
}

function captureState() {
  var buffer,
    dataURL;

  // Draw <video> to <canvas> and convert to buffer (image data)
  _ctx.drawImage(_video, 0, 0);
  dataURL = _canvas.toDataURL('image/png');
  buffer = new Buffer(dataURL.split(",")[1], 'base64');

  // Create an image from the data
  fs.writeFileSync('screenshot.png', buffer);
}

// Capture state every second
setInterval(function() {
  captureState();
}, 1000);

这段代码我没有运行,它是我代码中的简化版本,使其在 StackOverflow 上可读。

我尝试过的事情

  1. _video.pause()_video.play() 需要时。似乎没有改变 CPU 使用率。
  2. _video.stop()。这意味着我必须再次获取流,这会导致 CPU 使用率飙升,而不是保持打开状态。

我现在最好的方法是通过添加以下内容来更改帧速率:

  optional: [
    { minFrameRate: 1 },
    { frameRate: 1 }
  ]

帧速率极低就可以了。但是,我无法确定 frameRate 设置在这种情况下是否有效。 The docs 没有列出,我也没有更新的 mediaDevices.getUserMedia 可用。

是否可以为navigator.webkitGetUserMedia 设置极低的帧速率(或任何帧速率)?

有没有人能够以任何其他方式减少流的 CPU 使用率?

实现相同目标的任何替代方法(间隔状态捕获)也会有所帮助。

谢谢!

旁注

这是在 Windows 上的 Electron 应用程序中使用 DesktopCapturer 获取 chromeMediaSourceId


CPU 使用率更新

  1. 运行stream 的成本:6% CPU 使用率
  2. 每 1000 毫秒调用一次 captureState:5% 的 CPU 使用率

总电流:11%​​

目前正在根据 Csaba Toth 的建议减少 #2。我应该能够通过更改画布的捕获方式来减少captureState。完成后会更新。

对于 #1,如果我无法避免捕获视频流,我将不得不尝试通过优化 #2 将总 CPU 使用率限制在 6% 以上。

【问题讨论】:

  • 你有什么类型的CPU?恕我直言,6% + 5% 不一定太糟糕。如果它是 6-8 个核心,那将完全消耗一个核心。不要期望完全消除#2。也许看看是否可以在没有视频流的情况下截图是个好主意。
  • i5-6500。不,这并不可怕,它现在似乎运行良好。我之前出于另一个原因使用了多个流,但找到了一种解决方法,现在只需要一个流。如果我能让它保持在 10% 以下,我会很高兴,我认为您在下面的建议应该能让我达到目标。

标签: webkit cpu-usage electron getusermedia navigator


【解决方案1】:

这里发生了一些不必要的 base64 编码和操作,你获取数据的方式很奇怪:

dataURL = _canvas.toDataURL('image/png');
buffer = new Buffer(dataURL.split(",")[1], 'base64');

看看 QR 解码器是如何访问图像的:https://github.com/bulldogearthday/booths/blob/master/scripts/qrdecoder.js#L1991

var canvas_qr = document.getElementById("qr-canvas");
var context = canvas_qr.getContext('2d');
qrcode.width = canvas_qr.width;
qrcode.height = canvas_qr.height;
qrcode.imagedata = context.getImageData(0, 0, qrcode.width, qrcode.height);

(软件的另一端之前对画布做了drawImage)。现在的任务是找到一种方法,该方法不会将 PNG 数据不必要地转换为 base64,然后对其进行解码。我看到到处都建议使用这种 URI 编码,因为它的行数较少。但是在性能方面,不必要的编码/解码阶段是不可取的。 1920x1080 PNG 很大,不适用于 base64 内联。反正你是在nodejs中,尽量使用https://github.com/niegowski/node-pngjs或者类似的库来保存图片数据。

空间和时间之间总是有一个权衡,所以如果时间真的很重要,压缩率较低,您可以获得更高的性能:https://github.com/wheany/js-png-encoder

这里有一个折衷,因为 base64 URI 编码示例利用了浏览器的本机(C++,快速)png 编码,但随后进行了不必要的 base64 编码+解码。 node-pngjs 将在 JS 领域执行 PNG 编码,可能不如浏览器的内部编码那样高效。最好的办法是找到一种在没有 base64 的情况下利用浏览器编码的方法。


之前的建议

根据您显示的内容,我认为您的主要问题是您在gotStream 中执行_ctx.drawImage(_video, 0, 0); 和其他操作。

这是我的一个 Progressive Web App,它也执行二维码扫描:https://github.com/bulldogearthday/booths/blob/master/scripts/app.js 请注意,在“gotStream”(在我的情况下是匿名的 https://github.com/bulldogearthday/booths/blob/master/scripts/app.js#L67)中,我只将流连接到画布。

我的情况更容易,因为我不必强制大小(我希望你不要硬连线那些屏幕大小的像素数),但我也会定期执行处理(QR 码扫描尝试,每 500 毫秒)。我最初为此使用了计时器,但在一些迭代/滴答声后停止工作,所以从技术上讲,我发出一个超时,每次它击中我都会重新发出一个新的。请参阅初始超时 https://github.com/bulldogearthday/booths/blob/master/scripts/app.js#L209 和定期重新发布:https://github.com/bulldogearthday/booths/blob/master/scripts/app.js#L231

如您所见,我做“繁重工作”的唯一地方是app.scanQRCode,它每秒只发生两次。在那里我处理画布的内容: https://github.com/bulldogearthday/booths/blob/master/scripts/app.js#L218

我建议您以这种方式重构您的代码。因此,要么设置一个每秒滴答的计时器,要么像我一样重新发出超时。然后在该部分执行捕获+保存。希望这会减轻 CPU 负载,尽管每秒一次编码 1920x1080 PNG 可能会给 CPU 带来压力(将有 PNG 编码)。

(如果您想获取单个图像,这将是有益的。如果您最终还是想获得视频,那么我会尝试按照您的建议执行 1s FPS 视频并捕获直接视频流而不是单个图像。但是对于 CPU 负载,我的建议应该有助于恕我直言。)


在 README (https://github.com/bulldogearthday/booths) 中,您可以看到我查看的 getUserMedia 的主要来源之一:https://github.com/samdutton/simpl/blob/gh-pages/getusermedia/sources/js/main.js

我不喜欢发布.play().pause() 或任何东西。事实上,我的代码一直等到它收到播放开始的信号(默认情况下至少对于摄像机而言是自行开始):document.getElementById('qrVideo').addEventListener('playing', app.saveVideoSize, false);https://github.com/bulldogearthday/booths/blob/master/scripts/app.js#L67我的意图是尽可能不干扰自然过程.就我而言,我以这种温和的方式检测视频大小。看看 DesktopCapturer,他们也没有在自述文件 https://github.com/electron/electron/blob/master/docs/api/desktop-capturer.md 中的 gotStream 中执行任何额外操作,如图所示,理想情况下您只需将视频流与画布连接起来。

【讨论】:

  • 感谢您的详尽帖子!我的代码实际上是按照你的建议分开的——抓取流并保存它,在间隔调用一个单独的captureState。我已经更新了我的代码以反映这一点。现在只需阅读其余代码即可。
  • 所以我看到的唯一区别是您每次需要时都在获取画布上下文,而不是像我在_ctx 中那样保留对上下文的引用。我试试看有没有变化。
  • 当你谈到直接捕获视频流时,我会怎么做呢?我需要能够进行图像比较,并且我认为以这种方式呈现的 canvas 是实现这一目标的唯一方法。
  • @Matt 如果您需要进行图像比较,那么捕获视频流是没有意义的。
  • @Matt:添加了一些新见解
猜你喜欢
  • 1970-01-01
  • 2013-06-01
  • 2017-10-24
  • 2015-08-02
  • 1970-01-01
  • 2013-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多