blob: a899a379f2e95c07ec9362cafd21836917afdc9b [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::rc::Rc;
use anyhow::anyhow;
use anyhow::Context;
use libva::BufferType;
use libva::Display;
use libva::HevcSliceExtFlags;
use libva::IQMatrix;
use libva::IQMatrixBufferHEVC;
use libva::Picture as VaPicture;
use libva::PictureHEVC;
use libva::PictureParameterBufferHEVC;
use libva::SliceParameter;
use libva::SliceParameterBufferHEVC;
use libva::SliceParameterBufferHEVCRext;
use libva::SurfaceMemoryDescriptor;
use crate::backend::vaapi::decoder::DecodedHandle as VADecodedHandle;
use crate::backend::vaapi::decoder::PoolCreationMode;
use crate::backend::vaapi::decoder::VaStreamInfo;
use crate::backend::vaapi::decoder::VaapiBackend;
use crate::backend::vaapi::decoder::VaapiPicture;
use crate::codec::h265::dpb::Dpb;
use crate::codec::h265::parser::NaluType;
use crate::codec::h265::parser::Pps;
use crate::codec::h265::parser::Profile;
use crate::codec::h265::parser::Slice;
use crate::codec::h265::parser::Sps;
use crate::codec::h265::picture::PictureData;
use crate::codec::h265::picture::Reference;
use crate::decoder::stateless::h265::clip3;
use crate::decoder::stateless::h265::RefPicListEntry;
use crate::decoder::stateless::h265::RefPicSet;
use crate::decoder::stateless::h265::StatelessH265DecoderBackend;
use crate::decoder::stateless::h265::H265;
use crate::decoder::stateless::NewPictureError;
use crate::decoder::stateless::NewPictureResult;
use crate::decoder::stateless::NewStatelessDecoderError;
use crate::decoder::stateless::PoolLayer;
use crate::decoder::stateless::StatelessBackendResult;
use crate::decoder::stateless::StatelessDecoder;
use crate::decoder::stateless::StatelessDecoderBackend;
use crate::decoder::stateless::StatelessDecoderBackendPicture;
use crate::decoder::BlockingMode;
use crate::Resolution;
enum ScalingListType {
Sps,
Pps,
None,
}
impl VaStreamInfo for &Sps {
fn va_profile(&self) -> anyhow::Result<i32> {
let profile_idc = self.profile_tier_level.general_profile_idc;
let profile = Profile::try_from(profile_idc).map_err(|err| anyhow!(err))?;
let bit_depth = std::cmp::max(
self.bit_depth_luma_minus8 + 8,
self.bit_depth_chroma_minus8 + 8,
);
let chroma_format_idc = self.chroma_format_idc;
let err = Err(anyhow!(
"Invalid combination of profile, bit depth an chroma_format_idc: ({:?}, {}, {}",
profile,
bit_depth,
chroma_format_idc
));
// TODO: This can still be much improved in light of table A.2.
match profile {
Profile::Main | Profile::MainStill | Profile::Main10 => {
match (bit_depth, chroma_format_idc) {
(8, 0) | (8, 1) => Ok(libva::VAProfile::VAProfileHEVCMain),
(8, 3) => Ok(libva::VAProfile::VAProfileHEVCMain444),
(10, 0) | (10, 1) => Ok(libva::VAProfile::VAProfileHEVCMain10),
(10, 2) => Ok(libva::VAProfile::VAProfileHEVCMain422_10),
(12, 1) => Ok(libva::VAProfile::VAProfileHEVCMain12),
(12, 2) => Ok(libva::VAProfile::VAProfileHEVCMain422_12),
(12, 3) => Ok(libva::VAProfile::VAProfileHEVCMain444_12),
_ => err,
}
}
// See table A.4.
Profile::ScalableMain => match (bit_depth, chroma_format_idc) {
(8, 1) => Ok(libva::VAProfile::VAProfileHEVCSccMain),
(8, 3) => Ok(libva::VAProfile::VAProfileHEVCSccMain444),
(10, 1) => Ok(libva::VAProfile::VAProfileHEVCSccMain10),
(10, 3) => Ok(libva::VAProfile::VAProfileHEVCSccMain444_10),
_ => err,
},
_ => unimplemented!("Adding more profile support based on A.3. is still TODO"),
}
}
fn rt_format(&self) -> anyhow::Result<u32> {
let bit_depth = std::cmp::max(
self.bit_depth_luma_minus8 + 8,
self.bit_depth_chroma_minus8 + 8,
);
let chroma_format_idc = self.chroma_format_idc;
match (bit_depth, chroma_format_idc) {
(8, 0) | (8, 1) => Ok(libva::VA_RT_FORMAT_YUV420),
(8, 2) => Ok(libva::VA_RT_FORMAT_YUV422),
(8, 3) => Ok(libva::VA_RT_FORMAT_YUV444),
(9, 0) | (9, 1) | (10, 0) | (10, 1) => Ok(libva::VA_RT_FORMAT_YUV420_10),
(9, 2) | (10, 2) => Ok(libva::VA_RT_FORMAT_YUV422_10),
(9, 3) | (10, 3) => Ok(libva::VA_RT_FORMAT_YUV444_10),
(11, 0) | (11, 1) | (12, 0) | (12, 1) => Ok(libva::VA_RT_FORMAT_YUV420_12),
(11, 2) | (12, 2) => Ok(libva::VA_RT_FORMAT_YUV422_12),
(11, 3) | (12, 3) => Ok(libva::VA_RT_FORMAT_YUV444_12),
_ => Err(anyhow!(
"unsupported bit depth/chroma format pair {}, {}",
bit_depth,
chroma_format_idc
)),
}
}
fn min_num_surfaces(&self) -> usize {
self.max_dpb_size() + 4
}
fn coded_size(&self) -> Resolution {
Resolution::from((self.width().into(), self.height().into()))
}
fn visible_rect(&self) -> ((u32, u32), (u32, u32)) {
let rect = self.visible_rectangle();
((rect.min.x, rect.min.y), (rect.max.x, rect.max.y))
}
}
fn build_slice_ref_pic_list<M: SurfaceMemoryDescriptor>(
ref_pic_list: &[Option<RefPicListEntry<VADecodedHandle<M>>>; 16],
va_references: &[PictureHEVC; 15],
) -> [u8; 15] {
let mut va_refs = [0xff; 15];
for (ref_pic_list_idx, ref_pic_list_entry) in ref_pic_list.iter().enumerate() {
if ref_pic_list_idx == 15 {
break;
}
if let Some(ref_pic_list_entry) = ref_pic_list_entry {
for (va_ref_idx, va_ref) in va_references.iter().enumerate() {
if va_ref.picture_id() == libva::VA_INVALID_ID {
break;
}
let pic_order_cnt = match ref_pic_list_entry {
RefPicListEntry::CurrentPicture(p) => p.pic_order_cnt_val,
RefPicListEntry::DpbEntry(p) => p.0.borrow().pic_order_cnt_val,
};
if va_ref.pic_order_cnt() == pic_order_cnt {
va_refs[ref_pic_list_idx] = va_ref_idx as u8;
}
}
}
}
va_refs
}
fn va_rps_flag<M: SurfaceMemoryDescriptor>(
hevc_pic: &PictureData,
rps: &RefPicSet<VADecodedHandle<M>>,
) -> u32 {
if rps
.ref_pic_set_st_curr_before
.iter()
.flatten()
.any(|dpb_entry| *dpb_entry.0.borrow() == *hevc_pic)
{
libva::VA_PICTURE_HEVC_RPS_ST_CURR_BEFORE
} else if rps
.ref_pic_set_st_curr_after
.iter()
.flatten()
.any(|dpb_entry| *dpb_entry.0.borrow() == *hevc_pic)
{
libva::VA_PICTURE_HEVC_RPS_ST_CURR_AFTER
} else if rps
.ref_pic_set_lt_curr
.iter()
.flatten()
.any(|dpb_entry| *dpb_entry.0.borrow() == *hevc_pic)
{
libva::VA_PICTURE_HEVC_RPS_LT_CURR
} else {
0
}
}
/// Builds an invalid VaPictureHEVC. These pictures are used to fill empty
/// array slots there is no data to fill them with.
fn build_invalid_va_hevc_pic() -> libva::PictureHEVC {
libva::PictureHEVC::new(libva::VA_INVALID_ID, 0, libva::VA_PICTURE_HEVC_INVALID)
}
fn fill_va_hevc_pic<M: SurfaceMemoryDescriptor>(
hevc_pic: &PictureData,
surface_id: libva::VASurfaceID,
rps: &RefPicSet<VADecodedHandle<M>>,
) -> libva::PictureHEVC {
let mut flags = 0;
if matches!(hevc_pic.reference(), Reference::LongTerm) {
flags |= libva::VA_PICTURE_HEVC_LONG_TERM_REFERENCE;
}
flags |= va_rps_flag(hevc_pic, rps);
libva::PictureHEVC::new(surface_id, hevc_pic.pic_order_cnt_val, flags)
}
fn is_range_extension_profile(va_profile: libva::VAProfile::Type) -> bool {
matches!(
va_profile,
libva::VAProfile::VAProfileHEVCMain422_10
| libva::VAProfile::VAProfileHEVCMain444
| libva::VAProfile::VAProfileHEVCMain444_10
| libva::VAProfile::VAProfileHEVCMain12
| libva::VAProfile::VAProfileHEVCMain422_12
| libva::VAProfile::VAProfileHEVCMain444_12
)
}
fn is_scc_ext_profile(va_profile: libva::VAProfile::Type) -> bool {
matches!(
va_profile,
libva::VAProfile::VAProfileHEVCSccMain
| libva::VAProfile::VAProfileHEVCSccMain10
| libva::VAProfile::VAProfileHEVCSccMain444
| libva::VAProfile::VAProfileHEVCMain444_10,
)
}
fn build_picture_rext(sps: &Sps, pps: &Pps) -> anyhow::Result<BufferType> {
let sps_rext = &sps.range_extension;
let pps_rext = &pps.range_extension;
let range_extension_pic_fields = libva::HevcRangeExtensionPicFields::new(
sps_rext.transform_skip_rotation_enabled_flag as u32,
sps_rext.transform_skip_context_enabled_flag as u32,
sps_rext.implicit_rdpcm_enabled_flag as u32,
sps_rext.explicit_rdpcm_enabled_flag as u32,
sps_rext.extended_precision_processing_flag as u32,
sps_rext.intra_smoothing_disabled_flag as u32,
sps_rext.high_precision_offsets_enabled_flag as u32,
sps_rext.persistent_rice_adaptation_enabled_flag as u32,
sps_rext.cabac_bypass_alignment_enabled_flag as u32,
pps_rext.cross_component_prediction_enabled_flag as u32,
pps_rext.chroma_qp_offset_list_enabled_flag as u32,
);
let rext = libva::PictureParameterBufferHEVCRext::new(
&range_extension_pic_fields,
pps_rext.diff_cu_chroma_qp_offset_depth as u8,
pps_rext.chroma_qp_offset_list_len_minus1 as u8,
pps_rext.log2_sao_offset_scale_luma as u8,
pps_rext.log2_sao_offset_scale_chroma as u8,
pps_rext.log2_max_transform_skip_block_size_minus2 as u8,
pps_rext.cb_qp_offset_list.map(|x| x as i8),
pps_rext.cr_qp_offset_list.map(|x| x as i8),
);
Ok(BufferType::PictureParameter(
libva::PictureParameter::HEVCRext(rext),
))
}
fn build_picture_scc(sps: &Sps, pps: &Pps) -> anyhow::Result<BufferType> {
let sps_scc = &sps.scc_extension;
let pps_scc = &pps.scc_extension;
let scc_pic_fields = libva::HevcScreenContentPicFields::new(
pps_scc.curr_pic_ref_enabled_flag as u32,
sps_scc.palette_mode_enabled_flag as u32,
sps_scc.motion_vector_resolution_control_idc as u32,
sps_scc.intra_boundary_filtering_disabled_flag as u32,
pps_scc.residual_adaptive_colour_transform_enabled_flag as u32,
pps_scc.slice_act_qp_offsets_present_flag as u32,
);
let (predictor_palette_entries, predictor_palette_size) =
if pps_scc.palette_predictor_initializers_present_flag {
(
pps_scc
.palette_predictor_initializer
.map(|outer| outer.map(u16::from)),
pps_scc.num_palette_predictor_initializers,
)
} else if sps_scc.palette_predictor_initializers_present_flag {
(
sps_scc
.palette_predictor_initializer
.map(|outer| outer.map(|inner| inner as u16)),
sps_scc.num_palette_predictor_initializer_minus1 + 1,
)
} else {
([[0; 128]; 3], 0)
};
let scc = libva::PictureParameterBufferHEVCScc::new(
&scc_pic_fields,
sps_scc.palette_max_size,
sps_scc.delta_palette_max_predictor_size,
predictor_palette_size,
predictor_palette_entries,
pps_scc.act_y_qp_offset_plus5,
pps_scc.act_cb_qp_offset_plus5,
pps_scc.act_cr_qp_offset_plus3,
);
Ok(BufferType::PictureParameter(
libva::PictureParameter::HEVCScc(scc),
))
}
fn build_pic_param<M: SurfaceMemoryDescriptor>(
_: &Slice,
current_picture: &PictureData,
current_surface_id: libva::VASurfaceID,
dpb: &Dpb<VADecodedHandle<M>>,
rps: &RefPicSet<VADecodedHandle<M>>,
sps: &Sps,
pps: &Pps,
) -> anyhow::Result<(BufferType, [PictureHEVC; 15])> {
let curr_pic = fill_va_hevc_pic(current_picture, current_surface_id, rps);
let mut reference_frames = vec![];
for ref_pic in dpb.get_all_references() {
let surface_id = ref_pic.1.borrow().surface().id();
let ref_pic = fill_va_hevc_pic(&ref_pic.0.borrow(), surface_id, rps);
reference_frames.push(ref_pic);
}
// RefPicListL0 and RefPicListL1 may signal that they want to refer to
// the current picture. We must tell VA that it is a reference as it is
// not in the DPB at this point.
if pps.scc_extension.curr_pic_ref_enabled_flag {
if reference_frames.len() >= 15 {
log::warn!(
"Bug: Trying to set the current picture as a VA reference, but the VA DPB is full."
)
} else {
reference_frames.push(curr_pic);
}
}
for _ in reference_frames.len()..15 {
reference_frames.push(build_invalid_va_hevc_pic());
}
let reference_frames = reference_frames.try_into();
let reference_frames = match reference_frames {
Ok(va_refs) => va_refs,
Err(_) => {
// Can't panic, we guarantee len() == 15.
panic!("Bug: wrong number of references, expected 15");
}
};
let pic_fields = libva::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.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,
/* lets follow the FFMPEG and GStreamer train and set these to false */
0,
0,
);
let rap_pic_flag = current_picture.nalu_type as u32 >= NaluType::BlaWLp as u32
&& current_picture.nalu_type as u32 <= NaluType::CraNut as u32;
let slice_parsing_fields = libva::HevcSliceParsingFields::new(
pps.lists_modification_present_flag as u32,
sps.long_term_ref_pics_present_flag as u32,
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.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.deblocking_filter_disabled_flag as u32,
pps.slice_segment_header_extension_present_flag as u32,
rap_pic_flag as u32,
current_picture.nalu_type.is_idr() as u32,
current_picture.nalu_type.is_irap() as u32,
);
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[usize::from(sps.max_sub_layers_minus1)],
sps.bit_depth_luma_minus8,
sps.bit_depth_chroma_minus8,
sps.pcm_sample_bit_depth_luma_minus1,
sps.pcm_sample_bit_depth_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,
sps.log2_min_pcm_luma_coding_block_size_minus3,
sps.log2_diff_max_min_pcm_luma_coding_block_size,
sps.max_transform_hierarchy_depth_intra,
sps.max_transform_hierarchy_depth_inter,
pps.init_qp_minus26,
pps.diff_cu_qp_delta_depth,
pps.cb_qp_offset,
pps.cr_qp_offset,
pps.log2_parallel_merge_level_minus2,
pps.num_tile_columns_minus1,
pps.num_tile_rows_minus1,
pps.column_width_minus1.map(|x| x as u16),
pps.row_height_minus1.map(|x| x as u16),
&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.beta_offset_div2,
pps.tc_offset_div2,
pps.num_extra_slice_header_bits,
current_picture.short_term_ref_pic_set_size_bits,
);
Ok((
BufferType::PictureParameter(libva::PictureParameter::HEVC(pic_param)),
reference_frames,
))
}
fn find_scaling_list(sps: &Sps, pps: &Pps) -> ScalingListType {
if pps.scaling_list_data_present_flag
|| (sps.scaling_list_enabled_flag && !sps.scaling_list_data_present_flag)
{
ScalingListType::Pps
} else if sps.scaling_list_enabled_flag && sps.scaling_list_data_present_flag {
ScalingListType::Sps
} else {
ScalingListType::None
}
}
fn build_iq_matrix(sps: &Sps, pps: &Pps) -> BufferType {
let scaling_lists = match find_scaling_list(sps, pps) {
ScalingListType::Sps => &sps.scaling_list,
ScalingListType::Pps => &pps.scaling_list,
ScalingListType::None => panic!("No scaling list data available"),
};
let mut scaling_list_32x32 = [[0; 64]; 2];
for i in (0..6).step_by(3) {
for j in 0..64 {
scaling_list_32x32[i / 3][j] = scaling_lists.scaling_list_32x32[i][j];
}
}
let mut scaling_list_dc_32x32 = [0; 2];
for i in (0..6).step_by(3) {
scaling_list_dc_32x32[i / 3] =
(scaling_lists.scaling_list_dc_coef_minus8_32x32[i] + 8) as u8;
}
let mut scaling_list_4x4 = [[0; 16]; 6];
let mut scaling_list_8x8 = [[0; 64]; 6];
let mut scaling_list_16x16 = [[0; 64]; 6];
let mut scaling_list_32x32_r = [[0; 64]; 2];
(0..6).for_each(|i| {
super::get_raster_from_up_right_diagonal_4x4(
scaling_lists.scaling_list_4x4[i],
&mut scaling_list_4x4[i],
);
super::get_raster_from_up_right_diagonal_8x8(
scaling_lists.scaling_list_8x8[i],
&mut scaling_list_8x8[i],
);
super::get_raster_from_up_right_diagonal_8x8(
scaling_lists.scaling_list_16x16[i],
&mut scaling_list_16x16[i],
);
});
(0..2).for_each(|i| {
super::get_raster_from_up_right_diagonal_8x8(
scaling_list_32x32[i],
&mut scaling_list_32x32_r[i],
);
});
BufferType::IQMatrix(IQMatrix::HEVC(IQMatrixBufferHEVC::new(
scaling_list_4x4,
scaling_list_8x8,
scaling_list_16x16,
scaling_list_32x32_r,
scaling_lists
.scaling_list_dc_coef_minus8_16x16
.map(|x| (x + 8) as u8),
scaling_list_dc_32x32,
)))
}
impl<M: SurfaceMemoryDescriptor + 'static> VaapiBackend<M> {
fn submit_last_slice(
&mut self,
picture: &mut <Self as StatelessDecoderBackendPicture<H265>>::Picture,
) -> anyhow::Result<()> {
if let Some(last_slice) = picture.last_slice.take() {
let metadata = self.metadata_state.get_parsed()?;
let context = &metadata.context;
let picture = &mut picture.picture;
let slice_param = BufferType::SliceParameter(SliceParameter::HEVC(last_slice.0));
let slice_param = context.create_buffer(slice_param)?;
picture.add_buffer(slice_param);
if let Some(slice_param_rext) = last_slice.1 {
let slice_param_rext =
BufferType::SliceParameter(SliceParameter::HEVCRext(slice_param_rext));
let slice_param_rext = context.create_buffer(slice_param_rext)?;
picture.add_buffer(slice_param_rext);
}
let slice_data = BufferType::SliceData(last_slice.2);
let slice_data = context.create_buffer(slice_data)?;
picture.add_buffer(slice_data);
}
Ok(())
}
}
pub struct VaapiH265Picture<Picture> {
picture: Picture,
// We are always one slice behind, so that we can mark the last one in
// submit_picture()
last_slice: Option<(
SliceParameterBufferHEVC,
Option<SliceParameterBufferHEVCRext>,
Vec<u8>,
)>,
va_references: [PictureHEVC; 15],
}
impl<M: SurfaceMemoryDescriptor + 'static> StatelessDecoderBackendPicture<H265>
for VaapiBackend<M>
{
type Picture = VaapiH265Picture<VaapiPicture<M>>;
}
impl<M: SurfaceMemoryDescriptor + 'static> StatelessH265DecoderBackend for VaapiBackend<M> {
fn new_sequence(&mut self, sps: &Sps) -> StatelessBackendResult<()> {
self.new_sequence(sps, PoolCreationMode::Highest)
}
fn new_picture(
&mut self,
coded_resolution: Resolution,
timestamp: u64,
) -> NewPictureResult<Self::Picture> {
let layer = PoolLayer::Layer(coded_resolution);
let pool = self
.frame_pool(layer)
.pop()
.ok_or(NewPictureError::NoFramePool(coded_resolution))?;
let surface = pool
.get_surface()
.ok_or(NewPictureError::OutOfOutputBuffers)?;
let metadata = self.metadata_state.get_parsed()?;
Ok(VaapiH265Picture {
picture: VaPicture::new(timestamp, Rc::clone(&metadata.context), surface),
last_slice: Default::default(),
va_references: Default::default(),
})
}
fn begin_picture(
&mut self,
picture: &mut Self::Picture,
picture_data: &PictureData,
sps: &Sps,
pps: &Pps,
dpb: &Dpb<Self::Handle>,
rps: &RefPicSet<Self::Handle>,
slice: &Slice,
) -> crate::decoder::stateless::StatelessBackendResult<()> {
let metadata = self.metadata_state.get_parsed()?;
let context = &metadata.context;
let surface_id = picture.picture.surface().id();
let (pic_param, reference_frames) =
build_pic_param(slice, picture_data, surface_id, dpb, rps, sps, pps)?;
picture.va_references = reference_frames;
let picture = &mut picture.picture;
let pic_param = context
.create_buffer(pic_param)
.context("while creating picture parameter buffer")?;
picture.add_buffer(pic_param);
if !matches!(find_scaling_list(sps, pps), ScalingListType::None) {
let iq_matrix = build_iq_matrix(sps, pps);
let iq_matrix = context
.create_buffer(iq_matrix)
.context("while creating IQ matrix buffer")?;
picture.add_buffer(iq_matrix);
}
let va_profile = sps.va_profile()?;
if is_range_extension_profile(va_profile) || is_scc_ext_profile(va_profile) {
let rext = build_picture_rext(sps, pps)?;
let rext = context
.create_buffer(rext)
.context("while creating picture parameter range extension buffer")?;
picture.add_buffer(rext);
if is_scc_ext_profile(va_profile) {
let scc = build_picture_scc(sps, pps)?;
let scc = context
.create_buffer(scc)
.context("while creating picture screen content coding buffer")?;
picture.add_buffer(scc);
}
}
Ok(())
}
fn decode_slice(
&mut self,
picture: &mut Self::Picture,
slice: &Slice,
sps: &Sps,
_: &Pps,
ref_pic_list0: &[Option<RefPicListEntry<Self::Handle>>; 16],
ref_pic_list1: &[Option<RefPicListEntry<Self::Handle>>; 16],
) -> crate::decoder::stateless::StatelessBackendResult<()> {
self.submit_last_slice(picture)?;
let hdr = &slice.header;
let va_references = &picture.va_references;
let ref_pic_list0 = build_slice_ref_pic_list(ref_pic_list0, va_references);
let ref_pic_list1 = build_slice_ref_pic_list(ref_pic_list1, va_references);
let long_slice_flags = libva::HevcLongSliceFlags::new(
0,
hdr.dependent_slice_segment_flag as u32,
hdr.type_ as u32,
hdr.colour_plane_id as u32,
hdr.sao_luma_flag as u32,
hdr.sao_chroma_flag as u32,
hdr.mvd_l1_zero_flag as u32,
hdr.cabac_init_flag as u32,
hdr.temporal_mvp_enabled_flag as u32,
hdr.deblocking_filter_disabled_flag as u32,
hdr.collocated_from_l0_flag as u32,
hdr.loop_filter_across_slices_enabled_flag as u32,
);
let collocated_ref_idx = if hdr.temporal_mvp_enabled_flag {
hdr.collocated_ref_idx
} else {
0xff
};
let pwt = &hdr.pred_weight_table;
let mut delta_luma_weight_l0: [i8; 15usize] = Default::default();
let mut luma_offset_l0: [i8; 15usize] = Default::default();
let mut delta_chroma_weight_l0: [[i8; 2usize]; 15usize] = Default::default();
let mut chroma_offset_l0: [[i8; 2usize]; 15usize] = Default::default();
let mut delta_luma_weight_l1: [i8; 15usize] = Default::default();
let mut luma_offset_l1: [i8; 15usize] = Default::default();
let mut delta_chroma_weight_l1: [[i8; 2usize]; 15usize] = Default::default();
let mut chroma_offset_l1: [[i8; 2usize]; 15usize] = Default::default();
for i in 0..15 {
delta_luma_weight_l0[i] = pwt.delta_luma_weight_l0[i];
luma_offset_l0[i] = pwt.luma_offset_l0[i];
if hdr.type_.is_b() {
delta_luma_weight_l1[i] = pwt.delta_luma_weight_l1[i];
luma_offset_l1[i] = pwt.luma_offset_l1[i];
}
for j in 0..2 {
delta_chroma_weight_l0[i][j] = pwt.delta_chroma_weight_l0[i][j];
let delta_chroma_offset = pwt.delta_chroma_offset_l0[i][j];
let chroma_weight_l0 = (1 << pwt.chroma_log2_weight_denom)
+ i32::from(pwt.delta_chroma_weight_l0[i][j]);
let offset = sps.wp_offset_half_range_c as i32 + delta_chroma_offset as i32
- ((sps.wp_offset_half_range_c as i32 * chroma_weight_l0)
>> pwt.chroma_log2_weight_denom);
chroma_offset_l0[i][j] = clip3(
-(sps.wp_offset_half_range_c as i32),
(sps.wp_offset_half_range_c - 1) as i32,
offset,
) as _;
if hdr.type_.is_b() {
delta_chroma_weight_l1[i][j] = pwt.delta_chroma_weight_l1[i][j];
let delta_chroma_offset = pwt.delta_chroma_offset_l1[i][j];
let chroma_weight_l1 = (1 << pwt.chroma_log2_weight_denom)
+ i32::from(pwt.delta_chroma_weight_l1[i][j]);
let offset = sps.wp_offset_half_range_c as i32 + delta_chroma_offset as i32
- ((sps.wp_offset_half_range_c as i32 * chroma_weight_l1)
>> pwt.chroma_log2_weight_denom);
chroma_offset_l1[i][j] = clip3(
-(sps.wp_offset_half_range_c as i32),
(sps.wp_offset_half_range_c - 1) as i32,
offset,
) as _;
}
}
}
let slice_param = SliceParameterBufferHEVC::new(
slice.nalu.size as u32,
0,
libva::VA_SLICE_DATA_FLAG_ALL,
(hdr.header_bit_size / 8) as _,
hdr.segment_address,
[ref_pic_list0, ref_pic_list1],
&long_slice_flags,
collocated_ref_idx,
hdr.num_ref_idx_l0_active_minus1,
hdr.num_ref_idx_l1_active_minus1,
hdr.qp_delta,
hdr.cb_qp_offset,
hdr.cr_qp_offset,
hdr.beta_offset_div2,
hdr.tc_offset_div2,
pwt.luma_log2_weight_denom,
pwt.delta_chroma_log2_weight_denom,
delta_luma_weight_l0,
luma_offset_l0,
delta_chroma_weight_l0,
chroma_offset_l0,
delta_luma_weight_l1,
luma_offset_l1,
delta_chroma_weight_l1,
chroma_offset_l1,
hdr.five_minus_max_num_merge_cand,
hdr.num_entry_point_offsets as _,
0,
hdr.n_emulation_prevention_bytes as _,
);
let va_profile = sps.va_profile()?;
let slice_param_ext =
if is_range_extension_profile(va_profile) || is_scc_ext_profile(va_profile) {
let slice_ext_flags = HevcSliceExtFlags::new(
hdr.cu_chroma_qp_offset_enabled_flag as u32,
hdr.use_integer_mv_flag as u32,
);
let slice_param_ext = SliceParameterBufferHEVCRext::new(
luma_offset_l0.map(i16::from),
chroma_offset_l0.map(|outer| outer.map(i16::from)),
luma_offset_l1.map(i16::from),
chroma_offset_l1.map(|outer| outer.map(i16::from)),
&slice_ext_flags,
hdr.slice_act_y_qp_offset,
hdr.slice_act_cb_qp_offset,
hdr.slice_act_cr_qp_offset,
);
Some(slice_param_ext)
} else {
None
};
let slice_data = Vec::from(slice.nalu.as_ref());
picture.last_slice = Some((slice_param, slice_param_ext, slice_data));
Ok(())
}
fn submit_picture(
&mut self,
mut picture: Self::Picture,
) -> StatelessBackendResult<Self::Handle> {
if let Some(last_slice) = &mut picture.last_slice {
last_slice.0.set_as_last();
}
self.submit_last_slice(&mut picture)?;
self.process_picture::<H265>(picture.picture)
}
}
impl<M: SurfaceMemoryDescriptor + 'static> StatelessDecoder<H265, VaapiBackend<M>> {
// Creates a new instance of the decoder using the VAAPI backend.
pub fn new_vaapi<S>(
display: Rc<Display>,
blocking_mode: BlockingMode,
) -> Result<Self, NewStatelessDecoderError>
where
M: From<S>,
S: From<M>,
{
Self::new(VaapiBackend::new(display, false), blocking_mode)
}
}
#[cfg(test)]
mod tests {
use libva::Display;
use crate::bitstream_utils::NalIterator;
use crate::codec::h265::parser::Nalu;
use crate::decoder::stateless::h265::H265;
use crate::decoder::stateless::tests::test_decode_stream;
use crate::decoder::stateless::tests::TestStream;
use crate::decoder::stateless::StatelessDecoder;
use crate::decoder::BlockingMode;
use crate::utils::simple_playback_loop;
use crate::utils::simple_playback_loop_owned_frames;
use crate::DecodedFormat;
/// Run `test` using the vaapi decoder, in both blocking and non-blocking modes.
fn test_decoder_vaapi(
test: &TestStream,
output_format: DecodedFormat,
blocking_mode: BlockingMode,
) {
let display = Display::open().unwrap();
let decoder = StatelessDecoder::<H265, _>::new_vaapi::<()>(display, blocking_mode).unwrap();
test_decode_stream(
|d, s, f| {
simple_playback_loop(
d,
NalIterator::<Nalu>::new(s),
f,
&mut simple_playback_loop_owned_frames,
output_format,
blocking_mode,
)
},
decoder,
test,
true,
false,
);
}
#[test]
// Ignore this test by default as it requires libva-compatible hardware.
#[ignore]
fn test_64x64_progressive_i_block() {
use crate::decoder::stateless::h265::tests::DECODE_64X64_PROGRESSIVE_I;
test_decoder_vaapi(
&DECODE_64X64_PROGRESSIVE_I,
DecodedFormat::NV12,
BlockingMode::Blocking,
);
}
#[test]
// Ignore this test by default as it requires libva-compatible hardware.
#[ignore]
fn test_64x64_progressive_i_nonblock() {
use crate::decoder::stateless::h265::tests::DECODE_64X64_PROGRESSIVE_I;
test_decoder_vaapi(
&DECODE_64X64_PROGRESSIVE_I,
DecodedFormat::NV12,
BlockingMode::NonBlocking,
);
}
#[test]
// Ignore this test by default as it requires libva-compatible hardware.
#[ignore]
fn test_64x64_progressive_i_p_block() {
use crate::decoder::stateless::h265::tests::DECODE_64X64_PROGRESSIVE_I_P;
test_decoder_vaapi(
&DECODE_64X64_PROGRESSIVE_I_P,
DecodedFormat::NV12,
BlockingMode::Blocking,
);
}
#[test]
// Ignore this test by default as it requires libva-compatible hardware.
#[ignore]
fn test_64x64_progressive_i_p_nonblock() {
use crate::decoder::stateless::h265::tests::DECODE_64X64_PROGRESSIVE_I_P;
test_decoder_vaapi(
&DECODE_64X64_PROGRESSIVE_I_P,
DecodedFormat::NV12,
BlockingMode::NonBlocking,
);
}
#[test]
// Ignore this test by default as it requires libva-compatible hardware.
#[ignore]
fn test_64x64_progressive_i_p_b_p_block() {
use crate::decoder::stateless::h265::tests::DECODE_64X64_PROGRESSIVE_I_P_B_P;
test_decoder_vaapi(
&DECODE_64X64_PROGRESSIVE_I_P_B_P,
DecodedFormat::NV12,
BlockingMode::Blocking,
);
}
#[test]
// Ignore this test by default as it requires libva-compatible hardware.
#[ignore]
fn test_64x64_progressive_i_p_b_p_nonblock() {
use crate::decoder::stateless::h265::tests::DECODE_64X64_PROGRESSIVE_I_P_B_P;
test_decoder_vaapi(
&DECODE_64X64_PROGRESSIVE_I_P_B_P,
DecodedFormat::NV12,
BlockingMode::NonBlocking,
);
}
}