#![allow(non_upper_case_globals, clippy::identity_op)]
use crate::gpu_libs::{
self, VA_STATUS_SUCCESS, VABufferID, VAConfigID, VAContextID, VADisplay, VASurfaceID,
};
use std::ffi::c_void;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::ptr;
const VAProfileH264High: i32 = 8;
const VA_PROFILE_NONE: i32 = -1;
const VAEntrypointEncSliceLP: i32 = 8;
const VAEntrypointEncSlice: i32 = 6;
const VA_ENTRYPOINT_VIDEO_PROC: i32 = 10;
const VA_RT_FORMAT_YUV420: u32 = 0x00000001;
#[allow(dead_code)]
const VA_RT_FORMAT_YUV420_10: u32 = 0x00000100;
#[allow(dead_code)]
const VA_RT_FORMAT_YUV444: u32 = 0x00000004;
const VAEncCodedBufferType: i32 = 21;
const VAEncSequenceParameterBufferType: i32 = 22;
const VAEncPictureParameterBufferType: i32 = 23;
const VAEncSliceParameterBufferType: i32 = 24;
const VAEncPackedHeaderParameterBufferType: i32 = 25;
const VAEncPackedHeaderDataBufferType: i32 = 26;
const VA_ENC_PACKED_HEADER_SEQUENCE: u32 = 1;
const VA_ENC_PACKED_HEADER_PICTURE: u32 = 2;
const VAEncMiscParameterBufferType: i32 = 27;
const VAEncMiscParameterTypeFrameRate: u32 = 0;
const VAEncMiscParameterTypeQualityLevel: u32 = 6;
const REALTIME_FPS: u32 = 60;
const REALTIME_QUALITY_LEVEL: u32 = 7;
#[repr(C)]
struct VAEncMiscParameterFrameRate {
framerate: u32,
framerate_flags: u32,
va_reserved: [u32; 4],
}
#[repr(C)]
struct VAEncMiscParameterBufferQualityLevel {
quality_level: u32,
va_reserved: [u32; 4],
}
fn create_misc_param_buffer<T>(
va: &gpu_libs::VaFns,
display: VADisplay,
context: VAContextID,
sub_type: u32,
inner: T,
) -> Option<VABufferID> {
#[repr(C)]
struct Wrapper<T> {
type_: u32,
inner: T,
}
let wrapper = Wrapper {
type_: sub_type,
inner,
};
let mut buf_id: VABufferID = 0;
let st = unsafe {
(va.vaCreateBuffer)(
display,
context,
VAEncMiscParameterBufferType,
std::mem::size_of::<Wrapper<T>>() as u32,
1,
&wrapper as *const _ as *mut c_void,
&mut buf_id,
)
};
if st == VA_STATUS_SUCCESS {
Some(buf_id)
} else {
None
}
}
#[repr(C)]
struct VAEncPackedHeaderParameterBuffer {
r#type: u32,
bit_length: u32,
has_emulation_bytes: u8,
}
fn create_packed_header_buffers(
va: &gpu_libs::VaFns,
display: VADisplay,
context: VAContextID,
header_type: u32,
data: &[u8],
) -> Option<(VABufferID, VABufferID)> {
let param = VAEncPackedHeaderParameterBuffer {
r#type: header_type,
bit_length: (data.len() * 8) as u32,
has_emulation_bytes: 0,
};
let mut param_buf: VABufferID = 0;
let st = unsafe {
(va.vaCreateBuffer)(
display,
context,
VAEncPackedHeaderParameterBufferType,
std::mem::size_of::<VAEncPackedHeaderParameterBuffer>() as u32,
1,
¶m as *const _ as *mut c_void,
&mut param_buf,
)
};
if st != VA_STATUS_SUCCESS {
return None;
}
let mut data_buf: VABufferID = 0;
let st = unsafe {
(va.vaCreateBuffer)(
display,
context,
VAEncPackedHeaderDataBufferType,
data.len() as u32,
1,
data.as_ptr() as *mut c_void,
&mut data_buf,
)
};
if st != VA_STATUS_SUCCESS {
unsafe {
(va.vaDestroyBuffer)(display, param_buf);
}
return None;
}
Some((param_buf, data_buf))
}
const VA_INVALID_SURFACE: u32 = 0xFFFF_FFFF;
const VA_PICTURE_H264_INVALID: u32 = 0x01;
const SPS_SIZE: usize = 1132;
const PPS_SIZE: usize = 648;
const SLICE_SIZE: usize = 3140;
const VA_IMAGE_SIZE: usize = 120;
const CBS_BUF_OFF: usize = 16;
const CBS_NEXT_OFF: usize = 24;
const VAIMG_BUF_OFF: usize = 52;
const VAIMG_PITCHES_OFF: usize = 68;
const VAIMG_OFFSETS_OFF: usize = 80;
const VAIMG_ID_OFF: usize = 0;
struct CachedDerivedImage {
image_id: u32,
buf_id: u32,
y_pitch: usize,
uv_pitch: usize,
y_offset: usize,
uv_offset: usize,
}
#[repr(C)]
struct VADRMPRIMESurfaceDescriptor {
fourcc: u32,
width: u32,
height: u32,
num_objects: u32,
objects: [DRMObject; 4],
num_layers: u32,
layers: [DRMLayer; 4],
}
#[repr(C)]
#[derive(Copy, Clone, Default)]
struct DRMObject {
fd: i32,
size: u32,
drm_format_modifier: u64,
}
#[repr(C)]
#[derive(Copy, Clone, Default)]
struct DRMLayer {
drm_format: u32,
num_planes: u32,
object_index: [u32; 4],
offset: [u32; 4],
pitch: [u32; 4],
}
const NUM_NV12_SURFACES: usize = 6;
const NUM_BGRA_SURFACES: usize = 5;
pub(crate) struct GbmExportedBuffer {
pub fd: std::os::fd::OwnedFd,
pub stride: u32,
pub width: u32,
pub height: u32,
}
pub(crate) struct GbmNv12Buffer {
pub fd: std::sync::Arc<std::os::fd::OwnedFd>,
pub stride: u32,
pub uv_offset: u32,
pub modifier: u64,
pub va_surface: VASurfaceID,
}
pub(crate) struct VppContext {
va: &'static crate::gpu_libs::VaFns,
display: VADisplay,
config: u32,
context: u32,
nv12_surfaces: [u32; NUM_NV12_SURFACES],
enc_width: u32,
enc_height: u32,
#[allow(dead_code)]
bgra_width: u32,
#[allow(dead_code)]
bgra_height: u32,
pub(crate) gbm_buffers: Vec<GbmExportedBuffer>,
pub(crate) gbm_nv12_buffers: Vec<GbmNv12Buffer>,
#[allow(dead_code)]
verbose: bool,
}
impl VppContext {
#[allow(clippy::too_many_arguments)]
pub(crate) unsafe fn try_new(
va: &'static crate::gpu_libs::VaFns,
display: VADisplay,
width: u32,
height: u32,
bgra_width: u32,
bgra_height: u32,
drm_fd: std::os::fd::RawFd,
verbose: bool,
) -> Option<Self> {
let mut eps = [0i32; 16];
let mut n = 0i32;
let st = unsafe {
(va.vaQueryConfigEntrypoints)(display, VA_PROFILE_NONE, eps.as_mut_ptr(), &mut n)
};
if st != crate::gpu_libs::VA_STATUS_SUCCESS {
return None;
}
if !eps[..n as usize].contains(&VA_ENTRYPOINT_VIDEO_PROC) {
eprintln!("[vaapi-vpp] VAEntrypointVideoProc not available — dmabuf zerocopy disabled");
return None;
}
let mut config = 0u32;
let st = unsafe {
(va.vaCreateConfig)(
display,
VA_PROFILE_NONE,
VA_ENTRYPOINT_VIDEO_PROC,
ptr::null_mut(),
0,
&mut config,
)
};
if st != crate::gpu_libs::VA_STATUS_SUCCESS {
return None;
}
let mut nv12_surfaces = [0u32; NUM_NV12_SURFACES];
let st = unsafe {
(va.vaCreateSurfaces)(
display,
VA_RT_FORMAT_YUV420,
width,
height,
nv12_surfaces.as_mut_ptr(),
NUM_NV12_SURFACES as u32,
ptr::null_mut(),
0,
)
};
if st != crate::gpu_libs::VA_STATUS_SUCCESS {
unsafe {
(va.vaDestroyConfig)(display, config);
}
return None;
}
let mut context = 0u32;
let st = unsafe {
(va.vaCreateContext)(
display,
config,
width as i32,
height as i32,
0,
nv12_surfaces.as_mut_ptr(),
NUM_NV12_SURFACES as i32,
&mut context,
)
};
if st != crate::gpu_libs::VA_STATUS_SUCCESS {
unsafe {
(va.vaDestroySurfaces)(
display,
nv12_surfaces.as_mut_ptr(),
NUM_NV12_SURFACES as i32,
);
(va.vaDestroyConfig)(display, config);
}
return None;
}
let mut gbm_buffers = Vec::new();
if let Ok(gbm) = crate::gpu_libs::gbm() {
let gbm_fd = unsafe { libc::dup(drm_fd) };
if gbm_fd >= 0 {
let dev = unsafe { (gbm.gbm_create_device)(gbm_fd) };
if !dev.is_null() {
for i in 0..NUM_BGRA_SURFACES {
let bo = unsafe {
(gbm.gbm_bo_create)(
dev,
bgra_width,
bgra_height,
crate::gpu_libs::GBM_FORMAT_ARGB8888,
crate::gpu_libs::GBM_BO_USE_RENDERING
| crate::gpu_libs::GBM_BO_USE_LINEAR,
)
};
if bo.is_null() {
if verbose {
eprintln!("[vaapi-vpp] gbm_bo_create failed for buffer {i}");
}
break;
}
let fd = unsafe { (gbm.gbm_bo_get_fd)(bo) };
let stride = unsafe { (gbm.gbm_bo_get_stride)(bo) };
if fd < 0 {
break;
}
if verbose && i == 0 {
eprintln!(
"[vaapi-vpp] GBM buffer: {bgra_width}x{bgra_height} stride={stride} LINEAR",
);
}
gbm_buffers.push(GbmExportedBuffer {
fd: unsafe { std::os::fd::OwnedFd::from_raw_fd(fd) },
stride,
width: bgra_width,
height: bgra_height,
});
}
}
}
}
if verbose {
eprintln!(
"[vaapi-vpp] initialized {bgra_width}x{bgra_height} → {width}x{height} VPP ({} GBM buffers)",
gbm_buffers.len()
);
}
Some(Self {
va,
display,
config,
context,
nv12_surfaces,
enc_width: width,
enc_height: height,
bgra_width,
bgra_height,
gbm_buffers,
gbm_nv12_buffers: Vec::new(),
verbose,
})
}
pub(crate) fn allocate_nv12_buffers(&mut self, _drm_fd: std::os::fd::RawFd, count: usize) {
let w = self.enc_width;
let h = self.enc_height;
if !self.try_vaapi_nv12_export(w, h, count) {
eprintln!(
"[vaapi-vpp] NV12 export unavailable {w}x{h} — \
falling back to CPU BGRA→NV12",
);
}
}
fn destroy_nv12_compute_surfaces(&self, surfaces: &mut [u32]) {
let tracked: std::collections::HashSet<u32> =
self.gbm_nv12_buffers.iter().map(|b| b.va_surface).collect();
let to_destroy: Vec<u32> = surfaces
.iter()
.copied()
.filter(|s| *s != 0 && !tracked.contains(s))
.collect();
if !to_destroy.is_empty() {
let mut buf = to_destroy;
unsafe {
(self.va.vaDestroySurfaces)(self.display, buf.as_mut_ptr(), buf.len() as i32);
}
}
}
fn try_vaapi_nv12_export(&mut self, w: u32, h: u32, count: usize) -> bool {
let va = self.va;
let mut surfaces = vec![0u32; count];
let st = unsafe {
(va.vaCreateSurfaces)(
self.display,
VA_RT_FORMAT_YUV420,
w,
h,
surfaces.as_mut_ptr(),
count as u32,
ptr::null_mut(),
0,
)
};
if st != VA_STATUS_SUCCESS {
eprintln!("[vaapi-vpp] NV12 vaCreateSurfaces failed: st={st} {w}x{h}");
return false;
}
for (i, &surf) in surfaces.iter().enumerate() {
let mut desc: VADRMPRIMESurfaceDescriptor = unsafe { std::mem::zeroed() };
let st = unsafe {
(va.vaExportSurfaceHandle)(
self.display,
surf,
0x40000000, 0x07, &mut desc as *mut _ as *mut c_void,
)
};
if st != VA_STATUS_SUCCESS || desc.num_layers == 0 {
eprintln!(
"[vaapi-vpp] NV12 export failed: st={st} layers={}",
desc.num_layers,
);
if desc.objects[0].fd >= 0 && st == VA_STATUS_SUCCESS {
unsafe { libc::close(desc.objects[0].fd) };
}
self.destroy_nv12_compute_surfaces(&mut surfaces);
return false;
}
let fd = desc.objects[0].fd;
let modifier = desc.objects[0].drm_format_modifier;
let (stride, uv_offset) = if desc.num_layers >= 2 {
(desc.layers[0].pitch[0], desc.layers[1].offset[0])
} else if desc.layers[0].num_planes >= 2 {
(desc.layers[0].pitch[0], desc.layers[0].offset[1])
} else {
eprintln!(
"[vaapi-vpp] NV12 export: unexpected layout layers={} planes={}",
desc.num_layers, desc.layers[0].num_planes,
);
unsafe { libc::close(fd) };
self.destroy_nv12_compute_surfaces(&mut surfaces);
return false;
};
if self.verbose && i == 0 {
eprintln!(
"[vaapi-vpp] NV12 export: {w}x{h} stride={stride} \
uv_offset={uv_offset} modifier=0x{modifier:016x} va_surface={surf}",
);
}
self.gbm_nv12_buffers.push(GbmNv12Buffer {
fd: std::sync::Arc::new(unsafe { std::os::fd::OwnedFd::from_raw_fd(fd) }),
stride,
uv_offset,
modifier,
va_surface: surf,
});
}
eprintln!(
"[vaapi-vpp] exported {} NV12 surfaces for compute {w}x{h}",
self.gbm_nv12_buffers.len(),
);
true
}
#[allow(dead_code)]
pub(crate) fn va_display_usize(&self) -> usize {
self.display as usize
}
}
impl Drop for VppContext {
fn drop(&mut self) {
unsafe {
let va = self.va;
let mut compute_surfs: Vec<u32> = self
.gbm_nv12_buffers
.drain(..)
.map(|b| {
drop(b.fd);
b.va_surface
})
.collect();
if !compute_surfs.is_empty() {
(va.vaDestroySurfaces)(
self.display,
compute_surfs.as_mut_ptr(),
compute_surfs.len() as i32,
);
}
(va.vaDestroyContext)(self.display, self.context);
(va.vaDestroySurfaces)(
self.display,
self.nv12_surfaces.as_mut_ptr(),
NUM_NV12_SURFACES as i32,
);
(va.vaDestroyConfig)(self.display, self.config);
}
}
}
fn w8(buf: &mut [u8], off: usize, val: u8) {
buf[off] = val;
}
fn w16(buf: &mut [u8], off: usize, val: u16) {
buf[off..off + 2].copy_from_slice(&val.to_ne_bytes());
}
fn w32(buf: &mut [u8], off: usize, val: u32) {
buf[off..off + 4].copy_from_slice(&val.to_ne_bytes());
}
fn r32(buf: &[u8], off: usize) -> u32 {
u32::from_ne_bytes(buf[off..off + 4].try_into().unwrap())
}
const NUM_REF_SURFACES: usize = 2;
const NUM_INPUT_SURFACES: usize = 2;
const TOTAL_SURFACES: usize = NUM_REF_SURFACES + NUM_INPUT_SURFACES;
const NUM_CODED_BUFFERS: usize = 2;
struct BitstreamWriter {
buf: Vec<u8>,
byte: u8,
bits_left: u8,
}
impl BitstreamWriter {
fn new() -> Self {
Self {
buf: Vec::with_capacity(32),
byte: 0,
bits_left: 8,
}
}
fn write_bit(&mut self, b: u8) {
self.byte |= (b & 1) << (self.bits_left - 1);
self.bits_left -= 1;
if self.bits_left == 0 {
self.buf.push(self.byte);
self.byte = 0;
self.bits_left = 8;
}
}
fn write_bits(&mut self, val: u32, n: u8) {
for i in (0..n).rev() {
self.write_bit(((val >> i) & 1) as u8);
}
}
fn write_ue(&mut self, val: u32) {
let x = val + 1;
let leading = 31 - x.leading_zeros(); for _ in 0..leading {
self.write_bit(0);
}
self.write_bits(x, leading as u8 + 1);
}
fn write_se(&mut self, val: i32) {
if val > 0 {
self.write_ue((val as u32) * 2 - 1);
} else {
self.write_ue((-val as u32) * 2);
}
}
fn finish(mut self) -> Vec<u8> {
self.write_bit(1); if self.bits_left < 8 {
self.buf.push(self.byte);
}
self.buf
}
}
fn build_h264_sps_nal(width_in_mbs: u16, height_in_mbs: u16, width: u32, height: u32) -> Vec<u8> {
let max_fs = width_in_mbs as u32 * height_in_mbs as u32;
let level_idc: u8 = if max_fs <= 1620 {
31
} else if max_fs <= 8192 {
40
} else if max_fs <= 22080 {
50
} else if max_fs <= 36864 {
51
} else {
52
};
let mut w = BitstreamWriter::new();
w.write_bits(100, 8);
w.write_bits(0b00000000, 8);
w.write_bits(level_idc as u32, 8);
w.write_ue(0);
w.write_ue(1);
w.write_ue(0);
w.write_ue(0);
w.write_bit(0);
w.write_bit(0);
w.write_ue(0);
w.write_ue(2);
w.write_ue(1);
w.write_bit(0);
w.write_ue(width_in_mbs as u32 - 1);
w.write_ue(height_in_mbs as u32 - 1);
w.write_bit(1);
w.write_bit(1);
let crop_w = width_in_mbs as u32 * 16;
let crop_h = height_in_mbs as u32 * 16;
if crop_w != width || crop_h != height {
w.write_bit(1); w.write_ue(0); w.write_ue((crop_w - width) / 2); w.write_ue(0); w.write_ue((crop_h - height) / 2); } else {
w.write_bit(0);
}
w.write_bit(0);
let rbsp = w.finish();
let mut nal = Vec::with_capacity(4 + 1 + rbsp.len());
nal.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
nal.push(0x67); nal.extend_from_slice(&rbsp);
nal
}
fn build_h264_pps_nal() -> Vec<u8> {
let mut w = BitstreamWriter::new();
w.write_ue(0);
w.write_ue(0);
w.write_bit(1);
w.write_bit(0);
w.write_ue(0);
w.write_ue(0);
w.write_ue(0);
w.write_bit(0);
w.write_bits(0, 2);
w.write_se(0);
w.write_se(0);
w.write_se(0);
w.write_bit(1);
w.write_bit(0);
w.write_bit(0);
w.write_bit(1);
w.write_bit(0);
w.write_se(0);
let rbsp = w.finish();
let mut nal = Vec::with_capacity(4 + 1 + rbsp.len());
nal.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
nal.push(0x68); nal.extend_from_slice(&rbsp);
nal
}
fn find_annex_b_start(data: &[u8]) -> Option<usize> {
for i in 0..data.len().saturating_sub(2) {
if data[i] == 0 && data[i + 1] == 0 {
if data[i + 2] == 1 {
return Some(i);
}
if i + 3 < data.len() && data[i + 2] == 0 && data[i + 3] == 1 {
return Some(i);
}
}
}
None
}
#[allow(clippy::too_many_arguments)]
unsafe fn bgra_to_nv12_fast(
src: &[u8],
dst: *mut u8,
y_offset: usize,
uv_offset: usize,
y_pitch: usize,
uv_pitch: usize,
src_w: usize,
src_h: usize,
) {
debug_assert!(src.len() >= src_w * src_h * 4);
let src_ptr = src.as_ptr();
let chroma_h = src_h / 2;
let chroma_w = src_w / 2;
let row_stride = src_w * 4;
for cy in 0..chroma_h {
let row0 = cy * 2;
let src_row0 = unsafe { src_ptr.add(row0 * row_stride) };
let src_row1 = unsafe { src_ptr.add((row0 + 1) * row_stride) };
let dst_y0 = unsafe { dst.add(y_offset + row0 * y_pitch) };
let dst_y1 = unsafe { dst.add(y_offset + (row0 + 1) * y_pitch) };
let dst_uv = unsafe { dst.add(uv_offset + cy * uv_pitch) };
for cx in 0..chroma_w {
let off = cx * 8; let b00 = unsafe { *src_row0.add(off) } as i32;
let g00 = unsafe { *src_row0.add(off + 1) } as i32;
let r00 = unsafe { *src_row0.add(off + 2) } as i32;
let b01 = unsafe { *src_row0.add(off + 4) } as i32;
let g01 = unsafe { *src_row0.add(off + 5) } as i32;
let r01 = unsafe { *src_row0.add(off + 6) } as i32;
let b10 = unsafe { *src_row1.add(off) } as i32;
let g10 = unsafe { *src_row1.add(off + 1) } as i32;
let r10 = unsafe { *src_row1.add(off + 2) } as i32;
let b11 = unsafe { *src_row1.add(off + 4) } as i32;
let g11 = unsafe { *src_row1.add(off + 5) } as i32;
let r11 = unsafe { *src_row1.add(off + 6) } as i32;
let y00 = ((66 * r00 + 129 * g00 + 25 * b00 + 128) >> 8) + 16;
let y01 = ((66 * r01 + 129 * g01 + 25 * b01 + 128) >> 8) + 16;
let y10 = ((66 * r10 + 129 * g10 + 25 * b10 + 128) >> 8) + 16;
let y11 = ((66 * r11 + 129 * g11 + 25 * b11 + 128) >> 8) + 16;
unsafe {
*dst_y0.add(cx * 2) = y00.clamp(0, 255) as u8;
*dst_y0.add(cx * 2 + 1) = y01.clamp(0, 255) as u8;
*dst_y1.add(cx * 2) = y10.clamp(0, 255) as u8;
*dst_y1.add(cx * 2 + 1) = y11.clamp(0, 255) as u8;
}
let avg_r = (r00 + r01 + r10 + r11) >> 2;
let avg_g = (g00 + g01 + g10 + g11) >> 2;
let avg_b = (b00 + b01 + b10 + b11) >> 2;
let u = ((-38 * avg_r - 74 * avg_g + 112 * avg_b + 128) >> 8) + 128;
let v = ((112 * avg_r - 94 * avg_g - 18 * avg_b + 128) >> 8) + 128;
unsafe {
*dst_uv.add(cx * 2) = u.clamp(0, 255) as u8;
*dst_uv.add(cx * 2 + 1) = v.clamp(0, 255) as u8;
}
}
}
}
#[allow(clippy::too_many_arguments)]
unsafe fn bgra_to_nv12_padded(
src: &[u8],
dst: *mut u8,
y_offset: usize,
uv_offset: usize,
y_pitch: usize,
uv_pitch: usize,
src_w: usize,
src_h: usize,
enc_w: usize,
enc_h: usize,
) {
for row in 0..enc_h {
let sr = row.min(src_h - 1);
let dst_row = unsafe { dst.add(y_offset + row * y_pitch) };
for col in 0..enc_w {
let sc = col.min(src_w - 1);
let i = (sr * src_w + sc) * 4;
let r = src[i + 2] as i32;
let g = src[i + 1] as i32;
let b = src[i] as i32;
let y = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
unsafe { *dst_row.add(col) = y.clamp(0, 255) as u8 };
}
}
let chroma_h = enc_h / 2;
let chroma_w = enc_w / 2;
for cy in 0..chroma_h {
let dst_row = unsafe { dst.add(uv_offset + cy * uv_pitch) };
for cx in 0..chroma_w {
let row = cy * 2;
let col = cx * 2;
let mut r_sum = 0i32;
let mut g_sum = 0i32;
let mut b_sum = 0i32;
for dy in 0..2usize {
for dx in 0..2usize {
let sr = (row + dy).min(src_h - 1);
let sc = (col + dx).min(src_w - 1);
let i = (sr * src_w + sc) * 4;
r_sum += src[i + 2] as i32;
g_sum += src[i + 1] as i32;
b_sum += src[i] as i32;
}
}
let avg_r = r_sum >> 2;
let avg_g = g_sum >> 2;
let avg_b = b_sum >> 2;
let u = ((-38 * avg_r - 74 * avg_g + 112 * avg_b + 128) >> 8) + 128;
let v = ((112 * avg_r - 94 * avg_g - 18 * avg_b + 128) >> 8) + 128;
unsafe {
*dst_row.add(cx * 2) = u.clamp(0, 255) as u8;
*dst_row.add(cx * 2 + 1) = v.clamp(0, 255) as u8;
}
}
}
}
struct PendingFrame {
input_surface: VASurfaceID,
coded_buf: VABufferID,
was_idr: bool,
}
pub struct VaapiDirectEncoder {
va: &'static gpu_libs::VaFns,
display: VADisplay,
config: VAConfigID,
context: VAContextID,
surfaces: [VASurfaceID; TOTAL_SURFACES],
coded_bufs: [VABufferID; NUM_CODED_BUFFERS],
next_coded_slot: usize,
next_input_slot: usize,
pending: Option<PendingFrame>,
width: u32,
height: u32,
width_in_mbs: u16,
height_in_mbs: u16,
frame_num: u16,
idr_num: u32,
force_idr: bool,
cur_ref_idx: usize,
qp: u8,
_verbose: bool,
pub(crate) _drm_fd: OwnedFd,
pub(crate) vpp: Option<VppContext>,
cached_input_images: [Option<CachedDerivedImage>; NUM_INPUT_SURFACES],
}
unsafe impl Send for VaapiDirectEncoder {}
impl VaapiDirectEncoder {
pub fn try_new(
width: u32,
height: u32,
vaapi_device: &str,
qp: u8,
verbose: bool,
chroma: crate::surface_encoder::ChromaSubsampling,
) -> Result<Self, String> {
if chroma.is_444() {
return Err("VA-API H.264 4:4:4 encoding is not yet supported".into());
}
let va = gpu_libs::va().map_err(|e| format!("VA-API: {e}"))?;
let va_drm = gpu_libs::va_drm().map_err(|e| format!("VA-DRM: {e}"))?;
let drm_fd = {
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(vaapi_device)
.map_err(|e| format!("failed to open {vaapi_device}: {e}"))?;
OwnedFd::from(file)
};
let display = unsafe { (va_drm.vaGetDisplayDRM)(drm_fd.as_raw_fd()) };
if display.is_null() {
return Err("vaGetDisplayDRM returned null".into());
}
let mut major = 0i32;
let mut minor = 0i32;
let st = unsafe { (va.vaInitialize)(display, &mut major, &mut minor) };
if st != VA_STATUS_SUCCESS {
return Err(format!("vaInitialize failed: {st}"));
}
let mut entrypoints = [0i32; 16];
let mut num_ep = 0i32;
unsafe {
(va.vaQueryConfigEntrypoints)(
display,
VAProfileH264High,
entrypoints.as_mut_ptr(),
&mut num_ep,
);
}
let ep_slice = &entrypoints[..num_ep as usize];
let entrypoint = if ep_slice.contains(&VAEntrypointEncSliceLP) {
VAEntrypointEncSliceLP
} else if ep_slice.contains(&VAEntrypointEncSlice) {
VAEntrypointEncSlice
} else {
unsafe {
(va.vaTerminate)(display);
}
return Err("H.264 encode not supported on this VA-API device".into());
};
let mut config: VAConfigID = 0;
let st = unsafe {
(va.vaCreateConfig)(
display,
VAProfileH264High,
entrypoint,
ptr::null_mut(),
0,
&mut config,
)
};
if st != VA_STATUS_SUCCESS {
unsafe {
(va.vaTerminate)(display);
}
return Err(format!("vaCreateConfig failed: {st}"));
}
let mut surfaces = [0u32; TOTAL_SURFACES];
let st = unsafe {
(va.vaCreateSurfaces)(
display,
VA_RT_FORMAT_YUV420,
width,
height,
surfaces.as_mut_ptr(),
TOTAL_SURFACES as u32,
ptr::null_mut(),
0,
)
};
if st != VA_STATUS_SUCCESS {
unsafe {
(va.vaDestroyConfig)(display, config);
(va.vaTerminate)(display);
}
return Err(format!("vaCreateSurfaces failed: {st}"));
}
let mut context: VAContextID = 0;
let st = unsafe {
(va.vaCreateContext)(
display,
config,
width as i32,
height as i32,
0x00000002, surfaces.as_mut_ptr(),
TOTAL_SURFACES as i32,
&mut context,
)
};
if st != VA_STATUS_SUCCESS {
unsafe {
(va.vaDestroySurfaces)(display, surfaces.as_mut_ptr(), TOTAL_SURFACES as i32);
(va.vaDestroyConfig)(display, config);
(va.vaTerminate)(display);
}
return Err(format!("vaCreateContext failed: {st}"));
}
let coded_buf_size = width * height;
let mut coded_bufs = [0u32; NUM_CODED_BUFFERS];
for slot in 0..NUM_CODED_BUFFERS {
let st = unsafe {
(va.vaCreateBuffer)(
display,
context,
VAEncCodedBufferType,
coded_buf_size,
1,
ptr::null_mut(),
&mut coded_bufs[slot],
)
};
if st != VA_STATUS_SUCCESS {
unsafe {
for &prev in coded_bufs.iter().take(slot) {
(va.vaDestroyBuffer)(display, prev);
}
(va.vaDestroyContext)(display, context);
(va.vaDestroySurfaces)(display, surfaces.as_mut_ptr(), TOTAL_SURFACES as i32);
(va.vaDestroyConfig)(display, config);
(va.vaTerminate)(display);
}
return Err(format!("vaCreateBuffer(coded) failed: {st}"));
}
}
let width_in_mbs = width.div_ceil(16) as u16;
let height_in_mbs = height.div_ceil(16) as u16;
if verbose {
eprintln!(
"[vaapi-direct] initialized H.264 encoder for {width}x{height} \
(ep={entrypoint}, qp={qp})"
);
}
Ok(Self {
va,
display,
config,
context,
surfaces,
coded_bufs,
next_coded_slot: 0,
next_input_slot: 0,
pending: None,
width,
height,
width_in_mbs,
height_in_mbs,
frame_num: 0,
idr_num: 0,
force_idr: false,
cur_ref_idx: 0,
qp,
_verbose: verbose,
vpp: unsafe {
VppContext::try_new(
va,
display,
width,
height,
width,
height,
drm_fd.as_raw_fd(),
verbose,
)
},
_drm_fd: drm_fd,
cached_input_images: [None, None],
})
}
pub fn request_keyframe(&mut self) {
self.force_idr = true;
}
pub fn gbm_buffers(&self) -> &[GbmExportedBuffer] {
match &self.vpp {
Some(vpp) => &vpp.gbm_buffers,
None => &[],
}
}
pub fn gbm_nv12_buffers(&self) -> &[GbmNv12Buffer] {
match &self.vpp {
Some(vpp) => &vpp.gbm_nv12_buffers,
None => &[],
}
}
#[allow(dead_code)]
pub fn va_display_usize(&self) -> usize {
match &self.vpp {
Some(vpp) => vpp.va_display_usize(),
None => 0,
}
}
fn derive_input_image(&mut self, slot: usize) -> Option<&CachedDerivedImage> {
if self.cached_input_images[slot].is_none() {
let surface = self.surfaces[NUM_REF_SURFACES + slot];
let mut image = [0u8; VA_IMAGE_SIZE];
let st = unsafe {
(self.va.vaDeriveImage)(self.display, surface, image.as_mut_ptr() as *mut c_void)
};
if st != VA_STATUS_SUCCESS {
return None;
}
self.cached_input_images[slot] = Some(CachedDerivedImage {
image_id: r32(&image, VAIMG_ID_OFF),
buf_id: r32(&image, VAIMG_BUF_OFF),
y_pitch: r32(&image, VAIMG_PITCHES_OFF) as usize,
uv_pitch: r32(&image, VAIMG_PITCHES_OFF + 4) as usize,
y_offset: r32(&image, VAIMG_OFFSETS_OFF) as usize,
uv_offset: r32(&image, VAIMG_OFFSETS_OFF + 4) as usize,
});
}
self.cached_input_images[slot].as_ref()
}
pub fn encode_nv12(
&mut self,
y_data: &[u8],
uv_data: &[u8],
y_stride: usize,
uv_stride: usize,
) -> Option<(Vec<u8>, bool)> {
let slot = self.next_input_slot;
self.next_input_slot = (slot + 1) % NUM_INPUT_SURFACES;
self.upload_nv12(slot, y_data, uv_data, y_stride, uv_stride)?;
let input_surface = self.surfaces[NUM_REF_SURFACES + slot];
self.encode_surface(input_surface)
}
pub fn encode_bgra_padded(
&mut self,
bgra: &[u8],
src_w: usize,
src_h: usize,
) -> Option<(Vec<u8>, bool)> {
let slot = self.next_input_slot;
self.next_input_slot = (slot + 1) % NUM_INPUT_SURFACES;
self.upload_bgra(slot, bgra, src_w, src_h)?;
let input_surface = self.surfaces[NUM_REF_SURFACES + slot];
self.encode_surface(input_surface)
}
fn upload_nv12(
&mut self,
slot: usize,
y_data: &[u8],
uv_data: &[u8],
src_y_stride: usize,
src_uv_stride: usize,
) -> Option<()> {
let img = self.derive_input_image(slot)?;
let buf_id = img.buf_id;
let y_pitch = img.y_pitch;
let uv_pitch = img.uv_pitch;
let y_offset = img.y_offset;
let uv_offset = img.uv_offset;
let mut map_ptr: *mut c_void = ptr::null_mut();
let st = unsafe { (self.va.vaMapBuffer)(self.display, buf_id, &mut map_ptr) };
if st != VA_STATUS_SUCCESS {
return None;
}
let w = self.width as usize;
let h = self.height as usize;
let dst = map_ptr as *mut u8;
unsafe {
for row in 0..h {
let sr = row.min(h - 1);
let src_start = sr * src_y_stride;
let dst_start = y_offset + row * y_pitch;
let copy_len = w.min(y_data.len() - src_start);
ptr::copy_nonoverlapping(
y_data.as_ptr().add(src_start),
dst.add(dst_start),
copy_len,
);
}
let uv_h = h / 2;
for row in 0..uv_h {
let src_start = row * src_uv_stride;
let dst_start = uv_offset + row * uv_pitch;
let copy_len = w.min(uv_data.len() - src_start);
ptr::copy_nonoverlapping(
uv_data.as_ptr().add(src_start),
dst.add(dst_start),
copy_len,
);
}
(self.va.vaUnmapBuffer)(self.display, buf_id);
}
Some(())
}
fn upload_bgra(&mut self, slot: usize, bgra: &[u8], src_w: usize, src_h: usize) -> Option<()> {
let img = self.derive_input_image(slot)?;
let buf_id = img.buf_id;
let y_pitch = img.y_pitch;
let uv_pitch = img.uv_pitch;
let y_offset = img.y_offset;
let uv_offset = img.uv_offset;
let mut map_ptr: *mut c_void = ptr::null_mut();
let st = unsafe { (self.va.vaMapBuffer)(self.display, buf_id, &mut map_ptr) };
if st != VA_STATUS_SUCCESS {
return None;
}
let enc_w = self.width as usize;
let enc_h = self.height as usize;
let dst = map_ptr as *mut u8;
unsafe {
if src_w == enc_w && src_h == enc_h && src_w >= 2 && src_h >= 2 {
bgra_to_nv12_fast(
bgra, dst, y_offset, uv_offset, y_pitch, uv_pitch, src_w, src_h,
);
} else {
bgra_to_nv12_padded(
bgra, dst, y_offset, uv_offset, y_pitch, uv_pitch, src_w, src_h, enc_w, enc_h,
);
}
(self.va.vaUnmapBuffer)(self.display, buf_id);
}
Some(())
}
pub(crate) fn encode_surface(&mut self, input_surface: VASurfaceID) -> Option<(Vec<u8>, bool)> {
let is_idr = self.force_idr || self.frame_num == 0;
if is_idr {
self.frame_num = 0;
self.idr_num += 1;
self.force_idr = false;
}
let ref_surface = self.surfaces[self.cur_ref_idx];
let recon_idx = (self.cur_ref_idx + 1) % NUM_REF_SURFACES;
let recon_surface = self.surfaces[recon_idx];
let coded_slot = self.next_coded_slot;
let coded_buf = self.coded_bufs[coded_slot];
self.next_coded_slot = (coded_slot + 1) % NUM_CODED_BUFFERS;
let sps_buf = self.create_sps_buffer()?;
let pps_buf = self.create_pps_buffer(is_idr, ref_surface, recon_surface, coded_buf)?;
let slice_buf = self.create_slice_buffer(is_idr, ref_surface)?;
let mut buffers: Vec<VABufferID> = vec![sps_buf, pps_buf, slice_buf];
if let Some(b) = create_misc_param_buffer(
self.va,
self.display,
self.context,
VAEncMiscParameterTypeQualityLevel,
VAEncMiscParameterBufferQualityLevel {
quality_level: REALTIME_QUALITY_LEVEL,
va_reserved: [0; 4],
},
) {
buffers.push(b);
}
if let Some(b) = create_misc_param_buffer(
self.va,
self.display,
self.context,
VAEncMiscParameterTypeFrameRate,
VAEncMiscParameterFrameRate {
framerate: REALTIME_FPS,
framerate_flags: 0,
va_reserved: [0; 4],
},
) {
buffers.push(b);
}
if is_idr {
let sps_nal = build_h264_sps_nal(
self.width_in_mbs,
self.height_in_mbs,
self.width,
self.height,
);
let pps_nal = build_h264_pps_nal();
if let Some((p, d)) = create_packed_header_buffers(
self.va,
self.display,
self.context,
VA_ENC_PACKED_HEADER_SEQUENCE,
&sps_nal,
) {
buffers.push(p);
buffers.push(d);
}
if let Some((p, d)) = create_packed_header_buffers(
self.va,
self.display,
self.context,
VA_ENC_PACKED_HEADER_PICTURE,
&pps_nal,
) {
buffers.push(p);
buffers.push(d);
}
}
let submit_ok = unsafe {
let st = (self.va.vaBeginPicture)(self.display, self.context, input_surface);
if st != VA_STATUS_SUCCESS {
false
} else {
let st2 = (self.va.vaRenderPicture)(
self.display,
self.context,
buffers.as_mut_ptr(),
buffers.len() as i32,
);
let st3 = (self.va.vaEndPicture)(self.display, self.context);
st2 == VA_STATUS_SUCCESS && st3 == VA_STATUS_SUCCESS
}
};
self.destroy_buffers(&buffers);
let result = self.drain_pending();
if submit_ok {
self.frame_num += 1;
self.cur_ref_idx = recon_idx;
self.pending = Some(PendingFrame {
input_surface,
coded_buf,
was_idr: is_idr,
});
}
result
}
fn drain_pending(&mut self) -> Option<(Vec<u8>, bool)> {
let pending = self.pending.take()?;
let st = unsafe { (self.va.vaSyncSurface)(self.display, pending.input_surface) };
if st != VA_STATUS_SUCCESS {
return None;
}
let mut nal_data = self.read_coded_buffer(pending.coded_buf)?;
if nal_data.is_empty() {
return None;
}
let mut pos = 0;
while let Some(sc) = find_annex_b_start(&nal_data[pos..]) {
let abs = pos + sc;
let hdr_pos = abs + if nal_data[abs + 2] == 1 { 3 } else { 4 };
if hdr_pos < nal_data.len() {
let nal_type = nal_data[hdr_pos] & 0x1f;
if nal_type == 0 {
nal_data[hdr_pos] = if pending.was_idr {
0x65 } else {
0x41 };
}
}
pos = hdr_pos + 1;
}
if pending.was_idr {
let has_sps = {
let mut found = false;
let mut p = 0;
while let Some(sc) = find_annex_b_start(&nal_data[p..]) {
let abs = p + sc;
let hp = abs + if nal_data[abs + 2] == 1 { 3 } else { 4 };
if hp < nal_data.len() && (nal_data[hp] & 0x1f) == 7 {
found = true;
break;
}
p = hp + 1;
}
found
};
if !has_sps {
let mut out = build_h264_sps_nal(
self.width_in_mbs,
self.height_in_mbs,
self.width,
self.height,
);
out.extend_from_slice(&build_h264_pps_nal());
out.extend_from_slice(&nal_data);
return Some((out, true));
}
}
Some((nal_data, pending.was_idr))
}
fn create_sps_buffer(&self) -> Option<VABufferID> {
let mut sps = [0u8; SPS_SIZE];
w8(&mut sps, 0, 0);
let max_fs = self.width_in_mbs as u32 * self.height_in_mbs as u32;
let level_idc: u8 = if max_fs <= 1620 {
31 } else if max_fs <= 8192 {
40 } else if max_fs <= 22080 {
50 } else if max_fs <= 36864 {
51 } else {
52 };
w8(&mut sps, 1, level_idc);
w32(&mut sps, 4, 0x7FFF_FFFF);
w32(&mut sps, 8, 0x7FFF_FFFF);
w32(&mut sps, 12, 1);
w32(&mut sps, 16, 0);
w32(&mut sps, 20, 1);
w16(&mut sps, 24, self.width_in_mbs);
w16(&mut sps, 26, self.height_in_mbs);
let seq_fields: u32 = 1 | (1 << 2) | (1 << 5) | (0 << 6) | (2 << 10); w32(&mut sps, 28, seq_fields);
let crop_w = self.width_in_mbs as u32 * 16;
let crop_h = self.height_in_mbs as u32 * 16;
if crop_w != self.width || crop_h != self.height {
w8(&mut sps, 1068, 1); w32(&mut sps, 1076, (crop_w - self.width) / 2);
w32(&mut sps, 1084, (crop_h - self.height) / 2);
}
let mut buf_id: VABufferID = 0;
let st = unsafe {
(self.va.vaCreateBuffer)(
self.display,
self.context,
VAEncSequenceParameterBufferType,
SPS_SIZE as u32,
1,
sps.as_mut_ptr() as *mut c_void,
&mut buf_id,
)
};
if st != VA_STATUS_SUCCESS {
return None;
}
Some(buf_id)
}
fn create_pps_buffer(
&self,
is_idr: bool,
ref_surface: VASurfaceID,
recon_surface: VASurfaceID,
coded_buf: VABufferID,
) -> Option<VABufferID> {
let mut pps = [0u8; PPS_SIZE];
w32(&mut pps, 0, recon_surface);
w32(&mut pps, 12, (self.frame_num as u32) * 2);
for i in 0..16 {
let off = 36 + i * 36;
w32(&mut pps, off, VA_INVALID_SURFACE); w32(&mut pps, off + 8, VA_PICTURE_H264_INVALID); }
if !is_idr && self.frame_num > 0 {
w32(&mut pps, 36, ref_surface);
w32(&mut pps, 36 + 8, 0); w32(&mut pps, 36 + 12, ((self.frame_num - 1) as u32) * 2); }
w32(&mut pps, 612, coded_buf);
w8(&mut pps, 616, 0);
w8(&mut pps, 617, 0);
w16(&mut pps, 620, self.frame_num);
w8(&mut pps, 622, 26);
w8(&mut pps, 623, 0);
let mut pic_fields: u32 = 0;
if is_idr {
pic_fields |= 1; }
pic_fields |= 1 << 1; pic_fields |= 1 << 3; pic_fields |= 1 << 8; pic_fields |= 1 << 9; w32(&mut pps, 628, pic_fields);
let mut buf_id: VABufferID = 0;
let st = unsafe {
(self.va.vaCreateBuffer)(
self.display,
self.context,
VAEncPictureParameterBufferType,
PPS_SIZE as u32,
1,
pps.as_mut_ptr() as *mut c_void,
&mut buf_id,
)
};
if st != VA_STATUS_SUCCESS {
return None;
}
Some(buf_id)
}
fn create_slice_buffer(&self, is_idr: bool, ref_surface: VASurfaceID) -> Option<VABufferID> {
let mut slice = [0u8; SLICE_SIZE];
let num_mbs = self.width_in_mbs as u32 * self.height_in_mbs as u32;
w32(&mut slice, 0, 0);
w32(&mut slice, 4, num_mbs);
w8(&mut slice, 12, if is_idr { 2 } else { 0 });
for i in 0..32 {
let off = 36 + i * 36;
w32(&mut slice, off, VA_INVALID_SURFACE);
w32(&mut slice, off + 8, VA_PICTURE_H264_INVALID);
}
for i in 0..32 {
let off = 1188 + i * 36;
w32(&mut slice, off, VA_INVALID_SURFACE);
w32(&mut slice, off + 8, VA_PICTURE_H264_INVALID);
}
if !is_idr && self.frame_num > 0 {
w32(&mut slice, 36, ref_surface);
w32(&mut slice, 36 + 8, 0);
w32(&mut slice, 36 + 12, ((self.frame_num - 1) as u32) * 2);
}
slice[3119] = (self.qp as i8 - 26) as u8;
let mut buf_id: VABufferID = 0;
let st = unsafe {
(self.va.vaCreateBuffer)(
self.display,
self.context,
VAEncSliceParameterBufferType,
SLICE_SIZE as u32,
1,
slice.as_mut_ptr() as *mut c_void,
&mut buf_id,
)
};
if st != VA_STATUS_SUCCESS {
return None;
}
Some(buf_id)
}
fn read_coded_buffer(&self, coded_buf: VABufferID) -> Option<Vec<u8>> {
let mut buf_ptr: *mut c_void = ptr::null_mut();
let st = unsafe { (self.va.vaMapBuffer)(self.display, coded_buf, &mut buf_ptr) };
if st != VA_STATUS_SUCCESS {
return None;
}
let mut nal_data = Vec::new();
let mut seg_ptr = buf_ptr as *const u8;
loop {
if seg_ptr.is_null() {
break;
}
let size = unsafe { u32::from_ne_bytes(*(seg_ptr as *const [u8; 4])) } as usize;
let data_ptr = unsafe {
let p = seg_ptr.add(CBS_BUF_OFF);
*(p as *const *const u8)
};
if !data_ptr.is_null() && size > 0 {
let data = unsafe { std::slice::from_raw_parts(data_ptr, size) };
nal_data.extend_from_slice(data);
}
let next = unsafe {
let p = seg_ptr.add(CBS_NEXT_OFF);
*(p as *const *const u8)
};
seg_ptr = next;
}
unsafe {
(self.va.vaUnmapBuffer)(self.display, coded_buf);
}
Some(nal_data)
}
fn destroy_buffers(&self, buffers: &[VABufferID]) {
for &buf in buffers {
unsafe {
(self.va.vaDestroyBuffer)(self.display, buf);
}
}
}
}
impl Drop for VaapiDirectEncoder {
fn drop(&mut self) {
if let Some(pending) = self.pending.take() {
unsafe {
(self.va.vaSyncSurface)(self.display, pending.input_surface);
}
}
self.vpp.take();
for slot in 0..NUM_INPUT_SURFACES {
if let Some(img) = self.cached_input_images[slot].take() {
unsafe {
(self.va.vaDestroyImage)(self.display, img.image_id);
}
}
}
unsafe {
for &buf in &self.coded_bufs {
(self.va.vaDestroyBuffer)(self.display, buf);
}
(self.va.vaDestroyContext)(self.display, self.context);
(self.va.vaDestroySurfaces)(
self.display,
self.surfaces.as_mut_ptr(),
TOTAL_SURFACES as i32,
);
(self.va.vaDestroyConfig)(self.display, self.config);
(self.va.vaTerminate)(self.display);
}
}
}
const VAProfileAV1Profile0: i32 = 32;
const VA_PADDING_LOW: usize = 4;
const VA_PADDING_HIGH: usize = 16;
const AV1_NUM_SURFACES: usize = 3;
#[repr(C)]
struct Av1PackedHeaderParamBuffer {
type_: u32,
bit_length: u32,
has_emulation_bytes: u8,
_pad: [u8; 3],
va_reserved: [u32; VA_PADDING_LOW],
}
#[repr(C)]
struct VAEncSequenceParameterBufferAV1 {
seq_profile: u8,
seq_level_idx: u8,
seq_tier: u8,
hierarchical_flag: u8,
intra_period: u32,
ip_period: u32,
bits_per_second: u32,
seq_fields: u32,
order_hint_bits_minus_1: u8,
_pad0: [u8; 3],
va_reserved: [u32; VA_PADDING_HIGH],
}
#[repr(C)]
struct VARefFrameCtrlAV1 {
value: u32,
}
#[repr(C)]
struct VAEncSegParamAV1 {
seg_flags: u8,
segment_number: u8,
_pad0: [u8; 2],
feature_data: [[i16; 8]; 8],
feature_mask: [u8; 8],
va_reserved: [u32; VA_PADDING_LOW],
}
#[repr(C)]
struct VAEncWarpedMotionParamsAV1 {
wmtype: i32,
wmmat: [i32; 8],
invalid: u8,
_pad0: [u8; 3],
va_reserved: [u32; VA_PADDING_LOW],
}
#[repr(C)]
struct VAEncPictureParameterBufferAV1 {
frame_width_minus_1: u16,
frame_height_minus_1: u16,
reconstructed_frame: VASurfaceID,
coded_buf: VABufferID,
reference_frames: [VASurfaceID; 8],
ref_frame_idx: [u8; 7],
hierarchical_level_plus_1: u8,
primary_ref_frame: u8,
order_hint: u8,
refresh_frame_flags: u8,
reserved8bits1: u8,
ref_frame_ctrl_l0: VARefFrameCtrlAV1,
ref_frame_ctrl_l1: VARefFrameCtrlAV1,
picture_flags: u32,
seg_id_block_size: u8,
num_tile_groups_minus1: u8,
temporal_id: u8,
filter_level: [u8; 2],
filter_level_u: u8,
filter_level_v: u8,
loop_filter_flags: u8,
superres_scale_denominator: u8,
interpolation_filter: u8,
ref_deltas: [i8; 8],
mode_deltas: [i8; 2],
base_qindex: u8,
y_dc_delta_q: i8,
u_dc_delta_q: i8,
u_ac_delta_q: i8,
v_dc_delta_q: i8,
v_ac_delta_q: i8,
min_base_qindex: u8,
max_base_qindex: u8,
qmatrix_flags: u16,
reserved16bits1: u16,
mode_control_flags: u32,
segments: VAEncSegParamAV1,
tile_cols: u8,
tile_rows: u8,
reserved16bits2: u16,
width_in_sbs_minus_1: [u16; 63],
height_in_sbs_minus_1: [u16; 63],
context_update_tile_id: u16,
cdef_damping_minus_3: u8,
cdef_bits: u8,
cdef_y_strengths: [u8; 8],
cdef_uv_strengths: [u8; 8],
loop_restoration_flags: u16,
wm: [VAEncWarpedMotionParamsAV1; 7],
bit_offset_qindex: u32,
bit_offset_segmentation: u32,
bit_offset_loopfilter_params: u32,
bit_offset_cdef_params: u32,
size_in_bits_cdef_params: u32,
byte_offset_frame_hdr_obu_size: u32,
size_in_bits_frame_hdr_obu: u32,
tile_group_obu_hdr_info: u8,
number_skip_frames: u8,
reserved16bits3: u16,
skip_frames_reduced_size: i32,
va_reserved: [u32; VA_PADDING_HIGH],
}
#[repr(C)]
struct VAEncTileGroupBufferAV1 {
tg_start: u8,
tg_end: u8,
_pad0: [u8; 2],
va_reserved: [u32; VA_PADDING_LOW],
}
struct PackedData {
writes: Vec<(u64, usize)>,
outstanding_bits: usize,
}
impl PackedData {
fn new() -> Self {
Self {
writes: Vec::new(),
outstanding_bits: 0,
}
}
fn write(&mut self, val: u64, bits: usize) {
self.writes.push((val, bits));
self.outstanding_bits += bits;
}
fn write_bool(&mut self, val: bool) {
self.write(u64::from(val), 1);
}
fn write_obu_header(&mut self, obu_type: u8, extension_flag: bool, has_size: bool) {
self.write_bool(false);
self.write(obu_type as u64, 4);
self.write_bool(extension_flag);
self.write_bool(has_size);
self.write_bool(false);
}
fn encode_leb128(&mut self, mut value: u32, fixed_size: Option<usize>) {
for i in 0..fixed_size.unwrap_or(5) {
let mut cur = value & 0x7f;
value >>= 7;
if value != 0 || fixed_size.is_some() && i + 1 < fixed_size.unwrap() {
cur |= 0x80;
}
self.write(cur as u64, 8);
if value == 0 && fixed_size.is_none() {
break;
}
}
}
fn flush(mut self) -> Vec<u8> {
let mut out = Vec::new();
let mut cur = 0u8;
let mut rem = 8usize;
for (val, mut bits) in self.writes.drain(..) {
while bits > 0 {
if rem >= bits {
let mask = if bits >= 64 {
u64::MAX
} else {
(1u64 << bits) - 1
};
cur |= ((val & mask) as u8) << (rem - bits);
rem -= bits;
bits = 0;
} else {
cur |= ((val >> (bits - rem)) as u8) & (((1u16 << rem) - 1) as u8);
bits -= rem;
rem = 0;
}
if rem == 0 {
out.push(cur);
cur = 0;
rem = 8;
}
}
}
if rem != 8 {
out.push(cur);
}
out
}
}
#[derive(Default)]
struct PicParamOffsets {
q_idx_bit_offset: u32,
segmentation_bit_offset: u32,
loop_filter_params_bit_offset: u32,
cdef_params_bit_offset: u32,
cdef_params_size_bits: u32,
frame_hdr_obu_size_byte_offset: u32,
frame_hdr_obu_size_bits: u32,
}
fn compute_level(coded_w: u32, coded_h: u32, framerate: u32) -> u8 {
let samples_per_second = coded_w as u64 * coded_h as u64 * framerate as u64;
const SPECS: &[(u8, u32, u32, u64)] = &[
(0, 2048, 1152, 5529600),
(1, 2816, 1152, 10454400),
(4, 4352, 2448, 24969600),
(5, 5504, 3096, 39938400),
(8, 6144, 3456, 77856768),
(9, 6144, 3456, 155713536),
(12, 8192, 4352, 273715200),
(13, 8192, 4352, 547430400),
(16, 16384, 8704, 1176502272),
];
for &(level, max_w, max_h, max_rate) in SPECS {
if coded_w <= max_w && coded_h <= max_h && samples_per_second <= max_rate {
return level;
}
}
16
}
pub struct VaapiAv1Encoder {
va: &'static gpu_libs::VaFns,
display: VADisplay,
config: VAConfigID,
context: VAContextID,
surfaces: [VASurfaceID; AV1_NUM_SURFACES],
coded_buf: VABufferID,
width: u32,
height: u32,
source_width: u32,
source_height: u32,
level_idx: u8,
frame_num: u32,
force_idr: bool,
cur_ref_idx: usize,
base_qindex: u8,
_verbose: bool,
pub(crate) _drm_fd: OwnedFd,
pub(crate) vpp: Option<VppContext>,
cached_input_image: Option<CachedDerivedImage>,
}
unsafe impl Send for VaapiAv1Encoder {}
impl VaapiAv1Encoder {
#[allow(clippy::too_many_arguments)]
pub fn try_new(
width: u32,
height: u32,
source_width: u32,
source_height: u32,
vaapi_device: &str,
base_qindex: u8,
verbose: bool,
chroma: crate::surface_encoder::ChromaSubsampling,
) -> Result<Self, String> {
if chroma.is_444() {
return Err("VA-API AV1 4:4:4 encoding is not yet supported".into());
}
let va = gpu_libs::va().map_err(|e| format!("VA-API: {e}"))?;
let va_drm = gpu_libs::va_drm().map_err(|e| format!("VA-DRM: {e}"))?;
let drm_fd = {
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(vaapi_device)
.map_err(|e| format!("failed to open {vaapi_device}: {e}"))?;
OwnedFd::from(file)
};
let display = unsafe { (va_drm.vaGetDisplayDRM)(drm_fd.as_raw_fd()) };
if display.is_null() {
return Err("vaGetDisplayDRM returned null".into());
}
let mut major = 0i32;
let mut minor = 0i32;
let st = unsafe { (va.vaInitialize)(display, &mut major, &mut minor) };
if st != VA_STATUS_SUCCESS {
return Err(format!("vaInitialize failed: {st}"));
}
let mut entrypoints = [0i32; 16];
let mut num_ep = 0i32;
unsafe {
(va.vaQueryConfigEntrypoints)(
display,
VAProfileAV1Profile0,
entrypoints.as_mut_ptr(),
&mut num_ep,
);
}
let ep_slice = &entrypoints[..num_ep as usize];
let entrypoint = if ep_slice.contains(&VAEntrypointEncSliceLP) {
VAEntrypointEncSliceLP
} else if ep_slice.contains(&VAEntrypointEncSlice) {
VAEntrypointEncSlice
} else {
unsafe {
(va.vaTerminate)(display);
}
return Err("AV1 encode not supported on this VA-API device".into());
};
let mut config: VAConfigID = 0;
let st = unsafe {
(va.vaCreateConfig)(
display,
VAProfileAV1Profile0,
entrypoint,
ptr::null_mut(),
0,
&mut config,
)
};
if st != VA_STATUS_SUCCESS {
unsafe {
(va.vaTerminate)(display);
}
return Err(format!("vaCreateConfig(AV1) failed: {st}"));
}
let mut surfaces = [0u32; AV1_NUM_SURFACES];
let st = unsafe {
(va.vaCreateSurfaces)(
display,
VA_RT_FORMAT_YUV420,
width,
height,
surfaces.as_mut_ptr(),
AV1_NUM_SURFACES as u32,
ptr::null_mut(),
0,
)
};
if st != VA_STATUS_SUCCESS {
unsafe {
(va.vaDestroyConfig)(display, config);
(va.vaTerminate)(display);
}
return Err(format!("vaCreateSurfaces(AV1) failed: {st}"));
}
let mut context: VAContextID = 0;
let st = unsafe {
(va.vaCreateContext)(
display,
config,
width as i32,
height as i32,
0x00000002,
surfaces.as_mut_ptr(),
AV1_NUM_SURFACES as i32,
&mut context,
)
};
if st != VA_STATUS_SUCCESS {
unsafe {
(va.vaDestroySurfaces)(display, surfaces.as_mut_ptr(), AV1_NUM_SURFACES as i32);
(va.vaDestroyConfig)(display, config);
(va.vaTerminate)(display);
}
return Err(format!("vaCreateContext(AV1) failed: {st}"));
}
let mut coded_buf: VABufferID = 0;
let st = unsafe {
(va.vaCreateBuffer)(
display,
context,
VAEncCodedBufferType,
width * height * 2,
1,
ptr::null_mut(),
&mut coded_buf,
)
};
if st != VA_STATUS_SUCCESS {
unsafe {
(va.vaDestroyContext)(display, context);
(va.vaDestroySurfaces)(display, surfaces.as_mut_ptr(), AV1_NUM_SURFACES as i32);
(va.vaDestroyConfig)(display, config);
(va.vaTerminate)(display);
}
return Err(format!("vaCreateBuffer(coded,AV1) failed: {st}"));
}
let level_idx = compute_level(width, height, 60);
if verbose {
eprintln!(
"[vaapi-direct] initialized AV1 Profile0 encoder for {width}x{height} (ep={entrypoint})"
);
}
let vpp = unsafe {
VppContext::try_new(
va,
display,
width,
height,
source_width,
source_height,
drm_fd.as_raw_fd(),
verbose,
)
};
Ok(Self {
va,
display,
config,
context,
surfaces,
coded_buf,
width,
height,
source_width,
source_height,
level_idx,
frame_num: 0,
force_idr: false,
cur_ref_idx: 0,
base_qindex,
_verbose: verbose,
_drm_fd: drm_fd,
vpp,
cached_input_image: None,
})
}
pub fn request_keyframe(&mut self) {
self.force_idr = true;
}
fn derive_input_image(&mut self) -> Option<&CachedDerivedImage> {
if self.cached_input_image.is_none() {
let surface = self.surfaces[2];
let mut image = [0u8; VA_IMAGE_SIZE];
let st = unsafe {
(self.va.vaDeriveImage)(self.display, surface, image.as_mut_ptr() as *mut c_void)
};
if st != VA_STATUS_SUCCESS {
return None;
}
self.cached_input_image = Some(CachedDerivedImage {
image_id: r32(&image, VAIMG_ID_OFF),
buf_id: r32(&image, VAIMG_BUF_OFF),
y_pitch: r32(&image, VAIMG_PITCHES_OFF) as usize,
uv_pitch: r32(&image, VAIMG_PITCHES_OFF + 4) as usize,
y_offset: r32(&image, VAIMG_OFFSETS_OFF) as usize,
uv_offset: r32(&image, VAIMG_OFFSETS_OFF + 4) as usize,
});
}
self.cached_input_image.as_ref()
}
pub fn gbm_buffers(&self) -> &[GbmExportedBuffer] {
match &self.vpp {
Some(vpp) => &vpp.gbm_buffers,
None => &[],
}
}
pub fn gbm_nv12_buffers(&self) -> &[GbmNv12Buffer] {
match &self.vpp {
Some(vpp) => &vpp.gbm_nv12_buffers,
None => &[],
}
}
#[allow(dead_code)]
pub fn va_display_usize(&self) -> usize {
match &self.vpp {
Some(vpp) => vpp.va_display_usize(),
None => 0,
}
}
pub fn encode_nv12(
&mut self,
y_data: &[u8],
uv_data: &[u8],
y_stride: usize,
uv_stride: usize,
) -> Option<(Vec<u8>, bool)> {
self.upload_nv12(y_data, uv_data, y_stride, uv_stride)?;
let input_surface = self.surfaces[2];
self.encode_surface(input_surface)
}
pub fn encode_bgra_padded(
&mut self,
bgra: &[u8],
src_w: usize,
src_h: usize,
) -> Option<(Vec<u8>, bool)> {
self.upload_bgra(bgra, src_w, src_h)?;
let input_surface = self.surfaces[2];
self.encode_surface(input_surface)
}
fn upload_nv12(
&mut self,
y_data: &[u8],
uv_data: &[u8],
src_y_stride: usize,
src_uv_stride: usize,
) -> Option<()> {
let img = self.derive_input_image()?;
let buf_id = img.buf_id;
let y_pitch = img.y_pitch;
let uv_pitch = img.uv_pitch;
let y_offset = img.y_offset;
let uv_offset = img.uv_offset;
let mut map_ptr: *mut c_void = ptr::null_mut();
let st = unsafe { (self.va.vaMapBuffer)(self.display, buf_id, &mut map_ptr) };
if st != VA_STATUS_SUCCESS {
return None;
}
let w = self.width as usize;
let h = self.height as usize;
let dst = map_ptr as *mut u8;
unsafe {
for row in 0..h {
let sr = row.min(h - 1);
let src_start = sr * src_y_stride;
let dst_start = y_offset + row * y_pitch;
let copy_len = w.min(y_data.len() - src_start);
ptr::copy_nonoverlapping(
y_data.as_ptr().add(src_start),
dst.add(dst_start),
copy_len,
);
}
let uv_h = h / 2;
for row in 0..uv_h {
let src_start = row * src_uv_stride;
let dst_start = uv_offset + row * uv_pitch;
let copy_len = w.min(uv_data.len() - src_start);
ptr::copy_nonoverlapping(
uv_data.as_ptr().add(src_start),
dst.add(dst_start),
copy_len,
);
}
(self.va.vaUnmapBuffer)(self.display, buf_id);
}
Some(())
}
fn upload_bgra(&mut self, bgra: &[u8], src_w: usize, src_h: usize) -> Option<()> {
let img = self.derive_input_image()?;
let buf_id = img.buf_id;
let y_pitch = img.y_pitch;
let uv_pitch = img.uv_pitch;
let y_offset = img.y_offset;
let uv_offset = img.uv_offset;
let mut map_ptr: *mut c_void = ptr::null_mut();
let st = unsafe { (self.va.vaMapBuffer)(self.display, buf_id, &mut map_ptr) };
if st != VA_STATUS_SUCCESS {
return None;
}
let enc_w = self.width as usize;
let enc_h = self.height as usize;
let dst = map_ptr as *mut u8;
unsafe {
for row in 0..enc_h {
let sr = row.min(src_h - 1);
let dst_row = dst.add(y_offset + row * y_pitch);
for col in 0..enc_w {
let sc = col.min(src_w - 1);
let i = (sr * src_w + sc) * 4;
let r = bgra[i + 2] as i32;
let g = bgra[i + 1] as i32;
let b = bgra[i] as i32;
let y = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
*dst_row.add(col) = y.clamp(0, 255) as u8;
}
}
let chroma_h = enc_h / 2;
let chroma_w = enc_w / 2;
for cy in 0..chroma_h {
let dst_row = dst.add(uv_offset + cy * uv_pitch);
for cx in 0..chroma_w {
let row = cy * 2;
let col = cx * 2;
let mut u_sum = 0i32;
let mut v_sum = 0i32;
for dy in 0..2usize {
for dx in 0..2usize {
let sr = (row + dy).min(src_h - 1);
let sc = (col + dx).min(src_w - 1);
let i = (sr * src_w + sc) * 4;
let r = bgra[i + 2] as i32;
let g = bgra[i + 1] as i32;
let b = bgra[i] as i32;
u_sum += ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
v_sum += ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;
}
}
*dst_row.add(cx * 2) = (u_sum / 4).clamp(0, 255) as u8;
*dst_row.add(cx * 2 + 1) = (v_sum / 4).clamp(0, 255) as u8;
}
}
(self.va.vaUnmapBuffer)(self.display, buf_id);
}
Some(())
}
pub(crate) fn encode_surface(&mut self, input_surface: VASurfaceID) -> Option<(Vec<u8>, bool)> {
let is_key = self.force_idr || self.frame_num == 0;
if is_key {
self.frame_num = 0;
self.force_idr = false;
}
let mut pic_offsets = PicParamOffsets::default();
let seq_param = self.create_sequence_param();
let td = self.temporal_delimiter_obu();
let seq_hdr = self.sequence_header_obu();
let mut pic_param = self.make_picture_param(is_key);
pic_offsets.frame_hdr_obu_size_byte_offset = td.len() as u32;
if is_key {
pic_offsets.frame_hdr_obu_size_byte_offset += seq_hdr.len() as u32;
}
let frame_obu = self.frame_obu(&pic_param, &mut pic_offsets);
let preceding_bits = (td.len() as u32 + if is_key { seq_hdr.len() as u32 } else { 0 }) * 8;
pic_param.bit_offset_qindex = pic_offsets.q_idx_bit_offset + preceding_bits;
pic_param.bit_offset_segmentation = pic_offsets.segmentation_bit_offset + preceding_bits;
pic_param.bit_offset_loopfilter_params =
pic_offsets.loop_filter_params_bit_offset + preceding_bits;
pic_param.bit_offset_cdef_params = pic_offsets.cdef_params_bit_offset + preceding_bits;
pic_param.size_in_bits_cdef_params = pic_offsets.cdef_params_size_bits;
pic_param.byte_offset_frame_hdr_obu_size = pic_offsets.frame_hdr_obu_size_byte_offset;
pic_param.size_in_bits_frame_hdr_obu = pic_offsets.frame_hdr_obu_size_bits + preceding_bits;
let tile_group = VAEncTileGroupBufferAV1 {
tg_start: 0,
tg_end: 0,
_pad0: [0; 2],
va_reserved: [0; VA_PADDING_LOW],
};
let mut buffer_ids = Vec::new();
buffer_ids.push(self.create_buffer(VAEncSequenceParameterBufferType, &seq_param)?);
buffer_ids.extend(self.create_av1_packed_buffers(&td)?);
if is_key {
buffer_ids.extend(self.create_av1_packed_buffers(&seq_hdr)?);
}
buffer_ids.extend(self.create_av1_packed_buffers(&frame_obu)?);
buffer_ids.push(self.create_buffer(VAEncPictureParameterBufferType, &pic_param)?);
buffer_ids.push(self.create_buffer(VAEncSliceParameterBufferType, &tile_group)?);
let st = unsafe { (self.va.vaBeginPicture)(self.display, self.context, input_surface) };
if st != VA_STATUS_SUCCESS {
self.destroy_buffers(&buffer_ids);
return None;
}
let st = unsafe {
(self.va.vaRenderPicture)(
self.display,
self.context,
buffer_ids.as_mut_ptr(),
buffer_ids.len() as i32,
)
};
if st != VA_STATUS_SUCCESS {
unsafe {
(self.va.vaEndPicture)(self.display, self.context);
}
self.destroy_buffers(&buffer_ids);
return None;
}
let st = unsafe { (self.va.vaEndPicture)(self.display, self.context) };
if st != VA_STATUS_SUCCESS {
self.destroy_buffers(&buffer_ids);
return None;
}
let st = unsafe { (self.va.vaSyncSurface)(self.display, input_surface) };
if st != VA_STATUS_SUCCESS {
self.destroy_buffers(&buffer_ids);
return None;
}
let coded = self.read_coded_buffer();
self.destroy_buffers(&buffer_ids);
self.frame_num = self.frame_num.wrapping_add(1);
coded.map(|data| {
let recon_idx = if is_key {
0
} else {
(self.cur_ref_idx + 1) % 2
};
self.cur_ref_idx = recon_idx;
(data, is_key)
})
}
fn create_buffer<T>(&self, ty: i32, obj: &T) -> Option<VABufferID> {
let mut id = 0;
let st = unsafe {
(self.va.vaCreateBuffer)(
self.display,
self.context,
ty,
std::mem::size_of::<T>() as u32,
1,
obj as *const _ as *mut c_void,
&mut id,
)
};
(st == VA_STATUS_SUCCESS).then_some(id)
}
fn create_av1_packed_buffers(&self, data: &[u8]) -> Option<Vec<VABufferID>> {
let param = Av1PackedHeaderParamBuffer {
type_: VA_ENC_PACKED_HEADER_PICTURE,
bit_length: (data.len() * 8) as u32,
has_emulation_bytes: 0,
_pad: [0; 3],
va_reserved: [0; VA_PADDING_LOW],
};
let mut p = 0;
let st = unsafe {
(self.va.vaCreateBuffer)(
self.display,
self.context,
VAEncPackedHeaderParameterBufferType,
std::mem::size_of::<Av1PackedHeaderParamBuffer>() as u32,
1,
¶m as *const _ as *mut c_void,
&mut p,
)
};
if st != VA_STATUS_SUCCESS {
return None;
}
let mut d = 0;
let st = unsafe {
(self.va.vaCreateBuffer)(
self.display,
self.context,
VAEncPackedHeaderDataBufferType,
data.len() as u32,
1,
data.as_ptr() as *mut c_void,
&mut d,
)
};
if st != VA_STATUS_SUCCESS {
unsafe {
(self.va.vaDestroyBuffer)(self.display, p);
}
return None;
}
Some(vec![p, d])
}
fn create_sequence_param(&self) -> VAEncSequenceParameterBufferAV1 {
let mut seq: VAEncSequenceParameterBufferAV1 = unsafe { std::mem::zeroed() };
seq.seq_profile = 0;
seq.seq_level_idx = self.level_idx;
seq.seq_tier = 0;
seq.hierarchical_flag = 0;
seq.intra_period = 0x7FFF_FFFF;
seq.ip_period = 1;
seq.bits_per_second = 0;
seq.order_hint_bits_minus_1 = 7;
seq.seq_fields = (1 << 8) | (1 << 12) | (1 << 17) | (1 << 18);
seq
}
fn destroy_buffers(&self, bufs: &[VABufferID]) {
for &b in bufs {
unsafe {
(self.va.vaDestroyBuffer)(self.display, b);
}
}
}
fn temporal_delimiter_obu(&self) -> Vec<u8> {
let mut p = PackedData::new();
p.write_obu_header(2, false, true);
p.encode_leb128(0, None);
p.flush()
}
fn sequence_header_obu(&self) -> Vec<u8> {
let packed = self.pack_sequence_header();
let mut obu = PackedData::new();
obu.write_obu_header(1, false, true);
obu.encode_leb128(packed.len() as u32, None);
let mut out = obu.flush();
out.extend_from_slice(&packed);
out
}
fn pack_sequence_header(&self) -> Vec<u8> {
let mut ret = PackedData::new();
ret.write(0, 3); ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); ret.write(0, 5); ret.write(0, 12); ret.write(self.level_idx as u64, 5);
if self.level_idx > 7 {
ret.write_bool(false);
}
ret.write(15, 4);
ret.write(15, 4);
ret.write((self.source_width - 1) as u64, 16);
ret.write((self.source_height - 1) as u64, 16);
ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); ret.write_bool(true); ret.write_bool(false); ret.write_bool(false); ret.write_bool(true); ret.write_bool(false); ret.write_bool(false); ret.write(7, 3); ret.write_bool(false); ret.write_bool(true); ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); ret.write(0, 2); ret.write_bool(true); ret.write_bool(false); ret.write_bool(true); ret.flush()
}
fn make_picture_param(&self, is_key: bool) -> VAEncPictureParameterBufferAV1 {
let recon_idx = if is_key {
0
} else {
(self.cur_ref_idx + 1) % 2
};
let mut pic: VAEncPictureParameterBufferAV1 = unsafe { std::mem::zeroed() };
pic.frame_width_minus_1 = (self.source_width - 1) as u16;
pic.frame_height_minus_1 = (self.source_height - 1) as u16;
pic.reconstructed_frame = self.surfaces[recon_idx];
pic.coded_buf = self.coded_buf;
pic.reference_frames.fill(u32::MAX);
pic.ref_frame_idx.fill(0);
if !is_key {
pic.reference_frames[0] = self.surfaces[self.cur_ref_idx];
pic.ref_frame_ctrl_l0 = VARefFrameCtrlAV1 { value: 1 };
}
pic.hierarchical_level_plus_1 = 0;
pic.primary_ref_frame = if is_key { 7 } else { 0 };
pic.order_hint = (self.frame_num & 0xff) as u8;
pic.refresh_frame_flags = if is_key { 0xFF } else { 1 };
pic.picture_flags = 0;
pic.picture_flags |= if is_key { 0 } else { 1 }; pic.picture_flags |= 1 << 8; pic.picture_flags |= 1 << 9; pic.seg_id_block_size = 0;
pic.num_tile_groups_minus1 = 0;
pic.temporal_id = 0;
let qp = self.base_qindex as u32;
let lf_y = ((qp * 15) / 80).min(63) as u8;
let lf_uv = ((qp * 8) / 80).min(63) as u8;
pic.filter_level = [lf_y, lf_y];
pic.filter_level_u = lf_uv;
pic.filter_level_v = lf_uv;
pic.loop_filter_flags = 0;
pic.superres_scale_denominator = 0;
pic.interpolation_filter = 0;
pic.ref_deltas.fill(0);
pic.mode_deltas.fill(0);
pic.base_qindex = self.base_qindex;
pic.min_base_qindex = self.base_qindex;
pic.max_base_qindex = self.base_qindex;
pic.mode_control_flags = 0;
pic.mode_control_flags |= 2 << 7; pic.tile_cols = 1;
pic.tile_rows = 1;
pic.width_in_sbs_minus_1[0] = (self.width / 64 - 1) as u16;
pic.height_in_sbs_minus_1[0] = (self.height / 64 - 1) as u16;
pic.context_update_tile_id = 0;
pic.cdef_damping_minus_3 = 0;
pic.cdef_bits = 0; pic.tile_group_obu_hdr_info = 0b00000010; pic
}
fn frame_obu(
&self,
pic: &VAEncPictureParameterBufferAV1,
offsets: &mut PicParamOffsets,
) -> Vec<u8> {
let hdr = self.pack_frame_header(pic, offsets);
let mut obu = PackedData::new();
obu.write_obu_header(6, false, true); obu.encode_leb128(hdr.len() as u32, Some(4));
offsets.q_idx_bit_offset += obu.outstanding_bits as u32;
offsets.segmentation_bit_offset += obu.outstanding_bits as u32;
offsets.loop_filter_params_bit_offset += obu.outstanding_bits as u32;
offsets.cdef_params_bit_offset += obu.outstanding_bits as u32;
offsets.frame_hdr_obu_size_bits += obu.outstanding_bits as u32;
let mut out = obu.flush();
out.extend_from_slice(&hdr);
out
}
fn pack_frame_header(
&self,
pic: &VAEncPictureParameterBufferAV1,
offsets: &mut PicParamOffsets,
) -> Vec<u8> {
let frame_type = pic.picture_flags & 0x3;
let is_key = frame_type == 0;
let mut ret = PackedData::new();
ret.write_bool(false); ret.write(frame_type as u64, 2); ret.write_bool(true);
if !is_key {
ret.write_bool(false); }
ret.write(((pic.picture_flags >> 2) & 1) as u64, 1); ret.write_bool(false); ret.write_bool(false); ret.write(pic.order_hint as u64, 8);
if !is_key {
if true
{
ret.write(0, 3); }
ret.write(pic.refresh_frame_flags as u64, 8);
ret.write_bool(false); for _ in 0..7 {
ret.write(0, 3); }
ret.write_bool(true); ret.write_bool(true); ret.write_bool(false); ret.write_bool(false); ret.write(0, 2); ret.write_bool(false); } else {
ret.write_bool(true); }
ret.write(((pic.picture_flags >> 7) & 1) as u64, 1);
ret.write_bool(true); ret.write_bool(false); ret.write_bool(false); offsets.q_idx_bit_offset = ret.outstanding_bits as u32;
ret.write(pic.base_qindex as u64, 8);
ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); ret.write_bool(false); offsets.segmentation_bit_offset = ret.outstanding_bits as u32;
ret.write_bool(false); ret.write_bool(false); offsets.loop_filter_params_bit_offset = ret.outstanding_bits as u32;
ret.write(pic.filter_level[0] as u64, 6);
ret.write(pic.filter_level[1] as u64, 6);
ret.write(pic.filter_level_u as u64, 6);
ret.write(pic.filter_level_v as u64, 6);
ret.write(0, 3); ret.write(0, 1); offsets.cdef_params_bit_offset = ret.outstanding_bits as u32;
ret.write(pic.cdef_damping_minus_3 as u64, 2);
ret.write(pic.cdef_bits as u64, 2);
let num_cdef = 1usize << (pic.cdef_bits as usize);
for i in 0..num_cdef {
let ys = pic.cdef_y_strengths[i] as u64;
ret.write(ys >> 2, 4); ret.write(ys & 3, 2); let us = pic.cdef_uv_strengths[i] as u64;
ret.write(us >> 2, 4); ret.write(us & 3, 2); }
offsets.cdef_params_size_bits =
ret.outstanding_bits as u32 - offsets.cdef_params_bit_offset;
ret.write_bool(true);
if !is_key {
ret.write_bool(false); }
ret.write_bool(true);
if !is_key {
for _ in 0..7 {
ret.write_bool(false);
}
}
offsets.frame_hdr_obu_size_bits = ret.outstanding_bits as u32;
ret.flush()
}
fn read_coded_buffer(&self) -> Option<Vec<u8>> {
let mut map_ptr: *mut c_void = ptr::null_mut();
let st = unsafe { (self.va.vaMapBuffer)(self.display, self.coded_buf, &mut map_ptr) };
if st != VA_STATUS_SUCCESS {
return None;
}
let mut out = Vec::new();
let mut seg_ptr = map_ptr as *const u8;
loop {
if seg_ptr.is_null() {
break;
}
let size = unsafe { r32(std::slice::from_raw_parts(seg_ptr, 4), 0) as usize };
let data_ptr = unsafe { *(seg_ptr.add(CBS_BUF_OFF) as *const *const u8) };
if !data_ptr.is_null() && size > 0 {
let data = unsafe { std::slice::from_raw_parts(data_ptr, size) };
out.extend_from_slice(data);
}
let next = unsafe { *(seg_ptr.add(CBS_NEXT_OFF) as *const *const u8) };
seg_ptr = next;
}
unsafe {
(self.va.vaUnmapBuffer)(self.display, self.coded_buf);
}
if out.is_empty() { None } else { Some(out) }
}
}
impl Drop for VaapiAv1Encoder {
fn drop(&mut self) {
self.vpp.take();
if let Some(img) = self.cached_input_image.take() {
unsafe {
(self.va.vaDestroyImage)(self.display, img.image_id);
}
}
unsafe {
(self.va.vaDestroyBuffer)(self.display, self.coded_buf);
(self.va.vaDestroyContext)(self.display, self.context);
(self.va.vaDestroySurfaces)(
self.display,
self.surfaces.as_mut_ptr(),
AV1_NUM_SURFACES as i32,
);
(self.va.vaDestroyConfig)(self.display, self.config);
(self.va.vaTerminate)(self.display);
}
}
}