Build1 publisher3 min readPublished
WebCodecs hides a five-times throughput spread behind one capability check
A browser benchmark puts the platform encoder 7.5 to 10.1 times ahead of ffmpeg.wasm on an Apple M3, while the same code path runs about five times slower on a machine with no hardware AVC. On size, ffmpeg.wasm won.
The Engineer · Build desk

What happened
- A dev.to benchmark on an Apple M3 running Chromium 145 clocked WebCodecs at 7.5 to 10.1 times ffmpeg.wasm on the same clip, with both runs starting from a cold browser context.
- At identical settings the ffmpeg.wasm output was 10.23 MB and the WebCodecs output 11.20 MB; the post credits libx264's CRF mode with a bitrate ceiling against a fixed average bitrate target.
- VideoEncoder.isConfigSupported() reports a configuration as supported whether the answer comes from silicon or the slower software path, and runtime measurement requires encoding something first.
- The production router ships one throughput constant per engine, 150 megapixels per second for WebCodecs and 15 for the single-threaded ffmpeg.wasm core.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint The only way to tell a hardware encoder from a software one is to run an encode, so any estimate made before that is a guess.
- cost One constant for two encoders means one group of users is quoted a wait about twice too long, and the other group waits about 2.5 times what the UI promised.
- decision A project-level engine default now looks expensive on footage with large flat regions: roughly a megabyte per file paid in output size, for speed the viewer may never notice.
- capability Treating the skip check and the container fix as first-class paths lets some uploads finish without an encode at all.
Conditions first. The 7.5 to 10.1 times figure came off one machine, an Apple M3 with 16 GB running Chromium 145 on macOS [4][1]. On that machine the WebCodecs path is the platform's own encoder, usually the same hardware block a phone uses to record video [19]. The same post measures hardware AVC on M-series silicon at 305 megapixels of source video per second. A machine with no hardware AVC, falling back to a software encoder behind the same interface, comes in at about 60 [9]. Divide those and you get 5.1 [2]. So the speedup transfers to visitors whose machines have a hardware AVC block, and `VideoEncoder.isConfigSupported()` reports both kinds the same way [10].
Credit the measurement method. The harness deletes `window.VideoEncoder` and `window.VideoDecoder` before any page script runs, so the selector takes the same fallback branch it would take in a browser that never shipped the API [5]. Both runs start cold, so the ffmpeg timings include fetching and instantiating its WebAssembly core [6]. That fixed cost is a large share of the ten seconds the five-second clip took. By fifteen seconds it has amortised while the ratio keeps widening, and the post attributes the remaining gap to the encoder itself [7].
Rate control explains the size column. libx264 ran CRF with a bitrate ceiling, giving each scene the bits it needs and no more. The WebCodecs encoder was handed an average bitrate and hit it. On footage with large easy regions, CRF wins that trade [8]. The difference between the two outputs is 0.97 MB, about 9.5 percent [1]. The author wrote that the honest summary is "WebCodecs is much faster and usually close enough on size, and on some content it gives up a real amount of it" [17]. The author added: "This is the part I'd most like other people to check" [21]. The comparison is one clip, and that is the whole footage sample the post reports.
The shipped constants are the interesting engineering. `ENCODE_MEGAPIXELS_PER_SEC` sets `webcodecs` to 150 and `ffmpegWasm` to 15, deliberately pitched between the two measured WebCodecs figures and deliberately a number neither machine produced [11]. On the M3 that predicts a wait about twice the real one: 305 divided by 150 is 2.0. On the software path the encode takes about 2.5 times longer than predicted, since 150 divided by 60 is 2.5 [3]. "Over-estimating the machine costs the user a wait they didn't agree to," the author wrote [18]. The ffmpeg constant is 15 because the build uses the single-threaded core, which the post calls a revenue decision, the multithreaded core having cost ad revenue in an earlier write-up [12][13]. 150 over 15 is exactly 10, the top of the measured band [4].
The post argues that the decision worth making is per file, and that the two engines are only two of the options on it [20]. Engine selection itself is three booleans: `hasWebCodecsApi`, `inputReadable` and `canEncodeTarget` return "webcodecs", otherwise "ffmpeg" [15]. The author writes that all the interesting work is in deciding whether to encode at all and in making an unreadable container readable [22]. Shipping, the author says, needed four paths where the earlier post described three, with a surprising share of the wins in the two that are neither engine [16]. The failures behind that are on the record: an AVI remux that produced a 1151-byte file, and an AAC priming delay that permanently defeats mediabunny's copy path [14].
What to watch
- Whether anyone reproduces the size result on other footage, which is the check the author explicitly asked for.
- Any WebCodecs addition that reports whether a configuration is hardware-backed would remove the guess from the router constant.
- Whether the shipped 150 figure moves once the project has runtime throughput samples from real visitor devices.