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

Html5 canvas 即使在速度较慢的计算机上,也可以使用CanvasCaptureMediaStream以恒定的fps进行录制,html5-canvas,Html5 Canvas,我想以特定的帧速率从HTML元素录制视频 我将CanvasCaptureMediaStream与canvas.captureStream(fps)一起使用,并且还可以通过const track=stream.getVideoTracks()[0]访问视频曲目,因此我创建了track.requestFrame(),以便通过MediaRecorder将其写入输出视频缓冲区 我希望一次精确地捕获一帧,然后更改画布内容。更改画布内容可能需要一些时间(因为需要加载图像等)。因此,我无法实时捕获画布。 画布

我想以特定的帧速率从HTML
元素录制视频

我将CanvasCaptureMediaStream与canvas.captureStream(fps)一起使用,并且还可以通过
const track=stream.getVideoTracks()[0]
访问视频曲目,因此我创建了
track.requestFrame()
,以便通过
MediaRecorder
将其写入输出视频缓冲区

我希望一次精确地捕获一帧,然后更改画布内容。更改画布内容可能需要一些时间(因为需要加载图像等)。因此,我无法实时捕获画布。
画布上的某些更改将在500毫秒的实时时间内发生,因此这也需要调整为一次渲染一帧。

MediaRecorder API旨在录制实时-流,编辑不是它设计的目的,老实说,它做得不太好

MediaRecorder本身没有帧速率的概念,这通常由MediaStreamTrack定义。然而,CanvasCaptureStreamTrack并没有真正明确其帧速率是多少。
我们可以将一个参数传递给
HTMLCanvas.captureStream()
,但这只告诉我们每秒所需的最大帧数,它实际上不是fps参数。
此外,即使我们停止在画布上绘图,录像机仍将继续实时延长录制的视频的持续时间(我认为在这种情况下,技术上只录制一个长帧)

所以。。。我们得四处转转

我们可以用MediaRecorder做的一件事是
pause()
resume()
它。
那么,在进行长绘图操作之前暂停,并在完成后立即恢复,听起来很容易吗?对也不是那么容易…
同样,帧速率由MediaStreamTrack决定,但此MediaStreamTrack不能暂停。
实际上,有一种方法可以暂停一种特殊的媒体流跟踪,幸运的是,我正在谈论CanvaCaptureMediaStreamTracks。
当我们使用参数
0
调用捕获流时,我们基本上可以手动控制向流中添加新帧的时间。
因此,我们可以将MediaRecorder和MediaStreamTrack同步到我们想要的任何帧速率

基本工作流程是

await the_long_drawing_task;
resumeTheRecorder();
writeTheFrameToStream(); // track.requestFrame();
await wait( time_per_frame );
pauseTheRecorder();
这样,记录器只在我们决定的每帧时间被唤醒,在此期间,一个帧被传递到MediaStream,有效地模拟了MediaRecorder关注的恒定FPS图形

但和往常一样,在这个仍处于试验阶段的领域中,黑客会带来很多浏览器的怪异之处,下面的演示实际上只在当前的Chrome浏览器中有效

无论出于何种原因,Firefox生成的文件的帧数总是请求的帧数的两倍,而且它偶尔也会预加一个长的第一帧

还要注意的是,它将在绘图时更新画布流,即使我们以
0
的frameRequestRate启动此流。这意味着,如果你在一切准备就绪之前就开始画画,或者画布上的画画本身需要很长时间,那么我们的录音机将记录我们没有要求的半生不熟的画面。
为了解决这个错误,我们需要使用第二个画布,只用于流媒体。我们在画布上要做的就是绘制源图像,这将永远是一个足够快的操作。不要面对那只虫子

类FrameByFrameCanvasRecorder{
构造函数(源画布,FPS=30){
this.FPS=FPS;
this.source=source\u画布;
const canvas=this.canvas=source_canvas.cloneNode();
const ctx=this.drawingContext=canvas.getContext('2d');
//我们需要在画布上画些东西
ctx.drawImage(源画布,0,0);
const stream=this.stream=canvas.captureStream(0);
const track=this.track=stream.getVideoTracks()[0];
//Firefox仍然使用非标准的CanvaCaptureMediaStream
//而不是CanvaCaptureMediaStreamTrack
如果(!track.requestFrame){
track.requestFrame=()=>stream.requestFrame();
}
//准备好我们的媒体录音机
const rec=this.recorder=new media recorder(流);
const chunks=this.chunks=[];
rec.ondataavailable=(evt)=>chunks.push(evt.data);
rec.start();
//我们需要处于“暂停”状态
waitForEvent(记录“开始”)
。然后((evt)=>记录暂停();
//当它完成时,公开一个承诺
这._init=waitForEvent(rec,'暂停');
}
异步记录帧(){
等待此消息。_init;//我们必须等待记录器暂停
const rec=this.recorder;
const canvas=this.canvas;
const source=this.source;
const ctx=this.drawingContext;
如果(canvas.width!==source.width||
canvas.height!==source.height){
canvas.width=source.width;
canvas.height=source.height;
}
//现在就开始计时,这样就不会考虑两次之间发生的任何事情
const timer=wait(1000/this.FPS);
//叫醒录音机
恢复记录();
等待waitForEvent(记录“resume”);
//在内部画布上绘制源代码的当前状态(在Chrome中触发requestFrame)
clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(源,0,0);
//强制写入帧
this.track.requestFrame();
//等到我们的帧时间过去
等待计时器;
//睡眠记录器
建议暂停();
等待waitForEvent(记录“暂停”);
}
异步导出(){
这个.recorder.stop();
this.stream.getTracks().forEach((track)=>track.stop());
等待waitForEvent(此录音机为“停止”);
返回新Blob(this.chunks);
}
}
///////////////////
//如何使用:
(异步()=>{
常数FPS=30;
常数持续时间=5;//秒
设x=0;
设frame=0;
常数c