#![forbid(unsafe_code)]
#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "picture/plane dimensions below come from SPS macroblock counts, already \
validated to fit usize by the checked conversions at the top of this module"
)]
use super::bitreader::BitReader;
use super::error::H264Error;
use super::nal::NalUnit;
use super::pps::Pps;
use super::reconstruct::{self, McbContext, Picture};
use super::slice::SliceHeader;
use super::sps::Sps;
use mediaway_common::{Bytes, PixelFormat, VideoFrame, VideoFrameStorage};
pub fn decode_i_frame(sps: &Sps, pps: &Pps, slice_nal: &NalUnit) -> Result<VideoFrame, H264Error> {
if pps.entropy_coding_mode {
return Err(H264Error::UnsupportedEntropyCoding);
}
if sps.chroma_format_idc != 1 {
return Err(H264Error::UnsupportedChromaFormat);
}
let mut reader = BitReader::new(&slice_nal.rbsp);
let header = SliceHeader::parse(
&mut reader,
sps,
pps,
slice_nal.unit_type,
slice_nal.ref_idc,
)?;
if header.first_mb_in_slice != 0 {
return Err(H264Error::MultiSliceUnsupported);
}
let mb_width =
usize::try_from(sps.pic_width_in_mbs).map_err(|_err| H264Error::FieldOverflow)?;
let mb_height =
usize::try_from(sps.pic_height_in_mbs).map_err(|_err| H264Error::FieldOverflow)?;
let num_mbs = mb_width
.checked_mul(mb_height)
.ok_or(H264Error::FieldOverflow)?;
let mut picture = Picture::new(mb_width, mb_height);
let mut ctx = McbContext::new(num_mbs, mb_width);
let mut qp_prev = pps
.pic_init_qp
.checked_add(header.slice_qp_delta)
.ok_or(H264Error::FieldOverflow)?;
for mb_addr in 0..num_mbs {
reconstruct::decode_macroblock(
&mut reader,
pps,
mb_addr,
&mut qp_prev,
&mut picture,
&mut ctx,
)?;
}
pack_video_frame(sps, &picture)
}
fn pack_video_frame(sps: &Sps, picture: &Picture) -> Result<VideoFrame, H264Error> {
let width = usize::try_from(sps.width).map_err(|_err| H264Error::FieldOverflow)?;
let height = usize::try_from(sps.height).map_err(|_err| H264Error::FieldOverflow)?;
if width > picture.mb_width * 16 || height > picture.mb_height * 16 {
return Err(H264Error::FieldOverflow);
}
let chroma_width = width.div_ceil(2);
let chroma_height = height.div_ceil(2);
let mut data = Vec::with_capacity(width * height + 2 * chroma_width * chroma_height);
crop_plane_into(&picture.y, picture.y_stride, width, height, &mut data)?;
crop_plane_into(
&picture.u,
picture.c_stride,
chroma_width,
chroma_height,
&mut data,
)?;
crop_plane_into(
&picture.v,
picture.c_stride,
chroma_width,
chroma_height,
&mut data,
)?;
Ok(VideoFrame {
pts: 0,
duration: 0,
width: sps.width,
height: sps.height,
format: PixelFormat::I420,
storage: VideoFrameStorage::Cpu {
data: Bytes::from(data),
},
})
}
fn crop_plane_into(
plane: &[u8],
stride: usize,
width: usize,
height: usize,
out: &mut Vec<u8>,
) -> Result<(), H264Error> {
for row in 0..height {
let start = row.checked_mul(stride).ok_or(H264Error::FieldOverflow)?;
let end = start.checked_add(width).ok_or(H264Error::FieldOverflow)?;
out.extend_from_slice(plane.get(start..end).ok_or(H264Error::FieldOverflow)?);
}
Ok(())
}
#[cfg(test)]
#[path = "decode_tests.rs"]
mod tests;