From d896a6f4b659a60d0bbf5d07ec9ac3b99572c5d6 Mon Sep 17 00:00:00 2001 From: luciano Date: Tue, 8 Sep 2026 17:44:56 +0200 Subject: [PATCH] Recording: prefer hardware H.264 (VideoToolbox) for the realtime pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MediaRecorder in Electron 33 muxes MP4 with avc1 High profile + AAC; on macOS that encode runs on VideoToolbox hardware, whose realtime quality ceiling is far above software VP9 — the residual smearing came from there. Bitrate cap raised to 50 Mbps (hardware barely notices; the VP9 fallback undershoots regardless). ffmpeg sniffs containers by content, so the .webm temp name keeps working. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01XVMQ2JhsJ77pGKqcvvFHqc --- src/output.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/output.js b/src/output.js index 9833fde..c2b42a9 100644 --- a/src/output.js +++ b/src/output.js @@ -65,7 +65,18 @@ let recorder = null, recording = false, pendingRecOpts = null, recAutoStop = nul let recCanvas = null, recCtx = null, composeRAF = null; function pickMime() { - const cands = ['video/webm;codecs=vp9,opus', 'video/webm;codecs=vp8,opus', 'video/webm']; + // H.264 first: on macOS it runs on the VideoToolbox HARDWARE encoder, whose + // realtime quality ceiling is far above software VP9 — the visible smearing + // on fast abstract motion came from that first encode. ffmpeg sniffs the + // container by content, so the .webm temp extension stays harmless. + const cands = [ + 'video/mp4;codecs=avc1.640033,mp4a.40.2', + 'video/x-matroska;codecs=avc1', + 'video/webm;codecs=h264,opus', + 'video/webm;codecs=vp9,opus', + 'video/webm;codecs=vp8,opus', + 'video/webm' + ]; for (const m of cands) if (window.MediaRecorder && MediaRecorder.isTypeSupported(m)) return m; return 'video/webm'; } @@ -179,7 +190,7 @@ async function startRecording(opts) { // only preserve what survives this first encode. recorder = new MediaRecorder(stream, { mimeType: pickMime(), - videoBitsPerSecond: 30e6, + videoBitsPerSecond: 50e6, // hardware H.264 barely notices; VP9 fallback undershoots anyway audioBitsPerSecond: 192e3 }); // Serialise chunk delivery: ondataavailable is async, so without a chain the