use std::collections::VecDeque;
use std::rc::Rc;
use libva::VAProfile::Type as VAProfileType;
use libva::{
AV1FilmGrain, AV1FilmGrainFields, AV1LoopFilterFields, AV1LoopRestorationFields,
AV1ModeControlFields, AV1PicInfoFields, AV1QMatrixFields, AV1SegmentInfoFields,
AV1Segmentation, AV1SeqFields, AV1WarpedMotionParams, Buffer, BufferType, Config, Context,
Display, H264PicFields, H264SeqFields, HevcLongSliceFlags, HevcPicFields,
HevcRangeExtensionPicFields, HevcSliceExtFlags, HevcSliceParsingFields, IQMatrix,
IQMatrixBufferH264, IQMatrixBufferHEVC, Image, Picture, PictureEnd, PictureH264, PictureHEVC,
PictureNew, PictureParameter, PictureParameterBufferAV1, PictureParameterBufferH264,
PictureParameterBufferHEVC, PictureParameterBufferHEVCExtension,
PictureParameterBufferHEVCRext, PictureParameterBufferVP9, PictureRender, PictureSync,
SegmentParameterVP9, SliceParameter, SliceParameterBufferAV1, SliceParameterBufferH264,
SliceParameterBufferHEVC, SliceParameterBufferHEVCExtension, SliceParameterBufferHEVCRext,
SliceParameterBufferVP9, Surface, VP9PicFields, VP9SegmentFlags,
};
use libva::{
VA_INVALID_ID, VA_PICTURE_H264_BOTTOM_FIELD, VA_PICTURE_H264_INVALID,
VA_PICTURE_H264_LONG_TERM_REFERENCE, VA_PICTURE_H264_SHORT_TERM_REFERENCE,
VA_PICTURE_H264_TOP_FIELD, VA_PICTURE_HEVC_INVALID, VA_PICTURE_HEVC_LONG_TERM_REFERENCE,
VA_PICTURE_HEVC_RPS_LT_CURR, VA_PICTURE_HEVC_RPS_ST_CURR_AFTER,
VA_PICTURE_HEVC_RPS_ST_CURR_BEFORE, VA_SLICE_DATA_FLAG_ALL,
};
use vacc_core::{
codec::VideoCodec as CoreVideoCodec,
decoder::{Decoder, DecoderInfo},
format::{ChromaSubsampling, ComponentBitDepth, VideoFormat},
frame::{DecodedFrame, PixelData, PixelPlane},
picture::{Av1Sps, H264Pps, H264Sps, H265Pps, H265Sps, Vp9FrameData},
session::Extent2D,
};
use vacc_parser::{
DetectedVideoFormat, ParseResult, SliceHeader, VideoParser,
av1::Av1Parser,
bitstream::{BitstreamPacket, PacketFlags},
h264::H264Parser,
h264_dpb::{H264Dpb, H264MmcoCommand, MARKING_LONG, MARKING_SHORT},
h264_poc::PocCalculator,
h265::H265Parser,
h265_dpb::H265Dpb,
vp9::Vp9Parser,
vp9_dpb::Vp9Dpb,
};
use super::{Error, Result};
use crate::vp9_qlookup::{VP9_AC_QLOOKUP, VP9_DC_QLOOKUP};
#[derive(Clone, Copy, Default)]
struct DmaBufSurfaceDescriptor {
pixel_format: Option<u32>,
}
impl libva::SurfaceMemoryDescriptor for DmaBufSurfaceDescriptor {
fn add_attrs(
&mut self,
attrs: &mut Vec<libva::VASurfaceAttrib>,
) -> Option<Box<dyn std::any::Any>> {
if let Some(fourcc) = self.pixel_format {
attrs.push(libva::VASurfaceAttrib::new_pixel_format(fourcc));
attrs.push(libva::VASurfaceAttrib::new_memory_type(
libva::MemoryType::Va,
));
return None;
}
attrs.push(libva::VASurfaceAttrib::new_memory_type(
libva::MemoryType::DrmPrime2,
));
None
}
}
fn rt_format_to_fourcc(rt_format: u32) -> Option<u32> {
match rt_format {
libva::VA_RT_FORMAT_YUV444 => Some(0x56555958), libva::VA_RT_FORMAT_YUV422 => Some(0x59565955), _ => None,
}
}
const FOURCC_YV12: u32 = u32::from_ne_bytes(*b"YV12");
const FOURCC_I420: u32 = u32::from_ne_bytes(*b"I420");
const FOURCC_XYUV: u32 = u32::from_ne_bytes(*b"XYUV");
const FOURCC_Y410: u32 = u32::from_ne_bytes(*b"Y410");
fn rt_format_candidates(rt_format: u32) -> &'static [u32] {
match rt_format {
libva::VA_RT_FORMAT_YUV420 => &[libva::VA_FOURCC_NV12, FOURCC_YV12, FOURCC_I420],
libva::VA_RT_FORMAT_YUV420_10 => &[libva::VA_FOURCC_P016],
libva::VA_RT_FORMAT_YUV444 => &[FOURCC_XYUV, libva::VA_FOURCC_444P],
libva::VA_RT_FORMAT_YUV444_10 => &[FOURCC_Y410],
_ => &[libva::VA_FOURCC_NV12],
}
}
fn zigzag_to_raster_4x4(src: [u8; 16]) -> [u8; 16] {
const ZIGZAG_4X4: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
let mut dst = [0u8; 16];
for i in 0..16 {
dst[ZIGZAG_4X4[i]] = src[i];
}
dst
}
fn zigzag_to_raster_8x8(src: [u8; 64]) -> [u8; 64] {
const ZIGZAG_8X8: [usize; 64] = [
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27,
20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
];
let mut dst = [0u8; 64];
for i in 0..64 {
dst[ZIGZAG_8X8[i]] = src[i];
}
dst
}
fn h264_slice_type_to_vaapi(slice_type: u32) -> u8 {
match slice_type % 5 {
0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, _ => 2, }
}
fn dump_va_pic(p: &PictureParameterBufferH264) {
let pp = p.inner();
let cp = &pp.CurrPic;
println!(
"VADUMP PIC frame={} curr=(id={},fidx={},fl={},top={},bot={}) w_mbs={} h_mbs={} bdl={} bdc={} nrf={} seq={:#010x} picf={:#010x} nsg={} sgt={} qp={} qs={} c0={} c1={}",
pp.frame_num,
cp.picture_id,
cp.frame_idx,
cp.flags,
cp.TopFieldOrderCnt,
cp.BottomFieldOrderCnt,
pp.picture_width_in_mbs_minus1,
pp.picture_height_in_mbs_minus1,
pp.bit_depth_luma_minus8,
pp.bit_depth_chroma_minus8,
pp.num_ref_frames,
unsafe { pp.seq_fields.value },
unsafe { pp.pic_fields.value },
pp.num_slice_groups_minus1,
pp.slice_group_map_type,
pp.pic_init_qp_minus26,
pp.pic_init_qs_minus26,
pp.chroma_qp_index_offset,
pp.second_chroma_qp_index_offset,
);
for (i, r) in pp.ReferenceFrames.iter().enumerate() {
if r.flags != libva::VA_PICTURE_H264_INVALID {
eprintln!(
"VADUMP REF[{}]=(id={},fidx={},fl={},top={},bot={})",
i, r.picture_id, r.frame_idx, r.flags, r.TopFieldOrderCnt, r.BottomFieldOrderCnt
);
}
}
}
fn dump_va_slice(s: &SliceParameterBufferH264) {
for sp in s.inner().iter() {
let n0 = sp.num_ref_idx_l0_active_minus1 as usize + 1;
let n1 = sp.num_ref_idx_l1_active_minus1 as usize + 1;
let l0: Vec<String> = sp.RefPicList0[..n0.min(32)]
.iter()
.map(|r| {
format!(
"(id={},fidx={},fl={},top={})",
r.picture_id, r.frame_idx, r.flags, r.TopFieldOrderCnt
)
})
.collect();
let l1: Vec<String> = sp.RefPicList1[..n1.min(32)]
.iter()
.map(|r| {
format!(
"(id={},fidx={},fl={},top={})",
r.picture_id, r.frame_idx, r.flags, r.TopFieldOrderCnt
)
})
.collect();
println!(
"VADUMP SLICE size={} off={} flag={} bitoff={} firstmb={} stype={} dsp={} nrl0={} nrl1={} cabac={} qpdel={} disdeb={} alpha={} beta={} lwd={} cwd={} lw0f={} cw0f={} lw1f={} cw1f={}",
sp.slice_data_size,
sp.slice_data_offset,
sp.slice_data_flag,
sp.slice_data_bit_offset,
sp.first_mb_in_slice,
sp.slice_type,
sp.direct_spatial_mv_pred_flag,
sp.num_ref_idx_l0_active_minus1,
sp.num_ref_idx_l1_active_minus1,
sp.cabac_init_idc,
sp.slice_qp_delta,
sp.disable_deblocking_filter_idc,
sp.slice_alpha_c0_offset_div2,
sp.slice_beta_offset_div2,
sp.luma_log2_weight_denom,
sp.chroma_log2_weight_denom,
sp.luma_weight_l0_flag,
sp.chroma_weight_l0_flag,
sp.luma_weight_l1_flag,
sp.chroma_weight_l1_flag,
);
eprintln!("VADUMP L0[{}]", l0.join(","));
eprintln!("VADUMP L1[{}]", l1.join(","));
eprintln!(
"VADUMP LW0={:?} LO0={:?} CW0={:?}",
&sp.luma_weight_l0[..n0.min(32)],
&sp.luma_offset_l0[..n0.min(32)],
&sp.chroma_weight_l0[..n0.min(32)]
);
}
}
const MAX_H264_REFS: usize = 16;
#[derive(Debug, Clone, PartialEq)]
enum SurfaceState {
Free,
Pending(libva::VASurfaceID),
Ready(libva::VASurfaceID),
}
struct SurfaceEntry {
surface: Rc<Surface<DmaBufSurfaceDescriptor>>,
state: SurfaceState,
}
struct SurfacePool {
entries: Vec<SurfaceEntry>,
}
impl SurfacePool {
fn new(surfaces: Vec<Surface<DmaBufSurfaceDescriptor>>) -> Self {
let entries = surfaces
.into_iter()
.map(|s| SurfaceEntry {
surface: Rc::new(s),
state: SurfaceState::Free,
})
.collect();
Self { entries }
}
fn alloc_excluding(
&mut self,
used_pool: &std::collections::HashSet<usize>,
) -> Option<(usize, Rc<Surface<DmaBufSurfaceDescriptor>>)> {
for (i, entry) in self.entries.iter_mut().enumerate() {
if used_pool.contains(&i) {
continue;
}
let is_reusable = match entry.state {
SurfaceState::Free => true,
SurfaceState::Ready(_) => true,
SurfaceState::Pending(_) => false,
};
if is_reusable {
entry.state = SurfaceState::Pending(entry.surface.id());
return Some((i, Rc::clone(&entry.surface)));
}
}
None
}
fn mark_ready(&mut self, idx: usize) {
if let Some(entry) = self.entries.get_mut(idx)
&& let SurfaceState::Pending(id) = entry.state
{
entry.state = SurfaceState::Ready(id);
}
}
fn free(&mut self, idx: usize) {
if let Some(entry) = self.entries.get_mut(idx) {
entry.state = SurfaceState::Free;
}
}
fn sync_surface(&self, idx: usize) -> Result<()> {
if let Some(entry) = self.entries.get(idx) {
entry
.surface
.sync()
.map_err(|e| Error::VaApi(e.to_string()))?;
Ok(())
} else {
Err(Error::InvalidState("Invalid surface index".to_string()))
}
}
}
#[derive(Debug, Clone)]
struct StreamInfo {
codec: CoreVideoCodec,
profile: VAProfileType,
width: u32,
height: u32,
display_width: u32,
display_height: u32,
max_dpb: u32,
rt_format: u32,
vp9_profile: u8,
vp9_bit_depth: u8,
av1_bit_depth: u8,
sps: Option<H264Sps>,
pps: Option<H264Pps>,
h265_sps: Option<H265Sps>,
h265_pps: Option<H265Pps>,
av1_sps: Option<Av1Sps>,
}
struct H265Context {
dpb: H265Dpb,
slot_surfaces: Vec<Option<usize>>,
curr_poc: i32,
}
#[derive(Clone)]
struct H265SliceInfo {
nal_data: Vec<u8>,
slice_header: Option<SliceHeader>,
}
struct Vp9Context {
dpb: Vp9Dpb,
slot_surfaces: Vec<Option<usize>>,
}
struct Av1Context {
slot_surfaces: Vec<Option<usize>>,
}
struct H264Context {
dpb: H264Dpb,
poc_calc: PocCalculator,
slot_surfaces: Vec<Option<usize>>,
max_frame_num: u32,
curr_poc: i32,
}
struct H264SliceInfo {
nal_data: Vec<u8>,
slice_header: Option<SliceHeader>,
}
pub struct VaapiDecoder {
_display: Rc<Display>,
_config: Config,
context: Rc<Context>,
surface_pool: SurfacePool,
stream: StreamInfo,
pending_data: Vec<u8>,
parse_offset: usize,
stable_packet: Option<BitstreamPacket>,
eos: bool,
frame_count: u32,
pending_frames: VecDeque<(i64, DecodedFrame) >,
gop_count: u64,
pending_key: i64,
h265_last_emitted: Option<(usize, PixelData)>,
h265_last_idr: Option<(H265Sps, H265Pps, Vec<H265SliceInfo>)>,
h264_ctx: Option<H264Context>,
vp9_ctx: Option<Vp9Context>,
h265_ctx: Option<H265Context>,
av1_ctx: Option<Av1Context>,
parser: Option<H264Parser>,
vp9_parser: Option<Vp9Parser>,
h265_parser: Option<H265Parser>,
av1_parser: Option<Av1Parser>,
input_is_ivf: bool,
}
impl VaapiDecoder {
pub fn new(data: Vec<u8>) -> Result<Self> {
let display = Display::open()
.ok_or_else(|| Error::DecoderInit("No VA display available".to_string()))?;
let stream = parse_stream_info(&display, &data)?;
let is_ivf = data.len() >= 32 && data[0..4] == *b"DKIF";
let cfg_attrs = vec![libva::VAConfigAttrib {
type_: libva::VAConfigAttribType::VAConfigAttribRTFormat,
value: stream.rt_format,
}];
let mut config = display
.create_config(
cfg_attrs,
stream.profile,
libva::VAEntrypoint::VAEntrypointVLD,
)
.map_err(|e| Error::DecoderInit(e.to_string()))?;
if stream.codec == CoreVideoCodec::DecodeH264
&& stream.rt_format == libva::VA_RT_FORMAT_YUV422
{
let pixfmts: Vec<u32> = config
.query_surface_attributes_by_type(
libva::VASurfaceAttribType::VASurfaceAttribPixelFormat,
)
.map(|v| {
v.into_iter()
.map(|g| match g {
libva::GenericValue::Integer(x) => x as u32,
_ => 0,
})
.collect()
})
.unwrap_or_default();
const FOURCC_UYVY: u32 = 0x59565955;
const FOURCC_VYUY: u32 = 0x59555956;
if !pixfmts
.iter()
.any(|&f| f == FOURCC_UYVY || f == FOURCC_VYUY)
{
return Err(Error::DecoderInit(format!(
"HW does not support H.264 4:2:2 decode on this VAAPI driver (config accepted RTFormat=YUV422 but offers no 4:2:2 surface pixfmt; got {} pixfmts)",
pixfmts.len()
)));
}
}
let num_surfaces = (stream.max_dpb as usize).max(4) + 4;
let pixel_format = if stream.h265_sps.as_ref().is_some_and(|sps| {
sps.profile_idc >= 4 && sps.chroma_format_idc == 3 && sps.bit_depth_luma_minus8 == 0
}) {
Some(u32::from_ne_bytes(*b"XYUV"))
} else if stream.codec == CoreVideoCodec::DecodeH264
&& stream.rt_format == libva::VA_RT_FORMAT_YUV422
{
Some(0x59565955) } else {
None
};
let descriptors: Vec<DmaBufSurfaceDescriptor> = (0..num_surfaces)
.map(|_| DmaBufSurfaceDescriptor { pixel_format })
.collect();
let surfaces = display
.create_surfaces::<DmaBufSurfaceDescriptor>(
stream.rt_format,
rt_format_to_fourcc(stream.rt_format),
stream.width,
stream.height,
None,
descriptors,
)
.map_err(|e| Error::DecoderInit(e.to_string()))?;
let context = display
.create_context::<DmaBufSurfaceDescriptor>(
&config,
stream.width,
stream.height,
Some(&surfaces), true, )
.map_err(|e| Error::DecoderInit(e.to_string()))?;
let surface_pool = SurfacePool::new(surfaces);
let h264_ctx = if stream.codec == CoreVideoCodec::DecodeH264 {
let sps = stream
.sps
.as_ref()
.ok_or_else(|| Error::DecoderInit("H264 SPS not available".to_string()))?;
let num_slots = num_surfaces;
let num_ref_frames = sps.max_num_ref_frames.min(num_slots as u32).max(1);
let dpb = H264Dpb::new(num_slots, num_slots, num_ref_frames, sps.max_frame_num);
Some(H264Context {
dpb,
poc_calc: PocCalculator::new(),
slot_surfaces: vec![None; num_slots],
max_frame_num: sps.max_frame_num,
curr_poc: 0,
})
} else {
None
};
let parser = if stream.codec == CoreVideoCodec::DecodeH264 {
let mut p = H264Parser::new();
p.init(&DetectedVideoFormat::new(CoreVideoCodec::DecodeH264))
.map_err(|e| Error::Parser(e.to_string()))?;
Some(p)
} else {
None
};
let (vp9_ctx, vp9_parser) = if stream.codec == CoreVideoCodec::DecodeVp9 {
let num_slots = num_surfaces;
let mut p = Vp9Parser::new();
p.init(&DetectedVideoFormat::new(CoreVideoCodec::DecodeVp9))
.map_err(|e| Error::Parser(e.to_string()))?;
(
Some(Vp9Context {
dpb: Vp9Dpb::new(num_slots as u32),
slot_surfaces: vec![None; num_slots],
}),
Some(p),
)
} else {
(None, None)
};
let h265_ctx = if stream.codec == CoreVideoCodec::DecodeH265 {
let sps = stream
.h265_sps
.as_ref()
.ok_or_else(|| Error::DecoderInit("H265 SPS not available".to_string()))?;
let num_slots = num_surfaces;
let mut dpb = H265Dpb::new(num_slots);
dpb.set_max_num_reorder_frames(sps.max_num_reorder_pics[0] as u32);
Some(H265Context {
dpb,
slot_surfaces: vec![None; num_slots],
curr_poc: 0,
})
} else {
None
};
let h265_parser = if stream.codec == CoreVideoCodec::DecodeH265 {
let mut p = H265Parser::new();
p.init(&DetectedVideoFormat::new(CoreVideoCodec::DecodeH265))
.map_err(|e| Error::Parser(e.to_string()))?;
Some(p)
} else {
None
};
let (av1_ctx, av1_parser) = if stream.codec == CoreVideoCodec::DecodeAv1 {
let num_slots = num_surfaces;
let mut p = Av1Parser::new();
p.init(&DetectedVideoFormat::new(CoreVideoCodec::DecodeAv1))
.map_err(|e| Error::Parser(e.to_string()))?;
p.set_dpb_slots(num_slots as u32);
if let Some(sps) = stream.av1_sps.clone() {
p.set_active_sps(sps);
}
(
Some(Av1Context {
slot_surfaces: vec![None; num_slots],
}),
Some(p),
)
} else {
(None, None)
};
let h264_h265 = matches!(
stream.codec,
CoreVideoCodec::DecodeH264 | CoreVideoCodec::DecodeH265
);
let (pending_data, stable_packet, parse_offset) = if h264_h265 {
let payload = if is_ivf {
data[32..].to_vec()
} else {
data
};
(Vec::new(), Some(BitstreamPacket::new(payload)), 0)
} else {
(data, None, if is_ivf { 32 } else { 0 })
};
Ok(Self {
_display: display,
_config: config,
context,
surface_pool,
stream,
pending_data,
parse_offset,
stable_packet,
eos: false,
frame_count: 0,
pending_frames: VecDeque::new(),
gop_count: 0,
pending_key: 0,
h265_last_emitted: None,
h265_last_idr: None,
h264_ctx,
vp9_ctx,
h265_ctx,
av1_ctx,
parser,
vp9_parser,
h265_parser,
av1_parser,
input_is_ivf: is_ivf,
})
}
fn decode_h264_frame_multi_slice(
&mut self,
slices: &[H264SliceInfo],
timestamp: u64,
) -> Result<Option<DecodedFrame>> {
if slices.is_empty() {
return Ok(None);
}
let ctx = self
.h264_ctx
.as_mut()
.ok_or_else(|| Error::InvalidState("H264 context not initialized".to_string()))?;
let sps = self
.stream
.sps
.as_ref()
.ok_or_else(|| Error::InvalidState("H264 SPS not available".to_string()))?;
let pps = self
.stream
.pps
.as_ref()
.ok_or_else(|| Error::InvalidState("H264 PPS not available".to_string()))?;
let first_slice = &slices[0];
let slice_header = first_slice.slice_header.as_ref();
let (
nal_unit_type,
nal_ref_idc,
num_ref_idx_l0_active_minus1,
num_ref_idx_l1_active_minus1,
field_pic_flag,
bottom_field,
idr_pic_id,
no_output_of_prior_pics_flag,
long_term_reference,
frame_num,
slice_type,
mmco,
mod_l0,
mod_l1,
) = if let Some(SliceHeader::H264(h264_slh)) = slice_header {
(
h264_slh.nal_unit_type,
h264_slh.nal_ref_idc,
h264_slh.num_ref_idx_l0_active_minus1,
h264_slh.num_ref_idx_l1_active_minus1,
h264_slh.field_pic_flag,
h264_slh.bottom_field,
h264_slh.idr_pic_id,
h264_slh.no_output_of_prior_pics_flag,
h264_slh.long_term_reference,
h264_slh.frame_num,
h264_slh.slice_type % 5,
&h264_slh.dec_ref_pic_marking[..],
&h264_slh.ref_pic_list_modification_l0[..],
&h264_slh.ref_pic_list_modification_l1[..],
)
} else {
(
1,
3,
pps.num_ref_idx_l0_default_active_minus1,
pps.num_ref_idx_l1_default_active_minus1,
false,
false,
0,
false,
false,
0,
2,
&[] as &[vacc_parser::h264::DecRefPicMarkingEntry],
&[] as &[vacc_parser::h264::RefPicListModificationEntry],
&[] as &[vacc_parser::h264::RefPicListModificationEntry],
)
};
let is_idr = nal_unit_type == 5 || idr_pic_id > 0;
let is_ref = nal_ref_idc != 0;
let poc = ctx.curr_poc;
let (top_field_order_cnt, bottom_field_order_cnt) = if field_pic_flag {
if bottom_field { (0, poc) } else { (poc, 0) }
} else {
(poc, poc)
};
let mut flags = if mmco.iter().any(|o| o.memory_management_control_operation == 4)
|| (is_idr && long_term_reference)
{
VA_PICTURE_H264_LONG_TERM_REFERENCE
} else {
VA_PICTURE_H264_SHORT_TERM_REFERENCE
};
if field_pic_flag {
if bottom_field {
flags |= VA_PICTURE_H264_BOTTOM_FIELD;
} else {
flags |= VA_PICTURE_H264_TOP_FIELD;
}
}
let mmco_commands: Vec<H264MmcoCommand> = mmco
.iter()
.filter_map(|op| match op.memory_management_control_operation {
1 => Some(H264MmcoCommand::UnmarkShortTerm {
difference_of_pic_nums_minus1: op.value,
}),
2 => Some(H264MmcoCommand::UnmarkLongTerm {
long_term_frame_idx: op.value,
}),
3 => Some(H264MmcoCommand::AssignLongTerm {
difference_of_pic_nums_minus1: op.value,
long_term_frame_idx: op.value2,
}),
4 => Some(H264MmcoCommand::AssignLongTermToCurrent {
long_term_frame_idx: op.value,
}),
5 => Some(H264MmcoCommand::SetMaxLongTermFrameIdx {
max_long_term_frame_idx_plus1: op.value,
}),
6 => Some(H264MmcoCommand::UnmarkAll),
_ => None,
})
.collect();
ctx.dpb.picture_start(
frame_num,
poc,
is_ref,
is_idr,
long_term_reference,
no_output_of_prior_pics_flag,
!mmco_commands.is_empty(),
mmco_commands,
);
let slot_info: Vec<(Option<libva::VASurfaceID>, u32, i32, u8)> = ctx
.slot_surfaces
.iter()
.enumerate()
.map(|(i, s)| {
let sid = s.map(|pool_idx| self.surface_pool.entries[pool_idx].surface.id());
let dslot = &ctx.dpb.slots[i];
(
sid,
dslot.frame_num,
dslot.poc,
if dslot.state == 0 { 0 } else { dslot.marking },
)
})
.collect();
let make_pic = |info: &(Option<libva::VASurfaceID>, u32, i32, u8)| -> PictureH264 {
match (info.0, info.3) {
(Some(sid), MARKING_SHORT) => PictureH264::new(
sid,
info.1,
VA_PICTURE_H264_SHORT_TERM_REFERENCE,
info.2,
info.2,
),
(Some(sid), MARKING_LONG) => PictureH264::new(
sid,
info.1,
VA_PICTURE_H264_LONG_TERM_REFERENCE,
info.2,
info.2,
),
_ => PictureH264::new(VA_INVALID_ID, 0, VA_PICTURE_H264_INVALID, 0, 0),
}
};
fn invalid_pic() -> PictureH264 {
PictureH264::new(VA_INVALID_ID, 0, VA_PICTURE_H264_INVALID, 0, 0)
}
let mut slice_ref_lists: Vec<([PictureH264; 32], [PictureH264; 32])> =
Vec::with_capacity(slices.len());
for slice_info in slices.iter() {
let (st, l0m, l1m) = match &slice_info.slice_header {
Some(SliceHeader::H264(h)) => (
h.slice_type % 5,
&h.ref_pic_list_modification_l0[..],
&h.ref_pic_list_modification_l1[..],
),
_ => (slice_type, mod_l0, mod_l1),
};
let sl = ctx.dpb.build_ref_lists(
st,
num_ref_idx_l0_active_minus1,
num_ref_idx_l1_active_minus1,
l0m,
l1m,
);
let l0: [PictureH264; 32] = core::array::from_fn(|i| {
sl.l0
.get(i)
.map(|r| make_pic(&slot_info[r.slot]))
.unwrap_or_else(invalid_pic)
});
let l1: [PictureH264; 32] = core::array::from_fn(|i| {
sl.l1
.get(i)
.map(|r| make_pic(&slot_info[r.slot]))
.unwrap_or_else(invalid_pic)
});
slice_ref_lists.push((l0, l1));
}
let ref_slots = ctx.dpb.get_references();
let used_pool: std::collections::HashSet<usize> =
ctx.slot_surfaces.iter().filter_map(|s| *s).collect();
let (surface_idx, surface) = self
.surface_pool
.alloc_excluding(&used_pool)
.ok_or_else(|| Error::InvalidState("No free surfaces available".to_string()))?;
let surface_id = surface.id();
let curr_pic = PictureH264::new(
surface_id,
frame_num,
flags,
top_field_order_cnt,
bottom_field_order_cnt,
);
let refs: [PictureH264; MAX_H264_REFS] = core::array::from_fn(|i| {
ref_slots
.get(i)
.map(|&s| make_pic(&slot_info[s]))
.unwrap_or_else(invalid_pic)
});
let seq_fields = H264SeqFields::new(
sps.chroma_format_idc as u32,
sps.separate_colour_plane_flag as u32,
sps.gaps_in_frame_num_value_allowed_flag as u32,
sps.frame_mbs_only_flag as u32,
sps.mb_adaptive_frame_field_flag as u32,
sps.direct_8x8_inference_flag as u32,
(sps.level_idc >= 31) as u32,
sps.log2_max_frame_num_minus4 as u32,
sps.pic_order_cnt_type as u32,
sps.log2_max_pic_order_cnt_lsb_minus4 as u32,
sps.delta_pic_order_always_zero_flag as u32,
);
let picture_height_in_mbs_minus1 = if sps.frame_mbs_only_flag {
sps.pic_height_in_map_units_minus1
} else {
(((sps.pic_height_in_map_units_minus1 as u32 + 1) * 2) - 1)
.try_into()
.unwrap()
};
let pic_fields = H264PicFields::new(
pps.entropy_coding_mode_flag as u32,
pps.weighted_pred_flag as u32,
pps.weighted_bipred_idc as u32,
pps.transform_8x8_mode_flag as u32,
field_pic_flag as u32,
pps.constrained_intra_pred_flag as u32,
pps.bottom_field_pic_order_in_frame_present_flag as u32,
pps.deblocking_filter_control_present_flag as u32,
pps.redundant_pic_cnt_present_flag as u32,
(nal_ref_idc != 0) as u32,
);
if std::env::var("DBG_H264").is_ok() {
eprintln!(
"[DBG-PICPARAM] scaling_present={} 8x8={} chroma_fmt={} bitdepth_l={} bitdepth_c={} max_ref={} w_mbs={} h_mbs={} init_qp={} init_qs={} chroma_qp_off={} log2fn={} poc_type={} log2poc={} dpaz={} gaps={} fmo={} mbaff={} direct8x8={} minluma={} entropy={} wpred={} wbipred={} t8x8={} field={} cip={} bfpoc={} deblock={} redpic={} refpic={} frame_num={}",
sps.seq_scaling_matrix_present_flag,
pps.transform_8x8_mode_flag,
sps.chroma_format_idc,
sps.bit_depth_luma_minus8,
sps.bit_depth_chroma_minus8,
sps.max_num_ref_frames,
sps.pic_width_in_mbs_minus1,
picture_height_in_mbs_minus1,
pps.pic_init_qp_minus26,
pps.pic_init_qs_minus26,
pps.chroma_qp_index_offset,
sps.log2_max_frame_num_minus4,
sps.pic_order_cnt_type,
sps.log2_max_pic_order_cnt_lsb_minus4,
sps.delta_pic_order_always_zero_flag,
sps.gaps_in_frame_num_value_allowed_flag,
sps.frame_mbs_only_flag,
sps.mb_adaptive_frame_field_flag,
sps.direct_8x8_inference_flag,
(sps.level_idc >= 41) as u32,
pps.entropy_coding_mode_flag,
pps.weighted_pred_flag,
pps.weighted_bipred_idc,
pps.transform_8x8_mode_flag,
field_pic_flag,
pps.constrained_intra_pred_flag,
pps.bottom_field_pic_order_in_frame_present_flag,
pps.deblocking_filter_control_present_flag,
pps.redundant_pic_cnt_present_flag,
(nal_ref_idc != 0) as u32,
frame_num
);
}
let pic_param = PictureParameterBufferH264::new(
curr_pic,
refs,
sps.pic_width_in_mbs_minus1,
picture_height_in_mbs_minus1,
sps.bit_depth_luma_minus8,
sps.bit_depth_chroma_minus8,
sps.max_num_ref_frames as u8,
&seq_fields,
0,
0,
0,
pps.pic_init_qp_minus26 as i8,
pps.pic_init_qs_minus26 as i8,
pps.chroma_qp_index_offset as i8,
pps.second_chroma_qp_index_offset as i8,
&pic_fields,
frame_num as u16,
);
let (scaling_list_4x4, scaling_list_8x8) = if sps.seq_scaling_matrix_present_flag {
let scaling_list_4x4 =
std::array::from_fn(|i| zigzag_to_raster_4x4(sps.scaling_list_4x4[i]));
let scaling_list_8x8 =
std::array::from_fn(|i| zigzag_to_raster_8x8(sps.scaling_list_8x8[i]));
(scaling_list_4x4, scaling_list_8x8)
} else {
([[16u8; 16]; 6], [[16u8; 64]; 2])
};
let iq_matrix = IQMatrixBufferH264::new(scaling_list_4x4, scaling_list_8x8);
if std::env::var("VACC_VA_DUMP").is_ok() {
dump_va_pic(&pic_param);
}
let pic_param_buf = self
.context
.create_buffer(BufferType::PictureParameter(PictureParameter::H264(
pic_param,
)))
.map_err(|e| Error::VaApi(e.to_string()))?;
let iq_buf = self
.context
.create_buffer(BufferType::IQMatrix(IQMatrix::H264(iq_matrix)))
.map_err(|e| Error::VaApi(e.to_string()))?;
let mut picture = Picture::<PictureNew, Rc<Surface<DmaBufSurfaceDescriptor>>>::new(
timestamp,
Rc::clone(&self.context),
Rc::clone(&surface),
);
picture.add_buffer(pic_param_buf);
picture.add_buffer(iq_buf);
for (slice_info, (ref_pic_list_0, ref_pic_list_1)) in slices.iter().zip(slice_ref_lists) {
let slice_header_opt = slice_info.slice_header.as_ref();
let mut slice_data: Vec<u8> = slice_info.nal_data.clone();
if slice_data.last() == Some(&0) {
slice_data.pop();
}
let hbs = slice_header_opt.and_then(|sh| match sh {
SliceHeader::H264(h) => Some(h.header_bit_size),
_ => None,
});
let slice_data_bit_offset = hbs.map(|h| h + 8).unwrap_or(0);
let eff_slice_type = slice_header_opt
.and_then(|sh| match sh {
SliceHeader::H264(h) => Some(h.slice_type % 5),
_ => None,
})
.unwrap_or(slice_type);
let eff_ref_l0 = if eff_slice_type == 2 {
0
} else {
num_ref_idx_l0_active_minus1
};
let eff_ref_l1 = if eff_slice_type == 2 {
0
} else {
num_ref_idx_l1_active_minus1
};
let st_va = slice_header_opt
.and_then(|sh| match sh {
SliceHeader::H264(h) => Some(h.slice_type % 5),
_ => None,
})
.unwrap_or(2);
let has_pw_table = slice_header_opt
.and_then(|sh| match sh {
SliceHeader::H264(h) => Some(
((h.slice_type % 5 == 0 || h.slice_type % 5 == 3)
&& pps.weighted_pred_flag)
|| (h.slice_type % 5 == 1 && pps.weighted_bipred_idc == 1),
),
_ => None,
})
.unwrap_or(false);
let implicit_b = st_va == 1 && !has_pw_table;
let n_refs0 = if st_va == 2 {
0
} else {
eff_ref_l0 as usize + 1
};
let n_refs1 = if st_va != 1 {
0
} else {
eff_ref_l1 as usize + 1
};
let (
luma_log2_weight_denom,
chroma_log2_weight_denom,
luma_weight_l0_flag,
luma_weight_l0,
luma_offset_l0,
chroma_weight_l0_flag,
chroma_weight_l0,
chroma_offset_l0,
luma_weight_l1_flag,
luma_weight_l1,
luma_offset_l1,
chroma_weight_l1_flag,
chroma_weight_l1,
chroma_offset_l1,
) = if has_pw_table {
let h = match slice_header_opt {
Some(SliceHeader::H264(h)) => h,
_ => unreachable!(),
};
(
h.luma_log2_weight_denom,
h.chroma_log2_weight_denom,
h.luma_weight_l0_flag,
h.luma_weight_l0,
h.luma_offset_l0,
h.chroma_weight_l0_flag,
h.chroma_weight_l0,
h.chroma_offset_l0,
h.luma_weight_l1_flag,
h.luma_weight_l1,
h.luma_offset_l1,
h.chroma_weight_l1_flag,
h.chroma_weight_l1,
h.chroma_offset_l1,
)
} else if st_va == 2 {
(
0,
0,
0,
[0i16; 32],
[0i16; 32],
0,
[[0i16; 2]; 32],
[[0i16; 2]; 32],
0,
[0i16; 32],
[0i16; 32],
0,
[[0i16; 2]; 32],
[[0i16; 2]; 32],
)
} else {
let denom = if implicit_b { 5 } else { 0 };
let luma_def = 1i16 << denom;
let chroma_def = if sps.chroma_format_idc != 0 {
1i16 << denom
} else {
0
};
let mut luma_weight_l0 = [0i16; 32];
let mut chroma_weight_l0 = [[0i16; 2]; 32];
let mut luma_weight_l1 = [0i16; 32];
let mut chroma_weight_l1 = [[0i16; 2]; 32];
for i in 0..n_refs0 {
luma_weight_l0[i] = luma_def;
chroma_weight_l0[i][0] = chroma_def;
chroma_weight_l0[i][1] = chroma_def;
}
for i in 0..n_refs1 {
luma_weight_l1[i] = luma_def;
chroma_weight_l1[i][0] = chroma_def;
chroma_weight_l1[i][1] = chroma_def;
}
(
denom,
denom,
0,
luma_weight_l0,
[0i16; 32],
0,
chroma_weight_l0,
[[0i16; 2]; 32],
0,
luma_weight_l1,
[0i16; 32],
0,
chroma_weight_l1,
[[0i16; 2]; 32],
)
};
let slice_param = SliceParameterBufferH264::new(
slice_data.len() as u32,
0,
VA_SLICE_DATA_FLAG_ALL,
slice_data_bit_offset,
slice_header_opt
.and_then(|sh| match sh {
SliceHeader::H264(h) => Some(h.first_mb_in_slice as u16),
_ => None,
})
.unwrap_or(0),
slice_header_opt
.and_then(|sh| match sh {
SliceHeader::H264(h) => Some(h264_slice_type_to_vaapi(h.slice_type)),
_ => None,
})
.unwrap_or(0),
slice_header_opt
.and_then(|sh| match sh {
SliceHeader::H264(h) => Some(h.direct_spatial_mv_pred_flag as u8),
_ => None,
})
.unwrap_or(0),
eff_ref_l0 as u8,
eff_ref_l1 as u8,
slice_header_opt
.and_then(|sh| match sh {
SliceHeader::H264(h) => Some(h.cabac_init_idc),
_ => None,
})
.unwrap_or(0),
slice_header_opt
.and_then(|sh| match sh {
SliceHeader::H264(h) => Some(h.slice_qp_delta as i8),
_ => None,
})
.unwrap_or(0),
slice_header_opt
.and_then(|sh| match sh {
SliceHeader::H264(h) => Some(h.disable_deblocking_filter_idc as u8),
_ => None,
})
.unwrap_or(0),
slice_header_opt
.and_then(|sh| match sh {
SliceHeader::H264(h) => Some(h.slice_alpha_c0_offset_div2 as i8),
_ => None,
})
.unwrap_or(0),
slice_header_opt
.and_then(|sh| match sh {
SliceHeader::H264(h) => Some(h.slice_beta_offset_div2 as i8),
_ => None,
})
.unwrap_or(0),
ref_pic_list_0,
ref_pic_list_1,
luma_log2_weight_denom,
chroma_log2_weight_denom,
luma_weight_l0_flag,
luma_weight_l0,
luma_offset_l0,
chroma_weight_l0_flag,
chroma_weight_l0,
chroma_offset_l0,
luma_weight_l1_flag,
luma_weight_l1,
luma_offset_l1,
chroma_weight_l1_flag,
chroma_weight_l1,
chroma_offset_l1,
);
if std::env::var("VACC_VA_DUMP").is_ok() {
dump_va_slice(&slice_param);
}
let slice_param_buf = self
.context
.create_buffer(BufferType::SliceParameter(SliceParameter::H264(
slice_param,
)))
.map_err(|e| Error::VaApi(e.to_string()))?;
let slice_data_buf = self
.context
.create_buffer(BufferType::SliceData(slice_data))
.map_err(|e| Error::VaApi(e.to_string()))?;
picture.add_buffer(slice_param_buf);
picture.add_buffer(slice_data_buf);
}
let picture = picture.begin().map_err(|e| Error::VaApi(e.to_string()))?;
let picture: Picture<PictureRender, Rc<Surface<DmaBufSurfaceDescriptor>>> =
picture.render().map_err(|e| Error::VaApi(e.to_string()))?;
let picture: Picture<PictureEnd, Rc<Surface<DmaBufSurfaceDescriptor>>> =
picture.end().map_err(|e| Error::VaApi(e.to_string()))?;
let _synced: Picture<PictureSync, Rc<Surface<DmaBufSurfaceDescriptor>>> =
picture.sync().map_err(|e| Error::VaApi(e.0.to_string()))?;
self.surface_pool.mark_ready(surface_idx);
surface.sync().map_err(|e| Error::VaApi(e.to_string()))?;
let pixel_data = read_surface_pixels(
&surface,
self.stream.width,
self.stream.height,
self.stream.display_width,
self.stream.display_height,
rt_format_candidates(self.stream.rt_format),
)?;
let slot = ctx.dpb.prepare_current();
for (i, s) in ctx.dpb.slots.iter().enumerate() {
if s.state == 0 {
ctx.slot_surfaces[i] = None;
}
}
ctx.slot_surfaces[slot] = Some(surface_idx);
ctx.dpb.commit_current(slot);
if is_idr && self.frame_count > 0 {
self.gop_count += 1;
}
let display_poc = top_field_order_cnt;
let key = self.gop_count as i64 * 1_000_000 + display_poc as i64;
self.pending_key = key;
let mut frame = DecodedFrame::new(
self.frame_count,
timestamp as i64,
self.stream.display_width,
self.stream.display_height,
false,
);
frame.pixel_data = pixel_data;
self.frame_count += 1;
Ok(Some(frame))
}
}
impl Decoder for VaapiDecoder {
type Error = Error;
fn new(data: Vec<u8>) -> Result<Self> {
Self::new(data)
}
fn new_with_format(
_data: Vec<u8>,
_codec: CoreVideoCodec,
_format: &VideoFormat,
) -> Result<Self> {
Err(Error::DecoderInit(
"new_with_format not yet implemented".to_string(),
))
}
fn info(&self) -> DecoderInfo {
let (chroma_subsampling, luma_bit_depth, chroma_bit_depth, profile_idc) =
if self.stream.codec == CoreVideoCodec::DecodeVp9 {
let chroma = if self.stream.vp9_profile == 1 {
ChromaSubsampling::_444
} else {
ChromaSubsampling::_420
};
let bd = match self.stream.vp9_bit_depth {
10 => ComponentBitDepth::Bit10,
12 => ComponentBitDepth::Bit12,
_ => ComponentBitDepth::Bit8,
};
(chroma, bd, bd, Some(self.stream.vp9_profile as u32))
} else if let Some(ref sps) = self.stream.sps {
let chroma_subsampling = match sps.chroma_format_idc {
0 => ChromaSubsampling::Monochrome,
1 => ChromaSubsampling::_420,
2 => ChromaSubsampling::_422,
3 => ChromaSubsampling::_444,
_ => ChromaSubsampling::_420,
};
let luma_bit_depth = match 8 + sps.bit_depth_luma_minus8 {
8 => ComponentBitDepth::Bit8,
10 => ComponentBitDepth::Bit10,
12 => ComponentBitDepth::Bit12,
_ => ComponentBitDepth::Bit8,
};
let chroma_bit_depth = match 8 + sps.bit_depth_chroma_minus8 {
8 => ComponentBitDepth::Bit8,
10 => ComponentBitDepth::Bit10,
12 => ComponentBitDepth::Bit12,
_ => ComponentBitDepth::Bit8,
};
let profile_idc = Some(sps.profile_idc as u32);
(
chroma_subsampling,
luma_bit_depth,
chroma_bit_depth,
profile_idc,
)
} else {
(
ChromaSubsampling::_420,
ComponentBitDepth::Bit8,
ComponentBitDepth::Bit8,
None,
)
};
let dpb_slots = if let Some(ref sps) = self.stream.sps {
sps.max_num_ref_frames + 1
} else if let Some(ref sps) = self.stream.h265_sps {
sps.max_dec_pic_buffering_minus1[0] as u32 + 1
} else if self.stream.codec == CoreVideoCodec::DecodeAv1 {
16 } else if self.stream.codec == CoreVideoCodec::DecodeVp9 {
8 } else {
0
};
DecoderInfo {
backend: "vaapi".to_string(),
codec: self.stream.codec,
coded_size: Extent2D::new(self.stream.width, self.stream.height),
display_size: Extent2D::new(self.stream.display_width, self.stream.display_height),
chroma_subsampling,
luma_bit_depth,
chroma_bit_depth,
profile_idc,
dpb_slots,
}
}
fn submit(&mut self, data: &[u8]) -> Result<()> {
if let Some(pkt) = self.stable_packet.take() {
self.pending_data = pkt.payload[self.parse_offset..].to_vec();
if let Some(p) = self.parser.as_mut() {
p.invalidate_nal_cache();
}
if let Some(p) = self.h265_parser.as_mut() {
p.invalidate_nal_cache();
}
} else if self.parse_offset < self.pending_data.len() {
let unconsumed = self.pending_data[self.parse_offset..].to_vec();
self.pending_data = unconsumed;
} else {
self.pending_data.clear();
}
self.parse_offset = 0;
self.pending_data.extend_from_slice(data);
Ok(())
}
fn decode(&mut self) -> Result<Option<DecodedFrame>> {
loop {
if let Some(&(front_key, _)) = self.pending_frames.front() {
let exhausted =
self.stable_packet.is_none() && self.parse_offset >= self.pending_data.len();
if exhausted || front_key < self.pending_key {
return Ok(Some(self.pending_frames.pop_front().unwrap().1));
}
}
if self.stable_packet.is_none() && self.parse_offset >= self.pending_data.len() {
return Ok(None);
}
let offset_before = self.parse_offset;
if self.stream.codec == CoreVideoCodec::DecodeVp9 {
match self.decode_vp9_pending()? {
Some(frame) => return Ok(Some(frame)),
None => {
if self.parse_offset == offset_before {
return Ok(None);
}
continue;
}
}
}
if self.stream.codec == CoreVideoCodec::DecodeAv1 {
let offset_before = self.parse_offset;
match self.decode_av1_pending()? {
Some(frame) => return Ok(Some(frame)),
None => {
if self.parse_offset == offset_before {
return Ok(None);
}
continue;
}
}
}
let decoded = if self.stream.codec == CoreVideoCodec::DecodeH264 {
self.decode_h264_pending()?
} else if self.stream.codec == CoreVideoCodec::DecodeH265 {
self.decode_h265_pending()?
} else {
let frame = DecodedFrame::new(
self.frame_count,
self.frame_count as i64 * 33_333,
self.stream.display_width,
self.stream.display_height,
false,
);
self.frame_count += 1;
self.pending_data.clear();
return Ok(Some(frame));
};
match decoded {
Some(frame) => {
let key = self.pending_key;
let pos = self
.pending_frames
.iter()
.position(|(k, _)| *k > key)
.unwrap_or(self.pending_frames.len());
self.pending_frames.insert(pos, (key, frame));
continue; }
None => {
if self.parse_offset == offset_before {
return Ok(None);
}
continue;
}
}
}
}
fn flush(&mut self) -> Result<Vec<DecodedFrame>> {
self.eos = true;
let mut frames = Vec::new();
while let Some(frame) = self.decode()? {
frames.push(frame);
}
for i in 0..self.surface_pool.entries.len() {
if let SurfaceState::Pending(_) = self.surface_pool.entries[i].state {
self.surface_pool.sync_surface(i)?;
self.surface_pool.mark_ready(i);
}
}
for i in 0..self.surface_pool.entries.len() {
self.surface_pool.free(i);
}
if let Some(ctx) = self.h264_ctx.as_mut() {
ctx.dpb.invalidate_all();
ctx.poc_calc.reset();
ctx.curr_poc = 0;
for s in ctx.slot_surfaces.iter_mut() {
*s = None;
}
}
if let Some(ctx) = self.vp9_ctx.as_mut() {
ctx.dpb.reset();
for s in ctx.slot_surfaces.iter_mut() {
*s = None;
}
}
if let Some(ctx) = self.h265_ctx.as_mut() {
ctx.dpb.invalidate_all();
ctx.curr_poc = 0;
for s in ctx.slot_surfaces.iter_mut() {
*s = None;
}
}
if let Some(ctx) = self.av1_ctx.as_mut() {
for s in ctx.slot_surfaces.iter_mut() {
*s = None;
}
}
if let Some(parser) = self.av1_parser.as_mut() {
parser.dpb_mut().reset();
}
self.pending_data.clear();
self.stable_packet = None;
frames.extend(self.pending_frames.drain(..).map(|(_, f)| f));
Ok(frames)
}
fn reset(&mut self) -> Result<()> {
self.pending_data.clear();
self.parse_offset = 0;
self.stable_packet = None;
self.eos = false;
self.pending_frames.clear();
self.frame_count = 0;
self.gop_count = 0;
self.pending_key = 0;
for i in 0..self.surface_pool.entries.len() {
self.surface_pool.free(i);
}
if let Some(ctx) = self.h264_ctx.as_mut() {
ctx.dpb.invalidate_all();
ctx.poc_calc.reset();
ctx.curr_poc = 0;
for s in ctx.slot_surfaces.iter_mut() {
*s = None;
}
}
if let Some(ctx) = self.vp9_ctx.as_mut() {
ctx.dpb.reset();
for s in ctx.slot_surfaces.iter_mut() {
*s = None;
}
}
if let Some(ctx) = self.h265_ctx.as_mut() {
ctx.dpb.invalidate_all();
ctx.curr_poc = 0;
for s in ctx.slot_surfaces.iter_mut() {
*s = None;
}
}
if let Some(ctx) = self.av1_ctx.as_mut() {
for s in ctx.slot_surfaces.iter_mut() {
*s = None;
}
}
if let Some(ref mut parser) = self.parser {
parser.reset();
}
if let Some(ref mut parser) = self.vp9_parser {
parser.reset();
}
if let Some(ref mut parser) = self.h265_parser {
parser.reset();
}
if let Some(ref mut parser) = self.av1_parser {
parser.reset();
parser.dpb_mut().reset();
if let Some(sps) = self.stream.av1_sps.clone() {
parser.set_active_sps(sps);
}
}
Ok(())
}
}
impl VaapiDecoder {
fn parse_next_h264(&mut self) -> Result<ParseResult> {
if let Some(pkt) = &mut self.stable_packet {
pkt.flags.set(PacketFlags::TRUNCATED_TAIL, !self.eos);
let parser = self
.parser
.as_mut()
.ok_or_else(|| Error::InvalidState("H264 parser not initialized".to_string()))?;
return parser.parse(pkt).map_err(|e| Error::Parser(e.to_string()));
}
if self.parse_offset >= self.pending_data.len() {
return Ok(ParseResult::Nothing);
}
let remaining = &self.pending_data[self.parse_offset..];
let mut packet = BitstreamPacket::new(remaining.to_vec());
packet.flags.set(PacketFlags::TRUNCATED_TAIL, !self.eos);
let parser = self
.parser
.as_mut()
.ok_or_else(|| Error::InvalidState("H264 parser not initialized".to_string()))?;
parser.parse(&packet).map_err(|e| Error::Parser(e.to_string()))
}
fn parse_next_h265(&mut self) -> Result<ParseResult> {
if let Some(pkt) = &mut self.stable_packet {
pkt.flags.set(PacketFlags::TRUNCATED_TAIL, !self.eos);
let parser = self
.h265_parser
.as_mut()
.ok_or_else(|| Error::InvalidState("H265 parser not initialized".to_string()))?;
return parser.parse(pkt).map_err(|e| Error::Parser(e.to_string()));
}
if self.parse_offset >= self.pending_data.len() {
return Ok(ParseResult::Nothing);
}
let remaining = &self.pending_data[self.parse_offset..];
let mut packet = BitstreamPacket::new(remaining.to_vec());
packet.flags.set(PacketFlags::TRUNCATED_TAIL, !self.eos);
let parser = self
.h265_parser
.as_mut()
.ok_or_else(|| Error::InvalidState("H265 parser not initialized".to_string()))?;
parser.parse(&packet).map_err(|e| Error::Parser(e.to_string()))
}
fn decode_h264_pending(&mut self) -> Result<Option<DecodedFrame>> {
if self.stable_packet.is_none() && self.parse_offset >= self.pending_data.len() {
return Ok(None);
}
loop {
match self.parse_next_h264()? {
ParseResult::ParameterSet {
sps: Some(sps),
pps: Some(pps),
..
} => {
let sps = sps
.downcast_ref::<H264Sps>()
.ok_or_else(|| Error::DecoderInit("Invalid SPS type".to_string()))?;
let pps = pps
.downcast_ref::<H264Pps>()
.ok_or_else(|| Error::DecoderInit("Invalid PPS type".to_string()))?;
let ctx = self
.h264_ctx
.as_mut()
.ok_or_else(|| Error::InvalidState("H264 context not initialized".to_string()))?;
self.stream.sps = Some(sps.clone());
self.stream.pps = Some(pps.clone());
ctx.max_frame_num = sps.max_frame_num;
ctx.dpb.max_frame_num = sps.max_frame_num;
ctx.dpb.num_ref_frames = sps
.max_num_ref_frames
.min(ctx.slot_surfaces.len() as u32)
.max(1);
if std::env::var("DBG_H264").is_ok() {
eprintln!(
"[DBG] SPS log2_max_fn={} poc_type={} log2_max_poc_lsb={} max_poc_lsb={} max_ref_frames={} gaps={} frame_mbs_only={}",
sps.log2_max_frame_num_minus4,
sps.pic_order_cnt_type,
sps.log2_max_pic_order_cnt_lsb_minus4,
sps.max_pic_order_cnt_lsb,
sps.max_num_ref_frames,
sps.gaps_in_frame_num_value_allowed_flag,
sps.frame_mbs_only_flag
);
}
continue;
}
ParseResult::ParameterSet { sps: Some(sps), .. } => {
let sps = sps
.downcast_ref::<H264Sps>()
.ok_or_else(|| Error::DecoderInit("Invalid SPS type".to_string()))?;
let ctx = self
.h264_ctx
.as_mut()
.ok_or_else(|| Error::InvalidState("H264 context not initialized".to_string()))?;
self.stream.sps = Some(sps.clone());
ctx.max_frame_num = sps.max_frame_num;
ctx.dpb.max_frame_num = sps.max_frame_num;
ctx.dpb.num_ref_frames = sps
.max_num_ref_frames
.min(ctx.slot_surfaces.len() as u32)
.max(1);
continue;
}
ParseResult::ParameterSet { pps: Some(pps), .. } => {
let pps = pps
.downcast_ref::<H264Pps>()
.ok_or_else(|| Error::DecoderInit("Invalid PPS type".to_string()))?;
self.stream.pps = Some(pps.clone());
continue;
}
ParseResult::ParameterSet { .. } => {
continue;
}
ParseResult::Slice {
slices: parser_slices,
bytes_consumed,
} => {
if parser_slices.is_empty() {
return Ok(None);
}
let ctx = self
.h264_ctx
.as_mut()
.ok_or_else(|| Error::InvalidState("H264 context not initialized".to_string()))?;
let first_slice = &parser_slices[0];
let first_slice_header = first_slice.slice_header.clone();
let frame_num =
if let Some(vacc_parser::SliceHeader::H264(slh)) = &first_slice_header {
if slh.redundant_pic_cnt > 0 {
self.parse_offset += bytes_consumed;
return Ok(None);
}
slh.frame_num
} else {
0
};
let sps = self
.stream
.sps
.as_ref()
.expect("SPS should be available for H264 slice");
if let Some(vacc_parser::SliceHeader::H264(slh)) = &first_slice_header {
if slh.nal_unit_type == 5 {
ctx.poc_calc.reset();
}
let is_ref = slh.nal_ref_idc != 0;
ctx.curr_poc = ctx.poc_calc.calculate(sps, slh, is_ref);
}
if std::env::var("DBG_H264").is_ok() {
let (st, mmco) = first_slice_header
.as_ref()
.and_then(|sh| match sh {
SliceHeader::H264(h) => Some((
h.slice_type % 5,
h.dec_ref_pic_marking
.iter()
.map(|e| (e.memory_management_control_operation, e.value))
.collect::<Vec<_>>(),
)),
_ => None,
})
.unwrap_or((2, vec![]));
eprintln!(
"[DBG] fn={} poc={} st={} mmco={:?} dpb_refs={}",
frame_num,
ctx.curr_poc,
st,
mmco,
ctx.dpb.get_references().len()
);
for (si, se) in parser_slices.iter().enumerate() {
if let Some(SliceHeader::H264(h)) = &se.slice_header {
eprintln!(
"[DBG-SL] pic={si} fn={} poc_lsb={} hbs={} mmco={:?} rplm0={:?} nal0={:02x} len={}",
h.frame_num,
h.pic_order_cnt_lsb,
h.header_bit_size,
h.dec_ref_pic_marking
.iter()
.map(|e| (e.memory_management_control_operation, e.value))
.collect::<Vec<_>>(),
h.ref_pic_list_modification_l0
.iter()
.map(|e| (e.op, e.difference))
.collect::<Vec<_>>(),
se.nal_data.first().copied().unwrap_or(0),
se.nal_data.len()
);
}
}
}
let slices: Vec<H264SliceInfo> = parser_slices
.into_iter()
.map(|entry| H264SliceInfo {
nal_data: entry.nal_data,
slice_header: entry.slice_header,
})
.collect();
self.parse_offset += bytes_consumed;
if std::env::var("DBG_H264").is_ok() {
eprintln!(
"[DBG-OFFSET] fn={} bytes_consumed={} parse_offset={} remaining={}",
frame_num,
bytes_consumed,
self.parse_offset,
self.pending_data.len().saturating_sub(self.parse_offset)
);
}
let timestamp = self.frame_count as u64 * 33_333;
return self.decode_h264_frame_multi_slice(&slices, timestamp);
}
ParseResult::Nothing | ParseResult::EndOfStream => {
if self.eos {
if let Some(pkt) = self.stable_packet.take() {
self.parse_offset = pkt.payload.len();
} else {
self.parse_offset = self.pending_data.len();
}
}
return Ok(None);
}
}
}
}
fn decode_h265_pending(&mut self) -> Result<Option<DecodedFrame>> {
if self.stable_packet.is_none() && self.parse_offset >= self.pending_data.len() {
return Ok(None);
}
loop {
match self.parse_next_h265()? {
ParseResult::ParameterSet {
sps: Some(s), pps, ..
} => {
if let Some(sps) = s.downcast_ref::<H265Sps>() {
self.stream.h265_sps = Some(sps.clone());
}
if let Some(pb) = pps
&& let Some(pps) = pb.downcast_ref::<H265Pps>()
{
self.stream.h265_pps = Some(pps.clone());
}
continue;
}
ParseResult::ParameterSet { .. } => continue,
ParseResult::Slice {
slices,
bytes_consumed,
} => {
if slices.is_empty() {
return Ok(None);
}
let ctx = self
.h265_ctx
.as_mut()
.ok_or_else(|| Error::InvalidState("H265 context not initialized".to_string()))?;
let poc = slices[0]
.slice_header
.as_ref()
.and_then(|sh| match sh {
SliceHeader::H265(i) => Some(i.curr_pic_order_cnt_val),
_ => None,
})
.unwrap_or(ctx.curr_poc);
ctx.curr_poc = poc;
let h265_slices: Vec<H265SliceInfo> = slices
.into_iter()
.map(|e| H265SliceInfo {
nal_data: e.nal_data,
slice_header: e.slice_header,
})
.collect();
self.parse_offset += bytes_consumed;
let timestamp = self.frame_count as u64 * 33_333;
return self.decode_h265_frame(&h265_slices, timestamp);
}
ParseResult::Nothing | ParseResult::EndOfStream => {
if self.eos {
if let Some(pkt) = self.stable_packet.take() {
self.parse_offset = pkt.payload.len();
} else {
self.parse_offset = self.pending_data.len();
}
}
return Ok(None);
}
}
}
}
fn decode_h265_frame(
&mut self,
slices: &[H265SliceInfo],
timestamp: u64,
) -> Result<Option<DecodedFrame>> {
if slices.is_empty() {
return Ok(None);
}
let sps = self
.stream
.h265_sps
.as_ref()
.ok_or_else(|| Error::InvalidState("H265 SPS not available".to_string()))?;
let pps = self
.stream
.h265_pps
.as_ref()
.ok_or_else(|| Error::InvalidState("H265 PPS not available".to_string()))?;
let first_info = match &slices[0].slice_header {
Some(SliceHeader::H265(i)) => i,
_ => return Ok(None),
};
let is_idr = first_info.is_idr;
let is_ref = first_info.is_reference;
if is_idr {
self.h265_last_idr = Some((sps.clone(), pps.clone(), slices.to_vec()));
}
let ctx = self
.h265_ctx
.as_mut()
.ok_or_else(|| Error::InvalidState("H265 context not initialized".to_string()))?;
let poc = ctx.curr_poc;
let slot = ctx.dpb.picture_start(sps, first_info, is_ref);
if !h265_tile_coverage_complete(sps, pps, slices) {
if let Some((last_idx, last_pixels)) = &self.h265_last_emitted {
eprintln!(
"CONCEAL poc={}: slice segments do not cover the full tile grid; repeating previous frame",
poc
);
let used_pool: std::collections::HashSet<usize> =
ctx.slot_surfaces.iter().filter_map(|s| *s).collect();
let mut slot_surf = *last_idx;
if let Some((surf_idx, surf)) = self.surface_pool.alloc_excluding(&used_pool) {
let mut concealed = false;
if let Some(idr) = &self.h265_last_idr {
concealed = ghost_decode_h265_intra(&self.context, &surf, idr).is_ok()
&& put_cpu_pixels_to_surface(
&surf,
rt_format_candidates(self.stream.rt_format),
self.stream.width,
self.stream.height,
last_pixels,
)
.is_ok();
}
if !concealed {
let last_surf_id = self.surface_pool.entries[*last_idx].surface.id();
concealed = copy_surface_pixels(
self._display.handle(),
rt_format_candidates(self.stream.rt_format),
self.stream.width,
self.stream.height,
last_surf_id,
surf.id(),
)
.is_ok();
}
if concealed {
slot_surf = surf_idx;
} else {
self.surface_pool.mark_ready(surf_idx);
}
}
for (i, s) in ctx.dpb.slots().iter().enumerate() {
if !s.valid {
ctx.slot_surfaces[i] = None;
}
}
ctx.slot_surfaces[slot] = Some(slot_surf);
ctx.dpb.commit_current(slot);
if slot_surf != *last_idx {
self.surface_pool.mark_ready(slot_surf);
}
if !first_info.pic_output_flag {
return Ok(None);
}
if is_idr && self.frame_count > 0 {
self.gop_count += 1;
}
let key = self.gop_count as i64 * 1_000_000 + poc as i64;
self.pending_key = key;
let mut frame = DecodedFrame::new(
self.frame_count,
timestamp as i64,
self.stream.display_width,
self.stream.display_height,
false,
);
frame.pixel_data = Some(last_pixels.clone());
self.frame_count += 1;
return Ok(Some(frame));
}
}
let in_use = ctx.dpb.in_use_refs();
let mut reference_frames: [PictureHEVC; 15] =
core::array::from_fn(|_| PictureHEVC::new(VA_INVALID_ID, 0, VA_PICTURE_HEVC_INVALID));
let mut slot_to_refidx: std::collections::HashMap<usize, u8> =
std::collections::HashMap::new();
for (ri, &(s, p)) in in_use.iter().enumerate().take(15) {
let sid = ctx
.slot_surfaces
.get(s)
.and_then(|o| *o)
.map(|pi| self.surface_pool.entries[pi].surface.id());
let is_lt = ctx
.dpb
.slots()
.get(s)
.map(|sl| sl.is_long_term)
.unwrap_or(false);
let flags = if is_lt {
VA_PICTURE_HEVC_RPS_LT_CURR | VA_PICTURE_HEVC_LONG_TERM_REFERENCE
} else if p < poc {
VA_PICTURE_HEVC_RPS_ST_CURR_BEFORE
} else if p > poc {
VA_PICTURE_HEVC_RPS_ST_CURR_AFTER
} else {
0
};
reference_frames[ri] = PictureHEVC::new(sid.unwrap_or(VA_INVALID_ID), p, flags);
slot_to_refidx.insert(s, ri as u8);
}
let lists = ctx.dpb.build_ref_lists();
let make_list = |l: &Vec<vacc_parser::h265_dpb::H265RefPic>| -> [u8; 15] {
core::array::from_fn(|i| {
l.get(i)
.and_then(|r| {
(r.slot >= 0).then(|| slot_to_refidx.get(&(r.slot as usize)).copied())
})
.flatten()
.unwrap_or(0xFF)
})
};
let slice_ref_lists: Vec<([u8; 15], [u8; 15])> = (0..slices.len())
.map(|_| (make_list(&lists.l0), make_list(&lists.l1)))
.collect();
let used_pool: std::collections::HashSet<usize> =
ctx.slot_surfaces.iter().filter_map(|s| *s).collect();
let (surface_idx, surface) = self
.surface_pool
.alloc_excluding(&used_pool)
.ok_or_else(|| Error::InvalidState("No free surfaces available".to_string()))?;
let surface_id = surface.id();
let curr_pic = PictureHEVC::new(surface_id, poc, 0);
let va_buffers = build_h265_picture_buffers(
&self.context,
sps,
pps,
slices,
&reference_frames,
&slice_ref_lists,
curr_pic,
)?;
let mut picture = Picture::<PictureNew, Rc<Surface<DmaBufSurfaceDescriptor>>>::new(
timestamp,
Rc::clone(&self.context),
Rc::clone(&surface),
);
for (_, b) in va_buffers {
picture.add_buffer(b);
}
let picture = picture.begin().map_err(|e| Error::VaApi(e.to_string()))?;
let picture: Picture<PictureRender, Rc<Surface<DmaBufSurfaceDescriptor>>> =
picture.render().map_err(|e| Error::VaApi(e.to_string()))?;
let picture: Picture<PictureEnd, Rc<Surface<DmaBufSurfaceDescriptor>>> =
picture.end().map_err(|e| Error::VaApi(e.to_string()))?;
let _synced: Picture<PictureSync, Rc<Surface<DmaBufSurfaceDescriptor>>> =
picture.sync().map_err(|e| Error::VaApi(e.0.to_string()))?;
self.surface_pool.mark_ready(surface_idx);
surface.sync().map_err(|e| Error::VaApi(e.to_string()))?;
let pixel_data = read_surface_pixels(
&surface,
self.stream.width,
self.stream.height,
self.stream.display_width,
self.stream.display_height,
rt_format_candidates(self.stream.rt_format),
)?;
for (i, s) in ctx.dpb.slots().iter().enumerate() {
if !s.valid {
ctx.slot_surfaces[i] = None;
}
}
ctx.slot_surfaces[slot] = Some(surface_idx);
ctx.dpb.commit_current(slot);
if !first_info.pic_output_flag {
return Ok(None);
}
if is_idr && self.frame_count > 0 {
self.gop_count += 1;
}
let key = self.gop_count as i64 * 1_000_000 + poc as i64;
self.pending_key = key;
if let Some(pix) = &pixel_data {
self.h265_last_emitted = Some((surface_idx, pix.clone()));
}
let mut frame = DecodedFrame::new(
self.frame_count,
timestamp as i64,
self.stream.display_width,
self.stream.display_height,
false,
);
frame.pixel_data = pixel_data;
self.frame_count += 1;
Ok(Some(frame))
}
}
fn copy_surface_pixels(
dpy: libva::VADisplay,
fourccs: &[u32],
width: u32,
height: u32,
src: libva::VASurfaceID,
dst: libva::VASurfaceID,
) -> Result<()> {
for &fourcc in fourccs {
let mut fmt: libva::VAImageFormat = unsafe { std::mem::zeroed() };
fmt.fourcc = fourcc;
let mut img: libva::VAImage = unsafe { std::mem::zeroed() };
if unsafe { libva::vaCreateImage(dpy, &mut fmt, width as i32, height as i32, &mut img) }
!= 0
{
continue;
}
let ok = (unsafe { libva::vaGetImage(dpy, src, 0, 0, width, height, img.image_id) } == 0)
&& (unsafe {
libva::vaPutImage(
dpy,
dst,
img.image_id,
0,
0,
width,
height,
0,
0,
width,
height,
)
} == 0)
&& (unsafe { libva::vaSyncSurface(dpy, dst) } == 0);
let _ = unsafe { libva::vaDestroyImage(dpy, img.image_id) };
if ok {
return Ok(());
}
}
Err(Error::VaApi(
"surface copy failed: no working image format".to_string(),
))
}
#[allow(clippy::too_many_arguments)]
fn build_h265_picture_buffers(
va_ctx: &Rc<Context>,
sps: &H265Sps,
pps: &H265Pps,
slices: &[H265SliceInfo],
reference_frames: &[PictureHEVC; 15],
slice_ref_lists: &[([u8; 15], [u8; 15])],
curr_pic: PictureHEVC,
) -> Result<Vec<(String, Buffer)>> {
let first_info = match &slices[0].slice_header {
Some(SliceHeader::H265(i)) => i,
_ => return Err(Error::InvalidState("H265 slice header missing".to_string())),
};
let pic_fields = HevcPicFields::new(
sps.chroma_format_idc as u32,
sps.separate_colour_plane_flag as u32,
sps.pcm_enabled_flag as u32,
sps.scaling_list_enabled_flag as u32,
pps.transform_skip_enabled_flag as u32,
sps.amp_enabled_flag as u32,
sps.strong_intra_smoothing_enabled_flag as u32,
pps.sign_data_hiding_enabled_flag as u32,
pps.constrained_intra_pred_flag as u32,
pps.cu_qp_delta_enabled_flag as u32,
pps.weighted_pred_flag as u32,
pps.weighted_bipred_flag as u32,
pps.transquant_bypass_enabled_flag as u32,
pps.tiles_enabled_flag as u32,
pps.entropy_coding_sync_enabled_flag as u32,
pps.pps_loop_filter_across_slices_enabled_flag as u32,
pps.loop_filter_across_tiles_enabled_flag as u32,
sps.pcm_loop_filter_disabled_flag as u32,
0, 0, );
let slice_parsing_fields = HevcSliceParsingFields::new(
pps.lists_modification_present_flag as u32,
sps.long_term_ref_pics_present_flag as u32,
sps.sps_temporal_mvp_enabled_flag as u32,
pps.cabac_init_present_flag as u32,
pps.output_flag_present_flag as u32,
pps.dependent_slice_segments_enabled_flag as u32,
pps.pps_slice_chroma_qp_offsets_present_flag as u32,
sps.sample_adaptive_offset_enabled_flag as u32,
pps.deblocking_filter_override_enabled_flag as u32,
pps.pps_disable_deblocking_filter_flag as u32,
pps.slice_segment_header_extension_present_flag as u32,
first_info.is_rap as u32, first_info.is_idr as u32, (first_info.slice_type == 0) as u32, );
let pcm_luma_minus1: u8 = if sps.pcm_enabled_flag {
sps.pcm_sample_bit_depth_luma_minus1
} else {
255
};
let pcm_chroma_minus1: u8 = if sps.pcm_enabled_flag {
sps.pcm_sample_bit_depth_chroma_minus1
} else {
255
};
let log2_min_pcm_minus3: u8 = if sps.pcm_enabled_flag {
sps.log2_min_pcm_luma_coding_block_size_minus3
} else {
253
};
let log2_diff_pcm: u8 = if sps.pcm_enabled_flag {
sps.log2_diff_max_min_pcm_luma_coding_block_size
} else {
0
};
let pic_param = PictureParameterBufferHEVC::new(
curr_pic,
*reference_frames,
sps.pic_width_in_luma_samples,
sps.pic_height_in_luma_samples,
&pic_fields,
sps.max_dec_pic_buffering_minus1[0],
sps.bit_depth_luma_minus8,
sps.bit_depth_chroma_minus8,
pcm_luma_minus1, pcm_chroma_minus1, sps.log2_min_luma_coding_block_size_minus3,
sps.log2_diff_max_min_luma_coding_block_size,
sps.log2_min_luma_transform_block_size_minus2,
sps.log2_diff_max_min_luma_transform_block_size,
log2_min_pcm_minus3, log2_diff_pcm, sps.max_transform_hierarchy_depth_intra,
sps.max_transform_hierarchy_depth_inter,
pps.pps_init_qp_minus26 as i8,
pps.diff_cu_qp_delta_depth,
pps.pps_cb_qp_offset,
pps.pps_cr_qp_offset,
0, pps.num_tile_columns_minus1,
pps.num_tile_rows_minus1,
pps.column_width_minus1,
pps.row_height_minus1,
&slice_parsing_fields,
sps.log2_max_pic_order_cnt_lsb_minus4,
sps.num_short_term_ref_pic_sets,
sps.num_long_term_ref_pics_sps,
pps.num_ref_idx_l0_default_active_minus1,
pps.num_ref_idx_l1_default_active_minus1,
pps.pps_beta_offset_div2,
pps.pps_tc_offset_div2,
pps.num_extra_slice_header_bits,
if first_info.short_term_ref_pic_set_sps_flag {
0
} else {
first_info.num_bits_for_strps_in_slice as u32
},
);
let pic_param_buf = if sps.profile_idc >= 4 {
let rext_fields = HevcRangeExtensionPicFields::new(
sps.transform_skip_rotation_enabled_flag as u32,
sps.transform_skip_context_enabled_flag as u32,
sps.implicit_rdpcm_enabled_flag as u32,
sps.explicit_rdpcm_enabled_flag as u32,
sps.extended_precision_processing_flag as u32,
sps.intra_smoothing_disabled_flag as u32,
sps.high_precision_offsets_enabled_flag as u32,
sps.persistent_rice_adaptation_enabled_flag as u32,
sps.cabac_bypass_alignment_enabled_flag as u32,
pps.cross_component_prediction_enabled_flag as u32,
pps.chroma_qp_offset_list_enabled_flag as u32,
);
let rext = PictureParameterBufferHEVCRext::new(
&rext_fields,
pps.diff_cu_chroma_qp_offset_depth,
pps.chroma_qp_offset_list_len_minus1,
pps.log2_sao_offset_scale_luma,
pps.log2_sao_offset_scale_chroma,
pps.log2_max_transform_skip_block_size_minus2,
pps.cb_qp_offset_list,
pps.cr_qp_offset_list,
);
let ext = PictureParameterBufferHEVCExtension::new(&pic_param, &rext);
va_ctx
.create_buffer(BufferType::PictureParameter(
PictureParameter::HEVCExtension(ext),
))
.map_err(|e| Error::VaApi(e.to_string()))?
} else {
va_ctx
.create_buffer(BufferType::PictureParameter(PictureParameter::HEVC(
pic_param,
)))
.map_err(|e| Error::VaApi(e.to_string()))?
};
let iq_buf = if pps.pps_scaling_list_data_present_flag || sps.scaling_list_enabled_flag {
let sl = &sps.scaling_lists;
let buf = IQMatrixBufferHEVC::new(
sl.scaling_list_4x4,
sl.scaling_list_8x8,
sl.scaling_list_16x16,
sl.scaling_list_32x32,
core::array::from_fn(|i| sl.scaling_list_dc_coef_16x16[0][i] as u8),
core::array::from_fn(|i| sl.scaling_list_dc_coef_32x32[0][i] as u8),
);
Some(
va_ctx
.create_buffer(BufferType::IQMatrix(IQMatrix::HEVC(buf)))
.map_err(|e| Error::VaApi(e.to_string()))?,
)
} else {
None
};
let is_rext = sps.profile_idc >= 4;
let mut va_buffers: Vec<(String, Buffer)> = Vec::new();
va_buffers.push(("pic_param".to_string(), pic_param_buf));
if let Some(b) = iq_buf {
va_buffers.push(("iq_matrix".to_string(), b));
}
for (si, (slice_info, (ref_l0, ref_l1))) in slices.iter().zip(slice_ref_lists).enumerate() {
let is_last = si == slices.len() - 1;
let sh = match &slice_info.slice_header {
Some(SliceHeader::H265(i)) => i,
_ => first_info,
};
let slice_data_byte_offset = ((16u32 + sh.header_bit_size as u32) >> 3) + 1;
let nal_len = slice_info.nal_data.len();
let data_len = if nal_len > 0 && slice_info.nal_data[nal_len - 1] == 0 {
nal_len - 1
} else {
nal_len
};
let long_slice_flags = HevcLongSliceFlags::new(
is_last as u32, sh.dependent_slice_segment_flag as u32, match sh.slice_type {
0 => 2,
1 => 1,
2 => 0,
n => n,
} as u32, sh.colour_plane_id as u32, sh.slice_sao_luma_flag as u32,
sh.slice_sao_chroma_flag as u32,
sh.mvd_l1_zero_flag as u32, sh.cabac_init_flag as u32,
sh.slice_temporal_mvp_enabled_flag as u32,
sh.slice_deblocking_filter_disabled_flag as u32,
sh.collocated_from_l0_flag as u32, sh.slice_loop_filter_across_slices_enabled_flag as u32,
);
let collocated_ref_idx = if sh.slice_temporal_mvp_enabled_flag {
sh.collocated_ref_idx
} else {
0xFF
};
let (eff_l0, eff_l1) = if sh.slice_type == 0 {
(0u8, 0u8)
} else {
(
sh.num_ref_idx_l0_active_minus1,
sh.num_ref_idx_l1_active_minus1,
)
};
let chroma_denom = (sh.luma_log2_weight_denom as i32
+ sh.delta_chroma_log2_weight_denom as i32)
.clamp(0, 7);
let chroma_base = 1i32 << chroma_denom;
let norm_chroma_off = |offs: &[[i16; 2]; 15], dw: &[[i8; 2]; 15]| -> [[i8; 2]; 15] {
core::array::from_fn(|i| {
core::array::from_fn(|j| {
let w = chroma_base + dw[i][j] as i32;
(offs[i][j] as i32 - ((128 * w) >> chroma_denom) + 128).clamp(-128, 127)
as i8
})
})
};
let (base_luma_off_l0, base_chroma_off_l0, base_luma_off_l1, base_chroma_off_l1) =
if is_rext {
([0i8; 15], [[0i8; 2]; 15], [0i8; 15], [[0i8; 2]; 15])
} else {
(
sh.luma_offset_l0.map(|v| v as i8),
norm_chroma_off(&sh.chroma_offset_l0, &sh.delta_chroma_weight_l0),
sh.luma_offset_l1.map(|v| v as i8),
norm_chroma_off(&sh.chroma_offset_l1, &sh.delta_chroma_weight_l1),
)
};
let slice_param = SliceParameterBufferHEVC::new(
data_len as u32, 0, VA_SLICE_DATA_FLAG_ALL, slice_data_byte_offset, sh.slice_segment_address, [*ref_l0, *ref_l1], &long_slice_flags,
collocated_ref_idx,
eff_l0,
eff_l1,
sh.slice_qp_delta as i8,
sh.slice_cb_qp_offset as i8,
sh.slice_cr_qp_offset as i8,
sh.slice_beta_offset_div2 as i8,
sh.slice_tc_offset_div2 as i8,
sh.luma_log2_weight_denom,
sh.delta_chroma_log2_weight_denom,
sh.delta_luma_weight_l0,
base_luma_off_l0,
sh.delta_chroma_weight_l0,
base_chroma_off_l0,
sh.delta_luma_weight_l1,
base_luma_off_l1,
sh.delta_chroma_weight_l1,
base_chroma_off_l1,
sh.five_minus_max_num_merge_cand,
0, 0, 0, );
let slice_param_buf = if is_rext {
let rext_flags = HevcSliceExtFlags::new(
sh.cu_chroma_qp_offset_enabled_flag as u32,
sh.use_integer_mv_flag as u32,
);
let rext = SliceParameterBufferHEVCRext::new(
sh.luma_offset_l0,
sh.chroma_offset_l0,
sh.luma_offset_l1,
sh.chroma_offset_l1,
&rext_flags,
sh.slice_act_y_qp_offset as i8,
sh.slice_act_cb_qp_offset as i8,
sh.slice_act_cr_qp_offset as i8,
);
let ext = SliceParameterBufferHEVCExtension::new(&slice_param, &rext);
va_ctx
.create_buffer(BufferType::SliceParameter(SliceParameter::HEVCExtension(
ext,
)))
.map_err(|e| Error::VaApi(e.to_string()))?
} else {
va_ctx
.create_buffer(BufferType::SliceParameter(SliceParameter::HEVC(
slice_param,
)))
.map_err(|e| Error::VaApi(e.to_string()))?
};
let slice_data_buf = va_ctx
.create_buffer(BufferType::SliceData(
slice_info.nal_data[..data_len].to_vec(),
))
.map_err(|e| Error::VaApi(e.to_string()))?;
va_buffers.push((format!("slice{}:param", si), slice_param_buf));
va_buffers.push((format!("slice{}:data", si), slice_data_buf));
}
Ok(va_buffers)
}
fn ghost_decode_h265_intra(
va_ctx: &Rc<Context>,
surface: &Rc<Surface<DmaBufSurfaceDescriptor>>,
idr: &(H265Sps, H265Pps, Vec<H265SliceInfo>),
) -> Result<()> {
let (sps, pps, slices) = idr;
let first_info = match &slices[0].slice_header {
Some(SliceHeader::H265(i)) => i,
_ => return Err(Error::InvalidState("IDR slice header missing".to_string())),
};
let curr_pic = PictureHEVC::new(surface.id(), first_info.curr_pic_order_cnt_val, 0);
let reference_frames = [PictureHEVC::new(VA_INVALID_ID, 0, VA_PICTURE_HEVC_INVALID); 15];
let slice_ref_lists: Vec<([u8; 15], [u8; 15])> = vec![
(core::array::from_fn(|_| 0xFF), core::array::from_fn(|_| 0xFF));
slices.len()
];
let va_buffers = build_h265_picture_buffers(
va_ctx, sps, pps, slices, &reference_frames, &slice_ref_lists, curr_pic,
)?;
let mut picture = Picture::<PictureNew, Rc<Surface<DmaBufSurfaceDescriptor>>>::new(
0,
Rc::clone(va_ctx),
Rc::clone(surface),
);
for (_, b) in va_buffers {
picture.add_buffer(b);
}
let picture = picture.begin().map_err(|e| Error::VaApi(e.to_string()))?;
let picture: Picture<PictureRender, Rc<Surface<DmaBufSurfaceDescriptor>>> =
picture.render().map_err(|e| Error::VaApi(e.to_string()))?;
let picture: Picture<PictureEnd, Rc<Surface<DmaBufSurfaceDescriptor>>> =
picture.end().map_err(|e| Error::VaApi(e.to_string()))?;
let _synced: Picture<PictureSync, Rc<Surface<DmaBufSurfaceDescriptor>>> =
picture.sync().map_err(|e| Error::VaApi(e.0.to_string()))?;
surface.sync().map_err(|e| Error::VaApi(e.to_string()))?;
Ok(())
}
fn put_cpu_pixels_to_surface(
surface: &Surface<DmaBufSurfaceDescriptor>,
fourccs: &[u32],
width: u32,
height: u32,
pix: &PixelData,
) -> Result<()> {
let (fourcc, uv_first) = match pix.format.as_str() {
"NV12" => (libva::VA_FOURCC_NV12, false),
"I420" => (FOURCC_I420, false),
"YV12" => (FOURCC_YV12, true),
other => {
return Err(Error::InvalidState(format!(
"unsupported pixel format for surface write: {other:?}"
)))
}
};
if !fourccs.contains(&fourcc) {
return Err(Error::InvalidState(
"pixel format not in surface format candidates".to_string(),
));
}
let format = libva::VAImageFormat {
fourcc,
..Default::default()
};
let mut image = Image::create_from(surface, format, (width, height), (width, height))
.map_err(|e| Error::VaApi(e.to_string()))?;
let (offsets, pitches) = {
let im = image.image();
(im.offsets, im.pitches)
};
let data = image.as_mut();
let is_nv12 = fourcc == libva::VA_FOURCC_NV12;
let yw = pix.y.width.min(width as usize);
for row in 0..pix.y.height.min(height as usize) {
let src = unsafe { std::slice::from_raw_parts(pix.y.data.add(row * pix.y.pitch), yw) };
let start = offsets[0] as usize + row * pitches[0] as usize;
data[start..start + yw].copy_from_slice(src);
}
let uw = pix.u.width.min((width / 2) as usize);
let u_bytes = if is_nv12 { uw * 2 } else { uw };
let uh = pix.u.height.min((height / 2) as usize);
let u_idx = if uv_first { 2 } else { 1 }; for row in 0..uh {
let src = unsafe { std::slice::from_raw_parts(pix.u.data.add(row * pix.u.pitch), u_bytes) };
let start = offsets[u_idx] as usize + row * pitches[u_idx] as usize;
data[start..start + u_bytes].copy_from_slice(src);
}
if !is_nv12 {
let v = pix.v.as_ref().ok_or_else(|| Error::InvalidState("missing V plane".to_string()))?;
for row in 0..uh {
let src = unsafe { std::slice::from_raw_parts(v.data.add(row * v.pitch), u_bytes) };
let start = offsets[1] as usize + row * pitches[1] as usize;
data[start..start + u_bytes].copy_from_slice(src);
}
}
drop(image); surface.sync().map_err(|e| Error::VaApi(e.to_string()))?;
Ok(())
}
fn h265_tile_coverage_complete(
sps: &H265Sps,
pps: &H265Pps,
slices: &[H265SliceInfo],
) -> bool {
if !pps.tiles_enabled_flag {
return true;
}
let log2_ctb = sps.log2_min_luma_coding_block_size_minus3 as u32
+ 3
+ sps.log2_diff_max_min_luma_coding_block_size as u32;
let ctu = 1u32 << log2_ctb;
let ctb_w = (sps.pic_width_in_luma_samples as u32 + ctu - 1) / ctu;
let ctb_h = (sps.pic_height_in_luma_samples as u32 + ctu - 1) / ctu;
if ctb_w == 0 || ctb_h == 0 {
return true;
}
let num_tc = pps.num_tile_columns_minus1 as u32 + 1;
let num_tr = pps.num_tile_rows_minus1 as u32 + 1;
let col_cbs = pps.column_width_minus1[0] as u32 + 1;
let row_cbs = pps.row_height_minus1[0] as u32 + 1;
let mut covered = vec![false; (num_tc * num_tr) as usize];
for s in slices {
let Some(SliceHeader::H265(sh)) = &s.slice_header else {
continue;
};
if sh.dependent_slice_segment_flag {
continue;
}
let addr = sh.slice_segment_address;
let col = addr % ctb_w;
let row = addr / ctb_w;
let tile_col = (col / col_cbs).min(num_tc - 1);
let tile_row = (row / row_cbs).min(num_tr - 1);
let pos = (num_tr - 1 - tile_row) * num_tc + tile_col;
for j in 0..(1u32 + sh.num_entry_point_offsets as u32) {
let p = pos + j;
if p < num_tc * num_tr {
covered[((num_tr - 1 - p / num_tc) as usize) * num_tc as usize
+ (p % num_tc) as usize] = true;
}
}
}
covered.iter().all(|&c| c)
}
fn read_from_image(
image: Image,
display_width: u32,
display_height: u32,
) -> Result<Option<PixelData>> {
let va_image = image.image();
let data = image.as_ref();
if va_image.format.fourcc == FOURCC_Y410 && va_image.num_planes == 1 {
let out_width = display_width.min(va_image.width as u32) as usize;
let out_height = display_height.min(va_image.height as u32) as usize;
let pitch = va_image.pitches[0] as usize;
let off = va_image.offsets[0] as usize;
let plane_size = out_width * out_height;
let mut buffer = vec![0u8; 3 * plane_size * 2];
let src = data.as_ptr();
for row in 0..out_height {
let row_off = off + row * pitch;
for col in 0..out_width {
let p = unsafe { src.add(row_off + col * 4) };
let word = u32::from_ne_bytes([
unsafe { *p },
unsafe { *p.add(1) },
unsafe { *p.add(2) },
unsafe { *p.add(3) },
]);
let y = ((word >> 10) & 0x3FF) as u16;
let u = (word & 0x3FF) as u16;
let v = ((word >> 20) & 0x3FF) as u16;
buffer[(row * out_width + col) * 2..(row * out_width + col) * 2 + 2]
.copy_from_slice(&y.to_ne_bytes());
buffer[(plane_size + row * out_width + col) * 2
..(plane_size + row * out_width + col) * 2 + 2]
.copy_from_slice(&u.to_ne_bytes());
buffer[(2 * plane_size + row * out_width + col) * 2
..(2 * plane_size + row * out_width + col) * 2 + 2]
.copy_from_slice(&v.to_ne_bytes());
}
}
drop(image);
let y_ptr = buffer.as_ptr();
let u_ptr = unsafe { y_ptr.add(plane_size * 2) };
let v_ptr = unsafe { y_ptr.add(2 * plane_size * 2) };
return Ok(Some(PixelData {
format: "Y410P16".to_string(),
y: PixelPlane {
data: y_ptr,
pitch: out_width * 2,
width: out_width,
height: out_height,
},
u: PixelPlane {
data: u_ptr,
pitch: out_width * 2,
width: out_width,
height: out_height,
},
v: Some(PixelPlane {
data: v_ptr,
pitch: out_width * 2,
width: out_width,
height: out_height,
}),
buffer,
}));
}
if va_image.format.fourcc == u32::from_ne_bytes(*b"XYUV") && va_image.num_planes == 1 {
let out_width = display_width.min(va_image.width as u32) as usize;
let out_height = display_height.min(va_image.height as u32) as usize;
let pitch = va_image.pitches[0] as usize;
let off = va_image.offsets[0] as usize;
let plane_size = out_width * out_height;
let mut buffer = vec![0u8; 3 * plane_size];
let src = data.as_ptr();
for row in 0..out_height {
let row_off = off + row * pitch;
for col in 0..out_width {
let px = unsafe { src.add(row_off + col * 4) };
buffer[row * out_width + col] = unsafe { *px.add(2) }; buffer[plane_size + row * out_width + col] = unsafe { *px.add(1) }; buffer[2 * plane_size + row * out_width + col] = unsafe { *px };
}
}
drop(image);
let y_ptr = buffer.as_ptr();
let u_ptr = unsafe { y_ptr.add(plane_size) };
let v_ptr = unsafe { y_ptr.add(2 * plane_size) };
return Ok(Some(PixelData {
format: "YUV444".to_string(),
y: PixelPlane {
data: y_ptr,
pitch: out_width,
width: out_width,
height: out_height,
},
u: PixelPlane {
data: u_ptr,
pitch: out_width,
width: out_width,
height: out_height,
},
v: Some(PixelPlane {
data: v_ptr,
pitch: out_width,
width: out_width,
height: out_height,
}),
buffer,
}));
}
let fourcc = va_image.format.fourcc;
let is_nv12 = fourcc == libva::VA_FOURCC_NV12;
let is_xyuv = fourcc == u32::from_ne_bytes(*b"XYUV");
let is_ayuv = fourcc == libva::VA_FOURCC_AYUV;
let is_p016 = fourcc == libva::VA_FOURCC_P016;
let p016_shift = if fourcc == libva::VA_FOURCC_P010 {
Some(6u32)
} else if fourcc == libva::VA_FOURCC_P012 {
Some(4u32)
} else {
None
};
let is_444 = fourcc == libva::VA_FOURCC_444P || is_xyuv || is_ayuv;
let bps = if is_p016 { 2 } else { 1 };
let format_str = if is_nv12 || p016_shift.is_some() {
"NV12".to_string()
} else if is_p016 {
"P016".to_string()
} else if is_444 {
"YUV444".to_string()
} else if fourcc == u32::from_ne_bytes(*b"YV12") {
"YV12".to_string()
} else if fourcc == u32::from_ne_bytes(*b"I420") {
"I420".to_string()
} else {
return Err(Error::VaApi(format!(
"Unsupported image format: {:X}",
fourcc
)));
};
let min_planes = if is_ayuv {
1
} else if is_xyuv {
4
} else if is_nv12 || p016_shift.is_some() || is_p016 {
2
} else {
3
};
if va_image.num_planes < min_planes {
return Err(Error::VaApi(format!(
"Unexpected num_planes={} for format {}",
va_image.num_planes, format_str
)));
}
let buffer = data.to_vec();
let out_width = display_width.min(va_image.width as u32) as usize;
let out_height = display_height.min(va_image.height as u32) as usize;
let uv_width = if is_444 {
out_width
} else {
out_width.div_ceil(2)
};
let uv_height = if is_444 {
out_height
} else {
out_height.div_ceil(2)
};
let _ = bps;
if is_ayuv {
let y_pitch = va_image.pitches[0] as usize; let base = va_image.offsets[0] as usize;
let mut out = vec![0u8; out_width * 3 * out_height];
for y in 0..out_height {
let row = unsafe { buffer.as_ptr().add(base + y * y_pitch) };
for x in 0..out_width {
let p = unsafe { row.add(x * 4) };
out[y * out_width + x] = unsafe { *p.add(2) }; out[out_width * out_height + y * out_width + x] = unsafe { *p.add(1) }; out[2 * out_width * out_height + y * out_width + x] = unsafe { *p.add(0) };
}
}
drop(image);
return Ok(Some(PixelData {
format: format_str,
y: PixelPlane {
data: out.as_ptr(),
pitch: out_width,
width: out_width,
height: out_height,
},
u: PixelPlane {
data: unsafe { out.as_ptr().add(out_width * out_height) },
pitch: out_width,
width: out_width,
height: out_height,
},
v: Some(PixelPlane {
data: unsafe { out.as_ptr().add(2 * out_width * out_height) },
pitch: out_width,
width: out_width,
height: out_height,
}),
buffer: out,
}));
}
if let Some(shift) = p016_shift {
let bits = 16u32 - shift;
let half = 1u32 << (bits - 1);
let scale = |v: u16| -> u8 {
let x = (v >> shift) as u32;
(((x * 256 + half) >> bits).min(255)) as u8
};
let read_u16 = |base: *const u8, off: usize| -> u16 {
unsafe { u16::from_ne_bytes([*base.add(off), *base.add(off + 1)]) }
};
let y_offset = va_image.offsets[0] as usize;
let u_offset = va_image.offsets[1] as usize;
let y_pitch = va_image.pitches[0] as usize / 2; let uv_pitch = va_image.pitches[1] as usize / 2;
let mut out = vec![0u8; out_width * out_height + uv_width * 2 * uv_height];
for y in 0..out_height {
let row = unsafe { buffer.as_ptr().add(y_offset) };
for x in 0..out_width {
out[y * out_width + x] = scale(read_u16(row, y * y_pitch * 2 + x * 2));
}
}
let uv_row = unsafe { buffer.as_ptr().add(u_offset) };
for y in 0..uv_height {
for x in 0..uv_width {
let base = y * uv_pitch * 2 + x * 4;
out[out_width * out_height + y * (uv_width * 2) + x * 2] =
scale(read_u16(uv_row, base));
out[out_width * out_height + y * (uv_width * 2) + x * 2 + 1] =
scale(read_u16(uv_row, base + 2));
}
}
drop(image);
return Ok(Some(PixelData {
format: format_str,
y: PixelPlane {
data: out.as_ptr(),
pitch: out_width,
width: out_width,
height: out_height,
},
u: PixelPlane {
data: unsafe { out.as_ptr().add(out_width * out_height) },
pitch: uv_width * 2,
width: uv_width,
height: uv_height,
},
v: None,
buffer: out,
}));
}
let y_offset = va_image.offsets[0] as usize;
let uv_base = if is_xyuv { 2 } else { 1 };
let u_offset = va_image.offsets[uv_base] as usize;
let y_plane = PixelPlane {
data: unsafe { buffer.as_ptr().add(y_offset) },
pitch: va_image.pitches[0] as usize,
width: out_width,
height: out_height,
};
let u_plane = PixelPlane {
data: unsafe { buffer.as_ptr().add(u_offset) },
pitch: va_image.pitches[1] as usize,
width: uv_width,
height: uv_height,
};
let v_plane = if !is_nv12 && !is_p016 {
let v_offset = va_image.offsets[uv_base + 1] as usize;
Some(PixelPlane {
data: unsafe { buffer.as_ptr().add(v_offset) },
pitch: va_image.pitches[2] as usize,
width: uv_width,
height: uv_height,
})
} else {
None
};
drop(image);
Ok(Some(PixelData {
format: format_str,
y: y_plane,
u: u_plane,
v: v_plane,
buffer,
}))
}
fn read_from_image_p01x_native(
image: Image,
display_width: u32,
display_height: u32,
) -> Result<Option<PixelData>> {
let va_image = image.image();
let data = image.as_ref();
let fourcc = va_image.format.fourcc;
let (shift, format_str) = if fourcc == libva::VA_FOURCC_P010 {
(6u32, "P010LE".to_string())
} else if fourcc == libva::VA_FOURCC_P012 {
(4u32, "P012LE".to_string())
} else {
return Err(Error::VaApi(format!(
"Unsupported image format for native readback: {:X}",
fourcc
)));
};
if va_image.num_planes < 2 {
return Err(Error::VaApi(format!(
"Unexpected num_planes={} for format {}",
va_image.num_planes, format_str
)));
}
let buffer = data.to_vec();
let out_width = display_width.min(va_image.width as u32) as usize;
let out_height = display_height.min(va_image.height as u32) as usize;
let uv_width = out_width.div_ceil(2);
let uv_height = out_height.div_ceil(2);
let y_offset = va_image.offsets[0] as usize;
let uv_offset = va_image.offsets[1] as usize;
let y_pitch = va_image.pitches[0] as usize / 2; let uv_pitch = va_image.pitches[1] as usize / 2;
let y_samples = out_width * out_height;
let uv_samples = uv_width * uv_height;
let mut out = vec![0u8; (y_samples + 2 * uv_samples) * 2];
for y in 0..out_height {
for x in 0..out_width {
let off = y_offset + (y * y_pitch + x) * 2;
let v = u16::from_ne_bytes([buffer[off], buffer[off + 1]]);
out[(y * out_width + x) * 2..(y * out_width + x) * 2 + 2]
.copy_from_slice(&(v >> shift).to_le_bytes());
}
}
for y in 0..uv_height {
for x in 0..uv_width {
let off = uv_offset + (y * uv_pitch + x * 2) * 2;
let u = u16::from_ne_bytes([buffer[off], buffer[off + 1]]);
let v = u16::from_ne_bytes([buffer[off + 2], buffer[off + 3]]);
out[(y_samples + y * uv_width + x) * 2..(y_samples + y * uv_width + x) * 2 + 2]
.copy_from_slice(&(u >> shift).to_le_bytes());
out[(y_samples + uv_samples + y * uv_width + x) * 2
..(y_samples + uv_samples + y * uv_width + x) * 2 + 2]
.copy_from_slice(&(v >> shift).to_le_bytes());
}
}
drop(image);
Ok(Some(PixelData {
format: format_str,
y: PixelPlane {
data: out.as_ptr(),
pitch: out_width * 2,
width: out_width,
height: out_height,
},
u: PixelPlane {
data: unsafe { out.as_ptr().add(y_samples * 2) },
pitch: uv_width * 2,
width: uv_width,
height: uv_height,
},
v: Some(PixelPlane {
data: unsafe { out.as_ptr().add((y_samples + uv_samples) * 2) },
pitch: uv_width * 2,
width: uv_width,
height: uv_height,
}),
buffer: out,
}))
}
fn read_surface_pixels(
surface: &Surface<DmaBufSurfaceDescriptor>,
width: u32,
height: u32,
display_width: u32,
display_height: u32,
fourccs: &[u32],
) -> Result<Option<PixelData>> {
for &fourcc in fourccs {
let format = libva::VAImageFormat {
fourcc,
..Default::default()
};
if let Ok(image) = Image::create_from(surface, format, (width, height), (width, height)) {
return read_from_image(image, display_width, display_height);
}
}
if let Ok(img) = Image::derive_from(surface, (width, height)) {
let fourcc = img.image().format.fourcc;
if fourcc == libva::VA_FOURCC_NV12
|| fourcc == u32::from_ne_bytes(*b"YV12")
|| fourcc == u32::from_ne_bytes(*b"I420")
{
return read_from_image(img, display_width, display_height);
}
}
Err(Error::VaApi("All surface read methods failed".to_string()))
}
fn parse_stream_info(display: &Display, data: &[u8]) -> Result<StreamInfo> {
let codec = detect_codec(data);
match codec {
CoreVideoCodec::DecodeH264 => parse_h264_info(display, data),
CoreVideoCodec::DecodeH265 => parse_h265_info(display, data),
CoreVideoCodec::DecodeVp9 => parse_vp9_info(display, data),
CoreVideoCodec::DecodeAv1 => parse_av1_info(display, data),
_ => Err(Error::CodecNotSupported(format!(
"Unsupported codec: {:?}",
codec
))),
}
}
fn detect_codec(data: &[u8]) -> CoreVideoCodec {
if data.len() >= 32 && data[0..4] == *b"DKIF" {
let fourcc = &data[8..12];
return match fourcc {
b"AV01" => CoreVideoCodec::DecodeAv1,
_ => CoreVideoCodec::DecodeVp9,
};
}
if data.first().is_some_and(|&b| (b & 0xC0) == 0x40) {
return CoreVideoCodec::DecodeAv1;
}
for &b in data.iter().take(256) {
if b == 0 {
continue;
}
if (b & 0xC0) == 0x80 {
return CoreVideoCodec::DecodeVp9;
}
break;
}
for i in 0..data.len().min(4096) {
let start = if i + 4 <= data.len() && data[i..i + 4] == [0x00, 0x00, 0x00, 0x01] {
i + 4
} else if i + 3 <= data.len() && data[i..i + 3] == [0x00, 0x00, 0x01] {
i + 3
} else {
continue;
};
if start >= data.len() {
continue;
}
let b0 = data[start];
let h265_nal_type = (b0 >> 1) & 0x3F;
let h264_nal_type = b0 & 0x1F;
if h265_nal_type == 32 || h265_nal_type == 33 || h265_nal_type == 34 {
return CoreVideoCodec::DecodeH265;
}
if h264_nal_type == 7 || h264_nal_type == 8 {
return CoreVideoCodec::DecodeH264;
}
}
CoreVideoCodec::DecodeH264
}
fn parse_h264_info(display: &Display, data: &[u8]) -> Result<StreamInfo> {
let mut parser = H264Parser::new();
parser
.init(&DetectedVideoFormat::new(CoreVideoCodec::DecodeH264))
.map_err(|e| Error::Parser(e.to_string()))?;
let packet = BitstreamPacket::new(data.to_vec());
let mut width = 0u32;
let mut height = 0u32;
let mut display_width = 0u32;
let mut display_height = 0u32;
let mut max_dpb = 4u32;
let mut profile = libva::VAProfile::VAProfileH264Main;
let mut sps_opt: Option<H264Sps> = None;
let mut pps_opt: Option<H264Pps> = None;
match parser.parse(&packet) {
Ok(ParseResult::ParameterSet {
sps: Some(s), pps, ..
}) => {
if let Some(sps) = s.downcast_ref::<H264Sps>() {
sps_opt = Some(sps.clone());
if let Some(pps_box) = pps
&& let Some(pps_ref) = pps_box.downcast_ref::<H264Pps>()
{
pps_opt = Some(pps_ref.clone());
}
}
}
Ok(ParseResult::Slice { .. }) => {
if let Some(sps) = parser.active_sps() {
sps_opt = Some(sps.clone());
}
if let Some(pps) = parser.active_pps() {
pps_opt = Some(pps.clone());
}
}
_ => {}
}
if let Some(ref sps) = sps_opt {
width = (sps.pic_width_in_mbs_minus1 as u32 + 1) * 16;
height = if sps.frame_mbs_only_flag {
(sps.pic_height_in_map_units_minus1 as u32 + 1) * 16
} else {
(sps.pic_height_in_map_units_minus1 as u32 + 1) * 16 * 2
};
max_dpb = sps.max_num_ref_frames;
if sps.frame_cropping_flag {
let sub_width_c = if sps.chroma_format_idc == 1 || sps.chroma_format_idc == 2 {
2
} else {
1
};
let sub_height_c = if sps.chroma_format_idc == 1 { 2 } else { 1 };
let crop_left = sps.frame_crop_left_offset * sub_width_c;
let crop_right = sps.frame_crop_right_offset * sub_width_c;
let crop_top = sps.frame_crop_top_offset * sub_height_c;
let crop_bottom = sps.frame_crop_bottom_offset * sub_height_c;
display_width = if crop_left + crop_right < width {
width - crop_left - crop_right
} else {
width
};
display_height = if crop_top + crop_bottom < height {
height - crop_top - crop_bottom
} else {
height
};
} else {
display_width = width;
display_height = height;
}
profile = match sps.profile_idc {
66 => libva::VAProfile::VAProfileH264ConstrainedBaseline,
77 => libva::VAProfile::VAProfileH264Main,
88 => libva::VAProfile::VAProfileH264Main,
100 => libva::VAProfile::VAProfileH264High,
110 => libva::VAProfile::VAProfileH264High10,
122 | 244 => libva::VAProfile::VAProfileH264High,
_ => libva::VAProfile::VAProfileH264Main,
};
}
let preferred = profile;
let supported = |p: libva::VAProfile::Type| {
display
.query_config_entrypoints(p)
.map(|e| e.contains(&libva::VAEntrypoint::VAEntrypointVLD))
.unwrap_or(false)
};
if !supported(preferred) {
for p in [
libva::VAProfile::VAProfileH264ConstrainedBaseline,
libva::VAProfile::VAProfileH264Main,
libva::VAProfile::VAProfileH264High,
] {
if supported(p) {
profile = p;
break;
}
}
}
if width == 0 || height == 0 {
return Err(Error::DecoderInit(
"Failed to parse H.264 dimensions".to_string(),
));
}
let rt_format = if let Some(ref sps) = sps_opt {
let bit_depth = 8 + sps.bit_depth_luma_minus8;
let chroma_fmt = sps.chroma_format_idc;
match (bit_depth, chroma_fmt) {
(8, 0) | (8, 1) => libva::VA_RT_FORMAT_YUV420,
(8, 2) => libva::VA_RT_FORMAT_YUV422,
(8, 3) => libva::VA_RT_FORMAT_YUV444,
(10, 0) | (10, 1) => libva::VA_RT_FORMAT_YUV420_10,
(10, 2) => libva::VA_RT_FORMAT_YUV422_10,
(10, 3) => libva::VA_RT_FORMAT_YUV444_10,
(12, 0) | (12, 1) => libva::VA_RT_FORMAT_YUV420_12,
(12, 2) => libva::VA_RT_FORMAT_YUV422_12,
(12, 3) => libva::VA_RT_FORMAT_YUV444_12,
_ => libva::VA_RT_FORMAT_YUV420,
}
} else {
libva::VA_RT_FORMAT_YUV420
};
Ok(StreamInfo {
codec: CoreVideoCodec::DecodeH264,
profile,
width,
height,
display_width,
display_height,
max_dpb: max_dpb.clamp(1, 16),
rt_format,
vp9_profile: 0,
vp9_bit_depth: 8,
av1_bit_depth: 8,
sps: sps_opt,
pps: pps_opt,
h265_sps: None,
h265_pps: None,
av1_sps: None,
})
}
fn parse_h265_info(display: &Display, data: &[u8]) -> Result<StreamInfo> {
let mut parser = H265Parser::new();
parser
.init(&DetectedVideoFormat::new(CoreVideoCodec::DecodeH265))
.map_err(|e| Error::Parser(e.to_string()))?;
let packet = BitstreamPacket::new(data.to_vec());
let display_width;
let display_height;
let mut sps_opt: Option<H265Sps> = None;
let mut pps_opt: Option<H265Pps> = None;
if let Ok(ParseResult::ParameterSet {
sps: Some(s), pps, ..
}) = parser.parse(&packet)
{
if let Some(sps) = s.downcast_ref::<H265Sps>() {
sps_opt = Some(sps.clone());
}
if let Some(pps_box) = pps
&& let Some(pps) = pps_box.downcast_ref::<H265Pps>()
{
pps_opt = Some(pps.clone());
}
}
let sps = sps_opt
.as_ref()
.ok_or_else(|| Error::DecoderInit("Failed to parse H.265 SPS".to_string()))?;
let width = sps.pic_width_in_luma_samples as u32;
let height = sps.pic_height_in_luma_samples as u32;
let max_dpb = (sps.max_dec_pic_buffering_minus1[0] as u32 + 1)
.saturating_add(sps.max_num_reorder_pics[0] as u32)
.max(1);
let (sub_w, sub_h) = match sps.chroma_format_idc {
0 => (1u32, 1u32),
1 => (2, 2), 2 => (2, 1), _ => (1, 1), };
if sps.conformance_window_flag {
let cw = (sps.conf_win_left_offset + sps.conf_win_right_offset) * sub_w;
let ch = (sps.conf_win_top_offset + sps.conf_win_bottom_offset) * sub_h;
display_width = if cw < width { width - cw } else { width };
display_height = if ch < height { height - ch } else { height };
} else {
display_width = width;
display_height = height;
}
let bit_depth = 8 + sps.bit_depth_luma_minus8 as u32;
let preferred = match sps.profile_idc {
1 | 3 => libva::VAProfile::VAProfileHEVCMain,
2 | 5 => {
if sps.chroma_format_idc == 3 {
libva::VAProfile::VAProfileHEVCMain444_10
} else {
libva::VAProfile::VAProfileHEVCMain10
}
}
4 => match (sps.chroma_format_idc, bit_depth) {
(3, 8) => libva::VAProfile::VAProfileHEVCMain444,
(3, 10) => libva::VAProfile::VAProfileHEVCMain444_10,
(3, _) => libva::VAProfile::VAProfileHEVCMain444_12,
(2, 10) => libva::VAProfile::VAProfileHEVCMain422_10,
(2, 12) => libva::VAProfile::VAProfileHEVCMain422_12,
(_, 10) => libva::VAProfile::VAProfileHEVCMain10,
(_, 12) => libva::VAProfile::VAProfileHEVCMain12,
_ => libva::VAProfile::VAProfileHEVCMain,
},
_ => libva::VAProfile::VAProfileHEVCMain,
};
let supported = |p: VAProfileType| {
display
.query_config_entrypoints(p)
.map(|e| e.contains(&libva::VAEntrypoint::VAEntrypointVLD))
.unwrap_or(false)
};
let mut profile = preferred;
if !supported(profile) {
for p in [
libva::VAProfile::VAProfileHEVCMain10,
libva::VAProfile::VAProfileHEVCMain444,
libva::VAProfile::VAProfileHEVCMain444_10,
libva::VAProfile::VAProfileHEVCMain422_10,
libva::VAProfile::VAProfileHEVCMain,
] {
if supported(p) {
profile = p;
break;
}
}
}
let rt_format = match (bit_depth, sps.chroma_format_idc) {
(8, 0) | (8, 1) => libva::VA_RT_FORMAT_YUV420,
(8, 2) => libva::VA_RT_FORMAT_YUV422,
(8, 3) => libva::VA_RT_FORMAT_YUV444,
(10, 0) | (10, 1) => libva::VA_RT_FORMAT_YUV420_10,
(10, 2) => libva::VA_RT_FORMAT_YUV422_10,
(10, 3) => libva::VA_RT_FORMAT_YUV444_10,
_ => libva::VA_RT_FORMAT_YUV420,
};
Ok(StreamInfo {
codec: CoreVideoCodec::DecodeH265,
profile,
width,
height,
display_width,
display_height,
max_dpb: max_dpb.clamp(1, 16),
rt_format,
vp9_profile: 0,
vp9_bit_depth: 8,
av1_bit_depth: 8,
sps: None,
pps: None,
h265_sps: sps_opt,
h265_pps: pps_opt,
av1_sps: None,
})
}
fn parse_vp9_info(display: &Display, data: &[u8]) -> Result<StreamInfo> {
let first_payload: Vec<u8> = if data.len() >= 32 && data[0..4] == *b"DKIF" {
if data.len() < 32 + 12 {
return Err(Error::DecoderInit("IVF too short for a packet".to_string()));
}
let size = u32::from_le_bytes(data[32..36].try_into().unwrap()) as usize;
if data.len() < 32 + 12 + size {
return Err(Error::DecoderInit("IVF packet truncated".to_string()));
}
data[32 + 12..32 + 12 + size].to_vec()
} else {
data.to_vec()
};
let frames = expand_superframes(&first_payload);
if frames.is_empty() {
return Err(Error::DecoderInit("No VP9 frames found".to_string()));
}
let mut parser = Vp9Parser::new();
parser
.init(&DetectedVideoFormat::new(CoreVideoCodec::DecodeVp9))
.map_err(|e| Error::Parser(e.to_string()))?;
let parsed = parser
.parse_frame_with_offset(&frames[0].data, frames[0].superframe_frame_offset)
.map_err(|e| Error::Parser(e.to_string()))?;
let width = parsed.frame_width;
let height = parsed.frame_height;
if width == 0 || height == 0 {
return Err(Error::DecoderInit(
"Failed to parse VP9 dimensions".to_string(),
));
}
let display_width = if parsed.render_width > 0 {
parsed.render_width
} else {
width
};
let display_height = if parsed.render_height > 0 {
parsed.render_height
} else {
height
};
let profile_num = parsed.picture_info.profile as u8;
let bit_depth = parsed.color_config.bit_depth;
let profile = match profile_num {
0 => libva::VAProfile::VAProfileVP9Profile0,
1 => libva::VAProfile::VAProfileVP9Profile1,
_ => libva::VAProfile::VAProfileVP9Profile2,
};
if !display
.query_config_entrypoints(profile)
.map(|e| e.contains(&libva::VAEntrypoint::VAEntrypointVLD))
.unwrap_or(false)
{
return Err(Error::DecoderInit(format!(
"VA driver lacks VP9 profile {profile_num} decode"
)));
}
let rt_format = match (bit_depth, profile_num) {
(8, 1) => libva::VA_RT_FORMAT_YUV444,
(8, _) => libva::VA_RT_FORMAT_YUV420,
(10, _) => libva::VA_RT_FORMAT_YUV420_10,
(12, _) => libva::VA_RT_FORMAT_YUV420_12,
_ => {
return Err(Error::DecoderInit(format!(
"Unsupported VP9 bit depth {bit_depth}"
)));
}
};
Ok(StreamInfo {
codec: CoreVideoCodec::DecodeVp9,
profile,
width,
height,
display_width,
display_height,
max_dpb: 8,
rt_format,
vp9_profile: profile_num,
vp9_bit_depth: bit_depth,
av1_bit_depth: 8,
sps: None,
pps: None,
h265_sps: None,
h265_pps: None,
av1_sps: None,
})
}
fn av1_find_sps_obu(packet: &[u8]) -> Option<Vec<u8>> {
let mut pos = 0;
while pos < packet.len().saturating_sub(1) {
let first = packet[pos];
let obu_type = (first >> 3) & 0x0F;
let ext = (first >> 2) & 1;
let has_size = (first >> 1) & 1 != 0;
let header_size = 1 + ext as usize;
if has_size && pos + header_size < packet.len() {
let mut size: usize = 0;
let mut shift = 0;
let mut size_pos = pos + header_size;
loop {
if size_pos >= packet.len() {
break;
}
let b = packet[size_pos];
size |= ((b & 0x7F) as usize) << shift;
shift += 7;
size_pos += 1;
if b & 0x80 == 0 {
break;
}
}
if obu_type == 1 {
let payload_end = (size_pos + size).min(packet.len());
return Some(packet[size_pos..payload_end].to_vec());
}
let next = size_pos + size;
pos = if next > pos { next } else { size_pos + 1 };
} else {
pos += header_size.max(1);
}
}
None
}
struct Av1FrameObu {
payload: Vec<u8>,
temporal_id: u32,
spatial_id: u32,
}
fn av1_extract_frame_obus(packet: &[u8]) -> Vec<Av1FrameObu> {
let mut obus = Vec::new();
let mut pos = 0;
while pos < packet.len().saturating_sub(1) {
let first = packet[pos];
let obu_type = (first >> 3) & 0x0F;
let ext = (first >> 2) & 1;
let has_size = (first >> 1) & 1 != 0;
let header_size = 1 + ext as usize;
if has_size && pos + header_size < packet.len() {
let mut size: usize = 0;
let mut shift = 0;
let mut size_pos = pos + header_size;
loop {
if size_pos >= packet.len() {
break;
}
let b = packet[size_pos];
size |= ((b & 0x7F) as usize) << shift;
shift += 7;
size_pos += 1;
if b & 0x80 == 0 {
break;
}
}
let is_frame = obu_type == 6;
let is_show_existing = obu_type == 3
&& size > 0
&& size_pos < packet.len()
&& (packet[size_pos] & 0x80) != 0;
let is_split_frame_header = obu_type == 3
&& size > 0
&& size_pos < packet.len()
&& (packet[size_pos] & 0x80) == 0;
if is_frame || is_show_existing {
let payload_end = (size_pos + size).min(packet.len());
let (temporal_id, spatial_id) = if ext == 1 {
let e = packet[pos + 1];
(((e >> 5) & 0x7) as u32, (e & 0x1f) as u32)
} else {
(0, 0)
};
obus.push(Av1FrameObu {
payload: packet[size_pos..payload_end].to_vec(),
temporal_id,
spatial_id,
});
} else if is_split_frame_header {
let fh_end = (size_pos + size).min(packet.len());
let mut payload: Vec<u8> = packet[size_pos..fh_end].to_vec();
let mut p = fh_end;
let mut tiles = 0usize;
while p < packet.len().saturating_sub(1) {
let t_first = packet[p];
let t_type = (t_first >> 3) & 0x0F;
let t_ext = (t_first >> 2) & 1;
let t_header_size = 1 + t_ext as usize;
if ((t_first >> 1) & 1) == 0 || p + t_header_size >= packet.len() {
break;
}
let mut t_size: usize = 0;
let mut shift = 0;
let mut t_size_pos = p + t_header_size;
loop {
if t_size_pos >= packet.len() {
break;
}
let b = packet[t_size_pos];
t_size |= ((b & 0x7F) as usize) << shift;
shift += 7;
t_size_pos += 1;
if b & 0x80 == 0 {
break;
}
}
if t_type != 4 {
break;
}
let t_end = (t_size_pos + t_size).min(packet.len());
payload.extend_from_slice(&packet[t_size_pos..t_end]);
tiles += 1;
p = t_end;
}
if tiles > 0 {
let (temporal_id, spatial_id) = if ext == 1 {
let e = packet[pos + 1];
(((e >> 5) & 0x7) as u32, (e & 0x1f) as u32)
} else {
(0, 0)
};
obus.push(Av1FrameObu {
payload,
temporal_id,
spatial_id,
});
}
}
let next = size_pos + size;
pos = if next > pos { next } else { size_pos + 1 };
} else {
pos += header_size.max(1);
}
}
obus
}
fn parse_av1_info(display: &Display, data: &[u8]) -> Result<StreamInfo> {
let is_ivf = data.len() >= 32 && data[0..4] == *b"DKIF";
let mut pos = if is_ivf { 32 } else { 0 };
let mut sps_payload: Option<Vec<u8>> = None;
let mut frame_obu: Option<(Vec<u8>, u32, u32)> = None;
loop {
let payload: Vec<u8>;
if is_ivf {
if pos + 12 > data.len() {
break;
}
let size = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
if size == 0 || pos + 12 + size > data.len() {
break;
}
payload = data[pos + 12..pos + 12 + size].to_vec();
pos += 12 + size;
} else {
if pos >= data.len() {
break;
}
payload = data[pos..].to_vec();
pos = data.len();
}
if sps_payload.is_none()
&& let Some(s) = av1_find_sps_obu(&payload)
{
sps_payload = Some(s);
}
if frame_obu.is_none()
&& let Some(obu) = av1_extract_frame_obus(&payload).into_iter().next()
{
frame_obu = Some((obu.payload, obu.temporal_id, obu.spatial_id));
}
if sps_payload.is_some() && frame_obu.is_some() {
break;
}
}
let sps_data = sps_payload
.ok_or_else(|| Error::DecoderInit("AV1 sequence header OBU not found".to_string()))?;
let (frame_data, frame_temporal_id, frame_spatial_id) =
frame_obu.ok_or_else(|| Error::DecoderInit("No AV1 frame OBU found".to_string()))?;
let mut parser = Av1Parser::new();
parser
.init(&DetectedVideoFormat::new(CoreVideoCodec::DecodeAv1))
.map_err(|e| Error::Parser(e.to_string()))?;
let sps = parser
.parse_sequence_header_obu(&sps_data)
.map_err(|e| Error::Parser(e.to_string()))?;
if sps.mono_chrome || !(sps.subsampling_x == 1 && sps.subsampling_y == 1) {
let name = if sps.subsampling_x == 0 && sps.subsampling_y == 0 {
"4:4:4"
} else if sps.subsampling_y == 0 {
"4:2:2"
} else {
"monochrome"
};
return Err(Error::DecoderInit(format!(
"AV1 {name} not supported by this VAAPI backend (4:2:0 only)"
)));
}
let bit_depth = if sps.twelve_bit {
12
} else if sps.high_bitdepth {
10
} else {
8
};
if bit_depth == 12 {
return Err(Error::DecoderInit(
"AV1 12-bit not supported by this VAAPI backend (8/10-bit only)".to_string(),
));
}
let profile = match sps.profile {
0 => libva::VAProfile::VAProfileAV1Profile0,
1 => libva::VAProfile::VAProfileAV1Profile1,
p => {
return Err(Error::DecoderInit(format!(
"Unsupported AV1 profile {p} (VAAPI supports profiles 0/1)"
)));
}
};
if !display
.query_config_entrypoints(profile)
.map(|e| e.contains(&libva::VAEntrypoint::VAEntrypointVLD))
.unwrap_or(false)
{
return Err(Error::DecoderInit(format!(
"VA driver lacks AV1 profile {} decode",
sps.profile
)));
}
let rt_format = match bit_depth {
8 => libva::VA_RT_FORMAT_YUV420,
_ => libva::VA_RT_FORMAT_YUV420_10,
};
let fh = parser
.parse_frame_header(&frame_data, &sps, frame_temporal_id, frame_spatial_id)
.map_err(|e| Error::Parser(format!("Failed to parse AV1 frame dimensions: {e}")))?;
let width = fh.frame_width;
let height = fh.frame_height;
if width == 0 || height == 0 {
return Err(Error::DecoderInit(
"Failed to parse AV1 dimensions".to_string(),
));
}
let display_width = if fh.render_width > 0 {
fh.render_width
} else {
width
};
let display_height = if fh.render_height > 0 {
fh.render_height
} else {
height
};
Ok(StreamInfo {
codec: CoreVideoCodec::DecodeAv1,
profile,
width,
height,
display_width,
display_height,
max_dpb: 8, rt_format,
vp9_profile: 0,
vp9_bit_depth: 8,
av1_bit_depth: bit_depth,
sps: None,
pps: None,
h265_sps: None,
h265_pps: None,
av1_sps: Some(sps),
})
}
#[derive(Debug, Clone)]
struct ExpandedVp9Frame {
data: Vec<u8>,
superframe_frame_offset: u32,
}
fn expand_superframes(payload: &[u8]) -> Vec<ExpandedVp9Frame> {
let mut out = Vec::new();
let data_len = payload.len();
if data_len < 2 {
out.push(ExpandedVp9Frame {
data: payload.to_vec(),
superframe_frame_offset: 0,
});
return out;
}
let final_byte = payload[data_len - 1];
if (final_byte & 0xE0) != 0xC0 {
out.push(ExpandedVp9Frame {
data: payload.to_vec(),
superframe_frame_offset: 0,
});
return out;
}
let num_frames = (final_byte & 0x07) as usize + 1;
if num_frames <= 1 {
out.push(ExpandedVp9Frame {
data: payload.to_vec(),
superframe_frame_offset: 0,
});
return out;
}
let mag = (((final_byte >> 3) & 0x03) as usize) + 1;
let index_size = 2 + mag * num_frames;
if data_len < index_size {
out.push(ExpandedVp9Frame {
data: payload.to_vec(),
superframe_frame_offset: 0,
});
return out;
}
let index_start = data_len - index_size;
if payload[index_start] != final_byte {
out.push(ExpandedVp9Frame {
data: payload.to_vec(),
superframe_frame_offset: 0,
});
return out;
}
let frame_data_size = data_len - index_size;
let mut offset = 0usize;
let mut x = index_start + 1;
for _ in 0..num_frames {
let mut this_sz = 0usize;
for j in 0..mag {
this_sz |= (payload[x + j] as usize) << (j * 8);
}
x += mag;
if offset + this_sz <= frame_data_size {
out.push(ExpandedVp9Frame {
data: payload[offset..offset + this_sz].to_vec(),
superframe_frame_offset: offset as u32,
});
}
offset += this_sz;
}
out
}
impl VaapiDecoder {
fn vp9_image_fourcc(&self) -> u32 {
match (self.stream.vp9_profile, self.stream.vp9_bit_depth) {
(1, _) => libva::VA_FOURCC_AYUV,
(_, 10) => libva::VA_FOURCC_P010,
(_, 12) => libva::VA_FOURCC_P012,
_ => libva::VA_FOURCC_NV12,
}
}
fn decode_vp9_pending(&mut self) -> Result<Option<DecodedFrame>> {
if self.parse_offset >= self.pending_data.len() {
return Ok(None);
}
if !self.input_is_ivf {
let data = self.pending_data[self.parse_offset..].to_vec();
self.parse_offset = self.pending_data.len();
for f in expand_superframes(&data) {
if let Some(frame) = self.decode_vp9_frame(&f.data, f.superframe_frame_offset, 0)? {
return Ok(Some(frame));
}
}
return Ok(None);
}
loop {
if self.parse_offset + 12 > self.pending_data.len() {
return Ok(None);
}
let size = u32::from_le_bytes(
self.pending_data[self.parse_offset..self.parse_offset + 4]
.try_into()
.unwrap(),
) as usize;
if size == 0 || self.parse_offset + 12 + size > self.pending_data.len() {
return Ok(None);
}
let pts = u64::from_le_bytes(
self.pending_data[self.parse_offset + 4..self.parse_offset + 12]
.try_into()
.unwrap(),
);
let payload =
self.pending_data[self.parse_offset + 12..self.parse_offset + 12 + size].to_vec();
self.parse_offset += 12 + size;
for f in expand_superframes(&payload) {
if let Some(frame) =
self.decode_vp9_frame(&f.data, f.superframe_frame_offset, pts)?
{
return Ok(Some(frame));
}
}
}
}
fn decode_vp9_frame(
&mut self,
data: &[u8],
superframe_offset: u32,
timestamp: u64,
) -> Result<Option<DecodedFrame>> {
let parser = self
.vp9_parser
.as_mut()
.ok_or_else(|| Error::InvalidState("VP9 parser not initialized".to_string()))?;
let parsed = parser
.parse_frame_with_offset(data, superframe_offset)
.map_err(|e| Error::Parser(e.to_string()))?;
if parsed.show_existing_frame {
let slot = self
.vp9_ctx
.as_ref()
.ok_or_else(|| Error::InvalidState("VP9 context not initialized".to_string()))?
.dpb
.slot_of_frame_buffer(parsed.frame_to_show_map_idx as usize);
if slot < 0 {
return Err(Error::InvalidState(format!(
"show-existing-frame: frame buffer {} is empty",
parsed.frame_to_show_map_idx
)));
}
let pool_idx =
self.vp9_ctx.as_ref().unwrap().slot_surfaces[slot as usize].ok_or_else(|| {
Error::InvalidState("show-existing-frame: slot has no surface".to_string())
})?;
let surface = Rc::clone(&self.surface_pool.entries[pool_idx].surface);
self.surface_pool.sync_surface(pool_idx)?;
let pixel_data =
read_output_surface_pixels(&surface, &self.stream, self.vp9_image_fourcc())?;
let mut frame = DecodedFrame::new(
self.frame_count,
timestamp as i64,
self.stream.display_width,
self.stream.display_height,
false,
);
frame.pixel_data = pixel_data;
self.frame_count += 1;
return Ok(Some(frame));
}
let pi = &parsed.picture_info;
let is_key = parsed.frame_is_intra;
let (ref_pool_idxs, out_slot, used_pool) = {
let ctx = self
.vp9_ctx
.as_mut()
.ok_or_else(|| Error::InvalidState("VP9 context not initialized".to_string()))?;
let live: std::collections::HashSet<usize> = ctx
.dpb
.frame_buffer_slots()
.iter()
.filter(|s| **s >= 0)
.map(|s| *s as usize)
.collect();
for (s, entry) in ctx.slot_surfaces.iter_mut().enumerate() {
if !live.contains(&s) {
*entry = None;
}
}
let mut ref_pool_idxs = [None; 8];
for (i, &slot) in ctx.dpb.frame_buffer_slots().iter().enumerate() {
if slot >= 0 {
ref_pool_idxs[i] = ctx.slot_surfaces[slot as usize];
}
}
let out_slot = ctx.dpb.choose_output_slot() as usize;
let used_pool: std::collections::HashSet<usize> =
ctx.slot_surfaces.iter().flatten().copied().collect();
(ref_pool_idxs, out_slot, used_pool)
};
let mut ref_surfaces = [libva::VA_INVALID_ID; 8];
for (i, pool_idx) in ref_pool_idxs.iter().enumerate() {
if let Some(pool_idx) = pool_idx {
ref_surfaces[i] = self.surface_pool.entries[*pool_idx].surface.id();
}
}
let (pool_idx, surface) = self
.surface_pool
.alloc_excluding(&used_pool)
.ok_or_else(|| Error::DecoderInit("No free VA surface for VP9 frame".to_string()))?;
let (pic_type, slice_type) = build_vp9_va_buffers(&parsed, ref_surfaces, data.len() as u32);
let pic_buf = self
.context
.create_buffer(pic_type)
.map_err(|e| Error::VaApi(e.to_string()))?;
let slice_buf = self
.context
.create_buffer(slice_type)
.map_err(|e| Error::VaApi(e.to_string()))?;
let mut picture = Picture::<PictureNew, Rc<Surface<DmaBufSurfaceDescriptor>>>::new(
timestamp,
Rc::clone(&self.context),
surface.clone(),
);
picture.add_buffer(pic_buf);
picture.add_buffer(slice_buf);
let slice_data_buf = self
.context
.create_buffer(BufferType::SliceData(data.to_vec()))
.map_err(|e| Error::VaApi(e.to_string()))?;
picture.add_buffer(slice_data_buf);
let picture = picture.begin().map_err(|e| Error::VaApi(e.to_string()))?;
let picture = picture.render().map_err(|e| Error::VaApi(e.to_string()))?;
let picture = picture.end().map_err(|e| Error::VaApi(e.to_string()))?;
let _synced: Picture<PictureSync, Rc<Surface<DmaBufSurfaceDescriptor>>> =
picture.sync().map_err(|e| Error::VaApi(e.0.to_string()))?;
self.surface_pool.mark_ready(pool_idx);
{
let ctx = self
.vp9_ctx
.as_mut()
.ok_or_else(|| Error::InvalidState("VP9 context not initialized".to_string()))?;
ctx.dpb
.commit_frame(pi.refresh_frame_flags, out_slot as i32);
ctx.slot_surfaces[out_slot] = Some(pool_idx);
}
if pi.flags.show_frame == 0 {
return Ok(None);
}
self.surface_pool.sync_surface(pool_idx)?;
let pixel_data =
read_output_surface_pixels(&surface, &self.stream, self.vp9_image_fourcc())?;
let mut frame = DecodedFrame::new(
self.frame_count,
timestamp as i64,
self.stream.display_width,
self.stream.display_height,
is_key,
);
frame.pixel_data = pixel_data;
self.frame_count += 1;
Ok(Some(frame))
}
}
fn av1_image_fourcc(stream: &StreamInfo) -> u32 {
match stream.av1_bit_depth {
10 => libva::VA_FOURCC_P010,
12 => libva::VA_FOURCC_P012,
_ => libva::VA_FOURCC_NV12,
}
}
fn read_output_surface_pixels(
surface: &Surface<DmaBufSurfaceDescriptor>,
stream: &StreamInfo,
fourcc: u32,
) -> Result<Option<PixelData>> {
if fourcc == libva::VA_FOURCC_P010 || fourcc == libva::VA_FOURCC_P012 {
let format = libva::VAImageFormat {
fourcc,
..Default::default()
};
if let Ok(image) = Image::create_from(
surface,
format,
(stream.width, stream.height),
(stream.width, stream.height),
) {
return read_from_image_p01x_native(image, stream.display_width, stream.display_height);
}
return Err(Error::VaApi(format!(
"P010/P012 surface read failed (fourcc {:X})",
fourcc
)));
}
read_surface_pixels(
surface,
stream.width,
stream.height,
stream.display_width,
stream.display_height,
&[fourcc],
)
}
impl VaapiDecoder {
fn decode_av1_pending(&mut self) -> Result<Option<DecodedFrame>> {
if self.parse_offset >= self.pending_data.len() {
return Ok(None);
}
if !self.input_is_ivf {
let data = self.pending_data[self.parse_offset..].to_vec();
self.parse_offset = self.pending_data.len();
for obu in av1_extract_frame_obus(&data) {
if let Some(frame) =
self.decode_av1_frame(&obu.payload, obu.temporal_id, obu.spatial_id, 0)?
{
return Ok(Some(frame));
}
}
return Ok(None);
}
loop {
if self.parse_offset + 12 > self.pending_data.len() {
return Ok(None);
}
let size = u32::from_le_bytes(
self.pending_data[self.parse_offset..self.parse_offset + 4]
.try_into()
.unwrap(),
) as usize;
if size == 0 || self.parse_offset + 12 + size > self.pending_data.len() {
return Ok(None);
}
let pts = u64::from_le_bytes(
self.pending_data[self.parse_offset + 4..self.parse_offset + 12]
.try_into()
.unwrap(),
);
let payload =
self.pending_data[self.parse_offset + 12..self.parse_offset + 12 + size].to_vec();
self.parse_offset += 12 + size;
for obu in av1_extract_frame_obus(&payload) {
if let Some(frame) =
self.decode_av1_frame(&obu.payload, obu.temporal_id, obu.spatial_id, pts)?
{
return Ok(Some(frame));
}
}
}
}
fn decode_av1_frame(
&mut self,
data: &[u8],
temporal_id: u32,
spatial_id: u32,
timestamp: u64,
) -> Result<Option<DecodedFrame>> {
let sps = self
.stream
.av1_sps
.clone()
.ok_or_else(|| Error::InvalidState("AV1 SPS not available".to_string()))?;
let parser = self
.av1_parser
.as_mut()
.ok_or_else(|| Error::InvalidState("AV1 parser not initialized".to_string()))?;
let fh = parser
.parse_frame_header(data, &sps, temporal_id, spatial_id)
.map_err(|e| Error::Parser(e.to_string()))?;
if fh.show_existing_frame {
let slot = parser
.dpb()
.slot_of_frame_buffer(fh.frame_to_show_map_idx as usize);
if slot < 0 {
return Err(Error::InvalidState(format!(
"show-existing-frame: frame buffer {} is empty",
fh.frame_to_show_map_idx
)));
}
let pool_idx = self
.av1_ctx
.as_ref()
.ok_or_else(|| Error::InvalidState("AV1 context not initialized".to_string()))?
.slot_surfaces[slot as usize]
.ok_or_else(|| {
Error::InvalidState("show-existing-frame: slot has no surface".to_string())
})?;
let surface = Rc::clone(&self.surface_pool.entries[pool_idx].surface);
self.surface_pool.sync_surface(pool_idx)?;
let pixel_data =
read_output_surface_pixels(&surface, &self.stream, av1_image_fourcc(&self.stream))?;
let mut frame = DecodedFrame::new(
self.frame_count,
timestamp as i64,
self.stream.display_width,
self.stream.display_height,
false,
);
frame.pixel_data = pixel_data;
self.frame_count += 1;
parser.dpb_mut().note_displayed();
return Ok(Some(frame));
}
let is_key = fh.frame_type == 0;
let output_slot = {
let dpb = parser.dpb_mut();
if is_key || dpb.decoded_frames() == 0 {
dpb.reset_for_keyframe();
dpb.update_content(&fh);
0
} else {
dpb.allocate_output_slot()
}
};
let mut ref_frame_map = [libva::VA_INVALID_ID; 8];
{
let dpb = parser.dpb();
let ctx = self
.av1_ctx
.as_mut()
.ok_or_else(|| Error::InvalidState("AV1 context not initialized".to_string()))?;
let mut live = vec![false; ctx.slot_surfaces.len()];
for fb in &dpb.frame_buffers {
if fb.slot >= 0 && (fb.slot as usize) < live.len() {
live[fb.slot as usize] = true;
}
}
for (s, entry) in ctx.slot_surfaces.iter_mut().enumerate() {
if !live[s] {
*entry = None;
}
}
for (i, fb) in dpb.frame_buffers.iter().enumerate() {
if fb.slot >= 0
&& let Some(pool_idx) = ctx.slot_surfaces[fb.slot as usize]
{
ref_frame_map[i] = self.surface_pool.entries[pool_idx].surface.id();
}
}
}
let hdr = (fh.frame_header_size as usize).min(data.len());
let tile_len = data.len() - hdr;
if fh.tile_count > 1 {
return Err(Error::DecoderInit(format!(
"AV1 multi-tile frames (tile_count={}) are not supported by this VAAPI backend",
fh.tile_count
)));
}
if tile_len == 0 {
return Err(Error::DecoderInit(
"AV1 frame has no tile data (reduced still picture?)".to_string(),
));
}
let used_pool: std::collections::HashSet<usize> = self
.av1_ctx
.as_ref()
.ok_or_else(|| Error::InvalidState("AV1 context not initialized".to_string()))?
.slot_surfaces
.iter()
.flatten()
.copied()
.collect();
let (pool_idx, surface) = self
.surface_pool
.alloc_excluding(&used_pool)
.ok_or_else(|| Error::DecoderInit("No free VA surface for AV1 frame".to_string()))?;
let out_surface_id = self.surface_pool.entries[pool_idx].surface.id();
let (pic_type, slice_type) =
build_av1_va_buffers(&fh, &sps, ref_frame_map, out_surface_id, tile_len as u32)?;
let pic_buf = self
.context
.create_buffer(pic_type)
.map_err(|e| Error::VaApi(e.to_string()))?;
let slice_buf = self
.context
.create_buffer(slice_type)
.map_err(|e| Error::VaApi(e.to_string()))?;
let mut picture = Picture::<PictureNew, Rc<Surface<DmaBufSurfaceDescriptor>>>::new(
timestamp,
Rc::clone(&self.context),
surface.clone(),
);
picture.add_buffer(pic_buf);
picture.add_buffer(slice_buf);
let slice_data_buf = self
.context
.create_buffer(BufferType::SliceData(data[hdr..].to_vec()))
.map_err(|e| Error::VaApi(e.to_string()))?;
picture.add_buffer(slice_data_buf);
let picture = picture.begin().map_err(|e| Error::VaApi(e.to_string()))?;
let picture = picture.render().map_err(|e| Error::VaApi(e.to_string()))?;
let picture = picture.end().map_err(|e| Error::VaApi(e.to_string()))?;
let _synced: Picture<PictureSync, Rc<Surface<DmaBufSurfaceDescriptor>>> =
picture.sync().map_err(|e| Error::VaApi(e.0.to_string()))?;
self.surface_pool.mark_ready(pool_idx);
{
let ctx = self
.av1_ctx
.as_mut()
.ok_or_else(|| Error::InvalidState("AV1 context not initialized".to_string()))?;
ctx.slot_surfaces[output_slot as usize] = Some(pool_idx);
parser
.dpb_mut()
.commit_decoded(output_slot, &fh, sps.order_hint_bits_minus1 as u32);
}
if !fh.show_frame {
return Ok(None);
}
self.surface_pool.sync_surface(pool_idx)?;
let pixel_data =
read_output_surface_pixels(&surface, &self.stream, av1_image_fourcc(&self.stream))?;
let mut frame = DecodedFrame::new(
self.frame_count,
timestamp as i64,
self.stream.display_width,
self.stream.display_height,
false,
);
frame.pixel_data = pixel_data;
self.frame_count += 1;
parser.dpb_mut().note_displayed();
Ok(Some(frame))
}
}
fn build_av1_va_buffers(
fh: &vacc_parser::av1::Av1FrameHeader,
sps: &Av1Sps,
ref_frame_map: [libva::VASurfaceID; 8],
current_frame: libva::VASurfaceID,
tile_data_size: u32,
) -> Result<(BufferType, BufferType)> {
let lrd_type = |pl: usize| fh.loop_restoration_type[pl].min(3) as u16;
let lr_unit_shift = if fh.uses_lr {
(fh.loop_restoration_size[0].trailing_zeros() - 6) as u16
} else {
0
};
let lr_uv_shift = if fh.uses_lr
&& !sps.mono_chrome
&& fh.loop_restoration_size[1] < fh.loop_restoration_size[0]
{
1
} else {
0
};
let seq_fields = AV1SeqFields::new(
sps.still_picture as u32,
sps.use_128x128_superblock as u32,
sps.enable_filter_intra as u32,
sps.enable_intra_edge_filter as u32,
sps.enable_interintra_compound as u32,
sps.enable_masked_compound as u32,
sps.enable_dual_filter as u32,
sps.enable_order_hint as u32,
sps.enable_jnt_motion as u32,
sps.enable_cdef as u32,
sps.mono_chrome as u32,
sps.color_range as u32,
sps.subsampling_x as u32,
sps.subsampling_y as u32,
sps.chroma_sample_position as u32,
sps.film_grain_params_present as u32,
);
let pic_info = AV1PicInfoFields::new(
fh.frame_type as u32,
fh.show_frame as u32,
fh.showable_frame as u32,
fh.error_resilient_mode as u32,
fh.disable_cdf_update as u32,
fh.allow_screen_content_tools as u32,
fh.force_integer_mv as u32,
fh.allow_intrabc as u32,
fh.use_superres as u32,
fh.allow_high_precision_mv as u32,
fh.is_motion_mode_switchable as u32,
fh.use_ref_frame_mvs as u32,
fh.disable_frame_end_update_cdf as u32,
fh.uniform_tile_spacing_flag as u32,
fh.allow_warped_motion as u32,
0, );
let loop_filter_fields = AV1LoopFilterFields::new(
fh.loop_filter_sharpness,
fh.loop_filter_delta_enabled as u8,
fh.loop_filter_delta_update as u8,
);
let lrd_fields = AV1LoopRestorationFields::new(
lrd_type(0),
lrd_type(1),
lrd_type(2),
lr_unit_shift,
lr_uv_shift,
);
let wm: [AV1WarpedMotionParams; 7] = std::array::from_fn(|i| {
let mut wmmat = [0i32; 8];
wmmat[..6].copy_from_slice(&fh.global_motion_params[i]);
AV1WarpedMotionParams::new(fh.global_motion_type[i] as u32, wmmat, 0)
});
let seg_info = AV1Segmentation::new(
&AV1SegmentInfoFields::new(
fh.segmentation_enabled as u32,
fh.segmentation_update_map as u32,
fh.segmentation_temporal_update as u32,
fh.segmentation_update_data as u32,
),
std::array::from_fn(|seg| std::array::from_fn(|feat| fh.segment_feature_data[seg][feat])),
std::array::from_fn(|seg| fh.segment_feature_enabled[seg]),
);
let film_grain = AV1FilmGrain::new(
&AV1FilmGrainFields::new(0, 0, 0, 0, 0, 0, 0, 0),
0,
0,
[0; 14],
[0; 14],
0,
[0; 10],
[0; 10],
0,
[0; 10],
[0; 10],
[0; 24],
[0; 25],
[0; 25],
0,
0,
0,
0,
0,
0,
);
let qmatrix_fields = AV1QMatrixFields::new(
fh.using_qmatrix as u16,
fh.qm_y as u16,
fh.qm_u as u16,
fh.qm_v as u16,
);
let mode_control_fields = AV1ModeControlFields::new(
fh.delta_q_present as u32,
fh.delta_q_res as u32,
fh.delta_lf_present as u32,
fh.delta_lf_res as u32,
fh.delta_lf_multi as u32,
fh.tx_mode as u32,
fh.reference_select as u32,
fh.reduced_tx_set as u32,
fh.skip_mode_present as u32,
);
let cdef_y_strengths =
std::array::from_fn(|i| (fh.cdef_y_pri_strength[i] << 2) | (fh.cdef_y_sec_strength[i] & 3));
let cdef_uv_strengths = std::array::from_fn(|i| {
(fh.cdef_uv_pri_strength[i] << 2) | (fh.cdef_uv_sec_strength[i] & 3)
});
let mut width_in_sbs = [0u16; 63];
let mut height_in_sbs = [0u16; 63];
width_in_sbs.copy_from_slice(&fh.tile_width_in_sbs_minus_1.0[..63]);
height_in_sbs.copy_from_slice(&fh.tile_height_in_sbs_minus_1.0[..63]);
let pic = PictureParameterBufferAV1::new(
sps.profile,
sps.order_hint_bits_minus1,
if sps.twelve_bit {
2
} else if sps.high_bitdepth {
1
} else {
0
},
sps.matrix_coefficients,
&seq_fields,
current_frame,
current_frame, Vec::new(), (fh.frame_width - 1) as u16,
(fh.frame_height - 1) as u16,
0, 0, ref_frame_map,
fh.ref_frame_idx,
fh.primary_ref_frame,
fh.order_hint as u8,
&seg_info,
&film_grain,
fh.tile_cols as u8,
fh.tile_rows as u8,
width_in_sbs,
height_in_sbs,
0, fh.context_update_tile_id as u16,
&pic_info,
if fh.use_superres {
fh.coded_denom + 9 } else {
8
},
fh.interpolation_filter,
[fh.loop_filter_level[0], fh.loop_filter_level[1]],
fh.loop_filter_level_uv[0],
fh.loop_filter_level_uv[1],
&loop_filter_fields,
fh.loop_filter_ref_deltas,
fh.loop_filter_mode_deltas,
fh.base_q_index,
fh.delta_q_y_dc,
fh.delta_q_u_dc,
fh.delta_q_u_ac,
fh.delta_q_v_dc,
fh.delta_q_v_ac,
&qmatrix_fields,
&mode_control_fields,
fh.cdef_damping,
fh.cdef_bits,
cdef_y_strengths,
cdef_uv_strengths,
&lrd_fields,
&wm,
);
let mut slice = SliceParameterBufferAV1::new();
slice.add_slice_parameter(
tile_data_size,
0,
libva::VA_SLICE_DATA_FLAG_ALL,
0, 0, 0, 0, 0, 0, );
Ok((
BufferType::PictureParameter(PictureParameter::AV1(pic)),
BufferType::SliceParameter(SliceParameter::AV1(slice)),
))
}
fn build_vp9_va_buffers(
fd: &Vp9FrameData,
ref_surfaces: [libva::VASurfaceID; 8],
slice_data_size: u32,
) -> (BufferType, BufferType) {
let pi = &fd.picture_info;
let cc = &fd.color_config;
let lf = &fd.loop_filter;
let sg = &fd.segmentation;
let is_key = fd.frame_is_intra;
let pic_fields = VP9PicFields::new(
cc.subsampling_x as u32,
cc.subsampling_y as u32,
if is_key { 0 } else { 1 },
pi.flags.show_frame as u32,
pi.flags.error_resilient_mode as u32,
pi.flags.intra_only as u32,
if is_key {
0
} else {
pi.flags.allow_high_precision_mv as u32
},
pi.interpolation_filter as u32,
pi.flags.frame_parallel_decoding_mode as u32,
pi.flags.reset_frame_context as u32,
pi.flags.refresh_frame_context as u32,
pi.frame_context_idx as u32,
pi.flags.segmentation_enabled as u32,
sg.flags.segmentation_temporal_update as u32,
sg.flags.segmentation_update_map as u32,
fd.ref_frame_idx[0] as u32, ((pi.ref_frame_sign_bias_mask >> 1) & 1) as u32,
fd.ref_frame_idx[1] as u32, ((pi.ref_frame_sign_bias_mask >> 2) & 1) as u32,
fd.ref_frame_idx[2] as u32, ((pi.ref_frame_sign_bias_mask >> 3) & 1) as u32,
pi.lossless as u32,
);
let segment_pred_probs = if sg.flags.segmentation_temporal_update != 0 {
[
sg.segmentation_pred_prob[0],
sg.segmentation_pred_prob[1],
sg.segmentation_pred_prob[2],
]
} else {
[255, 255, 255]
};
let pic_param = PictureParameterBufferVP9::new(
fd.frame_width as u16,
fd.frame_height as u16,
ref_surfaces,
&pic_fields,
lf.loop_filter_level,
lf.loop_filter_sharpness,
pi.tile_rows_log2,
pi.tile_cols_log2,
fd.compressed_header_offset.min(255) as u8, fd.compressed_header_size.min(u16::MAX as u32) as u16, sg.segmentation_tree_probs[..7].try_into().unwrap(),
segment_pred_probs,
pi.profile as u8,
cc.bit_depth,
);
let seg_param = vp9_segment_params(fd);
let slice_param =
SliceParameterBufferVP9::new(slice_data_size, 0, VA_SLICE_DATA_FLAG_ALL, seg_param);
(
BufferType::PictureParameter(PictureParameter::VP9(pic_param)),
BufferType::SliceParameter(SliceParameter::VP9(slice_param)),
)
}
fn clip_uint(v: i32, bits: u32) -> usize {
v.clamp(0, (1i32 << bits) - 1) as usize
}
fn vp9_segment_params(fd: &Vp9FrameData) -> [SegmentParameterVP9; 8] {
let pi = &fd.picture_info;
let sg = &fd.segmentation;
let lf = &fd.loop_filter;
let enabled = pi.flags.segmentation_enabled != 0;
let abs_or_delta = sg.flags.segmentation_abs_or_delta_update != 0;
let bpp_index = match fd.color_config.bit_depth {
10 => 1,
12 => 2,
_ => 0,
};
let yac_qi = pi.base_q_idx as i32;
let sh = if lf.loop_filter_level >= 32 { 1 } else { 0 };
let lf_delta_enabled = lf.flags.loop_filter_delta_enabled != 0;
let ref_delta = &lf.loop_filter_ref_deltas;
let mode_delta = &lf.loop_filter_mode_deltas;
let mut out: [SegmentParameterVP9; 8] = unsafe { std::mem::zeroed() };
for (i, slot) in out.iter_mut().enumerate().take(if enabled { 8 } else { 1 }) {
let fe = sg.feature_enabled[i];
let q_enabled = enabled && (fe & 1) != 0;
let lf_enabled = enabled && (fe & 2) != 0;
let ref_enabled = enabled && (fe & 4) != 0;
let skip_enabled = (fe & 8) != 0;
let mut qyac: i32 = if q_enabled {
let qv = sg.feature_data[i][0] as i32;
if abs_or_delta { qv } else { yac_qi + qv }
} else {
yac_qi
};
let qydc = clip_uint(qyac + pi.delta_q_y_dc as i32, 8);
let quvdc = clip_uint(qyac + pi.delta_q_uv_dc as i32, 8);
let quvac = clip_uint(qyac + pi.delta_q_uv_ac as i32, 8);
qyac = clip_uint(qyac, 8) as i32;
let lflvl: i32 = if lf_enabled {
let lv = sg.feature_data[i][1] as i32;
if abs_or_delta {
lv
} else {
lf.loop_filter_level as i32 + lv
}
} else {
lf.loop_filter_level as i32
};
let mut flvl = [[0u8; 2]; 4];
if lf_delta_enabled {
flvl[0][0] = clip_uint(lflvl + ref_delta[0] as i32 * (1 << sh), 6) as u8;
flvl[0][1] = flvl[0][0];
for j in 1..4 {
flvl[j][0] = clip_uint(
lflvl + (ref_delta[j] as i32 + mode_delta[0] as i32) * (1 << sh),
6,
) as u8;
flvl[j][1] = clip_uint(
lflvl + (ref_delta[j] as i32 + mode_delta[1] as i32) * (1 << sh),
6,
) as u8;
}
} else {
for row in &mut flvl {
row[0] = clip_uint(lflvl, 6) as u8;
row[1] = row[0];
}
}
*slot = SegmentParameterVP9::new(
&VP9SegmentFlags::new(
if ref_enabled { 1 } else { 0 },
sg.feature_data[i][2] as u16,
if skip_enabled { 1 } else { 0 },
),
flvl,
VP9_AC_QLOOKUP[bpp_index][qyac as usize], VP9_DC_QLOOKUP[bpp_index][qydc], VP9_AC_QLOOKUP[bpp_index][quvac], VP9_DC_QLOOKUP[bpp_index][quvdc], );
}
out
}
unsafe impl Send for VaapiDecoder {}