fix(negotiate): double-probe quirk + board-proven 4K@30 pixel-rate cap for the Osmo Pocket 3 - #22
Merged
Merged
Conversation
The DJI Osmo Pocket 3 (2ca3:0023) advertises 3840x2160 at 60/50/48 fps in its H.264 UVC descriptor and cannot deliver any of them. negotiate() prefers the largest area at the highest fps, so it picked 4K@60 by construction: the stream control bound, uvc_start_streaming() returned OK, no frame ever arrived, and the 5 s silence watchdog reported a disconnect that had not happened (error -5). The descriptor is not misparsed - it really does carry dwFrameInterval 166666, confirmed byte-for-byte from a raw USB descriptor dump on both the USB-A and USB-C ports and independently by the kernel uvcvideo parser. So this is fixed at the policy layer, in the vid:pid quirk table that exists for it. Adds QUIRK_MAX_PIXEL_RATE: a row carries the highest width*height*fps proven to stream, and negotiate() drops every advertised rate above it before the preference runs - so a phantom rate can neither win the selection nor reach the caps published downstream. The Osmo row caps at 1920x1080x30 = 62208000 px/s, the highest rate measured good on hardware. 4K@30/48/50 are untested, and the cap deliberately sits at the last confirmed-good rate rather than the last known-bad one because a cap set too low only costs resolution while a cap set too high costs the whole stream. Raising it is a one-number change once a candidate mode is measured delivering advancing frames. The quirk lookup moves above the selection loop (it now informs selection, not just the probe count) and resolves into a uvc_quirk_limits_t, so callers test one field instead of re-checking the flag. QUIRK_DOUBLE_PROBE reuses it and its behaviour is unchanged. Tests: a new mock format mode replays the Osmo's real five-descriptor H.264 ladder, driving a red/green pair - an unquirked device must still pick 3840x2160@60 (pinning the untouched behaviour every other camera gets), and the quirked Osmo must land on 1920x1080@30. Verified red-first: disarming the cap fails the Osmo case with "got 3840x2160@60/1", the exact hardware signature. 163/163 ctest green; production sources clean at -Wall -Wextra.
…ps failing "Unable to get stream control: Invalid mode" ~170 ms into start() was being read as an intermittent, unexplained negotiation flake and as "the camera sometimes isn't detected". It is neither intermittent nor unexplained. libuvc's uvc_probe_stream_ctrl() SET_CURs the stream control it wants, GET_CURs it back, and rejects the mode when the readback disagrees (_uvc_stream_params_negotiated, src/stream.c). The Osmo answers that first GET_CUR out of the mode it had PREVIOUSLY committed, so every negotiation that asks for a LARGER mode than the one currently committed is rejected - and every request for the same or a smaller mode succeeds, because the stale readback happens to agree. Measured on hardware (192.168.78.131, 2026-07-30), one probe vs two, same binary otherwise: 1280x720@30 -> 1920x1080@30 1 probe: 3/3 FAIL 2 probes: 3/3 pass 1920x1080@30 -> 3840x2160@60 1 probe: 20/20 FAIL 2 probes: 4/4 pass same mode again, or smaller 1 probe: pass 23/23 on a mode increase, 0 otherwise. It only looks flaky in the field because whether it fires depends on what mode the device last committed. The 720p->1080p row is the one that matters: the element's own shipping mode is affected, so this was never a 4K-only concern. The workaround already existed for exactly this libuvc defect (#242) and no device was keyed to it. The Osmo row now sets QUIRK_DOUBLE_PROBE alongside its existing QUIRK_MAX_PIXEL_RATE cap; the flags are independent and compose. Also ruled out, so it stops being re-theorised: HOME is irrelevant here. The SPS/PPS cache is the only HOME consumer on this path and load_spspps() runs AFTER the probe, so the "HOME environment variable not set" warning can only appear on a SUCCEEDING negotiation - it is a marker of success, not a cause of failure. Confirmed live: 6/6 fail with HOME=/root and 6/6 fail with HOME unset, and the warning never appears on a failing run. Tests: quirks_osmo_row_double_probes pins that the shipped row probes twice and that the cap still lands 1920x1080@30, verified red-first (fails with "the shipped 2ca3:0023 row must set QUIRK_DOUBLE_PROBE"). 164/164 ctest green.
…ven 4K@30
The cap sat at 1920x1080x30 = 62 208 000 px/s because 1080p30 was the only rate
ever measured delivering frames through this element. It cost the operator 4K for
want of a capture, and the capture has now happened.
Board 192.168.78.131, 2026-07-30 (~09:14-09:16Z). The plugin .so was deployed
alone so the board's libuvc stayed at 4868e57 and the cap was the only variable
(the Dockerfile's pinned fork SHA f3eda76 is an ANCESTOR of what the board runs,
so shipping the bundled libuvc would have downgraded it mid-experiment). Two runs
of:
gst-launch-1.0 -v libuvch264src index="2ca3:0023" num-buffers=300 \
! video/x-h264,width=3840,height=2160,framerate=30/1 ! h264parse ! fakesink
run 1 clean EOS, exit 0, 10.71 s, h264parse SPS -> 3840x2160 / high / 5.2
run 2 exactly 300/300 access units, counted at an identity probe, 10.79 s,
same 3840x2160 / high / level 5.2 / 4:2:0
Zero errors, zero RESOURCE/READ, no silence-watchdog disconnect on either run.
Both logged `max pixel rate 248832000` and `quirk: dropped 3 non-deliverable
rate(s) at 3840x2160`. That is the bar this table has always demanded and nothing
less: frames ADVANCING for the full window, at a resolution read out of the SPS
rather than the requested caps echoed back, reproduced.
The new value is 4K@30 EXACTLY, not the full 4K@60 descriptor range, and that is
what makes the capture conclusive rather than suggestive: negotiate() prefers max
area then max fps, so at 248 832 000 the surviving top mode is 3840x2160@30 by
construction and the runs cannot have silently measured 4K@60 instead.
4K@60/50/48 stay excluded - an uncapped build did stream 4K@60 that same day, but
the original zero-frame observation was real and was never explained (one
candidate: the stale-readback defect QUIRK_DOUBLE_PROBE exists for can leave a
bound-but-silent stream when the readback happens to compare equal). So the cap
still sits at the last CONFIRMED-GOOD rate, not the last known-bad one.
Tests: the cap literal in test_quirks.c is a deliberate tripwire and it fired -
raising quirks.c on its own turned four cases red (the literal, the three
uvc_quirk_max_fps ceilings, and both end-to-end ladder outcomes). All are
re-pointed at the proven value, none relaxed: the phantom-rate exclusions stay
exact, both ladder cases still assert one exact mode, nothing is skipped. The
ceilings follow from the new cap exactly - 1080p 120 fps, 720p 270 fps, 4K 30 fps
- and the quirked Osmo now lands on 3840x2160@30.
109/109 ctest green without sanitizers, 164/164 with, 11/11 in the quirks subset;
quirks.c clean at -Wall -Wextra.
Board-Evidence-SHA256: 86934c9b5af02ad24deff4d53a229a060a28e253e97ee9d6f7502caeffa22c6f
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two stacked fixes to the Osmo Pocket 3 (
2ca3:0023) negotiation path:de5f4f9— aQUIRK_MAX_PIXEL_RATErow that stops the element from selecting the Osmo'sphantom 4K@60 mode (which negotiated cleanly but delivered zero frames).
0141874—QUIRK_DOUBLE_PROBE, which discards a stale readback so a mode-INCREASE stopsfailing with
Invalid mode. Without it, the shipped 1080p30 shipping mode itself fails tonegotiate whenever the device was last committed to something smaller — not a 4K-only issue.
598191a— raisesQUIRK_MAX_PIXEL_RATEfrom the 1080p30 value to a board-proven 4K@30(
248832000), now that a real capture shows the Osmo delivers real, sustained, SPS-verified4K@30 frames with the double-probe fix in place.
Why
The Osmo intermittently failed with
Invalid modeon any mode increase (root-caused to a staleGET_CURreadback issued while the previous mode was still committed on the device — see commit0141874's message for the full mechanism). Separately, the existing 4K exclusion cost theoperator real 4K capability once the double-probe fix made 4K@30 actually deliverable.
How to verify
Board hardware evidence for both parts is frozen and cited in the commit trailers
(
Board-Evidence-SHA256):de5f4f9+0141874:.omo/evidence/device-quality-wave3/task-f12-board-proof.mdin theroot workspace repo — 30/30 pre-deploy negative control, board-verified quirk resolution,
90-buffer real-time delivery proof, and an asymmetric 23/23-fail / 0/7-pass mode-increase
reproduction that pins the root cause.
598191a:.omo/evidence/device-quality-wave3/task-f12-4k30-cap-board-proof-freeze.md—two runs of
num-buffers=300at 3840x2160@30 on real hardware, exact 300/300 buffer delivery(identity-probe counted), zero errors,
h264parseSPS readback confirming real 4K.Locally:
109/109ctest without sanitizers,164/164with-DENABLE_SANITIZERS=ON,11/11inthe quirks-specific subset,
quirks.cclean at-Wall -Wextra.Risks
is not fully explained (see
task-f12-board-proof.md§6) — raising further than 4K@30 is aseparate capability/risk decision, not made here.
2ca3:0023only; no other device's negotiation path changes.