A decoder that refuses to start no longer takes the picture with it #419
Merged
enricobuehler
merged 2 commits from 2026-08-27 17:05:23 +00:00
worktree-android-decode-start-fallback into main
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c3e3333c40 |
fix(client/android): keep ASC alive when the overlay allocation is what start chokes on
android / android (pull_request) Successful in 6m37s
ci / bun-nix (pull_request) Successful in 32s
ci / web (pull_request) Successful in 1m37s
ci / rust-arm64 (pull_request) Successful in 4m27s
ci / docs-drift (pull_request) Successful in 22s
ci / docs-site (pull_request) Successful in 1m26s
ci / rust (pull_request) Failing after 7m10s
Why `configure()` passes and `start()` dies with an AImageReader output on the Mi TV Stick class of device: configure only records the window — start is where ACodec dequeues (and thereby gralloc-allocates) every codec output buffer from it, with a usage that is the OR of our reader's consumer bits and the vendor decoder's private producer bits. Our reader asked for GPU_SAMPLED_IMAGE | COMPOSER_OVERLAY; the Amlogic OMX component adds its contiguous-vdec-memory flags. On phones (Codec2, 64-bit, gralloc4) that triple is routine. On a 32-bit OMX-era BSP, an app-side consumer demanding overlay-scanout + GPU-sampled + vdec-writable in a single allocation is the exotic ask, and a refusal surfaces exactly as `start failed: ErrorUnknown` after a clean configure. So the ladder gets a middle rung that keeps ASC instead of abandoning it: retry with a reader asking for GPU_SAMPLED_IMAGE alone — the SurfaceTexture shape every TextureView/WebView video path exercises, the most universally allocatable there is. SurfaceFlinger then GPU-composites the layer (one 1080p quad — noise), and everything that makes ASC worth having survives: real latch times, real release fences, the learned panel period. setBuffer has no overlay requirement, so the only cost is losing the HWC direct-scanout optimization on devices that were never going to grant it anyway. Usage is the only reader axis worth a rung: READER_MAX_IMAGES is not a start-time factor (consumer-side images allocate lazily during streaming), so a start failure that survives the gpu-only rung genuinely needs the SurfaceView rungs behind it. The ladder is now: ASC overlay → ASC gpu-only → SurfaceView (aggressive keys) → SurfaceView (plain keys), deduped as before so a device that works pays nothing and each log line names the exact configuration that won or was refused. The "asc: backend up" line now carries the reader profile too. |
||
|
|
6ef7230ace |
fix(client/android): a decoder that refused to start took the picture with it
`configure()` succeeding says nothing about `start()` — start is where the codec negotiates buffers with its output consumer and allocates them, so a decoder that accepted the format can still refuse the surface it has to render into. On a Xiaomi Mi TV Stick (Android 11, armeabi-v7a, OMX.amlogic.hevc.decoder.awesome2) every session logged `start failed: ErrorUnknown` and the decode thread returned, so not one access unit was ever fed while the pump kept receiving video. The frame queue filled, the pump jumped to live once per FLUSH_COOLDOWN, and the host read that perfect 2 s keyframe cadence as a client too slow to sustain the stream. Audio, input and the library all kept working, so it presented as a permanent black screen with sound — nine sessions of it in one log, and nothing on the host able to see why. Give the async loop a bring-up ladder instead of one attempt. A codec that failed start is in an error state and cannot be reconfigured, so each rung builds a fresh one and sheds what a start can choke on, most-suspect first: the AImageReader the ASC presenter renders into (READER_MAX_IMAGES full-resolution PRIVATE COMPOSER_OVERLAY buffers, which the SurfaceView path does not allocate at all), then the aggressive low-latency key set. Every downstream branch already keys off `asc.is_some()`, so a fallen-back session simply runs the SurfaceView presenter that has always been the API < 29 / ASC-init-failure fallback. Rung 0 is always exactly what the session asked for, so a device that works pays nothing; the winning rung is logged, so the next device that needs one names its own culprit instead of leaving us to guess. The sync loop gets no ladder: it only runs with low-latency mode off, which is already the conservative key set, and it renders straight into the SurfaceView — both axes are already shed, and there is no simpler configuration to fall back to. It gets the diagnosis in its error line instead, since the session stays up around the failure there too. Also lifts the HDR static-info fetch above the ladder so a retry never pays its 250 ms wait again, and extracts the async-notify callback registration, which each rung's fresh codec now needs. |