Skip to content

fix(negotiate): double-probe quirk + board-proven 4K@30 pixel-rate cap for the Osmo Pocket 3 - #22

Merged
andrescera merged 3 commits into
mainfrom
fix/osmo-4k30-pixel-rate-cap
Jul 30, 2026
Merged

fix(negotiate): double-probe quirk + board-proven 4K@30 pixel-rate cap for the Osmo Pocket 3#22
andrescera merged 3 commits into
mainfrom
fix/osmo-4k30-pixel-rate-cap

Conversation

@andrescera

Copy link
Copy Markdown
Member

What

Two stacked fixes to the Osmo Pocket 3 (2ca3:0023) negotiation path:

  1. de5f4f9 — a QUIRK_MAX_PIXEL_RATE row that stops the element from selecting the Osmo's
    phantom 4K@60 mode (which negotiated cleanly but delivered zero frames).
  2. 0141874QUIRK_DOUBLE_PROBE, which discards a stale readback so a mode-INCREASE stops
    failing with Invalid mode. Without it, the shipped 1080p30 shipping mode itself fails to
    negotiate whenever the device was last committed to something smaller — not a 4K-only issue.
  3. 598191a — raises QUIRK_MAX_PIXEL_RATE from the 1080p30 value to a board-proven 4K@30
    (248832000), now that a real capture shows the Osmo delivers real, sustained, SPS-verified
    4K@30 frames with the double-probe fix in place.

Why

The Osmo intermittently failed with Invalid mode on any mode increase (root-caused to a stale
GET_CUR readback issued while the previous mode was still committed on the device — see commit
0141874's message for the full mechanism). Separately, the existing 4K exclusion cost the
operator 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.md in the
    root 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=300 at 3840x2160@30 on real hardware, exact 300/300 buffer delivery
    (identity-probe counted), zero errors, h264parse SPS readback confirming real 4K.

Locally: 109/109 ctest without sanitizers, 164/164 with -DENABLE_SANITIZERS=ON, 11/11 in
the quirks-specific subset, quirks.c clean at -Wall -Wextra.

Risks

  • 4K@60/50/48 stay excluded. The original zero-frame observation for those rates was real and
    is not fully explained (see task-f12-board-proof.md §6) — raising further than 4K@30 is a
    separate capability/risk decision, not made here.
  • Both quirk rows are keyed to 2ca3:0023 only; no other device's negotiation path changes.

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
@andrescera
andrescera merged commit 1297b19 into main Jul 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant