I tried the solution by @geoffrey, but it still didn't produce ideal results, so I did some digging.
According to mdn web docs for captureStream(fps)
, in regards to the fps
argument:
If not set, a new frame will be captured each time the canvas changes;
Wonderful, so even if I update my canvas once per second/day/fortnight, I still will get only frames that can be interpreted to be at a specific frame rate?
I tried doing ffmpeg -i 'video.webm' frames/%05d.png
, but it didn't work out: I still got a bunch of undesired/duplicate frames exported. It was quite weird, since ffmpeg
basically took a random frame-rate and went with it. I could use a deduplicate filter ffmpeg provides, but it seems hacky. What if two frames are almost identical? What if I want to have two identical frames recorded to imitate a pause in an animation?
I am no expert how the codecs work, but each frame gets assigned a timestamp, so we have a variable frame rate situation. After a few searches, I stumbled upon an article that answered my prayers:
https://superuser.com/questions/908295/ffmpeg-libx264-how-to-specify-a-variable-frame-rate-but-with-a-maximum
-vsync
parameter set to drop
does what I think I want:
As passthrough but destroys all timestamps, making the muxer generate fresh timestamps based on frame-rate.
The adjusted command becomes ffmpeg -i 'video.webm' -vsync drop frames/%05d.png
Volia! I got a bunch of frames that seem to correspond to updates of canvas. Then its a simple case of stitching them back up to a video with something like: fmpeg -framerate 60 -i "frames/%05d.png" -c:v libx264 -b:v 8M out.mp4
Notes
I couldn't find a way to directly convert from a VFR video to a fixed fps video without the intermediate step of generating a bunch of images.
When using MediaRecorder
, the default codec option might limit the bitrate too much, I got better results with:
const recorder = new MediaRecorder(stream, {
mimeType: `video/webm;codecs=h264,opus`,
// 200 MBits/s
// There seems to be an internal limit
bitsPerSecond: 1024 * 1024 * 200,
videoBitsPerSecond: 1024 * 1024 * 200,
});
Even setting a high bitrate might not be enough if recording too fast (recording at 60 fps). I had to manually throttle the "recording fps" to about 20fps to reduce encoding artefacts.
I'm rendering stuff with THREE.js, so probably canvas is updated only once per render. If doing multiple draws per desired frame (using canvas API f.e.), using a second canvas to copy over the result to trigger a recorded frame might be necessary.