gpt4 book ai didi

html5-canvas - 即使在速度较慢的计算机上,也可以使用 CanvasCaptureMediaStream 以恒定 fps 进行录制

转载 作者:行者123 更新时间:2023-12-04 00:23:26 27 4
gpt4 key购买 nike

我想从 HTML 中录制视频 <canvas>特定帧速率的元素。

我正在将 CanvasCaptureMediaStream 与 canvas.captureStream(fps) 一起使用并且还可以通过 const track = stream.getVideoTracks()[0] 访问视频轨道所以我创建 track.requestFrame()通过 MediaRecorder 将其写入输出视频缓冲区.

我想一次精确地捕捉一帧,然后更改 Canvas 内容。更改 Canvas 内容可能需要一些时间(因为需要加载图像等)。所以我无法实时捕捉 Canvas 。
Canvas 上的一些变化会在 500 毫秒内实时发生,因此这也需要调整以一次渲染一帧。

最佳答案

MediaRecorder API 旨在记录 直播 -streams,做版本不是它的设计目的,老实说它做得不是很好......
MediaRecorder 本身没有帧速率的概念,这通常由 MediaStreamTrack 定义。然而,CanvasCaptureStreamTrack 并没有真正说明它的帧速率是多少。
我们可以传递一个参数给 HTMLCanvas.captureStream() ,但这只是告诉我们每秒想要的最大帧数,它并不是真正的 fps 参数。
此外,即使我们停止在 Canvas 上绘图,录制器仍会继续实时延长录制视频的持续时间(我认为在这种情况下,技术上只录制了一个长帧)。
所以......我们必须四处闯荡......
我们可以使用 MediaRecorder 做的一件事是 pause()resume()它。
然后听起来很容易在进行长时间的绘图操作之前暂停并在完成后立即恢复?是的……而且也不是那么容易……
再一次,帧速率由 MediaStreamTrack 决定,但此 MediaStreamTrack 不能暂停。
嗯,实际上有一种方法可以暂停一种特殊的 MediaStreamTrack,幸运的是我正在谈论 CanvasCaptureMediaStreamTracks。
当我们使用 0 的参数调用我们的捕获流时,我们基本上可以手动控制何时将新帧添加到流中。
所以在这里我们可以将 MediaRecorder 和 MediaStreamTrack 同步到我们想要的任何帧速率。
基本工作流程是

await the_long_drawing_task;
resumeTheRecorder();
writeTheFrameToStream(); // track.requestFrame();
await wait( time_per_frame );
pauseTheRecorder();
这样做,记录器仅在我们决定的每帧时间唤醒,并且在此期间将单个帧传递给 MediaStream,有效地模拟了 MediaRecorder 所关注的恒定 FPS 绘图。
但与往常一样,这个仍处于实验阶段的黑客攻击带来了许多浏览器的怪异现象,以下演示实际上仅适用于当前的 Chrome...
无论出于何种原因,Firefox 总是会生成两倍于请求帧数的文件,并且它偶尔也会在第一帧前面加上很长的帧...
还要注意的是, Chrome has a bug它将在绘图时更新 Canvas 流,即使我们以 0 的 frameRequestRate| 启动此流.所以这意味着如果您在一切准备就绪之前开始绘图,或者如果您在 Canvas 上绘图本身需要很长时间,那么我们的记录器将记录我们没有要求的半生不熟的帧。
为了解决这个错误,我们需要使用第二个 Canvas ,仅用于流式传输。我们将在该 Canvas 上做的就是绘制源图像,这将始终是一个足够快的操作。不要面对那个错误。

class FrameByFrameCanvasRecorder {
constructor(source_canvas, FPS = 30) {

this.FPS = FPS;
this.source = source_canvas;
const canvas = this.canvas = source_canvas.cloneNode();
const ctx = this.drawingContext = canvas.getContext('2d');

// we need to draw something on our canvas
ctx.drawImage(source_canvas, 0, 0);
const stream = this.stream = canvas.captureStream(0);
const track = this.track = stream.getVideoTracks()[0];
// Firefox still uses a non-standard CanvasCaptureMediaStream
// instead of CanvasCaptureMediaStreamTrack
if (!track.requestFrame) {
track.requestFrame = () => stream.requestFrame();
}
// prepare our MediaRecorder
const rec = this.recorder = new MediaRecorder(stream);
const chunks = this.chunks = [];
rec.ondataavailable = (evt) => chunks.push(evt.data);
rec.start();
// we need to be in 'paused' state
waitForEvent(rec, 'start')
.then((evt) => rec.pause());
// expose a Promise for when it's done
this._init = waitForEvent(rec, 'pause');

}
async recordFrame() {

await this._init; // we have to wait for the recorder to be paused
const rec = this.recorder;
const canvas = this.canvas;
const source = this.source;
const ctx = this.drawingContext;
if (canvas.width !== source.width ||
canvas.height !== source.height) {
canvas.width = source.width;
canvas.height = source.height;
}

// start our timer now so whatever happens between is not taken in account
const timer = wait(1000 / this.FPS);

// wake up the recorder
rec.resume();
await waitForEvent(rec, 'resume');

// draw the current state of source on our internal canvas (triggers requestFrame in Chrome)
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(source, 0, 0);
// force write the frame
this.track.requestFrame();

// wait until our frame-time elapsed
await timer;

// sleep recorder
rec.pause();
await waitForEvent(rec, 'pause');

}
async export () {

this.recorder.stop();
this.stream.getTracks().forEach((track) => track.stop());
await waitForEvent(this.recorder, "stop");
return new Blob(this.chunks);

}
}

///////////////////
// how to use:
(async() => {
const FPS = 30;
const duration = 5; // seconds

let x = 0;
let frame = 0;
const ctx = canvas.getContext('2d');
ctx.textAlign = 'right';
draw(); // we must have drawn on our canvas context before creating the recorder

const recorder = new FrameByFrameCanvasRecorder(canvas, FPS);

// draw one frame at a time
while (frame++ < FPS * duration) {
await longDraw(); // do the long drawing
await recorder.recordFrame(); // record at constant FPS
}
// now all the frames have been drawn
const recorded = await recorder.export(); // we can get our final video file
vid.src = URL.createObjectURL(recorded);
vid.onloadedmetadata = (evt) => vid.currentTime = 1e100; // workaround https://crbug.com/642012
download(vid.src, 'movie.webm');

// Fake long drawing operations that make real-time recording impossible
function longDraw() {
x = (x + 1) % canvas.width;
draw(); // this triggers a bug in Chrome
return wait(Math.random() * 300)
.then(draw);
}

function draw() {
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'black';
ctx.fillRect(x, 0, 50, 50);
ctx.fillText(frame + " / " + FPS * duration, 290, 140);
};
})().catch(console.error);
<canvas id="canvas"></canvas>
<video id="vid" controls></video>

<script>
// Some helpers

// Promise based timer
function wait(ms) {
return new Promise(res => setTimeout(res, ms));
}
// implements a sub-optimal monkey-patch for requestPostAnimationFrame
// see https://stackoverflow.com/a/57549862/3702797 for details
if (!window.requestPostAnimationFrame) {
window.requestPostAnimationFrame = function monkey(fn) {
const channel = new MessageChannel();
channel.port2.onmessage = evt => fn(evt.data);
requestAnimationFrame((t) => channel.port1.postMessage(t));
};
}
// Promisifies EventTarget.addEventListener
function waitForEvent(target, type) {
return new Promise((res) => target.addEventListener(type, res, {
once: true
}));
}
// creates a downloadable anchor from url
function download(url, filename = "file.ext") {
a = document.createElement('a');
a.textContent = a.download = filename;
a.href = url;
document.body.append(a);
return a;
}
</script>

关于html5-canvas - 即使在速度较慢的计算机上,也可以使用 CanvasCaptureMediaStream 以恒定 fps 进行录制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58907270/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com