【问题标题】:CanvasCaptureMediaStream / MediaRecorder Frame SynchronizationCanvasCaptureMediaStream / MediaRecorder 帧同步
【发布时间】:2016-11-18 22:38:57
【问题描述】:

当使用CanvasCaptureMediaStream 和 MediaRecorder 时,有没有办法在每一帧上获取一个事件?

我需要的与requestAnimationFrame() 不同,但我需要 CanvasCaptureMediaStream(和/或 MediaRecorder)而不是窗口。 MediaRecorder 可能以与窗口不同的帧速率运行(可能以不规则的可分速率,例如 25 FPS 与 60 FPS),因此我想以帧速率而不是窗口的帧速率更新画布。

【问题讨论】:

    标签: canvas html5-canvas mediastream web-mediarecorder


    【解决方案1】:

    这个例子目前只完全适用于 FireFox,因为当标签模糊时,chrome 只会停止画布流... (可能与 this bug 相关,但很好,我的计时器似乎在工作,但不是在录音...)

    [编辑]:它现在实际上只适用于 chrome,因为它们已修复 this bug,但由于 this one(由 e10s 引起)而不再适用于 FF。


    MediaStream 上似乎没有任何事件让您知道帧何时被渲染,MediaRecorder 上也没有。

    即使是 MediaStream 的 currentTime 属性(目前仅在 FF 中可用)似乎也没有随着 captureStream() 方法中传递的 fps 参数而相应改变。

    但您似乎想要的是一个可靠的计时器,当当前选项卡没有聚焦时(这发生在 rAF),它不会失去其频率。
    幸运的是,WebAudio API 也有一个high precision timer,它基于硬件时钟,而不是屏幕刷新率。

    所以我们可以提供一个替代的定时循环,即使标签模糊也能保持其频率。

    /*
    	An alternative timing loop, based on AudioContext's clock
    
    	@arg callback : a callback function 
    		with the audioContext's currentTime passed as unique argument
    	@arg frequency : float in ms;
    	@returns : a stop function
    	
    */
    function audioTimerLoop(callback, frequency) {
    
      // AudioContext time parameters are in seconds
      var freq = frequency / 1000;
    
      var aCtx = new AudioContext();
      // Chrome needs our oscillator node to be attached to the destination
      // So we create a silent Gain Node
      var silence = aCtx.createGain();
      silence.gain.value = 0;
      silence.connect(aCtx.destination);
    
      onOSCend();
    
      var stopped = false;
      function onOSCend() {
        osc = aCtx.createOscillator();
        osc.onended = onOSCend;
        osc.connect(silence);
        osc.start(0);
        osc.stop(aCtx.currentTime + freq);
        callback(aCtx.currentTime);
        if (stopped) {
          osc.onended = function() {
            return;
          };
        }
      };
      // return a function to stop our loop
      return function() {
        stopped = true;
      };
    }
    
    
    function start() {
    
      // start our loop @25fps
      var stopAnim = audioTimerLoop(anim, 1000 / 25);
      // maximum stream rate set as 25 fps
      cStream = canvas.captureStream(25);
    
      let chunks = [];
      var recorder = new MediaRecorder(cStream);
      recorder.ondataavailable = e => chunks.push(e.data);
      recorder.onstop = e => {
        // we can stop our loop
        stopAnim();
        var url = URL.createObjectURL(new Blob(chunks));
        var v = document.createElement('video');
        v.src = url;
        v.controls = true;
        document.body.appendChild(v);
      }
      recorder.start();
      // stops the recorder in 20s, try to change tab during this time
      setTimeout(function() {
        recorder.stop();
      }, 20000)
    }
    
    
    // make something move on the canvas
    var ctx = canvas.getContext('2d');
    var x = 0;
    function anim() {
      x = (x + 2) % (canvas.width + 100);
      ctx.fillStyle = 'ivory';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.fillStyle = 'red';
      ctx.fillRect(x - 50, 20, 50, 50)
    };
    
    btn.onclick = start;
    <button id="btn">begin</button>
    <canvas id="canvas" width="500" height="200"></canvas>

    Nota Bene :
    在此示例中,我将频率设置为 25fps,但我们可以将其设置为 60fps,即使在我的旧笔记本上它似乎也能正常工作,至少对于这样一个简单的动画。

    【讨论】:

    • 有趣的创意解决方案。我会用它做实验。谢谢!
    • 此解决方案不再适用于当前的 chrome 版本,我建议使用此库:npmjs.com/package/worker-timers 进行任何类型的后台渲染/计时。
    • @JacobGreenway 计时器在这里工作。损坏的是画布到媒体流的步骤。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-26
    • 1970-01-01
    • 2018-03-11
    • 2013-02-24
    • 2021-10-09
    • 1970-01-01
    相关资源
    最近更新 更多