use std::collections::VecDeque;
use std::rc::Rc;
use crate::{DecodeError, VideoDecoder, VideoDecoderConfig, VideoOutputPreference};
use mediaway_common::{
Bytes, Packet, PixelFormat, StreamInfo, VideoFrame, VideoFrameStorage, VideoGeometry,
};
use mediaway_sw::h264::{BitReader, split_annex_b};
use cros_libva::{
BufferType, Config, Context, Display, HevcLongSliceFlags, HevcPicFields,
HevcSliceParsingFields, IQMatrix, IQMatrixBufferHEVC, Picture, PictureHEVC, PictureParameter,
PictureParameterBufferHEVC, SliceParameter, SliceParameterBufferHEVC, Surface, VA_FOURCC_NV12,
VA_INVALID_SURFACE, VA_PICTURE_HEVC_INVALID, VA_PICTURE_HEVC_RPS_ST_CURR_BEFORE,
VA_RT_FORMAT_YUV420, VAConfigAttrib, VAConfigAttribType, VAEntrypoint, VAImageFormat,
};
use super::codec::hevc_profile_candidates;
use super::dpb::derive_pic_order_cnt_msb;
use super::hevc_dpb::{self, HEVC_SURFACE_POOL_SIZE, HevcDpb};
use super::hevc_nal::{HevcNalUnit, HevcNalUnitType};
use super::hevc_pps::HevcPps;
use super::hevc_slice::HevcSliceSegmentHeader;
use super::hevc_sps::HevcSps;
use super::nv12::copy_nv12_from_planes;
struct HevcPipeline {
_config: Config,
context: Rc<Context>,
surfaces: Vec<Option<Surface<()>>>,
dpb: HevcDpb,
next_slot: usize,
coded_width: u32,
coded_height: u32,
nv12_format: VAImageFormat,
}
pub(crate) struct VaapiHevcDecoder {
display: Rc<Display>,
pipeline: Option<HevcPipeline>,
sps: Option<HevcSps>,
pps: Option<HevcPps>,
info: StreamInfo,
declared_width: u32,
declared_height: u32,
pending: VecDeque<VideoFrame>,
flushed: bool,
prev_poc_msb: i32,
prev_poc_lsb: u32,
}
impl VaapiHevcDecoder {
pub(crate) fn open(config: &VideoDecoderConfig) -> Result<Self, DecodeError> {
validate(config)?;
if config.output != VideoOutputPreference::CpuFramesOk {
return Err(DecodeError::Unsupported);
}
let display: Rc<Display> = Display::open().ok_or(DecodeError::Unsupported)?;
let (sps, pps) = seed_params(&config.extra_data);
Ok(Self {
display,
pipeline: None,
sps,
pps,
info: stream_info_from(config),
declared_width: config.width,
declared_height: config.height,
pending: VecDeque::new(),
flushed: false,
prev_poc_msb: 0,
prev_poc_lsb: 0,
})
}
fn ensure_pipeline(&mut self, sps: &HevcSps) -> Result<(), DecodeError> {
if self.pipeline.is_some() {
return Ok(());
}
let coded_width = sps.pic_width_in_luma_samples;
let coded_height = sps.pic_height_in_luma_samples;
if coded_width == 0 || coded_height == 0 {
return Err(DecodeError::InvalidInput);
}
if self.declared_width != 0
&& self.declared_height != 0
&& (coded_width > round_up_8(self.declared_width)
|| coded_height > round_up_8(self.declared_height))
{
return Err(DecodeError::InvalidInput);
}
let candidates = hevc_profile_candidates(sps.general_profile_idc)?;
let supported = self
.display
.query_config_profiles()
.map_err(|_| DecodeError::Backend)?;
let mut chosen = None;
for profile in candidates {
if !supported.contains(&profile) {
continue;
}
let entrypoints = self
.display
.query_config_entrypoints(profile)
.map_err(|_| DecodeError::Backend)?;
if entrypoints.contains(&VAEntrypoint::VAEntrypointVLD) {
chosen = Some(profile);
break;
}
}
let profile = chosen.ok_or(DecodeError::Unsupported)?;
let attrs = vec![VAConfigAttrib {
type_: VAConfigAttribType::VAConfigAttribRTFormat,
value: VA_RT_FORMAT_YUV420,
}];
let config = self
.display
.create_config(attrs, profile, VAEntrypoint::VAEntrypointVLD)
.map_err(|_| DecodeError::Backend)?;
let surfaces = self
.display
.create_surfaces(
VA_RT_FORMAT_YUV420,
Some(VA_FOURCC_NV12),
coded_width,
coded_height,
None,
vec![(); HEVC_SURFACE_POOL_SIZE],
)
.map_err(|_| DecodeError::Backend)?;
let context = self
.display
.create_context(&config, coded_width, coded_height, Some(&surfaces), true)
.map_err(|_| DecodeError::Backend)?;
let nv12_format = self
.display
.query_image_formats()
.map_err(|_| DecodeError::Backend)?
.into_iter()
.find(|f| f.fourcc == VA_FOURCC_NV12)
.ok_or(DecodeError::Unsupported)?;
self.pipeline = Some(HevcPipeline {
_config: config,
context,
surfaces: surfaces.into_iter().map(Some).collect(),
dpb: HevcDpb::new(),
next_slot: 0,
coded_width,
coded_height,
nv12_format,
});
Ok(())
}
#[allow(
clippy::too_many_lines,
reason = "linear per-picture decode sequence — mirrors super::h264::decode_picture's \
identical allow/reasoning"
)]
#[allow(
clippy::similar_names,
reason = "poc_msb/poc_lsb name the two halves of one ITU-T H.265 § 8.3.1 state pair \
(PicOrderCntMsb, pic_order_cnt_lsb) — matching, not confusable, names, mirrors \
super::dpb::derive_pic_order_cnt_msb's identical allow"
)]
fn decode_picture(
&mut self,
packet: &Packet,
original_nal: &[u8],
is_idr: bool,
is_reference: bool,
sps: &HevcSps,
pps: &HevcPps,
header: &HevcSliceSegmentHeader,
) -> Result<VideoFrame, DecodeError> {
if is_idr {
self.pipeline
.as_mut()
.ok_or(DecodeError::Backend)?
.dpb
.clear();
self.prev_poc_msb = 0;
self.prev_poc_lsb = 0;
}
let (pic_order_cnt, poc_msb, poc_lsb) = if is_idr {
(0i32, 0i32, 0u32)
} else {
let poc_lsb = header.pic_order_cnt_lsb.ok_or(DecodeError::InvalidInput)?;
let max_poc_lsb = 1u32
.checked_shl(sps.log2_max_pic_order_cnt_lsb)
.ok_or(DecodeError::InvalidInput)?;
let poc_msb = derive_pic_order_cnt_msb(
poc_lsb,
self.prev_poc_msb,
self.prev_poc_lsb,
max_poc_lsb,
);
let poc = poc_msb
.checked_add_unsigned(poc_lsb)
.ok_or(DecodeError::InvalidInput)?;
(poc, poc_msb, poc_lsb)
};
if is_reference {
self.prev_poc_msb = poc_msb;
self.prev_poc_lsb = poc_lsb;
}
let is_p_slice = header.short_term_rps.is_some();
let reference_slot = if is_p_slice {
let pipeline = self.pipeline.as_ref().ok_or(DecodeError::Backend)?;
let (slot_index, dpb_slot) =
pipeline.dpb.reference().ok_or(DecodeError::InvalidInput)?;
let surface_id = pipeline.surfaces[*slot_index]
.as_ref()
.ok_or(DecodeError::Backend)?
.id();
Some((surface_id, dpb_slot.pic_order_cnt))
} else {
None
};
let (dst_slot_index, surface) = {
let pipeline = self.pipeline.as_mut().ok_or(DecodeError::Backend)?;
let index = hevc_dpb::allocate_slot(&mut pipeline.next_slot, &pipeline.dpb);
let surface = pipeline.surfaces[index]
.take()
.ok_or(DecodeError::Backend)?;
(index, surface)
};
let (returned_surface, result) = self.decode_one(
surface,
packet,
original_nal,
is_idr,
sps,
pps,
header,
pic_order_cnt,
reference_slot,
);
if let Some(pipeline) = self.pipeline.as_mut() {
pipeline.surfaces[dst_slot_index] = Some(returned_surface);
}
let frame = result?;
if is_reference {
self.pipeline
.as_mut()
.ok_or(DecodeError::Backend)?
.dpb
.set_reference(dst_slot_index, pic_order_cnt);
}
Ok(frame)
}
#[allow(clippy::too_many_arguments)]
fn decode_one(
&self,
surface: Surface<()>,
packet: &Packet,
original_nal: &[u8],
is_idr: bool,
sps: &HevcSps,
pps: &HevcPps,
header: &HevcSliceSegmentHeader,
pic_order_cnt: i32,
reference_slot: Option<(cros_libva::VASurfaceID, i32)>,
) -> (Surface<()>, Result<VideoFrame, DecodeError>) {
let Some(pipeline) = self.pipeline.as_ref() else {
return (surface, Err(DecodeError::Backend));
};
let context = Rc::clone(&pipeline.context);
let coded_width = pipeline.coded_width;
let coded_height = pipeline.coded_height;
let nv12_format = pipeline.nv12_format;
let surface_id = surface.id();
let outcome = (|| -> Result<(Bytes, Surface<()>), DecodeError> {
let pic_param = build_pic_param(
sps,
pps,
is_idr,
header,
surface_id,
pic_order_cnt,
reference_slot,
)?;
let iq_matrix = IQMatrixBufferHEVC::new(
[[16u8; 16]; 6],
[[16u8; 64]; 6],
[[16u8; 64]; 6],
[[16u8; 64]; 2],
[16u8; 6],
[16u8; 2],
);
let slice_param = build_slice_param(header, original_nal.len(), reference_slot)?;
let slice_data_bytes = original_nal.to_vec();
let pic_param_buf = context
.create_buffer(BufferType::PictureParameter(PictureParameter::HEVC(
pic_param,
)))
.map_err(|_| DecodeError::Backend)?;
let iq_buf = context
.create_buffer(BufferType::IQMatrix(IQMatrix::HEVC(iq_matrix)))
.map_err(|_| DecodeError::Backend)?;
let slice_param_buf = context
.create_buffer(BufferType::SliceParameter(SliceParameter::HEVC(
slice_param,
)))
.map_err(|_| DecodeError::Backend)?;
let slice_data_buf = context
.create_buffer(BufferType::SliceData(slice_data_bytes))
.map_err(|_| DecodeError::Backend)?;
let timestamp = u64::try_from(packet.pts).unwrap_or(0);
let mut picture = Picture::new(timestamp, Rc::clone(&context), surface);
picture.add_buffer(pic_param_buf);
picture.add_buffer(iq_buf);
picture.add_buffer(slice_param_buf);
picture.add_buffer(slice_data_buf);
let picture = picture.begin::<()>().map_err(|_| DecodeError::Backend)?;
let picture = picture.render().map_err(|_| DecodeError::Backend)?;
let picture = picture.end().map_err(|_| DecodeError::Backend)?;
let picture = picture
.sync::<()>()
.map_err(|(_e, _pic)| DecodeError::Backend)?;
let image = picture
.create_image::<()>(
nv12_format,
(coded_width, coded_height),
(coded_width, coded_height),
)
.map_err(|_| DecodeError::Backend)?;
let va_image = *image.image();
let bytes = copy_nv12_from_planes(
image.as_ref(),
coded_width,
coded_height,
va_image.pitches[0],
va_image.offsets[0],
va_image.pitches[1],
va_image.offsets[1],
);
drop(image);
let surface = picture.take_surface().map_err(|_| DecodeError::Backend)?;
Ok((bytes, surface))
})();
match outcome {
Ok((data, returned_surface)) => {
let frame = VideoFrame {
pts: packet.pts,
duration: packet.duration,
width: coded_width,
height: coded_height,
format: PixelFormat::Nv12,
storage: VideoFrameStorage::Cpu { data },
};
(returned_surface, Ok(frame))
}
Err(e) => (
fresh_surface_or_placeholder(&context, coded_width, coded_height),
Err(e),
),
}
}
}
impl VideoDecoder for VaapiHevcDecoder {
fn stream_info(&self) -> &StreamInfo {
&self.info
}
fn push_packet(&mut self, packet: &Packet) -> Result<(), DecodeError> {
if self.flushed {
return Err(DecodeError::Closed);
}
if packet.is_discard {
return Ok(());
}
let nals = split_annex_b(packet.payload.as_ref()).map_err(|_| DecodeError::InvalidInput)?;
for nal in nals {
let unit = HevcNalUnit::parse(nal)?;
match unit.unit_type {
HevcNalUnitType::Sps => {
self.sps = Some(HevcSps::parse(&unit.rbsp)?);
}
HevcNalUnitType::Pps => {
self.pps = Some(HevcPps::parse(&unit.rbsp)?);
}
HevcNalUnitType::Cra => {
return Err(DecodeError::Unsupported);
}
HevcNalUnitType::Idr | HevcNalUnitType::Trail => {
let is_idr = matches!(unit.unit_type, HevcNalUnitType::Idr);
let sps = self.sps.ok_or(DecodeError::InvalidInput)?;
let pps = self.pps.ok_or(DecodeError::InvalidInput)?;
let mut reader = BitReader::new(&unit.rbsp);
let header = HevcSliceSegmentHeader::parse(&mut reader, &sps, &pps, is_idr)?;
if header.slice_pic_parameter_set_id != pps.pps_pic_parameter_set_id {
return Err(DecodeError::InvalidInput);
}
self.ensure_pipeline(&sps)?;
let frame = self.decode_picture(
packet,
nal,
is_idr,
unit.is_reference,
&sps,
&pps,
&header,
)?;
self.pending.push_back(frame);
}
HevcNalUnitType::Vps | HevcNalUnitType::Other(_) => {}
}
}
Ok(())
}
fn poll_frame(&mut self) -> Result<Option<VideoFrame>, DecodeError> {
Ok(self.pending.pop_front())
}
fn flush(&mut self) -> Result<(), DecodeError> {
self.flushed = true;
Ok(())
}
}
#[allow(
clippy::too_many_arguments,
clippy::similar_names,
clippy::too_many_lines,
reason = "PictureParameterBufferHEVC::new takes every SPS/PPS-derived field positionally — \
mirrors super::h264::build_pic_param's identical allow"
)]
fn build_pic_param(
sps: &HevcSps,
pps: &HevcPps,
is_idr: bool,
header: &HevcSliceSegmentHeader,
surface_id: cros_libva::VASurfaceID,
pic_order_cnt: i32,
reference_slot: Option<(cros_libva::VASurfaceID, i32)>,
) -> Result<PictureParameterBufferHEVC, DecodeError> {
let curr_pic = PictureHEVC::new(surface_id, pic_order_cnt, 0);
let mut reference_frames: [PictureHEVC; 15] = std::array::from_fn(|_| invalid_picture_hevc());
if let Some((ref_surface_id, ref_poc)) = reference_slot {
reference_frames[0] =
PictureHEVC::new(ref_surface_id, ref_poc, VA_PICTURE_HEVC_RPS_ST_CURR_BEFORE);
}
let pic_fields = HevcPicFields::new(
1, 0, 0, 0, u32::from(pps.transform_skip_enabled_flag),
u32::from(sps.amp_enabled_flag),
u32::from(sps.strong_intra_smoothing_enabled_flag),
u32::from(pps.sign_data_hiding_enabled_flag),
u32::from(pps.constrained_intra_pred_flag),
u32::from(pps.cu_qp_delta_enabled_flag),
u32::from(pps.weighted_pred_flag),
u32::from(pps.weighted_bipred_flag),
u32::from(pps.transquant_bypass_enabled_flag),
0, 0, u32::from(pps.pps_loop_filter_across_slices_enabled_flag),
0, 0, 1, 1, );
let slice_parsing_fields = HevcSliceParsingFields::new(
u32::from(pps.lists_modification_present_flag),
0, u32::from(sps.sps_temporal_mvp_enabled_flag),
u32::from(pps.cabac_init_present_flag),
u32::from(pps.output_flag_present_flag),
u32::from(pps.dependent_slice_segments_enabled_flag),
u32::from(pps.pps_slice_chroma_qp_offsets_present_flag),
u32::from(sps.sample_adaptive_offset_enabled_flag),
0, 0, 0, u32::from(is_idr), u32::from(is_idr), u32::from(is_idr), );
let sps_max_dec_pic_buffering_minus1 =
u8::try_from(sps.max_dec_pic_buffering.saturating_sub(1))
.map_err(|_| DecodeError::InvalidInput)?;
let pic_width_in_luma_samples =
u16::try_from(sps.pic_width_in_luma_samples).map_err(|_| DecodeError::InvalidInput)?;
let pic_height_in_luma_samples =
u16::try_from(sps.pic_height_in_luma_samples).map_err(|_| DecodeError::InvalidInput)?;
let log2_min_luma_coding_block_size_minus3 =
u8::try_from(sps.log2_min_cb_size.saturating_sub(3))
.map_err(|_| DecodeError::InvalidInput)?;
let log2_diff_max_min_luma_coding_block_size =
u8::try_from(sps.log2_diff_max_min_cb_size).map_err(|_| DecodeError::InvalidInput)?;
let log2_min_transform_block_size_minus2 = u8::try_from(sps.log2_min_tb_size.saturating_sub(2))
.map_err(|_| DecodeError::InvalidInput)?;
let log2_diff_max_min_transform_block_size =
u8::try_from(sps.log2_diff_max_min_tb_size).map_err(|_| DecodeError::InvalidInput)?;
let max_transform_hierarchy_depth_intra = u8::try_from(sps.max_transform_hierarchy_depth_intra)
.map_err(|_| DecodeError::InvalidInput)?;
let max_transform_hierarchy_depth_inter = u8::try_from(sps.max_transform_hierarchy_depth_inter)
.map_err(|_| DecodeError::InvalidInput)?;
let init_qp_minus26 = i8::try_from(pps.init_qp - 26).map_err(|_| DecodeError::InvalidInput)?;
let diff_cu_qp_delta_depth =
u8::try_from(pps.diff_cu_qp_delta_depth).map_err(|_| DecodeError::InvalidInput)?;
let pps_cb_qp_offset =
i8::try_from(pps.pps_cb_qp_offset).map_err(|_| DecodeError::InvalidInput)?;
let pps_cr_qp_offset =
i8::try_from(pps.pps_cr_qp_offset).map_err(|_| DecodeError::InvalidInput)?;
let log2_parallel_merge_level_minus2 = u8::try_from(pps.log2_parallel_merge_level_minus2)
.map_err(|_| DecodeError::InvalidInput)?;
let log2_max_pic_order_cnt_lsb_minus4 =
u8::try_from(sps.log2_max_pic_order_cnt_lsb.saturating_sub(4))
.map_err(|_| DecodeError::InvalidInput)?;
let num_ref_idx_l0_default_active_minus1 =
u8::try_from(pps.num_ref_idx_l0_default_active.saturating_sub(1))
.map_err(|_| DecodeError::InvalidInput)?;
let num_ref_idx_l1_default_active_minus1 =
u8::try_from(pps.num_ref_idx_l1_default_active.saturating_sub(1))
.map_err(|_| DecodeError::InvalidInput)?;
let num_extra_slice_header_bits =
u8::try_from(pps.num_extra_slice_header_bits).map_err(|_| DecodeError::InvalidInput)?;
Ok(PictureParameterBufferHEVC::new(
curr_pic,
reference_frames,
pic_width_in_luma_samples,
pic_height_in_luma_samples,
&pic_fields,
sps_max_dec_pic_buffering_minus1,
0, 0, 0, 0, log2_min_luma_coding_block_size_minus3,
log2_diff_max_min_luma_coding_block_size,
log2_min_transform_block_size_minus2,
log2_diff_max_min_transform_block_size,
0, 0, max_transform_hierarchy_depth_intra,
max_transform_hierarchy_depth_inter,
init_qp_minus26,
diff_cu_qp_delta_depth,
pps_cb_qp_offset,
pps_cr_qp_offset,
log2_parallel_merge_level_minus2,
0, 0, [0u16; 19], [0u16; 21], &slice_parsing_fields,
log2_max_pic_order_cnt_lsb_minus4,
0, 0, num_ref_idx_l0_default_active_minus1,
num_ref_idx_l1_default_active_minus1,
0, 0, num_extra_slice_header_bits,
header.st_rps_bits,
))
}
fn build_slice_param(
header: &HevcSliceSegmentHeader,
nal_len: usize,
reference_slot: Option<(cros_libva::VASurfaceID, i32)>,
) -> Result<SliceParameterBufferHEVC, DecodeError> {
let mut ref_pic_list0 = [0xFFu8; 15];
let ref_pic_list1 = [0xFFu8; 15];
if reference_slot.is_some() {
ref_pic_list0[0] = 0;
}
let slice_type: u8 = match header.slice_type {
super::hevc_slice::HevcSliceType::B => 0,
super::hevc_slice::HevcSliceType::P => 1,
super::hevc_slice::HevcSliceType::I => 2,
};
let long_slice_flags = HevcLongSliceFlags::new(
1, 0, u32::from(slice_type),
0, u32::from(header.slice_sao_luma_flag),
u32::from(header.slice_sao_chroma_flag),
0, u32::from(header.cabac_init_flag),
u32::from(header.slice_temporal_mvp_enabled_flag),
0, 1, u32::from(header.slice_loop_filter_across_slices_enabled_flag),
);
let slice_data_size = u32::try_from(nal_len).map_err(|_| DecodeError::InvalidInput)?;
let slice_data_byte_offset =
u32::try_from(2usize + header.bits_consumed / 8).map_err(|_| DecodeError::InvalidInput)?;
let num_ref_idx_l0_active_minus1 = u8::try_from(header.num_ref_idx_l0_active.saturating_sub(1))
.map_err(|_| DecodeError::InvalidInput)?;
let slice_qp_delta =
i8::try_from(header.slice_qp_delta).map_err(|_| DecodeError::InvalidInput)?;
let slice_cb_qp_offset =
i8::try_from(header.slice_cb_qp_offset).map_err(|_| DecodeError::InvalidInput)?;
let slice_cr_qp_offset =
i8::try_from(header.slice_cr_qp_offset).map_err(|_| DecodeError::InvalidInput)?;
let five_minus_max_num_merge_cand = u8::try_from(header.five_minus_max_num_merge_cand)
.map_err(|_| DecodeError::InvalidInput)?;
Ok(SliceParameterBufferHEVC::new(
slice_data_size,
0, 0, slice_data_byte_offset,
0, [ref_pic_list0, ref_pic_list1],
&long_slice_flags,
0, num_ref_idx_l0_active_minus1,
0, slice_qp_delta,
slice_cb_qp_offset,
slice_cr_qp_offset,
0, 0, 0, 0, [0i8; 15],
[0i8; 15],
[[0i8; 2]; 15],
[[0i8; 2]; 15],
[0i8; 15],
[0i8; 15],
[[0i8; 2]; 15],
[[0i8; 2]; 15],
five_minus_max_num_merge_cand,
0, 0, 0, ))
}
fn invalid_picture_hevc() -> PictureHEVC {
PictureHEVC::new(VA_INVALID_SURFACE, 0, VA_PICTURE_HEVC_INVALID)
}
fn fresh_surface_or_placeholder(
context: &Rc<Context>,
coded_width: u32,
coded_height: u32,
) -> Surface<()> {
let display = context.display();
display
.create_surfaces(
VA_RT_FORMAT_YUV420,
Some(VA_FOURCC_NV12),
coded_width,
coded_height,
None,
vec![()],
)
.ok()
.and_then(|mut v| v.pop())
.unwrap_or_else(|| placeholder_surface(display, coded_width, coded_height))
}
fn placeholder_surface(display: &Rc<Display>, coded_width: u32, coded_height: u32) -> Surface<()> {
#[allow(
clippy::expect_used,
reason = "session is already unrecoverable by this point; see caller doc comment"
)]
display
.create_surfaces(
VA_RT_FORMAT_YUV420,
Some(VA_FOURCC_NV12),
coded_width,
coded_height,
None,
vec![()],
)
.expect("VA-API display known-good at session open time")
.pop()
.expect("exactly one surface requested")
}
fn seed_params(extra_data: &Bytes) -> (Option<HevcSps>, Option<HevcPps>) {
if extra_data.is_empty() {
return (None, None);
}
let Ok(nals) = split_annex_b(extra_data.as_ref()) else {
return (None, None);
};
let mut sps = None;
let mut pps = None;
for nal in nals {
let Ok(unit) = HevcNalUnit::parse(nal) else {
continue;
};
match unit.unit_type {
HevcNalUnitType::Sps => {
if let Ok(s) = HevcSps::parse(&unit.rbsp) {
sps = Some(s);
}
}
HevcNalUnitType::Pps => {
if let Ok(p) = HevcPps::parse(&unit.rbsp) {
pps = Some(p);
}
}
_ => {}
}
}
(sps, pps)
}
fn validate(config: &VideoDecoderConfig) -> Result<(), DecodeError> {
if config.codec != mediaway_common::CodecKind::Hevc {
return Err(DecodeError::Unsupported);
}
if config.pixel_format != PixelFormat::Nv12 {
return Err(DecodeError::Unsupported);
}
if config.time_base.den == 0 {
return Err(DecodeError::InvalidInput);
}
Ok(())
}
const fn round_up_8(value: u32) -> u32 {
value.div_ceil(8) * 8
}
#[allow(clippy::missing_const_for_fn, reason = "StreamInfo holds Bytes")]
fn stream_info_from(config: &VideoDecoderConfig) -> StreamInfo {
StreamInfo::Video {
id: 0,
codec: config.codec,
time_base: config.time_base,
geometry: VideoGeometry {
width: config.width,
height: config.height,
},
extra_data: config.extra_data.clone(), }
}
#[cfg(test)]
#[path = "hevc_tests.rs"]
mod tests;