use std::cell::RefCell;
use std::collections::HashSet;
use std::os::fd::{AsFd, OwnedFd};
use std::sync::{Arc, Mutex};
use moq_vaapi::decode::ExportedFrame;
use moq_vaapi::dmabuf::{DmaBuf as VaapiDmaBuf, Plane};
use moq_vaapi::vpp::Processor;
use moq_vaapi::{Matrix, VA_FOURCC_NV12};
use super::{DmaBuf, DmaBufExport, DmaBufFrame, DmaBufPlane, DrmFormat, I420};
use crate::{Color, Error, Size};
pub(crate) fn import(buffer: &DmaBuf) -> Result<(VaapiDmaBuf, DmaBufExport), Error> {
let export = buffer
.export()
.map_err(|e| Error::Codec(anyhow::anyhow!("export a DMA-BUF for VA-API: {e}")))?;
let fd: OwnedFd = export
.as_fd()
.try_clone_to_owned()
.map_err(|e| Error::Codec(anyhow::anyhow!("duplicate a DMA-BUF descriptor: {e}")))?;
let descriptor = VaapiDmaBuf {
drm_format: buffer.format().as_raw(),
modifier: buffer.modifier(),
width: buffer.width(),
height: buffer.height(),
planes: buffer
.planes()
.iter()
.map(|plane| Plane {
offset: plane.offset(),
pitch: plane.stride(),
})
.collect(),
fd,
color: buffer.color().map(color),
};
Ok((descriptor, export))
}
pub(crate) fn color(color: Color) -> moq_vaapi::Color {
match color {
Color::Bt601Limited => moq_vaapi::Color {
matrix: Matrix::Bt601,
full_range: false,
},
Color::Bt601Full => moq_vaapi::Color {
matrix: Matrix::Bt601,
full_range: true,
},
Color::Bt709Limited => moq_vaapi::Color {
matrix: Matrix::Bt709,
full_range: false,
},
Color::Bt709Full => moq_vaapi::Color {
matrix: Matrix::Bt709,
full_range: true,
},
}
}
pub(crate) fn is_rgb(format: DrmFormat) -> bool {
matches!(
format,
DrmFormat::XRGB8888 | DrmFormat::ARGB8888 | DrmFormat::XBGR8888 | DrmFormat::ABGR8888
)
}
thread_local! {
static PROCESSOR: RefCell<Option<Result<Processor, String>>> = const { RefCell::new(None) };
static REFUSED: RefCell<HashSet<(DrmFormat, u64)>> = RefCell::new(HashSet::new());
}
pub(crate) fn resize(buffer: &DmaBuf, size: Size) -> Result<DmaBuf, Error> {
let key = (buffer.format(), buffer.modifier());
if REFUSED.with(|refused| refused.borrow().contains(&key)) {
return Err(Error::Unsupported(format!(
"the VA-API video processor refused {:?} with modifier {:#x}",
key.0, key.1
)));
}
let (input_space, output_space, label) = match is_rgb(buffer.format()) {
true => {
let inferred = Color::infer(Size::new(buffer.width(), buffer.height()));
(None, Some(color(inferred)), Some(inferred))
}
false => (buffer.color().map(color), buffer.color().map(color), buffer.color()),
};
let exported = PROCESSOR.with(|slot| -> Result<ExportedFrame, Error> {
let mut slot = slot.borrow_mut();
let processor = slot
.get_or_insert_with(|| Processor::open().map_err(|e| format!("{e:#}")))
.as_ref()
.map_err(|e| Error::Unsupported(format!("no VA-API video processor: {e}")))?;
let (descriptor, lease) = import(buffer)?;
let processed = processor.import(descriptor).and_then(|input| {
processor.process(
&input,
VA_FOURCC_NV12,
(size.width, size.height),
input_space,
output_space,
)
});
drop(lease);
let output = processed.map_err(|e| {
REFUSED.with(|refused| refused.borrow_mut().insert(key));
Error::Codec(e.context("scale a DMA-BUF with the VA-API video processor"))
})?;
ExportedFrame::from_surface(output, 0).map_err(Error::Codec)
})?;
adopt(exported, label).map_err(Error::Codec)
}
pub(crate) fn adopt(frame: ExportedFrame, color: Option<Color>) -> anyhow::Result<DmaBuf> {
let (width, height) = (frame.width, frame.height);
let [object] = frame.descriptor.objects.as_slice() else {
anyhow::bail!(
"VA-API exported {} objects, expected one holding every plane",
frame.descriptor.objects.len()
);
};
let [layer] = frame.descriptor.layers.as_slice() else {
anyhow::bail!(
"VA-API exported {} layers, expected one composed layer",
frame.descriptor.layers.len()
);
};
if layer.drm_format != DrmFormat::NV12.as_raw() {
anyhow::bail!("VA-API exported DRM format {:#x}, expected NV12", layer.drm_format);
}
let count = layer.num_planes as usize;
anyhow::ensure!(
count <= layer.offset.len(),
"VA-API exported {count} planes, more than a PRIME descriptor holds"
);
let planes = (0..count)
.map(|plane| DmaBufPlane::new(layer.offset[plane], layer.pitch[plane]))
.collect();
let modifier = object.drm_format_modifier;
DmaBuf::new(
DrmFormat::NV12,
modifier,
width,
height,
planes,
color,
Arc::new(Exported {
frame: Mutex::new(frame),
color,
}),
)
.map_err(|e| anyhow::anyhow!("{e}"))
}
struct Exported {
frame: Mutex<ExportedFrame>,
color: Option<Color>,
}
impl DmaBufFrame for Exported {
fn export(&self) -> std::io::Result<OwnedFd> {
let frame = self.frame.lock().expect("poisoned");
let object = frame.descriptor.objects.first().ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::InvalidData, "the VA-API export carries no object")
})?;
object.fd.as_fd().try_clone_to_owned()
}
fn download_i420(&self) -> Result<I420, Error> {
let frame = self.frame.lock().expect("poisoned");
let nv12 = frame
.download()
.map_err(|e| Error::Codec(anyhow::anyhow!("read a VA-API surface back: {e:?}")))?;
let i420 = I420::from_nv12(&nv12.data, crate::Size::new(nv12.width, nv12.height))?;
Ok(match self.color {
Some(color) => i420.with_color(color),
None => i420,
})
}
}
#[cfg(all(test, feature = "openh264"))]
pub(crate) mod testing {
use std::os::fd::OwnedFd;
use std::sync::Arc;
use moq_vaapi::{Display, Image, Surface as VaSurface, UsageHint, VA_FOURCC_BGRX, VA_RT_FORMAT_RGB32};
use super::super::{DmaBuf, DmaBufFrame, DmaBufPlane, DrmFormat, I420};
use crate::{Error, Size};
struct Allocated {
_surface: VaSurface<()>,
fd: OwnedFd,
}
impl DmaBufFrame for Allocated {
fn export(&self) -> std::io::Result<OwnedFd> {
self.fd.try_clone()
}
fn download_i420(&self) -> Result<I420, Error> {
Err(Error::Unsupported("a test buffer has no CPU path".into()))
}
}
struct Unimportable {
fd: OwnedFd,
pixels: I420,
}
impl DmaBufFrame for Unimportable {
fn export(&self) -> std::io::Result<OwnedFd> {
self.fd.try_clone()
}
fn download_i420(&self) -> Result<I420, Error> {
Ok(self.pixels.clone())
}
}
pub(crate) fn unimportable_dmabuf(pixels: I420) -> DmaBuf {
let fd = OwnedFd::from(std::fs::File::open("/dev/null").expect("open /dev/null"));
let (width, height) = (pixels.width(), pixels.height());
DmaBuf::new(
DrmFormat::NV12,
0x00ff_ffff_ffff_fffe,
width,
height,
vec![DmaBufPlane::new(0, width), DmaBufPlane::new(width * height, width)],
None,
Arc::new(Unimportable { fd, pixels }),
)
.expect("a valid description")
}
pub(crate) fn bgrx_dmabuf(rgba: &[u8], size: Size) -> Option<DmaBuf> {
let display = Display::open()?;
let (width, height) = (size.width, size.height);
let surface = display
.create_surfaces(
VA_RT_FORMAT_RGB32,
Some(VA_FOURCC_BGRX),
width,
height,
Some(UsageHint::USAGE_HINT_EXPORT),
vec![()],
)
.ok()?
.pop()?;
let format = display
.query_image_formats()
.ok()?
.into_iter()
.find(|format| format.fourcc == VA_FOURCC_BGRX)?;
{
let mut image = Image::create_from(&surface, format, (width, height), (width, height)).ok()?;
let va_image = *image.image();
let data = image.as_mut();
for y in 0..height as usize {
for x in 0..width as usize {
let from = (y * width as usize + x) * 4;
let to = va_image.offsets[0] as usize + y * va_image.pitches[0] as usize + x * 4;
let [r, g, b, _] = [rgba[from], rgba[from + 1], rgba[from + 2], rgba[from + 3]];
data[to..to + 4].copy_from_slice(&[b, g, r, 255]);
}
}
}
surface.sync().ok()?;
let mut exported = surface.export_prime().ok()?;
let layer = &exported.layers[0];
assert_eq!(layer.drm_format, DrmFormat::XRGB8888.as_raw());
let plane = DmaBufPlane::new(layer.offset[0], layer.pitch[0]);
let object = exported.objects.remove(0);
DmaBuf::new(
DrmFormat::XRGB8888,
object.drm_format_modifier,
width,
height,
vec![plane],
None,
Arc::new(Allocated {
_surface: surface,
fd: object.fd,
}),
)
.ok()
}
}