use std::collections::VecDeque;
use orfail::OrFail;
use shiguredo_mp4::boxes::SampleEntry;
use crate::{
encoder::VideoEncoderOptions,
types::CodecName,
video::{VideoFormat, VideoFrame},
video_av1, video_h264, video_h265,
};
#[derive(Debug)]
pub struct NvcodecEncoder {
inner: shiguredo_nvcodec::Encoder,
input_queue: VecDeque<VideoFrame>,
output_queue: VecDeque<VideoFrame>,
sample_entry: Option<SampleEntry>,
encoded_format: VideoFormat,
av1_sequence_header: Vec<u8>,
}
impl NvcodecEncoder {
pub fn new_h264(options: &VideoEncoderOptions) -> orfail::Result<Self> {
let width = options.width.get();
let height = options.height.get();
log::debug!("create nvcodec(H264) encoder: {}x{}", width, height);
let config = shiguredo_nvcodec::EncoderConfig {
width: width as u32,
height: height as u32,
fps_numerator: options.frame_rate.numerator.get() as u32,
fps_denominator: options.frame_rate.denumerator.get() as u32,
target_bitrate: Some(options.bitrate as u32),
..options.encode_params.nvcodec_h264.clone()
};
log::debug!("nvcodec h264 encoder config: {config:?}");
let mut inner = shiguredo_nvcodec::Encoder::new_h264(config).or_fail()?;
let seq_params = inner.get_sequence_params().or_fail()?;
let sample_entry =
video_h264::h264_sample_entry_from_annexb(width, height, &seq_params).or_fail()?;
Ok(Self {
inner,
input_queue: VecDeque::new(),
output_queue: VecDeque::new(),
sample_entry: Some(sample_entry),
encoded_format: VideoFormat::H264,
av1_sequence_header: Vec::new(),
})
}
pub fn new_h265(options: &VideoEncoderOptions) -> orfail::Result<Self> {
let width = options.width.get();
let height = options.height.get();
log::debug!("create nvcodec(H265) encoder: {}x{}", width, height);
let config = shiguredo_nvcodec::EncoderConfig {
width: width as u32,
height: height as u32,
fps_numerator: options.frame_rate.numerator.get() as u32,
fps_denominator: options.frame_rate.denumerator.get() as u32,
target_bitrate: Some(options.bitrate as u32),
..options.encode_params.nvcodec_h265.clone()
};
log::debug!("nvcodec h265 encoder config: {config:?}");
let mut inner = shiguredo_nvcodec::Encoder::new_h265(config).or_fail()?;
let seq_params = inner.get_sequence_params().or_fail()?;
let sample_entry = video_h265::h265_sample_entry_from_annexb(
width,
height,
options.frame_rate,
&seq_params,
)
.or_fail()?;
Ok(Self {
inner,
input_queue: VecDeque::new(),
output_queue: VecDeque::new(),
sample_entry: Some(sample_entry),
encoded_format: VideoFormat::H265,
av1_sequence_header: Vec::new(),
})
}
pub fn new_av1(options: &VideoEncoderOptions) -> orfail::Result<Self> {
let width = options.width;
let height = options.height;
log::debug!(
"create nvcodec(AV1) encoder: {}x{}",
width.get(),
height.get()
);
let config = shiguredo_nvcodec::EncoderConfig {
width: width.get() as u32,
height: height.get() as u32,
fps_numerator: options.frame_rate.numerator.get() as u32,
fps_denominator: options.frame_rate.denumerator.get() as u32,
target_bitrate: Some(options.bitrate as u32),
..options.encode_params.nvcodec_av1.clone()
};
log::debug!("nvcodec av1 encoder config: {config:?}");
let mut inner = shiguredo_nvcodec::Encoder::new_av1(config).or_fail()?;
let seq_params = inner.get_sequence_params().or_fail()?;
let sample_entry = video_av1::av1_sample_entry(width, height, &seq_params);
Ok(Self {
inner,
input_queue: VecDeque::new(),
output_queue: VecDeque::new(),
sample_entry: Some(sample_entry),
encoded_format: VideoFormat::Av1,
av1_sequence_header: seq_params,
})
}
pub fn encode(&mut self, frame: &VideoFrame) -> orfail::Result<()> {
(frame.format == VideoFormat::I420).or_fail()?;
let width = frame.width;
let height = frame.height;
let (y_plane, u_plane, v_plane) = frame.as_yuv_planes().or_fail()?;
let y_size = width * height;
let uv_width = width.div_ceil(2);
let uv_height = height.div_ceil(2);
let uv_size = uv_width * uv_height * 2; let total_size = y_size + uv_size;
let mut nv12_data = vec![0u8; total_size];
let (nv12_y, nv12_uv) = nv12_data.split_at_mut(y_size);
let src = shiguredo_libyuv::I420Planes {
y: y_plane,
y_stride: width,
u: u_plane,
u_stride: uv_width,
v: v_plane,
v_stride: uv_width,
};
let mut dst = shiguredo_libyuv::Nv12PlanesMut {
y: nv12_y,
y_stride: width,
uv: nv12_uv,
uv_stride: width,
};
let size = shiguredo_libyuv::ImageSize::new(width, height);
shiguredo_libyuv::i420_to_nv12(&src, &mut dst, size).or_fail()?;
self.inner.encode(&nv12_data).or_fail()?;
self.input_queue.push_back(frame.to_stripped());
self.handle_encoded_frames().or_fail()?;
Ok(())
}
pub fn finish(&mut self) -> orfail::Result<()> {
self.inner.finish().or_fail()?;
self.handle_encoded_frames().or_fail()?;
Ok(())
}
fn handle_encoded_frames(&mut self) -> orfail::Result<()> {
while let Some(encoded_frame) = self.inner.next_frame() {
let input_frame = self.input_queue.pop_front().or_fail()?;
let keyframe = matches!(
encoded_frame.picture_type(),
shiguredo_nvcodec::PictureType::I | shiguredo_nvcodec::PictureType::Idr
);
let frame_data = if self.encoded_format == VideoFormat::Av1 {
let mut data = encoded_frame.into_data();
if keyframe && !self.has_sequence_header(&data) {
log::debug!(
"prepending Sequence Header OBU to AV1 keyframe (seq_header: {} bytes, frame: {} bytes)",
self.av1_sequence_header.len(),
data.len()
);
let mut new_data =
Vec::with_capacity(self.av1_sequence_header.len() + data.len());
new_data.extend_from_slice(&self.av1_sequence_header);
new_data.extend_from_slice(&data);
data = new_data;
}
data
} else {
convert_annexb_to_mp4(encoded_frame.data()).or_fail()?
};
self.output_queue.push_back(VideoFrame {
source_id: input_frame.source_id.clone(),
data: frame_data,
format: self.encoded_format,
keyframe,
width: input_frame.width,
height: input_frame.height,
timestamp: input_frame.timestamp,
duration: input_frame.duration,
sample_entry: self.sample_entry.take(),
});
}
Ok(())
}
fn has_sequence_header(&self, data: &[u8]) -> bool {
if data.is_empty() {
return false;
}
let obu_header = data[0];
let obu_has_extension = (obu_header & 0b0010_0000) != 0;
if obu_has_extension && data.len() < 2 {
return false;
}
let obu_type = (obu_header >> 3) & 0x0F;
obu_type == 1
}
pub fn next_encoded_frame(&mut self) -> Option<VideoFrame> {
self.output_queue.pop_front()
}
pub fn codec(&self) -> CodecName {
self.encoded_format.codec_name().expect("infallible")
}
}
fn convert_annexb_to_mp4(annexb_data: &[u8]) -> orfail::Result<Vec<u8>> {
let mut mp4_data = Vec::new();
let mut pos = 0;
while pos < annexb_data.len() {
let start_code_len =
if pos + 4 <= annexb_data.len() && annexb_data[pos..pos + 4] == [0, 0, 0, 1] {
4
} else if pos + 3 <= annexb_data.len() && annexb_data[pos..pos + 3] == [0, 0, 1] {
3
} else if pos == 0 {
return Err(orfail::Failure::new("No start code found at beginning"));
} else {
break;
};
pos += start_code_len;
let nalu_start = pos;
let mut nalu_end = annexb_data.len();
for i in (pos + 3)..annexb_data.len() {
if i + 4 <= annexb_data.len() && annexb_data[i..i + 4] == [0, 0, 0, 1] {
nalu_end = i;
break;
}
if i + 3 <= annexb_data.len() && annexb_data[i..i + 3] == [0, 0, 1] {
nalu_end = i;
break;
}
}
let nalu_size = nalu_end - nalu_start;
mp4_data.extend_from_slice(&(nalu_size as u32).to_be_bytes());
mp4_data.extend_from_slice(&annexb_data[nalu_start..nalu_end]);
pos = nalu_end;
}
Ok(mp4_data)
}