fix(video): don't draw the encoder's alignment padding in Firefox (workaround)#272
fix(video): don't draw the encoder's alignment padding in Firefox (workaround)#272rmounce wants to merge 2 commits into
Conversation
Co-authored-by: vishalkadam47 <vishalkadam472@gmail.com>
Firefox's canvas drawImage scales the full BACKING surface of a decoder-output WebCodecs VideoFrame - the RGB conversion of the decoded image, alignment padding included - into the frame's nominal box. With full-frame H.264 painted through the 2D-canvas paths, 1920x1080 coded as 1920x1088 renders vertically squished with the 8 padding rows visible at the bottom: a green bar when the encoder zero-fills the padding (h264_vaapi), invisible when it edge-replicates (x264, which is why software encoding masked the bug). Chromium is unaffected. The bug cannot be detected at runtime: the frame's metadata is fully clean (codedWidth/Height == displayWidth/Height == visibleRect, the padding is nowhere reported), and synthetic CPU-backed frames draw correctly, so a visibleRect presence check and a drawn-pixel behavior probe both pass while real decoder output still leaks. Gate on the UA instead, and compute the padded backing size from H.264's 16x16 macroblock alignment: overscan the destination to that size so the visible image maps 1:1 onto the canvas and the padding lands off-canvas. Other browsers keep the plain draw, which crops to the visible region natively. The first drawn frame's geometry and the overscan decision are logged once per context for diagnosis. Applied at every full-frame draw site (the worker's OffscreenCanvas present and the main-thread canvas fallbacks); striped and JPEG paths are untouched (stripes are encoder-aligned by construction, JPEG carries exact dimensions). Co-authored-by: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request addresses a Firefox-specific rendering issue where canvas.drawImage scales the full backing surface of a decoder-output VideoFrame, including alignment padding, into the frame's nominal box. To resolve this, the changes introduce helper functions to detect Firefox, calculate the padded backing size based on H.264's 16x16 macroblock alignment, and overscan the destination so that the padding falls off-canvas and is clipped. These helpers are applied to both the main thread and the video worker. There are no review comments, so I have no feedback to provide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
32a3087 to
6f4e5c7
Compare
I went down this rabbit hole and concluded this was not it. The encoders are not at fault; it's a Firefox bug. So your fix is the correct one with the least computational cost. |
|
@rmounce This is a hardware decode artifact. You should see if software decode solves the problem. If so, I can prefer software decode for Firefox. |
|
Moreover, did you try Firefox 151 or later? I don't reproduce there on software decode. |
|
My testing was on Ubuntu 24.04 on Intel laptop with Firefox via snap (I did not note the version, I refreshed within the last week so probably 152). I am now out of the office for a few days so I cannot check further details or do more testing until next week. I do suspect this is a Firefox bug and I intend to try and report it upstream if so. This draft PR was a way to capture the workaround that helps to characterise the underlying bug. So I’m fine if we don’t adopt this workaround. I’ve been making the most of promotional access to Claude Fable 5 to accelerate testing and proposed bug fixes, which is now over so I’ll be back down to Opus with more manual guidance next week. I hope you don’t mind the prolific submissions. I’ve been trying to mark all AI generated output as such, but Fable is very autonomous so there are were a few generated comments without attribution. |
It's fine. I agree about Fable. Everything else was critically important. Nonetheless, my Playwright validations on Ubuntu 24.04 actually didn't find the line at the bottom, so I think it's worth more deliberate evaluation of what the cause is. Thank you so much for the test and bug hunts! |
3bab531 to
4de4c8a
Compare
Draft — this is a sub-optimal workaround; posting primarily for the analysis. Firefox renders WebCodecs decoder output incorrectly and, as far as we can tell, undetectably. This PR carries the pragmatic client-side mitigation plus our full findings.
Symptom: with full-frame H.264 through the 2D-canvas paths, Firefox shows an ~8px green bar at the bottom of a 1920x1080 stream from
h264_vaapior NVENC, and renders the picture vertically resampled ("skipped lines" on fine detail). Chromium is unaffected. Reproduced on Intel/VA-API and NVIDIA/NVENC.Root cause: H.264 codes 16x16 macroblocks, so 1080 rows are always coded as 1088 with 8 padding rows cropped via SPS. The padding content is encoder-specific: x264 edge-replicates (invisible), VA-API/NVENC zero-fill (green in YUV). Firefox's canvas
drawImage— andcreateImageBitmap— scale the frame's full backing surface, padding included, into the nominal 1080 box. So the padding shows, and every full-frame H.264 stream (x264 included) is silently resampled ~0.74% vertically in Firefox.Why it can't be feature-detected: on these frames Firefox reports
codedWidth/Height == displayWidth/Height == visibleRect == 1920x1080,format: BGRX— the padding appears nowhere. And synthetic CPU-backed VideoFrames draw correctly, so a runtime behavior probe passes while real decoder output leaks. We tried, in order: source-rect draw,visibleRectpresence gate, synthetic-frame behavior probe, coded>display gate — none can fire. Only a UA gate works.The workaround: gated on Firefox UA, overscan the draw destination to the padded size computed from macroblock alignment (
ceil16), so the visible image maps onto the canvas and the padding lands off-canvas. This eliminates the green bar and corrects the geometry, at the cost of Firefox's internal resample remaining visible at 1:1 (no worse than what Firefox already does to x264 streams).copyTo()+putImageDatais the only pixel-exact escape we found (drawing APIs all share the broken conversion), but it costs a ~2M-pixel per-frame CPU copy on top of Firefox's software decode — not included here.A cheaper full fix may be server-side: if pixelflux padded VA-API/NVENC captures by edge replication (as x264 effectively does), the bar would disappear for all clients with zero client-side cost. Happy to file that on linuxserver/pixelflux#18 if preferred.
Tested on the LSIO-based image against comprehensive-fixes (
da1b55e) on Intel/VA-API and NVIDIA/NVENC.🤖 Generated with Claude Code