#![allow(unsafe_code)]
#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
reason = "Vulkan FFI + AV1 syntax-derived counts are driver/bitstream-bounded and small — \
casts mirror decoder_hevc.rs's identical allow."
)]
use crate::{DecodeError, VideoOutputPreference};
use mediaway_common::{Bytes, Packet, PixelFormat, VideoFrame, VideoFrameStorage};
use crate::vulkan::av1_params::{Av1PictureInfoOptionals, Av1SequenceHeader, ObuType, scan_obus};
use crate::vulkan::av1_refs::Av1RefSlots;
use crate::vulkan::cpu_readback;
use crate::vulkan::decoder::{DecodedSession, VulkanVideoDecoder, map_err};
use crate::vulkan::session::{
DecodeDevice, DecodeProfile, create_session_parameters_av1, create_video_session,
query_capabilities, query_video_format,
};
use crate::vulkan::session_command::{
SessionResources, allocate_command_buffer, create_command_pool, create_dpb_image, create_fence,
create_host_buffer, transition_dpb_image_once, upload_to_host_memory,
};
use crate::vulkan::session_command_av1::{RecordParamsAv1, record_and_submit_av1};
use crate::vulkan::zero_copy;
use vulkanalia::vk;
const BITSTREAM_CAPACITY: vk::DeviceSize = 1 << 20;
const DPB_SLOT_COUNT: u32 = 2;
#[allow(
clippy::redundant_pub_crate,
reason = "conflicts with the workspace's own unreachable_pub lint (also enabled) — pub(crate) \
here is deliberate: `mod decoder_av1` is private, but decoder.rs (a sibling module) \
still needs this type"
)]
pub(crate) struct Av1Session {
pub(crate) resources: SessionResources,
command_buffer: vk::CommandBuffer,
coded_extent: vk::Extent2D,
bitstream_alignment: vk::DeviceSize,
ref_slots: Av1RefSlots,
seq: Av1SequenceHeader,
}
impl VulkanVideoDecoder {
pub(crate) fn build_session_av1(
&self,
seq: Av1SequenceHeader,
) -> Result<Av1Session, crate::vulkan::session::VulkanDecodeError> {
let instance = &self.instance_guard.instance;
let device = &self.device_guard.device;
let decode_device = DecodeDevice {
device,
queue: self.queue,
queue_family_index: self.queue_family_index,
};
let mut profile = DecodeProfile::new_av1();
let capabilities = query_capabilities(instance, self.physical_device, &mut profile)?;
let coded_extent = vk::Extent2D {
width: seq.width(),
height: seq.height(),
};
capabilities.validate_requested_extent(coded_extent.width, coded_extent.height)?;
let picture_format = query_video_format(
instance,
self.physical_device,
&mut profile,
vk::ImageUsageFlags::VIDEO_DECODE_DPB_KHR | vk::ImageUsageFlags::VIDEO_DECODE_DST_KHR,
)?;
let dpb_slot_count = DPB_SLOT_COUNT.min(capabilities.max_dpb_slots.max(1));
let mut resources = SessionResources::default();
let (session, session_memories) = create_video_session(
&decode_device,
&self.memory_properties,
&mut profile,
&capabilities,
coded_extent,
picture_format,
dpb_slot_count,
0, )?;
resources.session = session;
resources.session_memories = session_memories;
let color_config = seq.to_std_color_config();
let timing_info = Av1SequenceHeader::build_timing_info();
let std_seq_header = seq.to_std(&color_config, &timing_info);
resources.session_parameters =
create_session_parameters_av1(&decode_device, session, &std_seq_header)?;
let (dpb_image, dpb_image_memory, dpb_image_view) = create_dpb_image(
&decode_device,
&self.memory_properties,
&mut profile,
picture_format,
coded_extent,
dpb_slot_count,
)?;
resources.dpb_image = dpb_image;
resources.dpb_image_memory = dpb_image_memory;
resources.dpb_image_view = dpb_image_view;
let (bitstream_buffer, bitstream_memory) = create_host_buffer(
device,
&self.memory_properties,
BITSTREAM_CAPACITY,
vk::BufferUsageFlags::VIDEO_DECODE_SRC_KHR,
)?;
resources.bitstream_buffer = bitstream_buffer;
resources.bitstream_memory = bitstream_memory;
resources.bitstream_capacity = BITSTREAM_CAPACITY;
let readback_size = cpu_readback::nv12_byte_size(coded_extent.width, coded_extent.height);
let (readback_buffer, readback_memory) = create_host_buffer(
device,
&self.memory_properties,
readback_size,
vk::BufferUsageFlags::TRANSFER_DST,
)?;
resources.readback_buffer = readback_buffer;
resources.readback_memory = readback_memory;
resources.readback_size = readback_size;
resources.command_pool = create_command_pool(device, self.queue_family_index)?;
let command_buffer = allocate_command_buffer(device, resources.command_pool)?;
resources.fence = create_fence(device)?;
transition_dpb_image_once(&decode_device, &resources, dpb_slot_count, command_buffer)?;
Ok(Av1Session {
resources,
command_buffer,
coded_extent,
bitstream_alignment: capabilities.min_bitstream_buffer_size_alignment.max(1),
ref_slots: Av1RefSlots::new(dpb_slot_count as usize),
seq,
})
}
pub(crate) fn decode_frame_av1(&mut self, obu_payload: &[u8]) -> Result<(), DecodeError> {
let Some(DecodedSession::Av1(session)) = self.session.as_mut() else {
return Err(DecodeError::InvalidInput);
};
let (frame_header, tile_layout) =
crate::vulkan::av1_params::parse_frame_header(obu_payload, &session.seq)
.map_err(|e| map_err(e.into()))?;
if frame_header.frame_width != session.coded_extent.width
|| frame_header.frame_height != session.coded_extent.height
{
return Err(DecodeError::InvalidInput);
}
session
.ref_slots
.clear_all()
.map_err(|e| map_err(e.into()))?;
let dst_slot_index = session
.ref_slots
.allocate_slot()
.map_err(|e| map_err(e.into()))? as u32;
let aligned_len = vk::DeviceSize::try_from(obu_payload.len())
.unwrap_or(vk::DeviceSize::MAX)
.div_ceil(session.bitstream_alignment)
* session.bitstream_alignment;
if aligned_len > session.resources.bitstream_capacity {
return Err(DecodeError::InvalidInput);
}
let mut padded = obu_payload.to_vec();
padded.resize(aligned_len as usize, 0);
upload_to_host_memory(
&self.device_guard.device,
session.resources.bitstream_memory,
&padded,
)
.map_err(map_err)?;
let mut optionals = Av1PictureInfoOptionals::new(&frame_header);
optionals.finish();
let decode_device = DecodeDevice {
device: &self.device_guard.device,
queue: self.queue,
queue_family_index: self.queue_family_index,
};
let params = RecordParamsAv1 {
command_buffer: session.command_buffer,
coded_extent: session.coded_extent,
bitstream_len: aligned_len,
dst_slot_index,
tile_offset: tile_layout.tile_offset,
tile_size: tile_layout.tile_size,
frame_header: &frame_header,
optionals: &optionals,
};
record_and_submit_av1(&decode_device, &mut session.resources, ¶ms).map_err(map_err)?;
let output = match self.output {
VideoOutputPreference::CpuFramesOk => {
let bytes = cpu_readback::read_nv12(
&decode_device,
&session.resources,
session.command_buffer,
session.coded_extent,
dst_slot_index,
)
.map_err(map_err)?;
VideoFrameStorage::Cpu {
data: Bytes::from(bytes),
}
}
VideoOutputPreference::ZeroCopyGpu => {
session
.ref_slots
.mark_outstanding(dst_slot_index as usize)
.map_err(|e| map_err(e.into()))?;
let handle =
zero_copy::build_handle(&session.resources, dst_slot_index).map_err(map_err)?;
VideoFrameStorage::Gpu(handle)
}
};
self.pending.push_back(VideoFrame {
pts: 0,
duration: 0,
width: session.coded_extent.width,
height: session.coded_extent.height,
format: PixelFormat::Nv12,
storage: output,
});
Ok(())
}
}
#[allow(
clippy::redundant_pub_crate,
reason = "conflicts with the workspace's own unreachable_pub lint (also enabled) — pub(crate) \
here is deliberate: `mod decoder_av1` is private, but decoder.rs (a sibling module) \
still needs this function"
)]
pub(crate) fn scan_parameter_sets(data: &[u8], seq: &mut Option<Av1SequenceHeader>) {
if data.is_empty() {
return;
}
let Ok(obus) = scan_obus(data) else {
return;
};
for obu in obus {
if matches!(obu.obu_type, ObuType::SequenceHeader)
&& let Ok(parsed) = Av1SequenceHeader::parse(obu.payload)
{
*seq = Some(parsed);
}
}
}
#[allow(
clippy::redundant_pub_crate,
reason = "conflicts with the workspace's own unreachable_pub lint (also enabled) — pub(crate) \
here is deliberate: `mod decoder_av1` is private, but decoder.rs (a sibling module) \
still needs this function"
)]
pub(crate) fn push_packet_av1(
decoder: &mut VulkanVideoDecoder,
packet: &Packet,
) -> Result<(), DecodeError> {
let obus = scan_obus(&packet.payload).map_err(|e| map_err(e.into()))?;
for obu in obus {
match obu.obu_type {
ObuType::SequenceHeader => {
let seq = Av1SequenceHeader::parse(obu.payload).map_err(|e| map_err(e.into()))?;
if decoder.session.is_none() {
decoder.session = Some(DecodedSession::Av1(
decoder.build_session_av1(seq).map_err(map_err)?,
));
}
}
ObuType::Frame => {
decoder.decode_frame_av1(obu.payload)?;
}
ObuType::TemporalDelimiter | ObuType::Metadata | ObuType::Padding => {}
ObuType::FrameHeader
| ObuType::TileGroup
| ObuType::RedundantFrameHeader
| ObuType::TileList
| ObuType::Other(_) => {
return Err(DecodeError::Unsupported);
}
}
}
Ok(())
}