use std::borrow::Cow;
use std::collections::VecDeque;
use std::sync::mpsc;
use orfail::OrFail;
use crate::layout_decode_params::LayoutDecodeParams;
use crate::video::{VideoFormat, VideoFrame};
use crate::video_h264::{H264_NALU_TYPE_PPS, H264_NALU_TYPE_SPS};
use crate::video_h265::{
H265_NALU_TYPE_PPS, H265_NALU_TYPE_SPS, H265_NALU_TYPE_VPS, NALU_HEADER_LENGTH,
};
#[derive(Debug)]
struct DecodedFrameWithMeta {
width: usize,
height: usize,
nv12_data: Vec<u8>,
y_stride: usize,
uv_stride: usize,
input_frame: VideoFrame,
}
type NvcodecDecodeHandler =
shiguredo_nvcodec::FnDecodeHandler<VideoFrame, shiguredo_nvcodec::Error>;
type DecodeMessage = Result<DecodedFrameWithMeta, orfail::Failure>;
#[derive(Debug)]
pub struct NvcodecDecoder {
inner: shiguredo_nvcodec::Decoder<NvcodecDecodeHandler>,
rx: mpsc::Receiver<DecodeMessage>,
ok_buffer: VecDeque<DecodedFrameWithMeta>,
pending_error: Option<orfail::Failure>,
parameter_sets: Option<Vec<u8>>, }
impl NvcodecDecoder {
pub fn new_h264(params: &LayoutDecodeParams) -> orfail::Result<Self> {
log::debug!("create nvcodec(H264) decoder");
Self::new_common(params.nvcodec_h264.clone())
}
pub fn new_h265(params: &LayoutDecodeParams) -> orfail::Result<Self> {
log::debug!("create nvcodec(H265) decoder");
Self::new_common(params.nvcodec_h265.clone())
}
pub fn new_av1(params: &LayoutDecodeParams) -> orfail::Result<Self> {
log::debug!("create nvcodec(AV1) decoder");
Self::new_common(params.nvcodec_av1.clone())
}
pub fn new_vp8(params: &LayoutDecodeParams) -> orfail::Result<Self> {
log::debug!("create nvcodec(VP8) decoder");
Self::new_common(params.nvcodec_vp8.clone())
}
pub fn new_vp9(params: &LayoutDecodeParams) -> orfail::Result<Self> {
log::debug!("create nvcodec(VP9) decoder");
Self::new_common(params.nvcodec_vp9.clone())
}
fn new_common(config: shiguredo_nvcodec::DecoderConfig) -> orfail::Result<Self> {
let (tx, rx) = mpsc::channel();
let handler = shiguredo_nvcodec::FnDecodeHandler::new(move |result| {
handle_decode_callback(&tx, result);
});
let inner = shiguredo_nvcodec::Decoder::new(config, handler).or_fail()?;
Ok(Self {
inner,
rx,
ok_buffer: VecDeque::new(),
pending_error: None,
parameter_sets: None,
})
}
pub fn decode(&mut self, frame: &VideoFrame) -> orfail::Result<()> {
self.take_pending_error()?;
matches!(
frame.format,
VideoFormat::H264
| VideoFormat::H264AnnexB
| VideoFormat::H265
| VideoFormat::Vp8
| VideoFormat::Vp9
| VideoFormat::Av1
)
.or_fail()?;
if let Some(sample_entry) = &frame.sample_entry {
self.parameter_sets =
Some(extract_parameter_sets_annexb(sample_entry, frame.format).or_fail()?);
}
let data = if matches!(
frame.format,
VideoFormat::Vp8 | VideoFormat::Vp9 | VideoFormat::Av1
) {
Cow::Borrowed(&frame.data)
} else if frame.format == VideoFormat::H264AnnexB {
Cow::Borrowed(&frame.data)
} else {
let mut data = &frame.data[..];
let mut data_annexb = Vec::new();
if frame.keyframe
&& let Some(parameter_sets) = &self.parameter_sets
&& !contains_parameter_sets(data, frame.format)
{
data_annexb.extend_from_slice(parameter_sets);
}
while !data.is_empty() {
(data.len() >= NALU_HEADER_LENGTH).or_fail()?;
let n = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize;
data = &data[NALU_HEADER_LENGTH..];
(data.len() >= n).or_fail()?;
data_annexb.extend_from_slice(&[0, 0, 0, 1]);
data_annexb.extend_from_slice(&data[..n]);
data = &data[n..];
}
Cow::Owned(data_annexb)
};
self.inner.decode(&data, frame.to_stripped()).or_fail()?;
Ok(())
}
pub fn finish(&mut self) -> orfail::Result<()> {
self.inner.flush().or_fail()?;
self.take_pending_error()?;
Ok(())
}
fn drain_rx(&mut self) {
while let Ok(msg) = self.rx.try_recv() {
match msg {
Ok(decoded) => self.ok_buffer.push_back(decoded),
Err(err) => {
self.pending_error.get_or_insert(err);
}
}
}
}
pub fn take_pending_error(&mut self) -> orfail::Result<()> {
self.drain_rx();
if let Some(err) = self.pending_error.take() {
return Err(err);
}
Ok(())
}
pub fn next_decoded_frame(&mut self) -> Option<VideoFrame> {
self.drain_rx();
let decoded = self.ok_buffer.pop_front()?;
let width = decoded.width;
let height = decoded.height;
let uv_width = width.div_ceil(2);
let uv_height = height.div_ceil(2);
let y_size = width * height;
let uv_size = uv_width * uv_height;
let total_size = y_size + uv_size * 2;
let mut i420_data = vec![0u8; total_size];
let (y_plane, rest) = i420_data.split_at_mut(y_size);
let (u_plane, v_plane) = rest.split_at_mut(uv_size);
let (nv12_y, nv12_uv) = decoded.nv12_data.split_at(decoded.y_stride * height);
let src = shiguredo_libyuv::Nv12Planes {
y: nv12_y,
y_stride: decoded.y_stride,
uv: nv12_uv,
uv_stride: decoded.uv_stride,
};
let mut dst = shiguredo_libyuv::I420PlanesMut {
y: y_plane,
y_stride: width,
u: u_plane,
u_stride: uv_width,
v: v_plane,
v_stride: uv_width,
};
let size = shiguredo_libyuv::ImageSize::new(width, height);
if let Err(e) = shiguredo_libyuv::nv12_to_i420(&src, &mut dst, size) {
log::error!("libyuv nv12_to_i420 failed: {e}");
self.pending_error
.get_or_insert_with(|| orfail::Failure::new(format!("libyuv nv12_to_i420 failed: {e}")));
return None;
}
Some(VideoFrame::new_i420(
decoded.input_frame,
width,
height,
y_plane,
u_plane,
v_plane,
width,
uv_width,
uv_width,
))
}
}
fn handle_decode_callback(
tx: &mpsc::Sender<DecodeMessage>,
result: std::result::Result<
shiguredo_nvcodec::DecodedFrame<VideoFrame>,
shiguredo_nvcodec::Error,
>,
) {
let message = match result {
Ok(decoded) => {
let width = decoded.width();
let height = decoded.height();
let y_stride = decoded.y_stride();
let uv_stride = decoded.uv_stride();
let (nv12_data, input_frame) = decoded.into_parts();
Ok(DecodedFrameWithMeta {
width,
height,
nv12_data,
y_stride,
uv_stride,
input_frame,
})
}
Err(err) => Err(orfail::Failure::new(format!("nvcodec decode error: {err}"))),
};
let _ = tx.send(message);
}
fn extract_parameter_sets_annexb(
sample_entry: &shiguredo_mp4::boxes::SampleEntry,
format: VideoFormat,
) -> orfail::Result<Vec<u8>> {
use shiguredo_mp4::boxes::SampleEntry;
match (sample_entry, format) {
(SampleEntry::Hev1(entry), VideoFormat::H265) => {
let mut annexb_data = Vec::new();
for array in &entry.hvcc_box.nalu_arrays {
for nalu in &array.nalus {
annexb_data.extend_from_slice(&[0, 0, 0, 1]);
annexb_data.extend_from_slice(nalu);
}
}
Ok(annexb_data)
}
(SampleEntry::Avc1(entry), VideoFormat::H264) => {
let mut annexb_data = Vec::new();
for sps in &entry.avcc_box.sps_list {
annexb_data.extend_from_slice(&[0, 0, 0, 1]);
annexb_data.extend_from_slice(sps);
}
for pps in &entry.avcc_box.pps_list {
annexb_data.extend_from_slice(&[0, 0, 0, 1]);
annexb_data.extend_from_slice(pps);
}
Ok(annexb_data)
}
_ => {
Ok(Vec::new())
}
}
}
fn contains_parameter_sets(data: &[u8], format: VideoFormat) -> bool {
if data.len() < NALU_HEADER_LENGTH + 1 {
return false;
}
match format {
VideoFormat::H265 => {
let nal_unit_type = (data[NALU_HEADER_LENGTH] >> 1) & 0x3F;
matches!(
nal_unit_type,
H265_NALU_TYPE_PPS | H265_NALU_TYPE_SPS | H265_NALU_TYPE_VPS
)
}
VideoFormat::H264 => {
let nal_unit_type = data[NALU_HEADER_LENGTH] & 0x1F;
matches!(nal_unit_type, H264_NALU_TYPE_SPS | H264_NALU_TYPE_PPS)
}
VideoFormat::Av1 => {
false
}
_ => false,
}
}