forked from marfrit/libva-v4l2-request-fourier
988b848908
Closes three internal carry items in one fork commit. iter6 deferred
these as TODOs; iter7 lands the implementations + supporting tests.
# Track B — slot-leak error recovery (src/)
iter6 documented the RequestSyncSurface error paths as a "bounded
leak we accept" — slots stayed busy=true after REINIT/DQBUF failures
until RequestTerminate ran. With pool=16 and rare errors this was
acceptable, but a sustained-error scenario could starve the pool.
Adds request_pool_force_release(pool, index) which:
1. Tries media_request_reinit on the slot's fd (cheap path)
2. Falls back to close + media_request_alloc (recovery)
3. Leaves the slot dead-busy if even alloc fails (other slots
unaffected, pool capacity reduced by 1 until destroy)
Wires it into surface.c RequestSyncSurface error paths only for
errors before the OUTPUT-DQBUF attempt. After OUTPUT-DQBUF failure
the V4L2 buffer is in indeterminate kernel state, so a separate
error label (`error_buffer_indeterminate`) leaves the slot
dead-busy — reusing the slot would QBUF on a kernel-still-held
buffer and EINVAL.
Phase 5 sonnet review caught this discriminator subtlety pre-commit.
Files: request_pool.{h,c}, surface.c.
# Track C — cap_pool race synthetic harness (tests/)
iter5 sonnet C4 / iter6 candidate A: cap_pool resolution-change
race was organically exercised by YT's quality renegotiations
(iter6 close, 4 cap_pool_init events clean) but had no
deterministic regression test.
tests/cap_pool_probe_pattern.c — ~170-line C program: opens
libva display, vaCreateConfig, vaCreateSurfaces(small) +
vaCreateContext (triggers OUTPUT pool init at small resolution),
dispose, vaCreateSurfaces(big) + vaCreateContext (forces S_FMT
on the new resolution against an in-use OUTPUT pool — the actual
race-hitting path).
Phase 5 sonnet flagged that without vaCreateContext the test
would pass trivially (OUTPUT pool never init'd, REQBUFS(0) on
empty queue is a no-op). Fixed before commit.
tests/run_cap_pool_probe.sh — runner; greps driver stderr for
REQBUFS / EBUSY / "Unable to set format" race indicators.
# Track A — msync pixel-correctness verify harness (tests/)
iter5 sweep removed msync(MS_SYNC|MS_INVALIDATE) from CAPTURE
DQBUF path. iter5 sonnet C3 flagged: no formal pixel verification.
tests/run_msync_pixel_verify.sh — runs FFmpeg SW decode (libavcodec
reference) and FFmpeg HW decode (via our v4l2_request driver),
compares NV12 byte streams. Probes fixture dimensions via ffprobe
and uses crop=$W:$H after hwdownload to normalize MB-padding
artifacts (hantro pads height to 16-line align; SW returns
crop-aligned).
Phase 5 sonnet flagged the stride-mismatch false-failure risk
pre-commit. Fixed: explicit crop + diagnostic that distinguishes
genuine pixel divergence from MB-padding stride artifacts.
# Phase 5 sonnet code review
Verdict: APPROVE-WITH-CHANGES. Three actionable findings, all
addressed before this commit:
1. surface.c error path: separated OUTPUT-DQBUF-failure into
error_buffer_indeterminate label, slot stays dead-busy
2. cap_pool_probe_pattern.c: added vaCreateContext to actually
exercise the OUTPUT pool init at the small resolution
3. run_msync_pixel_verify.sh: explicit crop on HW path,
stride-mismatch diagnostic distinguished from corruption
Empirical verification (Phase 6+7 deploy + run): pending operator
ohm-tools availability.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
108 lines
3.9 KiB
C
108 lines
3.9 KiB
C
/*
|
|
* Copyright (C) 2026 Markus Fritsche <fritsche.markus@gmail.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
|
*/
|
|
|
|
#ifndef _REQUEST_POOL_H_
|
|
#define _REQUEST_POOL_H_
|
|
|
|
#include <stdbool.h>
|
|
|
|
/*
|
|
* OUTPUT (bitstream-input) buffer pool, decoupled from caller-allocated
|
|
* VA surfaces. Sizing is driven by codec pipeline depth (typically 4
|
|
* for H.264), not by the consumer's surface count.
|
|
*
|
|
* The pool owns the V4L2 buffer indices and their mmap pointers. A
|
|
* decode request "borrows" a slot at vaBeginPicture, fills it across
|
|
* vaRenderPicture calls, queues it at vaEndPicture, and releases it
|
|
* after VIDIOC_DQBUF returns.
|
|
*
|
|
* This replaces the per-surface OUTPUT-buffer ownership model in the
|
|
* pre-refactor code, where object_surface.source_* fields permanently
|
|
* held a single OUTPUT buffer per surface — incorrect because OUTPUT
|
|
* buffers are request-time resources, not picture-time resources, and
|
|
* because the per-surface loop in RequestCreateContext only ran when
|
|
* surfaces_count > 0 (breaking ffmpeg's vaapi-copy num_render_targets=0
|
|
* convention).
|
|
*/
|
|
|
|
struct request_pool_slot {
|
|
unsigned int index; /* V4L2 buffer index in OUTPUT queue */
|
|
void *data; /* mmap pointer for this slot */
|
|
unsigned int size; /* mmap size in bytes */
|
|
bool busy; /* true while borrowed for a request */
|
|
int request_fd; /* per-slot media-request fd, allocated
|
|
* once at pool init, REINIT'd between
|
|
* uses. iter6: replaces iter4 close+
|
|
* alloc-per-frame to eliminate cross-
|
|
* slot fd-reuse race that broke Firefox
|
|
* MediaSource's multi-surface decode. */
|
|
};
|
|
|
|
struct request_pool {
|
|
struct request_pool_slot *slots;
|
|
unsigned int count;
|
|
unsigned int next; /* round-robin acquire cursor */
|
|
int media_fd; /* iter7: kept for
|
|
* force_release re-alloc */
|
|
bool initialized;
|
|
};
|
|
|
|
/*
|
|
* Allocate count OUTPUT buffers via VIDIOC_CREATE_BUFS, query and mmap
|
|
* each, populate pool->slots[]. Caller must have already done
|
|
* VIDIOC_S_FMT on the OUTPUT queue. Returns 0 on success, -1 on
|
|
* failure.
|
|
*/
|
|
int request_pool_init(struct request_pool *pool, int video_fd, int media_fd,
|
|
unsigned int output_type, unsigned int count);
|
|
|
|
/*
|
|
* Munmap all slots and free the slots array. Idempotent.
|
|
*/
|
|
void request_pool_destroy(struct request_pool *pool);
|
|
|
|
/*
|
|
* Claim the next free slot (round-robin). Returns the slot's V4L2
|
|
* buffer index on success (slot in pool->slots[] is determined by
|
|
* the returned index), or -1 if all slots are busy.
|
|
*/
|
|
int request_pool_acquire(struct request_pool *pool);
|
|
|
|
/*
|
|
* Mark the slot at pool->slots[i] free for reuse. Caller must pass the
|
|
* V4L2 buffer index returned earlier from request_pool_acquire().
|
|
*/
|
|
void request_pool_release(struct request_pool *pool, unsigned int index);
|
|
|
|
/*
|
|
* iter7: error-recovery release. Called from RequestSyncSurface error
|
|
* paths when media_request_reinit or VIDIOC_DQBUF failed mid-cycle and
|
|
* the slot's request_fd is now in an undefined state. REINITs the fd;
|
|
* if REINIT fails (kernel-side request object too far gone), close
|
|
* the fd and re-alloc a fresh one. If realloc also fails, the slot
|
|
* is left busy=true (effectively dead, count decremented by 1) — pool
|
|
* survives but with reduced capacity until driver terminate. Other
|
|
* slots are unaffected.
|
|
*
|
|
* Caller passes the V4L2 buffer index from request_pool_acquire().
|
|
*/
|
|
void request_pool_force_release(struct request_pool *pool,
|
|
unsigned int index);
|
|
|
|
/*
|
|
* Look up the pool slot owning a given V4L2 buffer index. Returns
|
|
* pointer to the slot on success, NULL if the index is out of range.
|
|
* The returned pointer is valid until pool destruction; do not free.
|
|
*/
|
|
struct request_pool_slot *request_pool_slot(struct request_pool *pool,
|
|
unsigned int index);
|
|
|
|
#endif
|