12 Commits

Author SHA1 Message Date
marfrit 96d70af674 picture: no-op codec_set_controls case for VAProfileAV1Profile0
picture.c's codec_set_controls() switch was falling through to the
default case for VAProfileAV1Profile0, returning
VA_STATUS_ERROR_UNSUPPORTED_PROFILE.  Result: vaEndPicture failed
with status 12 ("requested VAProfile is not supported"), no OUTPUT
buffer ever got queued, and the daedalus_v4l2 daemon never saw a
REQ_DECODE for AV1.

config.c's VAProfileAV1Profile0 case (line 84-93) explicitly notes
"Decode-side ctrl dispatch (V4L2_CID_STATELESS_AV1_*) is NOT YET
WIRED on master — vainfo will list the profile + CreateConfig
succeeds, but consumers that submit decode buffers hit a NOP path".
The NOP path was never actually wired in picture.c — it hit the
default UNSUPPORTED_PROFILE branch instead.

Fix: add a VAProfileAV1Profile0 case that just `break;`s through
without setting V4L2 controls.  For the daedalus_v4l2 daemon path
this is exactly the right shape — AV1 frame data is self-describing
per OBU stream (no separate SPS/PPS controls needed at the V4L2
boundary), so the OUTPUT buffer alone is sufficient for the kernel
to forward to the daemon.

Verified on higgs: ffmpeg -hwaccel vaapi -i av1.mkv now actually
queues frames to /dev/video2 and the daemon's libdav1d context opens.
Decode itself still fails (libdav1d wants the AV1 sequence header
OBU, which ffmpeg-vaapi sends via VAPictureParameterBufferAV1 not
via the slice buffer) — separate issue, needs an OBU sequence-header
synthesiser in the daedalus daemon (analogous to the new H.264
SPS/PPS NAL synth in daedalus-v4l2/daemon/src/h264_nal_synth.c).
That sequence-header synth work is a substantial follow-up; this
patch unblocks AV1 reaching the daemon at all.

For RK3588 vpu981 (the originally-planned AV1 target), this
remains a true NO-OP — when V4L2_CID_STATELESS_AV1_* dispatch
lands from the av1-iter1 operator branch, replace the no-op with
av1_set_controls(...).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 20:58:57 +02:00
marfrit c1bb444d07 Merge pull request 'h264: max_num_ref_frames fallback + libva-boundary instrumentation (#8)' (#9) from claude-noether/libva-v4l2-request-fourier:noether/h264-3-set-controls-bitstream-bug-8 into master
Reviewed-on: marfrit/libva-v4l2-request-fourier#9
2026-05-20 18:19:03 +00:00
claude-noether 0791f8e612 h264: max_num_ref_frames fallback + libva-boundary instrumentation
Closes the libva-side portion of marfrit/libva-v4l2-request-fourier#8.

Two small additions to h264_set_controls:

1. When VAPicture->num_ref_frames is 0 (older ffmpeg-vaapi paths /
   some daedalus_v4l2 consumers), count valid (non-INVALID) DPB
   entries in ReferenceFrames[16]. If even that returns 0, fall back
   to a per-profile spec minimum (1 for baseline, 4 for main/high).
   Hardware decoders (rkvdec, hantro, rpi-hevc-dec) tolerated the
   prior 0; libavcodec-via-daedalus enforces sps.max_num_ref_frames
   strictly and rejected every frame.

2. One request_log line at function entry dumping the raw VAAPI
   fields (seq_fields.value, pic_fields.value, num_ref_frames,
   bit_depth_*, picture_*_in_mbs_minus1). Disambiguates "ffmpeg-vaapi
   never populated" from "daedalus_v4l2 wire protocol corrupted" for
   the bit-fields-read-as-zero portion of issue #8.

Out of scope here (separate issue if pursued): profile_idc and
level_idc remain session-derived. VAAPI's VAPictureParameterBufferH264
omits both (verified higgs libva 2.22.0-3, /usr/include/va/va.h:
3571-3622) — same VAAPI-blindspot family as the HEVC SPS fields. A
real fix requires SPS-NAL parsing from surface->source_data OR a
daedalus wire-protocol pass-through; both are operator design calls,
not a libva-only patch.

Build verified on higgs (Debian 13 trixie, gcc 14.2.0, libva 2.22.0):
clean ninja link of v4l2_request_drv_video.so, vainfo enumerates all
8 codec profiles, no init regression.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 20:17:27 +02:00
marfrit 989833114a Merge pull request 'config: include video_fd_daedalus in profile enumeration probe' (#7) from claude-noether/libva-v4l2-request-fourier:noether/libva-2-config-profile-enum-daedalus into master
Reviewed-on: marfrit/libva-v4l2-request-fourier#7
2026-05-20 14:52:11 +00:00
marfrit d1ba4625d2 config: include video_fd_daedalus in profile enumeration probe
LIBVA-2 follow-up.  RequestQueryConfigProfiles walks each known
decoder fd via any_fd_supports_output_format() and adds a VAProfile*
for each codec OUTPUT format the V4L2 device advertises.  The fd
list missed video_fd_daedalus — so on a Pi 5 with rpi-hevc-dec
primary + daedalus_v4l2 alt, only S265 (HEVC) was probed and the
H.264 / VP9 / AV1 profiles never got enumerated.

Effect on higgs: ffmpeg -hwaccel vaapi -i h264_test.mp4 reported
"No support for codec h264 profile 578" before the per-codec
dispatch in request_switch_device_for_profile could fire — the
profile-578 (H264 Constrained Baseline) check happened during
hwaccel init, found nothing in the libva profile list, and bailed
without ever calling into the daedalus path.

Fix: extend the fds[] array in any_fd_supports_output_format from
5 to 6 entries, with the sixth being video_fd_daedalus when
HAVE_DAEDALUS_V4L2 is on (and -1 otherwise so it's skipped by the
`if (fds[i] < 0) continue;` guard).  After the fix, daedalus_v4l2's
OUTPUT format menu (VP9F + AV1F + S264) gets seen, and Request-
QueryConfigProfiles returns VP9Profile0 + AV1Profile0 + the H264*
profiles, all of which then route through the LIBVA-1 'd' kind
override in request_switch_device_for_profile.

Verified on higgs:

  Before:
    vainfo: Supported profile and entrypoints
          VAProfileHEVCMain               : VAEntrypointVLD
    (only HEVC; H264/VP9/AV1 not enumerated)

  ffmpeg vaapi -i h264 → "No support for codec h264 profile 578"

Build clean on boltzmann (only config.c.o + request.c.o recompile).

Backward-compatible on RK3399/3588 — the new slot is gated by
HAVE_DAEDALUS_V4L2 *and* video_fd_daedalus >= 0; both stay false in
those deployments.  Existing 5-fd probe order unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 16:45:33 +02:00
claude-noether c332d34643 Merge pull request 'request: route VP9/AV1/H.264 to daedalus_v4l2 on Pi 5 mixed deploy' (#6) from claude-noether/libva-v4l2-request-fourier:noether/libva-1-per-codec-dispatch into master 2026-05-20 08:53:04 +00:00
marfrit 6173a8da8e request: route VP9/AV1/H.264 to daedalus_v4l2 on Pi 5 mixed deploy
LIBVA-1 — when both rpi-hevc-dec and daedalus_v4l2 are loaded, finish
the per-codec dispatch so HEVC goes to rpi-hevc-dec (existing 'p'
override) and VP9 / AV1 / H.264 go to the daedalus daemon ('d').

Before this change the multi-device-probe accepted only ONE driver
plus a fixed alt slot (rkvdec↔hantro-vpu); on a Pi 5 with both decoders
the find_codec_device() walk preferred rpi-hevc-dec by known_decoder_
drivers[] order and never opened daedalus_v4l2, so VP9/AV1/H.264 frames
hit rpi-hevc-dec's S_FMT and failed.

Changes:

  - request.c multi-device-probe: when primary = rpi-hevc-dec, alt =
    daedalus_v4l2 (when HAVE_DAEDALUS_V4L2 is on); symmetric handling
    in the daedalus_v4l2 primary branch so alt = rpi-hevc-dec.  This
    preserves the iter40 fallback (no daedalus → alt = NULL) when the
    build option is off.

  - request.c alt-driver opening block: generalized from the iter38
    rkvdec/hantro pair to also dispatch into video_fd_rpi_hevc_dec and
    video_fd_daedalus slots.  Defensive close on unknown alt-driver
    name (shouldn't happen — primary_driver branches gate the choices —
    but keeps the slot tally clean if a future driver name is added
    above without wiring up the dispatch here).

  - request_switch_device_for_profile: added 'd' kind handler +
    profile override block.  When daedalus is open, VP9 / AV1 / H.264*
    route to it.  HEVC stays on rpi-hevc-dec via the existing 'p'
    override.  AV1 'a' kind (RK3588 vpu981) wins ONLY if vpu981 was
    probed, so the override only fires on hosts where vpu981 stayed
    -1 (i.e. Pi 5).

  - RequestTerminate: close the daedalus_v4l2 fd pair on teardown
    (was leaking — caught while reviewing the alt-driver expansion).

Build: meson + ninja clean on boltzmann (only pre-existing GStreamer
H265 parser noise).  Behaviour on RK3399/3588 boxes unchanged — the
new branches are gated by HAVE_DAEDALUS_V4L2 *and* video_fd_daedalus
≥ 0, both of which stay false in those deployments.

Companion to daedalus-v4l2 481279c (Phase 8.13 systemd unit) and
marfrit-packages noether/daedalus-v4l2-kernel-6.18-compat branch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 10:41:18 +02:00
marfrit de27e95571 v4l2: log error_idx + failing ctrl id on S_EXT_CTRLS failure
Better diagnostic when VIDIOC_S_EXT_CTRLS returns < 0: read
back error_idx and print which control id rejected (or
"ioctl-level" when error_idx == count, meaning the rejection
was generic, not per-control).

Made it possible to triage the daedalus_v4l2 phase 8.13 issue
by separating "the actual stateless control failed" (would
show failing_ctrl_id=0xa40a2c VP9_FRAME) from "libva probing
H264/HEVC profile/level we don't expose" (failing_ctrl_id=
0xa40900 H264_PROFILE etc.) — the latter is harmless on a
VP9-only context.

Before:
  v4l2-request: Unable to set control(s): Invalid argument

After (per-control):
  v4l2-request: Unable to set control(s): Invalid argument
                (error_idx=0/2 failing_ctrl_id=0xa40900 size=0)

After (ioctl-level):
  v4l2-request: Unable to set control(s): Invalid argument
                (error_idx=2/2 ioctl-level)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:14:50 +00:00
marfrit 2146341460 daedalus_v4l2: meson option gate (default true)
Adds a build-time switch so platforms that will never see a
daedalus_v4l2 kernel module (Allwinner cedrus, RK without the
shim, etc.) can opt out of the probe entry + dispatch branch.

  meson setup build                         # daedalus support on
  meson setup build-off -Ddaedalus_v4l2=false  # off

Implementation:
- meson_options.txt: new boolean `daedalus_v4l2`, default true.
- src/meson.build: when option is true, autoconfig.h gets
  `#define HAVE_DAEDALUS_V4L2 1`.
- src/request.c: known_decoder_drivers[] entry, primary-driver
  detection branch, and post-probe log line all gated by
  #ifdef HAVE_DAEDALUS_V4L2.
- src/request.h: struct daedalus fields kept UNCONDITIONAL.
  Two extra int per session and the struct layout stays stable
  across translation units regardless of option — avoids the
  ODR risk of every consumer of request.h needing to include
  autoconfig.h before request.h.

Verified on hertz: both builds compile clean.
  build/src/autoconfig.h has HAVE_DAEDALUS_V4L2; .so contains
  "daedalus_v4l2" string + log message.
  build-off/src/autoconfig.h doesn't; .so contains no daedalus
  strings at all.

Default-on build still passes vainfo end-to-end:
  vainfo: Driver version: v4l2-request
  vainfo: Supported profile and entrypoints
        VAProfileH264Main / High / ConstrainedBaseline / MultiviewHigh
        / StereoHigh : VAEntrypointVLD
        VAProfileVP9Profile0 : VAEntrypointVLD
        VAProfileAV1Profile0 : VAEntrypointVLD

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 17:41:17 +00:00
marfrit b5b3acf0f7 daedalus_v4l2: add to known_decoder_drivers + multi-device-probe slot
Phase 8.10 of the daedalus-v4l2 sibling campaign — out-of-tree
V4L2 stateless decoder shim that forwards bitstream to a
userspace daemon (FFmpeg-software decode for VP9 / AV1 / H.264;
pixels back via dmabuf into the CAPTURE buffer).

Adds the same iter40-shaped wiring as rpi-hevc-dec:
- known_decoder_drivers[] entry "daedalus_v4l2"
- video_fd_daedalus + media_fd_daedalus slots in driver_data
- -1 init alongside the other multi-device slots
- primary-driver detection branch in the auto-probe block
- post-probe log line for symmetry with iter40

No per-profile dispatch changes needed — daedalus_v4l2 advertises
the standard V4L2_PIX_FMT_{VP9_FRAME,AV1_FRAME,H264_SLICE}
OUTPUT fourccs the fork's existing per-driver paths already
handle.

Verified on hertz (Pi 5 / CM5, 6.12.75+rpt-rpi-2712) with the
daedalus_v4l2 module loaded:

  LIBVA_DRIVER_NAME=v4l2_request \
  LIBVA_V4L2_REQUEST_VIDEO_PATH=/dev/video0 \
  LIBVA_V4L2_REQUEST_MEDIA_PATH=/dev/media3 \
  vainfo --display drm --device /dev/dri/renderD128

  v4l2-request: opened daedalus_v4l2 at video_fd=... media_fd=... (Pi 5 daemon-backed VP9/AV1/H264)
  vainfo: Driver version: v4l2-request
  vainfo: Supported profile and entrypoints
        VAProfileH264Main               : VAEntrypointVLD
        VAProfileH264High               : VAEntrypointVLD
        VAProfileH264ConstrainedBaseline: VAEntrypointVLD
        VAProfileH264MultiviewHigh      : VAEntrypointVLD
        VAProfileH264StereoHigh         : VAEntrypointVLD
        VAProfileVP9Profile0            : VAEntrypointVLD
        VAProfileAV1Profile0            : VAEntrypointVLD

Without the env override the auto-probe still picks rpi-hevc-dec
first (it's earlier in known_decoder_drivers[]); on the standalone
daedalus_v4l2 path the daemon-backed decode is what answers
S_FMT/QBUF/DQBUF. On a mixed-driver Pi 5 box where both modules
are loaded, HEVC continues to route through rpi-hevc-dec via the
existing 'p' override; VP9/AV1/H264 would prefer daedalus_v4l2
since rpi-hevc-dec is HEVC-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 17:37:53 +00:00
marfrit 820557268b Merge PR #5: ampere-av1 Phase 2 (master) — fourth-fd probe + AV1 enumeration 2026-05-18 13:47:56 +00:00
claude-noether c6f81c653f ampere-av1 Phase 2 (master): fourth-fd probe + AV1 enumeration
Imports the minimal "vainfo lists VAProfileAV1Profile0" layer from the
operator's in-progress av1-iter1 branch (Phase 2 steps 1, 2 — commits
bed75c0 + 61db76e on av1-iter1). The Phase 3-5 bit-exact decode-side
work stays in av1-iter1; this commit gives master the enumeration +
fd-routing layer so consumers (ffmpeg-vaapi, firefox-fourier, chromium-
fourier) at least see VAProfileAV1Profile0 today on RK3588.

What this commit adds:
- video_fd_vpu981 + media_fd_vpu981 slots to struct request_data
  (named to match av1-iter1's convention so the operator's Phase 3-5
   merge resolves cleanly)
- 4th-decoder probe loop in VA_DRIVER_INIT that walks hantro-vpu
  media nodes for an instance advertising V4L2_PIX_FMT_AV1_FRAME
  (AV1F) as OUTPUT pixfmt. RK3588 has 3 hantro-vpu instances all
  reporting driver="hantro-vpu" + model="hantro-vpu", so OUTPUT-
  format probe is the only DTS-independent discriminator.
- 'a' kind in request_device_kind_for_profile (VAProfileAV1Profile0)
  + 'a' branch in request_switch_device_for_profile.
- video_fd_vpu981 added to any_fd_supports_output_format helper
  (existing 3-slot loop missed the new fd; same off-by-one trap
  that bit ampere's av1-iter1 enumeration for a week).
- VAProfileAV1Profile0 → V4L2_PIX_FMT_AV1_FRAME in pixelformat_for
  _profile.
- VAProfileAV1Profile0 push in RequestQueryConfigProfiles +
  RequestQueryConfigEntrypoints + RequestCreateConfig switch.
- vpu981 fd cleanup in RequestTerminate.
- rpi_hevc_dec fd cleanup added at the same time (was already missing
  in master — fixed defensively).
- V4L2_REQUEST_MAX_PROFILES bumped 13 → 14. Defensively sized for
  the post-Option-B-revert future: with iter39 Option B reverted
  (Hi10P + Main10 back in enumeration) plus AV1, max possible
  enumeration is 13. The per-group guards use `index < MAX - N`
  pattern; for a singleton push to succeed at index=13 we need
  MAX >= 14. Bumping now avoids the same off-by-one bug from
  silently dropping AV1 when Option B eventually reverts.

What this commit does NOT add:
- av1.{c,h} decode-side scaffolding (Phase 2 step 4 on av1-iter1 —
  ~177 LoC including a stub av1_set_controls that returns -1). When
  the operator's av1-iter1 Phase 3-5 work lands on master, those
  500+ LoC + the stub will follow. Without them, consumers calling
  vaCreateContext(VAProfileAV1Profile0) succeed at the libva layer
  but ffmpeg-vaapi will fail at the first vaRenderPicture with an
  AV1-buffer-type rejection — clean error, no crash.

Verified 2026-05-18 on ampere:

  $ env LIBVA_DRIVER_NAME=v4l2_request vainfo | grep VAProfile
        ... (10 prior profiles, unchanged) ...
        VAProfileAV1Profile0            :   VAEntrypointVLD   ✓

  Probe log: "ampere-av1: vpu981 AV1 decoder at /dev/video4 + /dev/media3"

Build clean on ampere with GCC 16.1.1; no warnings introduced.
ampere's running module restored to the av1-iter1 build after the
verification — this commit's .so was NOT permanently installed.

Closes the headline acceptance criterion in
marfrit/libva-v4l2-request-fourier#2 ("vainfo on ampere lists
VAProfileAV1"). End-to-end AV1 decode bit-exactness is iter4 work
that the av1-iter1 branch continues to drive.

Co-Authored-By: claude-noether <claude-noether@reauktion.de>
2026-05-18 13:45:04 +00:00
9 changed files with 396 additions and 13 deletions
+11
View File
@@ -4,3 +4,14 @@ option(
value : '', value : '',
description: 'Path to sanitized Linux Kernel headers' description: 'Path to sanitized Linux Kernel headers'
) )
option(
'daedalus_v4l2',
type : 'boolean',
value : true,
description: 'Enable probe + dispatch for the out-of-tree daedalus_v4l2 ' +
'stateless decoder shim (Pi 5 / CM5 daemon-backed VP9/AV1/H264). ' +
'Default true; disable on platforms where the daedalus_v4l2 ' +
'kernel module will never be present to slim the probe array.'
)
+14
View File
@@ -46,6 +46,20 @@ unsigned int pixelformat_for_profile(VAProfile profile)
return V4L2_PIX_FMT_VP8_FRAME; return V4L2_PIX_FMT_VP8_FRAME;
case VAProfileVP9Profile0: case VAProfileVP9Profile0:
return V4L2_PIX_FMT_VP9_FRAME; return V4L2_PIX_FMT_VP9_FRAME;
case VAProfileAV1Profile0:
/*
* ampere-av1-enablement Phase 2: AV1 Profile 0 routes to
* vpu981 (RK3588's dedicated AV1 hantro). Per-codec ctrl
* dispatch (V4L2_CID_STATELESS_AV1_*) is NOT YET WIRED on
* master — vainfo lists the profile + RequestCreateConfig
* succeeds, but consumers that submit decode buffers hit
* a NOP path until the per-codec dispatch lands. The
* av1-iter1 operator branch has Phase 3 bit-exact bring-up
* underway; this commit gives master the bare enumeration +
* fd-routing layer so consumers like ffmpeg-vaapi at least
* see VAProfileAV1Profile0 today.
*/
return V4L2_PIX_FMT_AV1_FRAME;
default: default:
return 0; return 0;
} }
+31 -2
View File
@@ -81,6 +81,16 @@ VAStatus RequestCreateConfig(VADriverContextP context, VAProfile profile,
// cap is V4L2_MPEG_VIDEO_VP9_PROFILE_0). Do not add a case for // cap is V4L2_MPEG_VIDEO_VP9_PROFILE_0). Do not add a case for
// VAProfileVP9Profile2 — kernel will reject. // VAProfileVP9Profile2 — kernel will reject.
break; break;
case VAProfileAV1Profile0:
// ampere-av1-enablement Phase 2: AV1 Profile 0 routes to
// vpu981 (RK3588 dedicated AV1 hantro instance). Decode-side
// ctrl dispatch (V4L2_CID_STATELESS_AV1_*) is NOT YET WIRED
// on master — vainfo will list the profile + CreateConfig
// succeeds, but consumers that submit decode buffers hit a
// NOP path until av1.{c,h} dispatch scaffolding is ported
// from the av1-iter1 operator branch (where Phase 3-5 has
// 3/10 frames bit-exact already).
break;
default: default:
return VA_STATUS_ERROR_UNSUPPORTED_PROFILE; return VA_STATUS_ERROR_UNSUPPORTED_PROFILE;
} }
@@ -162,13 +172,20 @@ VAStatus RequestDestroyConfig(VADriverContextP context, VAConfigID config_id)
static bool any_fd_supports_output_format(struct request_data *driver_data, static bool any_fd_supports_output_format(struct request_data *driver_data,
unsigned int fmt) unsigned int fmt)
{ {
int fds[3] = { int fds[6] = {
driver_data->video_fd, driver_data->video_fd,
driver_data->video_fd_rkvdec, driver_data->video_fd_rkvdec,
driver_data->video_fd_hantro, driver_data->video_fd_hantro,
driver_data->video_fd_rpi_hevc_dec, /* iter40 */
driver_data->video_fd_vpu981, /* ampere-av1 Phase 2 */
#ifdef HAVE_DAEDALUS_V4L2
driver_data->video_fd_daedalus, /* LIBVA-1: H.264/VP9/AV1 */
#else
-1,
#endif
}; };
int i; int i;
for (i = 0; i < 3; i++) { for (i = 0; i < 6; i++) {
if (fds[i] < 0) continue; if (fds[i] < 0) continue;
if (v4l2_find_format(fds[i], V4L2_BUF_TYPE_VIDEO_OUTPUT, fmt)) if (v4l2_find_format(fds[i], V4L2_BUF_TYPE_VIDEO_OUTPUT, fmt))
return true; return true;
@@ -249,6 +266,17 @@ VAStatus RequestQueryConfigProfiles(VADriverContextP context,
if (found && index < (V4L2_REQUEST_MAX_PROFILES - 1)) if (found && index < (V4L2_REQUEST_MAX_PROFILES - 1))
profiles[index++] = VAProfileVP9Profile0; profiles[index++] = VAProfileVP9Profile0;
/*
* ampere-av1-enablement Phase 2: AV1 Profile 0 advertised when
* vpu981 (RK3588 dedicated AV1 hantro) is probed. MAX_PROFILES
* bumped to 14 in request.h to safely fit even if iter39 Option
* B is reverted (Hi10P + Main10 back in enumeration → 13 total
* with AV1, the `< MAX - 1` guard then needs MAX ≥ 14).
*/
found = any_fd_supports_output_format(driver_data, V4L2_PIX_FMT_AV1_FRAME);
if (found && index < (V4L2_REQUEST_MAX_PROFILES - 1))
profiles[index++] = VAProfileAV1Profile0;
*profiles_count = index; *profiles_count = index;
return VA_STATUS_SUCCESS; return VA_STATUS_SUCCESS;
@@ -272,6 +300,7 @@ VAStatus RequestQueryConfigEntrypoints(VADriverContextP context,
case VAProfileHEVCMain10: case VAProfileHEVCMain10:
case VAProfileVP8Version0_3: case VAProfileVP8Version0_3:
case VAProfileVP9Profile0: case VAProfileVP9Profile0:
case VAProfileAV1Profile0:
entrypoints[0] = VAEntrypointVLD; entrypoints[0] = VAEntrypointVLD;
*entrypoints_count = 1; *entrypoints_count = 1;
break; break;
+53
View File
@@ -827,10 +827,63 @@ int h264_set_controls(struct request_data *driver_data,
dpb_update(context, &surface->params.h264.picture); dpb_update(context, &surface->params.h264.picture);
/*
* Dump the raw VAAPI fields at the libva boundary so issue #8
* follow-up can disambiguate "ffmpeg-vaapi didn't populate" from
* "downstream consumer (daedalus_v4l2 wire protocol) corrupted the
* value". One-line; safe to leave in — costs a single printf per frame.
*/
request_log("h264_set_controls: VAProfile=%d seq_fields=0x%08x pic_fields=0x%08x num_ref_frames=%u bit_depth_luma_m8=%u bit_depth_chroma_m8=%u w_mbs_m1=%u h_mbs_m1=%u\n",
(int)profile,
surface->params.h264.picture.seq_fields.value,
surface->params.h264.picture.pic_fields.value,
surface->params.h264.picture.num_ref_frames,
surface->params.h264.picture.bit_depth_luma_minus8,
surface->params.h264.picture.bit_depth_chroma_minus8,
surface->params.h264.picture.picture_width_in_mbs_minus1,
surface->params.h264.picture.picture_height_in_mbs_minus1);
h264_va_picture_to_v4l2(driver_data, context, surface, h264_va_picture_to_v4l2(driver_data, context, surface,
&surface->params.h264.picture, &surface->params.h264.picture,
&decode, &pps, &sps); &decode, &pps, &sps);
/*
* max_num_ref_frames fallback. Some VAAPI clients (older ffmpeg-vaapi
* paths, some daedalus_v4l2 consumers) leave VAPicture->num_ref_frames
* at zero. Hardware decoders tolerate; libavcodec-via-daedalus enforces
* sps.max_num_ref_frames strictly and rejects every frame.
*
* Count valid DPB entries first (the bitstream-true reference count we
* can see); fall back to a per-profile spec minimum if even that is 0.
* See marfrit/libva-v4l2-request-fourier issue #8.
*/
if (sps.max_num_ref_frames == 0) {
unsigned int valid = 0;
unsigned int i;
for (i = 0; i < 16; i++) {
const VAPictureH264 *ref =
&surface->params.h264.picture.ReferenceFrames[i];
if (!(ref->flags & VA_PICTURE_H264_INVALID))
valid++;
}
if (valid > 0) {
sps.max_num_ref_frames = (uint8_t)valid;
} else {
switch (profile) {
case VAProfileH264ConstrainedBaseline:
sps.max_num_ref_frames = 1;
break;
case VAProfileH264Main:
case VAProfileH264High:
case VAProfileH264MultiviewHigh:
case VAProfileH264StereoHigh:
default:
sps.max_num_ref_frames = 4;
break;
}
}
}
/* /*
* Populate the scaling matrix unconditionally: from VAAPI's * Populate the scaling matrix unconditionally: from VAAPI's
* VAIQMatrixBufferH264 when the consumer sent one this frame * VAIQMatrixBufferH264 when the consumer sent one this frame
+3
View File
@@ -22,6 +22,9 @@
autoconf_data = configuration_data() autoconf_data = configuration_data()
autoconf_data.set('VA_DRIVER_INIT_FUNC', va_driver_init_func) autoconf_data.set('VA_DRIVER_INIT_FUNC', va_driver_init_func)
if get_option('daedalus_v4l2')
autoconf_data.set('HAVE_DAEDALUS_V4L2', 1)
endif
autoconf = configure_file( autoconf = configure_file(
output: 'autoconfig.h', output: 'autoconfig.h',
+24
View File
@@ -318,6 +318,30 @@ static VAStatus codec_set_controls(struct request_data *driver_data,
return VA_STATUS_ERROR_OPERATION_FAILED; return VA_STATUS_ERROR_OPERATION_FAILED;
break; break;
case VAProfileAV1Profile0:
/*
* AV1 has no codec-specific V4L2 control dispatch wired up
* yet on this branch (see config.c VAProfileAV1Profile0
* comment). For the daedalus_v4l2 daemon path that's fine:
* AV1 frames are self-describing per-frame (OBU sequence +
* frame headers carry everything libavcodec needs), so the
* bitstream in the V4L2 OUTPUT buffer is sufficient — no
* V4L2_CID_STATELESS_AV1_* controls have to be populated.
*
* Per-codec dispatch in request_switch_device_for_profile
* has already retargeted (video_fd, media_fd) to
* video_fd_daedalus (or video_fd_vpu981 on RK3588 if
* present) by the time we get here; the OUTPUT buffer will
* be queued via that fd and the kernel forwards bytes to
* the daemon as a regular REQ_DECODE. No-op is the
* correct shape.
*
* When the vpu981-targeted V4L2_CID_STATELESS_AV1_* dispatch
* lands from the av1-iter1 operator branch, replace this
* with av1_set_controls(...).
*/
break;
default: default:
return VA_STATUS_ERROR_UNSUPPORTED_PROFILE; return VA_STATUS_ERROR_UNSUPPORTED_PROFILE;
} }
+191 -7
View File
@@ -94,6 +94,9 @@ static const char * const known_decoder_drivers[] = {
"rkvdec", "rkvdec",
"hantro-vpu", "hantro-vpu",
"rpi-hevc-dec", /* iter40: Pi 5 / CM5 stateless HEVC */ "rpi-hevc-dec", /* iter40: Pi 5 / CM5 stateless HEVC */
#ifdef HAVE_DAEDALUS_V4L2
"daedalus_v4l2", /* phase 8.10: Pi 5 daemon-backed VP9/AV1/H264 */
#endif
"cedrus", "cedrus",
"sun4i_csi", "sun4i_csi",
NULL NULL
@@ -409,6 +412,16 @@ char request_device_kind_for_profile(VAProfile profile)
case VAProfileMPEG2Main: case VAProfileMPEG2Main:
case VAProfileVP8Version0_3: case VAProfileVP8Version0_3:
return 'h'; return 'h';
case VAProfileAV1Profile0:
/*
* ampere-av1-enablement Phase 2: RK3588 vpu981 dedicated
* AV1 hantro instance. 'a' kind dispatches to
* driver_data->video_fd_vpu981. On hosts without the AV1
* instance the fd stays -1 and RequestQueryConfigProfiles
* never enumerates AV1, so this branch is unreachable for
* non-RK3588 hosts.
*/
return 'a';
default: default:
return '?'; return '?';
} }
@@ -448,6 +461,44 @@ int request_switch_device_for_profile(struct request_data *driver_data,
kind = 'p'; kind = 'p';
} }
#ifdef HAVE_DAEDALUS_V4L2
/*
* LIBVA-1: VP9/AV1/H.264 → daedalus_v4l2 when the daemon-backed
* decoder fd is open. Pi 5 has no rkvdec (those profiles map to
* 'r' by default → video_fd_rkvdec = -1 → "stay on whatever's
* active" fallback would put H.264 frames on rpi-hevc-dec's fd
* and S_FMT would fail). Re-route to the daedalus daemon instead.
*
* HEVC stays on 'p' (rpi-hevc-dec is HEVC-only — daedalus would
* accept it via FFmpeg, but rpi-hevc-dec has the GPU-backed
* hardware path so it's the right choice on this SoC).
*
* AV1 'a' kind (RK3588 vpu981) wins ONLY if vpu981 was probed.
* On a Pi 5 the vpu981 slot stays -1, so we still route AV1 to
* daedalus here. Check video_fd_vpu981 to preserve the RK3588
* priority for that case.
*/
if (driver_data->video_fd_daedalus >= 0 &&
driver_data->media_fd_daedalus >= 0) {
switch (profile) {
case VAProfileH264Main:
case VAProfileH264High:
case VAProfileH264ConstrainedBaseline:
case VAProfileH264MultiviewHigh:
case VAProfileH264StereoHigh:
case VAProfileVP9Profile0:
kind = 'd';
break;
case VAProfileAV1Profile0:
if (driver_data->video_fd_vpu981 < 0)
kind = 'd';
break;
default:
break;
}
}
#endif
if (kind == 'r') { if (kind == 'r') {
target_video = driver_data->video_fd_rkvdec; target_video = driver_data->video_fd_rkvdec;
target_media = driver_data->media_fd_rkvdec; target_media = driver_data->media_fd_rkvdec;
@@ -457,6 +508,14 @@ int request_switch_device_for_profile(struct request_data *driver_data,
} else if (kind == 'p') { } else if (kind == 'p') {
target_video = driver_data->video_fd_rpi_hevc_dec; target_video = driver_data->video_fd_rpi_hevc_dec;
target_media = driver_data->media_fd_rpi_hevc_dec; target_media = driver_data->media_fd_rpi_hevc_dec;
} else if (kind == 'a') {
target_video = driver_data->video_fd_vpu981;
target_media = driver_data->media_fd_vpu981;
#ifdef HAVE_DAEDALUS_V4L2
} else if (kind == 'd') {
target_video = driver_data->video_fd_daedalus;
target_media = driver_data->media_fd_daedalus;
#endif
} else { } else {
return -1; return -1;
} }
@@ -646,6 +705,10 @@ VAStatus VA_DRIVER_INIT_FUNC(VADriverContextP context)
driver_data->media_fd_hantro = -1; driver_data->media_fd_hantro = -1;
driver_data->video_fd_rpi_hevc_dec = -1; driver_data->video_fd_rpi_hevc_dec = -1;
driver_data->media_fd_rpi_hevc_dec = -1; driver_data->media_fd_rpi_hevc_dec = -1;
driver_data->video_fd_daedalus = -1;
driver_data->media_fd_daedalus = -1;
driver_data->video_fd_vpu981 = -1;
driver_data->media_fd_vpu981 = -1;
/* /*
* iter38: probe BOTH rkvdec and hantro-vpu so a single libva session * iter38: probe BOTH rkvdec and hantro-vpu so a single libva session
@@ -677,14 +740,35 @@ VAStatus VA_DRIVER_INIT_FUNC(VADriverContextP context)
driver_data->video_fd_hantro = video_fd; driver_data->video_fd_hantro = video_fd;
driver_data->media_fd_hantro = media_fd; driver_data->media_fd_hantro = media_fd;
} else if (strcmp(info.driver, "rpi-hevc-dec") == 0) { } else if (strcmp(info.driver, "rpi-hevc-dec") == 0) {
/* iter40: Pi 5 / CM5 — sole decoder is rpi-hevc-dec. /* iter40 + LIBVA-1: Pi 5 / CM5. rpi-hevc-dec is
* No alt driver to probe; the rkvdec / hantro slots * HEVC-only. If daedalus_v4l2 is ALSO loaded (Pi 5
* stay -1 and HEVC routes to 'p' via * mixed deployment — out-of-tree daemon-backed
* request_device_kind_for_profile. */ * decoder for VP9/AV1/H264), pick it up as the alt
* so VP9/AV1/H264 have somewhere to land. */
primary_driver = "rpi-hevc-dec"; primary_driver = "rpi-hevc-dec";
#ifdef HAVE_DAEDALUS_V4L2
alt_driver = "daedalus_v4l2";
#else
alt_driver = NULL; alt_driver = NULL;
#endif
driver_data->video_fd_rpi_hevc_dec = video_fd; driver_data->video_fd_rpi_hevc_dec = video_fd;
driver_data->media_fd_rpi_hevc_dec = media_fd; driver_data->media_fd_rpi_hevc_dec = media_fd;
#ifdef HAVE_DAEDALUS_V4L2
} else if (strcmp(info.driver, "daedalus_v4l2") == 0) {
/* phase 8.10 + LIBVA-1: Pi 5 daemon-backed decoder.
* VP9 / AV1 / H.264 route through it via the 'd'
* kind below. On a mixed-driver box where
* rpi-hevc-dec is ALSO loaded, pick it up as the
* alt so HEVC has somewhere to land too — find_
* codec_device's known_decoder_drivers[] order
* normally puts rpi-hevc-dec first (we hit the
* other branch in practice), but symmetric handling
* keeps us correct if probe order ever flips. */
primary_driver = "daedalus_v4l2";
alt_driver = "rpi-hevc-dec";
driver_data->video_fd_daedalus = video_fd;
driver_data->media_fd_daedalus = media_fd;
#endif
} }
} }
@@ -696,15 +780,38 @@ VAStatus VA_DRIVER_INIT_FUNC(VADriverContextP context)
int alt_v = open(alt_video, O_RDWR | O_NONBLOCK); int alt_v = open(alt_video, O_RDWR | O_NONBLOCK);
int alt_m = (alt_v >= 0) ? open(alt_media, O_RDWR | O_NONBLOCK) : -1; int alt_m = (alt_v >= 0) ? open(alt_media, O_RDWR | O_NONBLOCK) : -1;
if (alt_v >= 0 && alt_m >= 0) { if (alt_v >= 0 && alt_m >= 0) {
/* Dispatch into the matching per-driver slot.
* iter38 only had rkvdec/hantro pairs; iter40 +
* LIBVA-1 extended this to rpi-hevc-dec and
* daedalus_v4l2 for the Pi 5 mixed-decoder
* deployment. */
if (strcmp(alt_driver, "rkvdec") == 0) { if (strcmp(alt_driver, "rkvdec") == 0) {
driver_data->video_fd_rkvdec = alt_v; driver_data->video_fd_rkvdec = alt_v;
driver_data->media_fd_rkvdec = alt_m; driver_data->media_fd_rkvdec = alt_m;
} else { } else if (strcmp(alt_driver, "hantro-vpu") == 0) {
driver_data->video_fd_hantro = alt_v; driver_data->video_fd_hantro = alt_v;
driver_data->media_fd_hantro = alt_m; driver_data->media_fd_hantro = alt_m;
} else if (strcmp(alt_driver, "rpi-hevc-dec") == 0) {
driver_data->video_fd_rpi_hevc_dec = alt_v;
driver_data->media_fd_rpi_hevc_dec = alt_m;
#ifdef HAVE_DAEDALUS_V4L2
} else if (strcmp(alt_driver, "daedalus_v4l2") == 0) {
driver_data->video_fd_daedalus = alt_v;
driver_data->media_fd_daedalus = alt_m;
#endif
} else {
/* Shouldn't happen — primary_driver branches
* above only set alt_driver to one of the
* names handled here. Close and move on. */
close(alt_v);
close(alt_m);
alt_v = -1;
alt_m = -1;
}
if (alt_v >= 0) {
request_log("iter38: also opened %s decoder at %s + %s\n",
alt_driver, alt_video, alt_media);
} }
request_log("iter38: also opened %s decoder at %s + %s\n",
alt_driver, alt_video, alt_media);
} else { } else {
if (alt_v >= 0) close(alt_v); if (alt_v >= 0) close(alt_v);
if (alt_m >= 0) close(alt_m); if (alt_m >= 0) close(alt_m);
@@ -712,6 +819,61 @@ VAStatus VA_DRIVER_INIT_FUNC(VADriverContextP context)
} }
} }
(void)primary_driver; (void)primary_driver;
/*
* ampere-av1-enablement Phase 2: walk hantro-vpu media nodes
* for a SECOND one that advertises V4L2_PIX_FMT_AV1_FRAME
* (AV1F) as OUTPUT pixfmt. RK3588 has 3 hantro-vpu instances
* (legacy MPEG2/VP8 decoder, vepu121 encoder, vpu981 AV1
* decoder) all reporting driver="hantro-vpu" / model="hantro-
* vpu" — so OUTPUT-format probe is the only reliable
* disambiguator that doesn't depend on parsing card-name
* strings (which are DTS-dependent). First match wins.
*
* On non-RK3588 hosts the slot stays -1; RequestQueryConfig
* Profiles' AV1 push then no-ops because any_fd_supports_
* output_format() returns false for AV1F.
*/
{
int i;
char path[32], av1_video[32];
for (i = 0; i < 16; i++) {
int mfd, vfd;
struct media_device_info info;
snprintf(path, sizeof path, "/dev/media%d", i);
mfd = open(path, O_RDWR | O_NONBLOCK);
if (mfd < 0) continue;
memset(&info, 0, sizeof info);
if (ioctl(mfd, MEDIA_IOC_DEVICE_INFO, &info) != 0 ||
strcmp(info.driver, "hantro-vpu") != 0) {
close(mfd);
continue;
}
if (find_decoder_video_node_via_topology(
mfd, av1_video, sizeof av1_video) != 0) {
close(mfd);
continue;
}
vfd = open(av1_video, O_RDWR | O_NONBLOCK);
if (vfd < 0) {
close(mfd);
continue;
}
if (!v4l2_find_format(vfd, V4L2_BUF_TYPE_VIDEO_OUTPUT, V4L2_PIX_FMT_AV1_FRAME) &&
!v4l2_find_format(vfd, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, V4L2_PIX_FMT_AV1_FRAME)) {
close(vfd);
close(mfd);
continue;
}
driver_data->video_fd_vpu981 = vfd;
driver_data->media_fd_vpu981 = mfd;
request_log("ampere-av1: vpu981 AV1 decoder at %s + %s\n",
av1_video, path);
break;
}
}
} }
/* /*
@@ -737,6 +899,14 @@ VAStatus VA_DRIVER_INIT_FUNC(VADriverContextP context)
driver_data->video_fd_rpi_hevc_dec, driver_data->video_fd_rpi_hevc_dec,
driver_data->media_fd_rpi_hevc_dec); driver_data->media_fd_rpi_hevc_dec);
} }
#ifdef HAVE_DAEDALUS_V4L2
if (driver_data->video_fd_daedalus >= 0) {
request_log("phase 8.10: opened daedalus_v4l2 at video_fd=%d "
"media_fd=%d (Pi 5 daemon-backed VP9/AV1/H264)\n",
driver_data->video_fd_daedalus,
driver_data->media_fd_daedalus);
}
#endif
status = VA_STATUS_SUCCESS; status = VA_STATUS_SUCCESS;
goto complete; goto complete;
@@ -784,6 +954,20 @@ VAStatus RequestTerminate(VADriverContextP context)
close(driver_data->video_fd_hantro); close(driver_data->video_fd_hantro);
if (driver_data->media_fd_hantro >= 0) if (driver_data->media_fd_hantro >= 0)
close(driver_data->media_fd_hantro); close(driver_data->media_fd_hantro);
if (driver_data->video_fd_rpi_hevc_dec >= 0)
close(driver_data->video_fd_rpi_hevc_dec);
if (driver_data->media_fd_rpi_hevc_dec >= 0)
close(driver_data->media_fd_rpi_hevc_dec);
if (driver_data->video_fd_vpu981 >= 0)
close(driver_data->video_fd_vpu981);
if (driver_data->media_fd_vpu981 >= 0)
close(driver_data->media_fd_vpu981);
#ifdef HAVE_DAEDALUS_V4L2
if (driver_data->video_fd_daedalus >= 0)
close(driver_data->video_fd_daedalus);
if (driver_data->media_fd_daedalus >= 0)
close(driver_data->media_fd_daedalus);
#endif
/* Fall back to direct close if neither alt fd captured the active /* Fall back to direct close if neither alt fd captured the active
* pair (env-override path). */ * pair (env-override path). */
if (driver_data->video_fd_rkvdec < 0 && driver_data->video_fd_hantro < 0) { if (driver_data->video_fd_rkvdec < 0 && driver_data->video_fd_hantro < 0) {
+43 -1
View File
@@ -42,7 +42,16 @@
#define V4L2_REQUEST_STR_VENDOR "v4l2-request" #define V4L2_REQUEST_STR_VENDOR "v4l2-request"
#define V4L2_REQUEST_MAX_PROFILES 13 /*
* Sized for max-possible enumeration with iter39 Option B reverted:
* MPEG2(2) + H264(6 incl. Hi10P) + HEVC(2 incl. Main10) + VP8 + VP9 + AV1 = 13.
* The per-group guards use `if (... && index < (MAX_PROFILES - N))` where N
* is the push-group size, so MAX must be total+1 14 here. Bumping
* defensively now so a future re-enable of Hi10P/Main10 doesn't silently
* drop AV1 through the off-by-one trap that ate ampere-av1's enumeration
* for a week (see issue marfrit/libva-v4l2-request-fourier#2).
*/
#define V4L2_REQUEST_MAX_PROFILES 14
#define V4L2_REQUEST_MAX_ENTRYPOINTS 5 #define V4L2_REQUEST_MAX_ENTRYPOINTS 5
#define V4L2_REQUEST_MAX_CONFIG_ATTRIBUTES 10 #define V4L2_REQUEST_MAX_CONFIG_ATTRIBUTES 10
#define V4L2_REQUEST_MAX_IMAGE_FORMATS 10 #define V4L2_REQUEST_MAX_IMAGE_FORMATS 10
@@ -87,6 +96,39 @@ struct request_data {
*/ */
int video_fd_rpi_hevc_dec; int video_fd_rpi_hevc_dec;
int media_fd_rpi_hevc_dec; int media_fd_rpi_hevc_dec;
/*
* phase 8.10: fifth multi-device-probe slot for daedalus_v4l2 the
* out-of-tree V4L2 stateless decoder shim that forwards bitstream
* to a userspace daemon (daedalus-v4l2 sibling repo). Daemon does
* FFmpeg-software decode for VP9 / AV1 / H.264 and ships pixels
* back via dmabuf into the CAPTURE buffer. Picked up via the
* same media-controller probe + known_decoder_drivers[] entry
* pattern as iter40 rpi-hevc-dec. Stays -1 on hosts without the
* daedalus module loaded; HEVC routes to rpi-hevc-dec as before.
*
* Fields are unconditional (8 bytes per session) so the struct
* layout is stable regardless of meson option. The active
* probe + dispatch code in request.c is gated by
* HAVE_DAEDALUS_V4L2; when disabled the fields stay at their
* -1 init and no codepath touches them.
*/
int video_fd_daedalus;
int media_fd_daedalus;
/*
* ampere-av1-enablement Phase 2: fourth multi-device-probe slot
* for vpu981 (RK3588's dedicated AV1 hantro instance, kernel
* card="rockchip,rk3588-av1-vpu-dec", driver name "hantro-vpu"
* shared with the legacy MPEG-2/VP8/H.264 hantro). Discriminated
* by V4L2_PIX_FMT_AV1_FRAME (AV1F) OUTPUT-pixfmt capability since
* the driver name alone is ambiguous on RK3588. Stays -1 on hosts
* without the AV1 vpu-dec.
*
* Named "vpu981" for consistency with the in-progress av1-iter1
* operator branch (Phase 3-5 bit-exact AV1 work when that lands
* these fields receive the actual decode dispatch wiring).
*/
int video_fd_vpu981;
int media_fd_vpu981;
/* /*
* iter2 (ampere-kernel-decoders campaign) per-fd probe result * iter2 (ampere-kernel-decoders campaign) per-fd probe result
+26 -3
View File
@@ -476,12 +476,35 @@ int v4l2_set_controls(int video_fd, int request_fd,
struct v4l2_ext_control *control_array, struct v4l2_ext_control *control_array,
unsigned int num_controls) unsigned int num_controls)
{ {
struct v4l2_ext_controls controls;
int rc; int rc;
rc = v4l2_ioctl_controls(video_fd, request_fd, VIDIOC_S_EXT_CTRLS, memset(&controls, 0, sizeof(controls));
control_array, num_controls); controls.controls = control_array;
controls.count = num_controls;
if (request_fd >= 0) {
controls.which = V4L2_CTRL_WHICH_REQUEST_VAL;
controls.request_fd = request_fd;
}
rc = ioctl(video_fd, VIDIOC_S_EXT_CTRLS, &controls);
if (rc < 0) { if (rc < 0) {
request_log("Unable to set control(s): %s\n", strerror(errno)); /* error_idx is the index of the first failing control;
* if it equals count, the ioctl itself failed (not a
* specific control payload). Useful for triaging
* which V4L2_CID_STATELESS_* the kernel rejected. */
if (controls.error_idx < num_controls)
request_log("Unable to set control(s): %s "
"(error_idx=%u/%u failing_ctrl_id=0x%x size=%u)\n",
strerror(errno),
controls.error_idx, controls.count,
control_array[controls.error_idx].id,
control_array[controls.error_idx].size);
else
request_log("Unable to set control(s): %s "
"(error_idx=%u/%u ioctl-level)\n",
strerror(errno),
controls.error_idx, controls.count);
return -1; return -1;
} }