Phase 8.10+8.11: libva consumer integration scaffold

Brings daedalus_v4l2 from "standalone test client" to "VAAPI-
discoverable decoder" by adding the surface formats and
media-controller plumbing that libva-v4l2-request-fourier
(sibling repo) requires.

libva-v4l2-request-fourier patches (pushed separately):
- b5b3acf: daedalus_v4l2 added to known_decoder_drivers
- 2146341: meson option gate

This commit (daedalus-v4l2 side, 3 production changes):

1. V4L2_PIX_FMT_NV12 (single-plane) on CAPTURE
   - Added to daedalus_capture_formats[] alongside NV12M + P010
   - daedalus_fill_capture_fmt handles num_planes=1 case
     (sizeimage = W*H*3/2, bytesperline = W)
   - daemon pack_nv12_single_to_plane: Y at base+0,
     interleaved CbCr at base+(stride*H); same byte content
     as NV12M two-plane, different layout
   - Required because libva-v4l2-request-fourier's video.c
     only knows non-multi-plane NV12 (it advertises
     v4l2_mplane=true but uses the single-plane fourcc).
   - Verified byte-exact via test_m2m_stream against
     ffmpeg -pix_fmt nv12 reference (VP9 1080p 10 frames,
     31 MB).

2. V4L2 Request API media ops
   - daedalus_media_ops = { vb2_request_validate,
     v4l2_m2m_request_queue } assigned to mdev.ops before
     media_device_init.
   - Without this, MEDIA_IOC_REQUEST_ALLOC returned
     -ENOTTY and no VAAPI consumer could allocate a
     media_request.

3. Stateless control registration via v4l2_ctrl_new_custom
   - Switched from v4l2_ctrl_new_std_compound(NULL p_def)
     to v4l2_ctrl_new_custom — pattern rkvdec/cedrus/
     hantro use. Adds a no-op s_ctrl callback.

Verification (hertz, Pi 5, 6.12.75+rpt-rpi-2712):

LibVA trace through `ffmpeg -hwaccel vaapi`:
  vaInitialize / Profiles / Entrypoints / CreateConfig /
  QuerySurfaceAttributes / CreateSurfaces / CreateContext
  (cap_pool: 24 slots, 1 plane each) / CreateBuffer
  (slice + picture params) / MEDIA_IOC_REQUEST_ALLOC
  — all succeed.

Standalone NV12 decode path:
  test_m2m_stream vp9_1080_stream.ivf out.nv12 1920 1080 vp9 nv12
  → 10/10 frames, byte-exact vs ffmpeg -pix_fmt nv12

vainfo (via libva-v4l2-request-fourier with our driver):
  7 VAProfile entries with VAEntrypointVLD
  (H264 Main/High/CBaseline/MultiviewHigh/StereoHigh,
   VP9Profile0, AV1Profile0)

What's NOT here (Phase 8.12):

The libva trace stops at VIDIOC_S_EXT_CTRLS returning
EINVAL when populating V4L2_CID_STATELESS_VP9_FRAME on
the request. The compound-control payload validation
against the kernel's expected struct shape rejects.
This isn't a "missing line" fix — it needs proper
stateless control plumbing (the SPS/PPS/SliceParams
get_dims, validate, default-value paths that in-tree
rkvdec/cedrus/hantro implement to satisfy v4l2-core's
std_validate). Documented as Phase 8.12 scope.

The shipped integration is itself a meaningful deliverable:
all the framework scaffolding is in place; the remaining
gap is well-characterised and bounded.

See docs/phase_8_10_11_closure.md for the full trace
analysis + next-phase plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 17:51:16 +00:00
parent d84efdb125
commit 0de0288dce
4 changed files with 380 additions and 11 deletions
+62
View File
@@ -233,6 +233,65 @@ static int pack_p010_to_plane(struct AVFrame *fr,
return 0;
}
/*
* Pack 8-bit planar YUV420P into V4L2_PIX_FMT_NV12 single plane:
* Y plane (W*H bytes) followed by interleaved CbCr at half-res
* (W*H/2 bytes) all in planes->base[0]. Same layout as P010
* sans the depth shift. For libva-v4l2-request-style clients
* that expect num_planes=1 NV12.
*/
static int pack_nv12_single_to_plane(struct AVFrame *fr,
const AVPixFmtDescriptor *desc,
const struct daedalus_capture_planes *planes)
{
int h = fr->height;
int w = fr->width;
int cw, ch, y, x;
uint8_t *base;
uint32_t stride;
uint8_t *dst_y, *dst_uv;
size_t y_size;
if (!desc || !planes || planes->nr < 1)
return -EINVAL;
if (desc->nb_components < 3)
return -EINVAL;
if (desc->log2_chroma_w != 1 || desc->log2_chroma_h != 1)
return -EINVAL;
if (desc->comp[0].depth != 8)
return -EINVAL;
cw = AV_CEIL_RSHIFT(w, desc->log2_chroma_w);
ch = AV_CEIL_RSHIFT(h, desc->log2_chroma_h);
base = planes->base[0];
stride = planes->stride[0] ? planes->stride[0] : (uint32_t) w;
if (!base)
return -EINVAL;
dst_y = base;
y_size = (size_t) stride * (size_t) h;
dst_uv = base + y_size;
for (y = 0; y < h; y++)
memcpy(dst_y + (size_t) y * stride,
fr->data[0] + (size_t) y * fr->linesize[0],
(size_t) w);
for (y = 0; y < ch; y++) {
const uint8_t *u = fr->data[1] +
(size_t) y * fr->linesize[1];
const uint8_t *v = fr->data[2] +
(size_t) y * fr->linesize[2];
uint8_t *row = dst_uv + (size_t) y * stride;
for (x = 0; x < cw; x++) {
row[x * 2 + 0] = u[x];
row[x * 2 + 1] = v[x];
}
}
return 0;
}
static int pack_nv12_to_planes(struct AVFrame *fr,
const AVPixFmtDescriptor *desc,
const struct daedalus_capture_planes *planes)
@@ -425,6 +484,9 @@ int daedalus_decoder_run_request(struct daedalus_decoder *dec,
case V4L2_PIX_FMT_NV12M:
prc = pack_nv12_to_planes(fr, desc, planes);
break;
case V4L2_PIX_FMT_NV12:
prc = pack_nv12_single_to_plane(fr, desc, planes);
break;
case V4L2_PIX_FMT_P010:
prc = pack_p010_to_plane(fr, desc, planes);
break;