mirror of
https://github.com/storytold/filmcraft.git
synced 2026-11-02 18:09:21 +00:00
e741267125
HEVC through VA-API the same way as H.264: the software HEVC decoder's parsers and DPB (now generic over the picture type in filmcraft-hevc: POC, reference picture sets, reference lists, output order, RASL handling) drive the stateless hardware. Main, Main 10 and Main Still Picture, 4:2:0 8/10-bit (P010 read-back); scaling lists, tiles, WPP, dependent slices, weighted prediction. Missing references and range extensions go to the software decoder. Tests: hardware parity on HEVC Main / Main 10 incl. open GOP, scaling lists, slices+WPP, weighted prediction, tskip+AMP, 1080p/2160p (streamed comparison to stay within memory); HEVC front end against a recording stand-in on every OS; ABI checks extended to va_dec_hevc.h. Benchmark on Iris Xe: 4K HEVC 203 -> 10 ms CPU per frame, 4K Main 10 212 -> 18 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
526 lines
24 KiB
Rust
526 lines
24 KiB
Rust
//! libva itself (Linux): `libva.so.2` and `libva-drm.so.2` are loaded at run time (a system without
|
|
//! them simply has no hardware decoder), a display is opened on a DRM render node, and a [`Session`]
|
|
//! (decode configuration, context and surfaces for one stream) decodes pictures and reads them back
|
|
//! as NV12 images.
|
|
//!
|
|
//! FFI module (docs/adr/0001-platform-ffi.md): every `unsafe` block has a `// SAFETY:` comment, the
|
|
//! public API is safe, and the one callback into Rust (libva's error messages) runs under
|
|
//! `catch_unwind`.
|
|
|
|
use std::ffi::{CStr, c_char, c_int, c_uint, c_void};
|
|
use std::fs::{File, OpenOptions};
|
|
use std::os::fd::AsRawFd;
|
|
use std::sync::OnceLock;
|
|
|
|
use filmcraft_frame::VideoFrame;
|
|
use libloading::Library;
|
|
|
|
use super::Accel;
|
|
use super::ffi::{self, VAConfigAttrib, VAImage, VAImageFormat, VASurfaceID};
|
|
use crate::biplanar::{self, Biplanar, Geometry};
|
|
|
|
/// The libva entry points (the libraries stay loaded for the life of the process).
|
|
struct Api {
|
|
_va: Library,
|
|
_drm: Library,
|
|
initialize: ffi::vaInitialize,
|
|
terminate: ffi::vaTerminate,
|
|
error_str: ffi::vaErrorStr,
|
|
vendor_string: ffi::vaQueryVendorString,
|
|
set_info_callback: ffi::vaSetInfoCallback,
|
|
set_error_callback: ffi::vaSetErrorCallback,
|
|
get_config_attributes: ffi::vaGetConfigAttributes,
|
|
create_config: ffi::vaCreateConfig,
|
|
destroy_config: ffi::vaDestroyConfig,
|
|
create_surfaces: ffi::vaCreateSurfaces,
|
|
destroy_surfaces: ffi::vaDestroySurfaces,
|
|
create_context: ffi::vaCreateContext,
|
|
destroy_context: ffi::vaDestroyContext,
|
|
create_buffer: ffi::vaCreateBuffer,
|
|
destroy_buffer: ffi::vaDestroyBuffer,
|
|
begin_picture: ffi::vaBeginPicture,
|
|
render_picture: ffi::vaRenderPicture,
|
|
end_picture: ffi::vaEndPicture,
|
|
sync_surface: ffi::vaSyncSurface,
|
|
create_image: ffi::vaCreateImage,
|
|
destroy_image: ffi::vaDestroyImage,
|
|
get_image: ffi::vaGetImage,
|
|
put_image: ffi::vaPutImage,
|
|
map_buffer: ffi::vaMapBuffer,
|
|
unmap_buffer: ffi::vaUnmapBuffer,
|
|
get_display_drm: ffi::vaGetDisplayDRM,
|
|
}
|
|
|
|
static API: OnceLock<Result<Api, String>> = OnceLock::new();
|
|
|
|
fn api() -> Result<&'static Api, String> {
|
|
API.get_or_init(load).as_ref().map_err(Clone::clone)
|
|
}
|
|
|
|
fn load() -> Result<Api, String> {
|
|
// SAFETY: loading libva runs its library initialisers, which only set up libva's own state;
|
|
// it is the system's VA-API runtime, loaded the way every VA-API client loads it.
|
|
let va = unsafe { Library::new("libva.so.2") }.map_err(|e| format!("libva.so.2: {e}"))?;
|
|
// SAFETY: as above, for libva's DRM display library.
|
|
let drm = unsafe { Library::new("libva-drm.so.2") }.map_err(|e| format!("libva-drm.so.2: {e}"))?;
|
|
macro_rules! sym {
|
|
($lib:expr, $name:ident) => {{
|
|
// SAFETY: `ffi::$name` is the C signature of the libva function of that name,
|
|
// transcribed from va.h and checked against it (abi_tests.rs). The pointer is copied
|
|
// out; the library it points into is kept loaded in `Api` for the life of the process.
|
|
let s = unsafe { $lib.get::<ffi::$name>(concat!(stringify!($name), "\0").as_bytes()) };
|
|
*s.map_err(|e| format!("{}: {e}", stringify!($name)))?
|
|
}};
|
|
}
|
|
Ok(Api {
|
|
initialize: sym!(va, vaInitialize),
|
|
terminate: sym!(va, vaTerminate),
|
|
error_str: sym!(va, vaErrorStr),
|
|
vendor_string: sym!(va, vaQueryVendorString),
|
|
set_info_callback: sym!(va, vaSetInfoCallback),
|
|
set_error_callback: sym!(va, vaSetErrorCallback),
|
|
get_config_attributes: sym!(va, vaGetConfigAttributes),
|
|
create_config: sym!(va, vaCreateConfig),
|
|
destroy_config: sym!(va, vaDestroyConfig),
|
|
create_surfaces: sym!(va, vaCreateSurfaces),
|
|
destroy_surfaces: sym!(va, vaDestroySurfaces),
|
|
create_context: sym!(va, vaCreateContext),
|
|
destroy_context: sym!(va, vaDestroyContext),
|
|
create_buffer: sym!(va, vaCreateBuffer),
|
|
destroy_buffer: sym!(va, vaDestroyBuffer),
|
|
begin_picture: sym!(va, vaBeginPicture),
|
|
render_picture: sym!(va, vaRenderPicture),
|
|
end_picture: sym!(va, vaEndPicture),
|
|
sync_surface: sym!(va, vaSyncSurface),
|
|
create_image: sym!(va, vaCreateImage),
|
|
destroy_image: sym!(va, vaDestroyImage),
|
|
get_image: sym!(va, vaGetImage),
|
|
put_image: sym!(va, vaPutImage),
|
|
map_buffer: sym!(va, vaMapBuffer),
|
|
unmap_buffer: sym!(va, vaUnmapBuffer),
|
|
get_display_drm: sym!(drm, vaGetDisplayDRM),
|
|
_va: va,
|
|
_drm: drm,
|
|
})
|
|
}
|
|
|
|
/// libva's error messages, into our log (instead of stderr).
|
|
extern "C" fn on_error(_user: *mut c_void, message: *const c_char) {
|
|
let _ = std::panic::catch_unwind(|| {
|
|
if message.is_null() {
|
|
return;
|
|
}
|
|
// SAFETY: libva passes a NUL-terminated message that is valid for the duration of the call.
|
|
let text = unsafe { CStr::from_ptr(message) }.to_string_lossy();
|
|
log::warn!("libva: {}", text.trim_end());
|
|
});
|
|
}
|
|
|
|
/// An initialised VA display on a DRM render node.
|
|
pub struct Display {
|
|
api: &'static Api,
|
|
dpy: ffi::VADisplay,
|
|
vendor: String,
|
|
/// The render node; open for as long as the display uses it.
|
|
_device: File,
|
|
}
|
|
|
|
// SAFETY: a VA display is not tied to the thread that opened it, and a `Display` is only used
|
|
// through its single owner (`&mut` or `&` of one `Session` at a time), never concurrently.
|
|
unsafe impl Send for Display {}
|
|
|
|
impl Display {
|
|
/// The first DRM render node with a working VA-API driver.
|
|
pub fn open() -> Result<Self, String> {
|
|
let api = api()?;
|
|
let mut why = "no DRM render node (/dev/dri/renderD*)".to_string();
|
|
for n in 128..144 {
|
|
let path = format!("/dev/dri/renderD{n}");
|
|
let Ok(file) = OpenOptions::new().read(true).write(true).open(&path) else { continue };
|
|
match Self::init(api, file) {
|
|
Ok(d) => return Ok(d),
|
|
Err(e) => why = format!("{path}: {e}"),
|
|
}
|
|
}
|
|
Err(why)
|
|
}
|
|
|
|
fn init(api: &'static Api, device: File) -> Result<Self, String> {
|
|
// SAFETY: the descriptor is an open render node, kept open by `device` (moved into the
|
|
// returned display) for as long as the display exists.
|
|
let dpy = unsafe { (api.get_display_drm)(device.as_raw_fd()) };
|
|
if dpy.is_null() {
|
|
return Err("vaGetDisplayDRM failed".into());
|
|
}
|
|
// From here on `Drop` terminates the display.
|
|
let mut d = Self { api, dpy, vendor: String::new(), _device: device };
|
|
// SAFETY: `dpy` is a display from vaGetDisplayDRM; a null info callback turns libva's info
|
|
// messages off, and `on_error` matches `VAMessageCallback` and never unwinds.
|
|
unsafe {
|
|
(api.set_info_callback)(dpy, None, std::ptr::null_mut());
|
|
(api.set_error_callback)(dpy, Some(on_error), std::ptr::null_mut());
|
|
}
|
|
let (mut major, mut minor): (c_int, c_int) = (0, 0);
|
|
// SAFETY: `dpy` is valid; the version outputs point to live locals.
|
|
let st = unsafe { (api.initialize)(dpy, &mut major, &mut minor) };
|
|
d.check(st, "vaInitialize")?;
|
|
// SAFETY: `dpy` is an initialised display; the vendor string is NUL-terminated and owned by
|
|
// the driver for the life of the display (copied out here).
|
|
let vendor = unsafe { (api.vendor_string)(dpy) };
|
|
if !vendor.is_null() {
|
|
// SAFETY: non-null, NUL-terminated (see above).
|
|
d.vendor = unsafe { CStr::from_ptr(vendor) }.to_string_lossy().into_owned();
|
|
}
|
|
Ok(d)
|
|
}
|
|
|
|
/// The driver's description ("Intel iHD driver …").
|
|
pub fn vendor(&self) -> &str {
|
|
&self.vendor
|
|
}
|
|
|
|
fn check(&self, st: ffi::VAStatus, what: &str) -> Result<(), String> {
|
|
if st == ffi::VA_STATUS_SUCCESS {
|
|
return Ok(());
|
|
}
|
|
// SAFETY: vaErrorStr takes any status and returns a static NUL-terminated string.
|
|
let text = unsafe { (self.api.error_str)(st) };
|
|
let text = if text.is_null() {
|
|
String::new()
|
|
} else {
|
|
// SAFETY: non-null static NUL-terminated string (see above).
|
|
unsafe { CStr::from_ptr(text) }.to_string_lossy().into_owned()
|
|
};
|
|
Err(format!("{what}: {text} ({st:#x})"))
|
|
}
|
|
}
|
|
|
|
impl Drop for Display {
|
|
fn drop(&mut self) {
|
|
// SAFETY: `dpy` came from vaGetDisplayDRM and is terminated exactly once, here, after
|
|
// every session object made on it was destroyed (`Session` drops its objects first).
|
|
unsafe { (self.api.terminate)(self.dpy) };
|
|
}
|
|
}
|
|
|
|
/// One stream's decoder: a VLD configuration of a profile, its surfaces and context, and the image
|
|
/// pictures are read back through.
|
|
pub struct Session {
|
|
display: Display,
|
|
config: ffi::VAConfigID,
|
|
context: ffi::VAContextID,
|
|
surfaces: Vec<VASurfaceID>,
|
|
image: Option<VAImage>,
|
|
/// Coded size of the surfaces (luma samples).
|
|
size: (u32, u32),
|
|
geometry: Geometry,
|
|
}
|
|
|
|
/// The surface and image formats of 8-bit (NV12) or 10-bit (P010) 4:2:0 pictures:
|
|
/// (render-target format, image fourcc, bits per pixel of the image).
|
|
fn formats(bits: u32) -> Result<(c_uint, u32, u32), String> {
|
|
match bits {
|
|
8 => Ok((ffi::VA_RT_FORMAT_YUV420, ffi::VA_FOURCC_NV12, 12)),
|
|
10 => Ok((ffi::VA_RT_FORMAT_YUV420_10, ffi::VA_FOURCC_P010, 24)),
|
|
_ => Err(format!("{bits}-bit pictures are not decoded through VA-API")),
|
|
}
|
|
}
|
|
|
|
impl Session {
|
|
/// A decoder for the first of `profiles` the driver decodes (VLD, 4:2:0 of `geometry.bits`:
|
|
/// 8 or 10), with `count` surfaces of `size` (coded luma samples); pictures read back are cut
|
|
/// to `geometry`.
|
|
pub fn new(profiles: &[ffi::VAProfile], size: (u32, u32), count: usize, geometry: Geometry) -> Result<Self, String> {
|
|
let (rt_format, _, _) = formats(geometry.bits)?;
|
|
let display = Display::open()?;
|
|
let api = display.api;
|
|
let dpy = display.dpy;
|
|
let mut config = None;
|
|
let mut why = "no VA-API profile for the stream".to_string();
|
|
for &profile in profiles {
|
|
let mut attr = VAConfigAttrib { type_: ffi::VAConfigAttribRTFormat, value: 0 };
|
|
// SAFETY: `dpy` is initialised; `attr` is one live attribute (count 1).
|
|
let st = unsafe { (api.get_config_attributes)(dpy, profile, ffi::VAEntrypointVLD, &mut attr, 1) };
|
|
if let Err(e) = display.check(st, "vaGetConfigAttributes") {
|
|
why = e;
|
|
continue;
|
|
}
|
|
if attr.value & rt_format == 0 {
|
|
why = format!("profile {profile} does not decode to {}-bit 4:2:0", geometry.bits);
|
|
continue;
|
|
}
|
|
attr.value = rt_format;
|
|
let mut id = ffi::VA_INVALID_ID;
|
|
// SAFETY: as above; `id` is a live output.
|
|
let st = unsafe { (api.create_config)(dpy, profile, ffi::VAEntrypointVLD, &mut attr, 1, &mut id) };
|
|
match display.check(st, "vaCreateConfig") {
|
|
Ok(()) => {
|
|
config = Some(id);
|
|
break;
|
|
}
|
|
Err(e) => why = e,
|
|
}
|
|
}
|
|
let config = config.ok_or(why)?;
|
|
// From here on `Drop` releases what was made.
|
|
let mut s = Self { display, config, context: ffi::VA_INVALID_ID, surfaces: Vec::new(), image: None, size, geometry };
|
|
let count_c = c_uint::try_from(count).map_err(|_| "too many surfaces")?;
|
|
let mut surfaces = vec![ffi::VA_INVALID_SURFACE; count];
|
|
// SAFETY: `surfaces` has room for `count` ids; no attributes are passed (null, 0).
|
|
let st = unsafe { (api.create_surfaces)(dpy, rt_format, size.0, size.1, surfaces.as_mut_ptr(), count_c, std::ptr::null_mut(), 0) };
|
|
s.display.check(st, "vaCreateSurfaces")?;
|
|
s.surfaces = surfaces;
|
|
let (w, h) = (c_int::try_from(size.0).map_err(|_| "picture too wide")?, c_int::try_from(size.1).map_err(|_| "picture too tall")?);
|
|
let n = c_int::try_from(count).map_err(|_| "too many surfaces")?;
|
|
let mut context = ffi::VA_INVALID_ID;
|
|
// SAFETY: `config` and the `count` surfaces were made on this display; `context` is a live output.
|
|
let st = unsafe { (api.create_context)(dpy, s.config, w, h, ffi::VA_PROGRESSIVE, s.surfaces.as_mut_ptr(), n, &mut context) };
|
|
s.display.check(st, "vaCreateContext")?;
|
|
s.context = context;
|
|
Ok(s)
|
|
}
|
|
|
|
pub fn vendor(&self) -> &str {
|
|
self.display.vendor()
|
|
}
|
|
|
|
/// A buffer of `ty` holding a copy of `len` bytes at `data`.
|
|
fn buffer(&self, ty: ffi::VABufferType, data: *const c_void, len: usize) -> Result<ffi::VABufferID, String> {
|
|
let size = c_uint::try_from(len).map_err(|_| "buffer too large")?;
|
|
let mut id = ffi::VA_INVALID_ID;
|
|
// SAFETY: `data` points to `len` readable bytes (the callers pass a live value or slice);
|
|
// libva copies them into the new buffer and never writes through the pointer.
|
|
let st = unsafe { (self.display.api.create_buffer)(self.display.dpy, self.context, ty, size, 1, data.cast_mut(), &mut id) };
|
|
self.display.check(st, "vaCreateBuffer")?;
|
|
Ok(id)
|
|
}
|
|
|
|
fn value_buffer<T>(&self, ty: ffi::VABufferType, value: &T) -> Result<ffi::VABufferID, String> {
|
|
self.buffer(ty, (value as *const T).cast(), std::mem::size_of::<T>())
|
|
}
|
|
|
|
fn render(&self, buffers: &mut [ffi::VABufferID]) -> Result<(), String> {
|
|
let n = c_int::try_from(buffers.len()).map_err(|_| "too many buffers")?;
|
|
// SAFETY: `buffers` are live buffer ids made on this context, `n` of them.
|
|
let st = unsafe { (self.display.api.render_picture)(self.display.dpy, self.context, buffers.as_mut_ptr(), n) };
|
|
self.display.check(st, "vaRenderPicture")
|
|
}
|
|
|
|
/// Decode one picture into `surface`: the picture parameters `pic`, the scaling matrices `iq`
|
|
/// when given, and a parameter + data buffer per slice; the buffers made go into `buffers`.
|
|
fn decode_into<P, Q, S>(
|
|
&self,
|
|
surface: VASurfaceID,
|
|
buffers: &mut Vec<ffi::VABufferID>,
|
|
pic: &P,
|
|
iq: Option<&Q>,
|
|
slices: &[(S, Vec<u8>)],
|
|
) -> Result<(), String> {
|
|
buffers.push(self.value_buffer(ffi::VAPictureParameterBufferType, pic)?);
|
|
if let Some(iq) = iq {
|
|
buffers.push(self.value_buffer(ffi::VAIQMatrixBufferType, iq)?);
|
|
}
|
|
let head = buffers.len();
|
|
for (params, data) in slices {
|
|
buffers.push(self.value_buffer(ffi::VASliceParameterBufferType, params)?);
|
|
buffers.push(self.buffer(ffi::VASliceDataBufferType, data.as_ptr().cast(), data.len())?);
|
|
}
|
|
let api = self.display.api;
|
|
// SAFETY: `surface` is one of this context's render targets.
|
|
let st = unsafe { (api.begin_picture)(self.display.dpy, self.context, surface) };
|
|
self.display.check(st, "vaBeginPicture")?;
|
|
let (picture, slice_buffers) = buffers.split_at_mut(head);
|
|
let mut rendered = self.render(picture);
|
|
for pair in slice_buffers.chunks_mut(2) {
|
|
if rendered.is_err() {
|
|
break;
|
|
}
|
|
rendered = self.render(pair);
|
|
}
|
|
// a begun picture is always ended, so the context stays usable
|
|
// SAFETY: the picture was begun on this context above.
|
|
let st = unsafe { (api.end_picture)(self.display.dpy, self.context) };
|
|
rendered?;
|
|
self.display.check(st, "vaEndPicture")
|
|
}
|
|
|
|
fn image(&mut self) -> Result<VAImage, String> {
|
|
if let Some(image) = self.image {
|
|
return Ok(image);
|
|
}
|
|
let (_, fourcc, bits_per_pixel) = formats(self.geometry.bits)?;
|
|
let mut format = VAImageFormat { fourcc, byte_order: 1, bits_per_pixel, ..Default::default() };
|
|
let mut image = VAImage { image_id: ffi::VA_INVALID_ID, buf: ffi::VA_INVALID_ID, ..Default::default() };
|
|
let (w, h) = (c_int::try_from(self.size.0).map_err(|_| "picture too wide")?, c_int::try_from(self.size.1).map_err(|_| "picture too tall")?);
|
|
// SAFETY: `format` and `image` are live; the driver fills `image` in.
|
|
let st = unsafe { (self.display.api.create_image)(self.display.dpy, &mut format, w, h, &mut image) };
|
|
self.display.check(st, "vaCreateImage")?;
|
|
self.image = Some(image);
|
|
Ok(image)
|
|
}
|
|
|
|
/// The picture in surface `index`, copied into the session's image once decoded.
|
|
fn get_image(&mut self, index: usize) -> Result<VAImage, String> {
|
|
let surface = *self.surfaces.get(index).ok_or("surface out of range")?;
|
|
let image = self.image()?;
|
|
let api = self.display.api;
|
|
// SAFETY: `surface` is one of this session's surfaces.
|
|
let st = unsafe { (api.sync_surface)(self.display.dpy, surface) };
|
|
self.display.check(st, "vaSyncSurface")?;
|
|
// SAFETY: the image and the surface were made on this display, with the same size.
|
|
let st = unsafe { (api.get_image)(self.display.dpy, surface, 0, 0, self.size.0, self.size.1, image.image_id) };
|
|
self.display.check(st, "vaGetImage")?;
|
|
Ok(image)
|
|
}
|
|
|
|
/// Every sample of `image` set to mid-gray: 128 in NV12, 512 (0x8000 in the high bits of
|
|
/// little-endian 16-bit words) in P010.
|
|
fn write_gray(&self, image: &VAImage) -> Result<(), String> {
|
|
let api = self.display.api;
|
|
let mut ptr: *mut c_void = std::ptr::null_mut();
|
|
// SAFETY: `image.buf` is the data buffer of an image made on this display; `ptr` is a live output.
|
|
let st = unsafe { (api.map_buffer)(self.display.dpy, image.buf, &mut ptr) };
|
|
self.display.check(st, "vaMapBuffer")?;
|
|
if !ptr.is_null() {
|
|
// SAFETY: a mapped image buffer holds `data_size` writable bytes at `ptr` until it is
|
|
// unmapped below; the slice does not outlive this block.
|
|
let data = unsafe { std::slice::from_raw_parts_mut(ptr.cast::<u8>(), image.data_size as usize) };
|
|
if self.geometry.bits > 8 {
|
|
for w in data.as_chunks_mut::<2>().0 {
|
|
w.copy_from_slice(&0x8000u16.to_le_bytes());
|
|
}
|
|
} else {
|
|
data.fill(128);
|
|
}
|
|
}
|
|
// SAFETY: the buffer was mapped above.
|
|
let st = unsafe { (api.unmap_buffer)(self.display.dpy, image.buf) };
|
|
self.display.check(st, "vaUnmapBuffer")?;
|
|
if ptr.is_null() { Err("vaMapBuffer returned no data".into()) } else { Ok(()) }
|
|
}
|
|
|
|
fn decode_picture<P, Q, S>(&mut self, target: usize, pic: &P, iq: Option<&Q>, slices: &[(S, Vec<u8>)]) -> Result<(), String> {
|
|
let surface = *self.surfaces.get(target).ok_or("surface out of range")?;
|
|
let mut buffers = Vec::with_capacity(2 + 2 * slices.len());
|
|
let decoded = self.decode_into(surface, &mut buffers, pic, iq, slices);
|
|
for id in buffers {
|
|
// SAFETY: each id is a buffer made above on this display, destroyed once; vaEndPicture
|
|
// has returned, so the driver no longer reads it.
|
|
unsafe { (self.display.api.destroy_buffer)(self.display.dpy, id) };
|
|
}
|
|
decoded
|
|
}
|
|
|
|
fn read_image(&self, image: &VAImage) -> Result<VideoFrame, String> {
|
|
let api = self.display.api;
|
|
let mut ptr: *mut c_void = std::ptr::null_mut();
|
|
// SAFETY: `image.buf` is the data buffer of an image made on this display; `ptr` is a live output.
|
|
let st = unsafe { (api.map_buffer)(self.display.dpy, image.buf, &mut ptr) };
|
|
self.display.check(st, "vaMapBuffer")?;
|
|
let result = if ptr.is_null() {
|
|
Err("vaMapBuffer returned no data".to_string())
|
|
} else {
|
|
// SAFETY: a mapped image buffer holds `data_size` bytes at `ptr` until it is unmapped
|
|
// below; the slice does not outlive this block.
|
|
let data = unsafe { std::slice::from_raw_parts(ptr.cast::<u8>().cast_const(), image.data_size as usize) };
|
|
biplanar_view(image, data).and_then(|b| biplanar::to_frame(&b, &self.geometry))
|
|
};
|
|
// SAFETY: the buffer was mapped above; `data` is no longer used.
|
|
let st = unsafe { (api.unmap_buffer)(self.display.dpy, image.buf) };
|
|
let frame = result?;
|
|
self.display.check(st, "vaUnmapBuffer")?;
|
|
Ok(frame)
|
|
}
|
|
}
|
|
|
|
/// The two planes of a mapped NV12 / P010 image.
|
|
fn biplanar_view<'a>(image: &VAImage, data: &'a [u8]) -> Result<Biplanar<'a>, String> {
|
|
if !matches!(image.format.fourcc, ffi::VA_FOURCC_NV12 | ffi::VA_FOURCC_P010) || image.num_planes < 2 {
|
|
return Err(format!("the read-back image is not NV12 / P010 (fourcc {:#x}, {} planes)", image.format.fourcc, image.num_planes));
|
|
}
|
|
let [p0, p1, _] = image.pitches;
|
|
if p0 != p1 {
|
|
return Err(format!("NV12 planes with different pitches ({p0}, {p1})"));
|
|
}
|
|
let luma = data.get(image.offsets[0] as usize..).ok_or("NV12 luma offset out of range")?;
|
|
let chroma = data.get(image.offsets[1] as usize..).ok_or("NV12 chroma offset out of range")?;
|
|
Ok(Biplanar { luma, chroma, stride: p0 as usize, rows: usize::from(image.height) })
|
|
}
|
|
|
|
impl Accel for Session {
|
|
fn surfaces(&self) -> &[VASurfaceID] {
|
|
&self.surfaces
|
|
}
|
|
|
|
fn decode_h264(
|
|
&mut self,
|
|
target: usize,
|
|
pic: &ffi::VAPictureParameterBufferH264,
|
|
iq: &ffi::VAIQMatrixBufferH264,
|
|
slices: &[(ffi::VASliceParameterBufferH264, Vec<u8>)],
|
|
) -> Result<(), String> {
|
|
self.decode_picture(target, pic, Some(iq), slices)
|
|
}
|
|
|
|
fn decode_hevc(
|
|
&mut self,
|
|
target: usize,
|
|
pic: &ffi::VAPictureParameterBufferHEVC,
|
|
iq: Option<&ffi::VAIQMatrixBufferHEVC>,
|
|
slices: &[(ffi::VASliceParameterBufferHEVC, Vec<u8>)],
|
|
) -> Result<(), String> {
|
|
self.decode_picture(target, pic, iq, slices)
|
|
}
|
|
|
|
fn read(&mut self, index: usize) -> Result<VideoFrame, String> {
|
|
let image = self.get_image(index)?;
|
|
self.read_image(&image)
|
|
}
|
|
|
|
fn fill(&mut self, index: usize, from: Option<usize>) -> Result<(), String> {
|
|
let surface = *self.surfaces.get(index).ok_or("surface out of range")?;
|
|
let image = match from {
|
|
Some(from) => self.get_image(from)?,
|
|
None => {
|
|
let image = self.image()?;
|
|
self.write_gray(&image)?;
|
|
image
|
|
}
|
|
};
|
|
let (w, h) = self.size;
|
|
// SAFETY: the image and the surface were made on this display, with the same size.
|
|
let st = unsafe { (self.display.api.put_image)(self.display.dpy, surface, image.image_id, 0, 0, w, h, 0, 0, w, h) };
|
|
self.display.check(st, "vaPutImage")
|
|
}
|
|
}
|
|
|
|
impl Drop for Session {
|
|
fn drop(&mut self) {
|
|
let (api, dpy) = (self.display.api, self.display.dpy);
|
|
// SAFETY: each object below was made on this display and is destroyed once; the display
|
|
// itself is terminated afterwards (when the `display` field is dropped).
|
|
unsafe {
|
|
if let Some(image) = self.image.take() {
|
|
(api.destroy_image)(dpy, image.image_id);
|
|
}
|
|
if self.context != ffi::VA_INVALID_ID {
|
|
(api.destroy_context)(dpy, self.context);
|
|
}
|
|
if !self.surfaces.is_empty() {
|
|
let n = c_int::try_from(self.surfaces.len()).unwrap_or(0);
|
|
(api.destroy_surfaces)(dpy, self.surfaces.as_mut_ptr(), n);
|
|
}
|
|
if self.config != ffi::VA_INVALID_ID {
|
|
(api.destroy_config)(dpy, self.config);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Whether this system has a working VA-API driver on a DRM render node (checked once), with the
|
|
/// driver's description, or why not.
|
|
pub fn probe() -> &'static Result<String, String> {
|
|
static PROBE: OnceLock<Result<String, String>> = OnceLock::new();
|
|
PROBE.get_or_init(|| Display::open().map(|d| d.vendor().to_string()))
|
|
}
|