iter38: multi-device probe — single libva session serves all 5 codecs

Probe BOTH rkvdec and hantro-vpu at VA_DRIVER_INIT and keep their
{video,media}_fd pairs in driver_data. RequestQueryConfigProfiles
enumerates the union of supported profiles from all open fds.
RequestCreateConfig retargets driver_data->{video,media}_fd to the
device that serves the requested profile; if a switch is needed
(active fd is wrong), tears down output_pool, capture_pool, video_format
cache, and fmt_valid so the next RequestCreateContext rebuilds them
on the new device.

Profile→device map (RK3399-shaped):
  H264 / HEVC / VP9  → rkvdec
  MPEG-2 / VP8       → hantro-vpu

Honours LIBVA_V4L2_REQUEST_VIDEO_PATH / MEDIA_PATH explicit overrides
(skips alt-probe when those are set).

Closes the 'libva multi-device probe' open item from iter36/iter37
campaign-close.
This commit is contained in:
2026-05-14 18:52:12 +00:00
parent 25d3e5f06f
commit c56a77bd4c
3 changed files with 302 additions and 71 deletions
+234 -39
View File
@@ -25,10 +25,12 @@
*/
#include "buffer.h"
#include "cap_pool.h"
#include "config.h"
#include "context.h"
#include "image.h"
#include "picture.h"
#include "request_pool.h"
#include "subpicture.h"
#include "surface.h"
@@ -274,56 +276,168 @@ out:
* guards against encoder-only devices that happen to share the same driver
* name (e.g. hantro-vpu encoder vs decoder inside one /dev/mediaN).
*/
static int find_codec_device(char *video_out, size_t video_out_sz,
char *media_out, size_t media_out_sz)
/*
* iter38: locate a /dev/mediaN whose driver name matches `want_driver`
* AND exposes at least one MEDIA_ENT_F_PROC_VIDEO_DECODER entity (rules
* out encoder-only devices sharing the same driver name). Resolves the
* matching /dev/videoM via topology graph walk.
*
* `want_driver`:
* - non-NULL → match only that exact driver name
* - NULL → match any name in known_decoder_drivers[]
*/
static int find_decoder_device_by_driver(const char *want_driver,
char *video_out, size_t video_out_sz,
char *media_out, size_t media_out_sz)
{
struct media_device_info info;
char path[32];
const char * const *kd;
int fd, i, pass;
int fd, i;
for (pass = 0; pass < 2; pass++) {
for (i = 0; i < 16; i++) {
bool match;
for (i = 0; i < 16; i++) {
bool match;
snprintf(path, sizeof path, "/dev/media%d", i);
fd = open(path, O_RDWR | O_NONBLOCK);
if (fd < 0)
continue;
memset(&info, 0, sizeof info);
if (ioctl(fd, MEDIA_IOC_DEVICE_INFO, &info) != 0) {
close(fd);
continue;
}
if (pass == 0) {
/* Pass 1: rkvdec only. */
match = (strcmp(info.driver, "rkvdec") == 0);
} else {
/* Pass 2: any known decoder driver. */
match = false;
for (kd = known_decoder_drivers; *kd; kd++) {
if (strcmp(info.driver, *kd) == 0) {
match = true;
break;
}
snprintf(path, sizeof path, "/dev/media%d", i);
fd = open(path, O_RDWR | O_NONBLOCK);
if (fd < 0)
continue;
memset(&info, 0, sizeof info);
if (ioctl(fd, MEDIA_IOC_DEVICE_INFO, &info) != 0) {
close(fd);
continue;
}
if (want_driver != NULL) {
match = (strcmp(info.driver, want_driver) == 0);
} else {
match = false;
for (kd = known_decoder_drivers; *kd; kd++) {
if (strcmp(info.driver, *kd) == 0) {
match = true;
break;
}
}
if (!match) {
close(fd);
continue;
}
if (find_decoder_video_node_via_topology(
fd, video_out, video_out_sz) == 0) {
snprintf(media_out, media_out_sz, "%s", path);
close(fd);
return 0;
}
close(fd);
}
if (!match) {
close(fd);
continue;
}
if (find_decoder_video_node_via_topology(
fd, video_out, video_out_sz) == 0) {
snprintf(media_out, media_out_sz, "%s", path);
close(fd);
return 0;
}
close(fd);
}
return -1;
}
static int find_codec_device(char *video_out, size_t video_out_sz,
char *media_out, size_t media_out_sz)
{
if (find_decoder_device_by_driver("rkvdec",
video_out, video_out_sz,
media_out, media_out_sz) == 0)
return 0;
return find_decoder_device_by_driver(NULL,
video_out, video_out_sz,
media_out, media_out_sz);
}
/*
* iter38: profile → which physical decoder device should serve it on
* RK3399. Returns 'r' for rkvdec, 'h' for hantro, '?' for unknown.
*
* This is RK3399-shaped knowledge — a more general impl would interrogate
* each open device's supported OUTPUT formats. For the campaign-scope
* five codecs, the mapping is stable and explicit.
*/
char request_device_kind_for_profile(VAProfile profile);
char request_device_kind_for_profile(VAProfile profile)
{
switch (profile) {
case VAProfileH264Main:
case VAProfileH264High:
case VAProfileH264ConstrainedBaseline:
case VAProfileH264MultiviewHigh:
case VAProfileH264StereoHigh:
case VAProfileHEVCMain:
case VAProfileVP9Profile0:
return 'r';
case VAProfileMPEG2Simple:
case VAProfileMPEG2Main:
case VAProfileVP8Version0_3:
return 'h';
default:
return '?';
}
}
/*
* iter38: retarget driver_data->{video,media}_fd to the device kind
* required by `profile`. If a switch is needed, tear down any per-device
* pool state so the next RequestCreateContext rebuilds it against the
* new device. Returns 0 on success, -1 if the required device wasn't
* probed (e.g. trying VP8 on a system without hantro).
*
* Safe to call repeatedly with the same profile: if the active fd
* already matches, the function is a no-op.
*/
int request_switch_device_for_profile(struct request_data *driver_data,
VAProfile profile);
int request_switch_device_for_profile(struct request_data *driver_data,
VAProfile profile)
{
char kind = request_device_kind_for_profile(profile);
int target_video, target_media;
if (kind == 'r') {
target_video = driver_data->video_fd_rkvdec;
target_media = driver_data->media_fd_rkvdec;
} else if (kind == 'h') {
target_video = driver_data->video_fd_hantro;
target_media = driver_data->media_fd_hantro;
} else {
return -1;
}
/* Either side never probed (e.g. env-override single-device init,
* or this kind isn't present on the running kernel) → tolerate by
* staying on whatever's already active. RequestCreateConfig still
* accepted the profile via the format probe, so the active fd
* supports it. */
if (target_video < 0 || target_media < 0)
return 0;
if (driver_data->video_fd == target_video &&
driver_data->media_fd == target_media)
return 0; /* already active, nothing to do */
/*
* Tear down any per-device pool state. cap_pool needs capture_type,
* which comes from video_format. Both rkvdec and hantro use
* V4L2_PIX_FMT_NV12 MPLANE on RK3399 (verified Phase 0 inventory)
* so the MPLANE form is always right here.
*/
if (driver_data->capture_pool.initialized) {
cap_pool_destroy(&driver_data->capture_pool,
driver_data->video_fd,
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
}
if (driver_data->output_pool.initialized)
request_pool_destroy(&driver_data->output_pool);
/* video_format is a static-ref pointer; re-probe on next
* CreateContext since the new device's format menu may differ. */
driver_data->video_format = NULL;
driver_data->fmt_valid = false;
driver_data->video_fd = target_video;
driver_data->media_fd = target_media;
return 0;
}
/* Set default visibility for the init function only. */
VAStatus __attribute__((visibility("default")))
VA_DRIVER_INIT_FUNC(VADriverContextP context);
@@ -467,6 +581,68 @@ VAStatus VA_DRIVER_INIT_FUNC(VADriverContextP context)
driver_data->video_fd = video_fd;
driver_data->media_fd = media_fd;
driver_data->video_fd_rkvdec = -1;
driver_data->media_fd_rkvdec = -1;
driver_data->video_fd_hantro = -1;
driver_data->media_fd_hantro = -1;
/*
* iter38: probe BOTH rkvdec and hantro-vpu so a single libva session
* can serve all 5 codecs. Tag the primary fd (already opened above)
* by inspecting which driver the media_fd is on, then probe the OTHER
* driver and open its fds if present. RequestCreateConfig retargets
* driver_data->{video,media}_fd to the right pair per profile.
*
* Skip the alt-probe when the user provided explicit
* LIBVA_V4L2_REQUEST_VIDEO_PATH / MEDIA_PATH overrides — they signal
* a specific single device intent.
*/
if (!getenv("LIBVA_V4L2_REQUEST_VIDEO_PATH") &&
!getenv("LIBVA_V4L2_REQUEST_MEDIA_PATH")) {
struct media_device_info info;
const char *primary_driver = NULL;
const char *alt_driver = NULL;
memset(&info, 0, sizeof info);
if (ioctl(media_fd, MEDIA_IOC_DEVICE_INFO, &info) == 0) {
if (strcmp(info.driver, "rkvdec") == 0) {
primary_driver = "rkvdec";
alt_driver = "hantro-vpu";
driver_data->video_fd_rkvdec = video_fd;
driver_data->media_fd_rkvdec = media_fd;
} else if (strcmp(info.driver, "hantro-vpu") == 0) {
primary_driver = "hantro-vpu";
alt_driver = "rkvdec";
driver_data->video_fd_hantro = video_fd;
driver_data->media_fd_hantro = media_fd;
}
}
if (alt_driver != NULL) {
static char alt_video[32], alt_media[32];
if (find_decoder_device_by_driver(alt_driver,
alt_video, sizeof alt_video,
alt_media, sizeof alt_media) == 0) {
int alt_v = open(alt_video, O_RDWR | O_NONBLOCK);
int alt_m = (alt_v >= 0) ? open(alt_media, O_RDWR | O_NONBLOCK) : -1;
if (alt_v >= 0 && alt_m >= 0) {
if (strcmp(alt_driver, "rkvdec") == 0) {
driver_data->video_fd_rkvdec = alt_v;
driver_data->media_fd_rkvdec = alt_m;
} else {
driver_data->video_fd_hantro = alt_v;
driver_data->media_fd_hantro = alt_m;
}
request_log("iter38: also opened %s decoder at %s + %s\n",
alt_driver, alt_video, alt_media);
} else {
if (alt_v >= 0) close(alt_v);
if (alt_m >= 0) close(alt_m);
}
}
}
(void)primary_driver;
}
status = VA_STATUS_SUCCESS;
goto complete;
@@ -501,8 +677,27 @@ VAStatus RequestTerminate(VADriverContextP context)
*/
request_pool_destroy(&driver_data->output_pool);
close(driver_data->video_fd);
close(driver_data->media_fd);
/*
* iter38: close both probed device pairs. video_fd / media_fd above
* are ACTIVE pointers into one of these pairs; close the underlying
* fds explicitly. Each may be -1 if its device wasn't found.
*/
if (driver_data->video_fd_rkvdec >= 0)
close(driver_data->video_fd_rkvdec);
if (driver_data->media_fd_rkvdec >= 0)
close(driver_data->media_fd_rkvdec);
if (driver_data->video_fd_hantro >= 0)
close(driver_data->video_fd_hantro);
if (driver_data->media_fd_hantro >= 0)
close(driver_data->media_fd_hantro);
/* Fall back to direct close if neither alt fd captured the active
* pair (env-override path). */
if (driver_data->video_fd_rkvdec < 0 && driver_data->video_fd_hantro < 0) {
if (driver_data->video_fd >= 0)
close(driver_data->video_fd);
if (driver_data->media_fd >= 0)
close(driver_data->media_fd);
}
/* Cleanup leftover buffers. */