CanvasCaptureMediaStream / MediaRecorder 帧同步
CanvasCaptureMediaStream / MediaRecorder Frame Synchronization
当使用 CanvasCaptureMediaStream 和 MediaRecorder 时,有没有办法在每一帧上获取一个事件?
我需要的与 requestAnimationFrame()
没有什么不同,但我需要它用于 CanvasCaptureMediaStream(and/or MediaRecorder)而不是 window。 MediaRecorder 可能 运行 的帧速率与 window 不同(可能是不规则可分的速率,例如 25 FPS 与 60 FPS),所以我想更新 canvas以其帧速率而不是 window 的帧速率。
此示例目前仅在 FireFox 上完全有效,因为 chrome 只是在选项卡模糊时停止 canvas 流。 .. (可能与 this bug 有关,但是好吧,我的计时器似乎在工作,但没有记录...)
[编辑]:它现在实际上只在 chrome 中有效,因为他们已经修复了 this bug,但在 FF 中不再有效,因为 这个(由e10s引起)。
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>
诺塔贝内 :
在这个例子中,我将频率设置为 25fps,但我们可以将它设置为 60fps,即使在我的旧笔记本上它似乎也能正常工作,至少对于这么简单的动画是这样。
当使用 CanvasCaptureMediaStream 和 MediaRecorder 时,有没有办法在每一帧上获取一个事件?
我需要的与 requestAnimationFrame()
没有什么不同,但我需要它用于 CanvasCaptureMediaStream(and/or MediaRecorder)而不是 window。 MediaRecorder 可能 运行 的帧速率与 window 不同(可能是不规则可分的速率,例如 25 FPS 与 60 FPS),所以我想更新 canvas以其帧速率而不是 window 的帧速率。
此示例目前仅在 FireFox 上完全有效,因为 chrome 只是在选项卡模糊时停止 canvas 流。 .. (可能与 this bug 有关,但是好吧,我的计时器似乎在工作,但没有记录...)
[编辑]:它现在实际上只在 chrome 中有效,因为他们已经修复了 this bug,但在 FF 中不再有效,因为 这个(由e10s引起)。
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>
诺塔贝内 :
在这个例子中,我将频率设置为 25fps,但我们可以将它设置为 60fps,即使在我的旧笔记本上它似乎也能正常工作,至少对于这么简单的动画是这样。