use std::collections::VecDeque;
use std::sync::{Arc, OnceLock, mpsc};
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)]
struct EncodedFrameWithMeta {
data: Vec<u8>,
keyframe: bool,
input_frame: VideoFrame,
}
struct HandlerContext {
av1_sequence_header: Option<Vec<u8>>,
}
type HandlerContextSlot = Arc<OnceLock<HandlerContext>>;
type NvcodecHandler = shiguredo_nvcodec::FnEncodeHandler<VideoFrame, shiguredo_nvcodec::Error>;
type EncodeMessage = Result<EncodedFrameWithMeta, orfail::Failure>;
#[derive(Debug)]
pub struct NvcodecEncoder {
inner: shiguredo_nvcodec::Encoder<NvcodecHandler>,
rx: mpsc::Receiver<EncodeMessage>,
ok_buffer: VecDeque<EncodedFrameWithMeta>,
pending_error: Option<orfail::Failure>,
encoded_format: VideoFormat,
sample_entry: Option<SampleEntry>,
in_flight: usize,
in_flight_limit: usize,
}
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 mut config = options.encode_params.nvcodec_h264.clone();
Self::override_config_size_and_bitrate(&mut config, options);
log::debug!("nvcodec h264 encoder config: {config:?}");
Self::build_encoder(config, VideoFormat::H264, |seq_params| {
let entry =
video_h264::h264_sample_entry_from_annexb(width, height, &seq_params).or_fail()?;
Ok((
entry,
HandlerContext {
av1_sequence_header: None,
},
))
})
}
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 mut config = options.encode_params.nvcodec_h265.clone();
Self::override_config_size_and_bitrate(&mut config, options);
log::debug!("nvcodec h265 encoder config: {config:?}");
let frame_rate = options.frame_rate;
Self::build_encoder(config, VideoFormat::H265, move |seq_params| {
let entry = video_h265::h265_sample_entry_from_annexb(
width,
height,
frame_rate,
&seq_params,
)
.or_fail()?;
Ok((
entry,
HandlerContext {
av1_sequence_header: None,
},
))
})
}
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 mut config = options.encode_params.nvcodec_av1.clone();
Self::override_config_size_and_bitrate(&mut config, options);
log::debug!("nvcodec av1 encoder config: {config:?}");
Self::build_encoder(config, VideoFormat::Av1, move |seq_params| {
let entry = video_av1::av1_sample_entry(width, height, &seq_params);
Ok((
entry,
HandlerContext {
av1_sequence_header: Some(seq_params),
},
))
})
}
fn build_encoder(
config: shiguredo_nvcodec::EncoderConfig,
encoded_format: VideoFormat,
make_context: impl FnOnce(Vec<u8>) -> orfail::Result<(SampleEntry, HandlerContext)>,
) -> orfail::Result<Self> {
let context_slot: HandlerContextSlot = Arc::new(OnceLock::new());
let (tx, rx) = mpsc::channel();
let in_flight_limit = config.frame_interval_p as usize + 2;
let handler = build_handler(tx, context_slot.clone(), encoded_format);
let inner = shiguredo_nvcodec::Encoder::new(config, handler).or_fail()?;
let seq_params = inner.get_sequence_params().or_fail()?;
let (sample_entry, context) = make_context(seq_params).or_fail()?;
context_slot
.set(context)
.ok()
.expect("BUG: HandlerContext must not be set before Encoder::new returns");
Ok(Self {
inner,
rx,
ok_buffer: VecDeque::new(),
pending_error: None,
encoded_format,
sample_entry: Some(sample_entry),
in_flight: 0,
in_flight_limit,
})
}
fn override_config_size_and_bitrate(
config: &mut shiguredo_nvcodec::EncoderConfig,
options: &VideoEncoderOptions,
) {
config.width = options.width.get() as u32;
config.height = options.height.get() as u32;
config.framerate_num = options.frame_rate.numerator.get() as u32;
config.framerate_den = options.frame_rate.denumerator.get() as u32;
config.average_bitrate = Some(options.bitrate as u32);
}
pub fn encode(&mut self, frame: &VideoFrame) -> orfail::Result<()> {
self.take_pending_error()?;
(frame.format == VideoFormat::I420).or_fail()?;
if self.in_flight >= self.in_flight_limit {
self.inner.flush().or_fail()?;
self.in_flight = 0;
}
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()?;
let encode_options = shiguredo_nvcodec::EncodeOptions {
force_intra: false,
force_idr: false,
output_spspps: false,
};
self.inner
.encode(&nv12_data, &encode_options, frame.to_stripped())
.or_fail()?;
self.in_flight += 1;
Ok(())
}
pub fn finish(&mut self) -> orfail::Result<()> {
self.inner.flush().or_fail()?;
self.in_flight = 0;
self.take_pending_error()?;
Ok(())
}
fn drain_rx(&mut self) {
while let Ok(msg) = self.rx.try_recv() {
match msg {
Ok(encoded) => self.ok_buffer.push_back(encoded),
Err(err) => {
self.pending_error.get_or_insert(err);
}
}
}
}
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_encoded_frame(&mut self) -> Option<VideoFrame> {
self.drain_rx();
let encoded = self.ok_buffer.pop_front()?;
Some(VideoFrame {
source_id: encoded.input_frame.source_id.clone(),
data: encoded.data,
format: self.encoded_format,
keyframe: encoded.keyframe,
width: encoded.input_frame.width,
height: encoded.input_frame.height,
timestamp: encoded.input_frame.timestamp,
duration: encoded.input_frame.duration,
sample_entry: self.sample_entry.take(),
})
}
pub fn codec(&self) -> CodecName {
self.encoded_format.codec_name().expect("infallible")
}
}
fn build_handler(
tx: mpsc::Sender<EncodeMessage>,
context_slot: HandlerContextSlot,
encoded_format: VideoFormat,
) -> NvcodecHandler {
shiguredo_nvcodec::FnEncodeHandler::new(move |result| {
handle_encode_callback(&tx, &context_slot, encoded_format, result);
})
}
fn handle_encode_callback(
tx: &mpsc::Sender<EncodeMessage>,
context_slot: &OnceLock<HandlerContext>,
encoded_format: VideoFormat,
result: std::result::Result<
shiguredo_nvcodec::EncodedFrame<VideoFrame>,
shiguredo_nvcodec::Error,
>,
) {
let message = match result {
Ok(encoded_frame) => {
let context = context_slot
.get()
.expect("BUG: HandlerContext must be set before first encode() call");
let keyframe = matches!(
encoded_frame.picture_type(),
shiguredo_nvcodec::PictureType::I | shiguredo_nvcodec::PictureType::Idr
);
let (data, input_frame) = encoded_frame.into_parts();
convert_encoded_data(encoded_format, data, keyframe, context).map(|frame_data| {
EncodedFrameWithMeta {
data: frame_data,
keyframe,
input_frame,
}
})
}
Err(err) => Err(orfail::Failure::new(format!("nvcodec encode error: {err}"))),
};
let _ = tx.send(message);
}
fn convert_encoded_data(
encoded_format: VideoFormat,
data: Vec<u8>,
keyframe: bool,
context: &HandlerContext,
) -> orfail::Result<Vec<u8>> {
if encoded_format == VideoFormat::Av1 {
let seq_header = context
.av1_sequence_header
.as_deref()
.expect("BUG: AV1 encoder must have av1_sequence_header set");
Ok(prepend_av1_sequence_header_if_needed(
data, keyframe, seq_header,
))
} else {
convert_annexb_to_mp4(&data)
}
}
fn prepend_av1_sequence_header_if_needed(
data: Vec<u8>,
keyframe: bool,
seq_header: &[u8],
) -> Vec<u8> {
if !keyframe || has_sequence_header(&data) {
return data;
}
log::debug!(
"prepending Sequence Header OBU to AV1 keyframe (seq_header: {} bytes, frame: {} bytes)",
seq_header.len(),
data.len()
);
let mut new_data = Vec::with_capacity(seq_header.len() + data.len());
new_data.extend_from_slice(seq_header);
new_data.extend_from_slice(&data);
new_data
}
fn has_sequence_header(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
}
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)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn has_sequence_header_sh() {
assert!(has_sequence_header(&[0x08]));
}
#[test]
fn has_sequence_header_td() {
assert!(!has_sequence_header(&[0x10]));
}
#[test]
fn has_sequence_header_frame_header() {
assert!(!has_sequence_header(&[0x30]));
}
#[test]
fn has_sequence_header_empty() {
assert!(!has_sequence_header(&[]));
}
#[test]
fn convert_annexb_to_mp4_4byte_start_code() {
let annexb = [0, 0, 0, 1, 0xAA, 0xBB, 0xCC, 0xDD];
let mp4 = convert_annexb_to_mp4(&annexb).expect("should succeed");
assert_eq!(mp4, [0, 0, 0, 4, 0xAA, 0xBB, 0xCC, 0xDD]);
}
#[test]
fn convert_annexb_to_mp4_3byte_start_code() {
let annexb = [0, 0, 1, 0xAA, 0xBB, 0xCC];
let mp4 = convert_annexb_to_mp4(&annexb).expect("should succeed");
assert_eq!(mp4, [0, 0, 0, 3, 0xAA, 0xBB, 0xCC]);
}
#[test]
fn convert_annexb_to_mp4_multiple_nalus() {
let annexb = [
0, 0, 0, 1, 0xAA, 0xBB, 0xCC, 0xDD, 0, 0, 0, 1, 0xEE, 0xFF, ];
let mp4 = convert_annexb_to_mp4(&annexb).expect("should succeed");
assert_eq!(
mp4,
[
0, 0, 0, 4, 0xAA, 0xBB, 0xCC, 0xDD, 0, 0, 0, 2, 0xEE, 0xFF, ]
);
}
#[test]
fn convert_annexb_to_mp4_no_start_code_at_beginning() {
let annexb = [0xAA, 0xBB];
assert!(convert_annexb_to_mp4(&annexb).is_err());
}
#[test]
fn convert_annexb_to_mp4_empty() {
let annexb: [u8; 0] = [];
let mp4 = convert_annexb_to_mp4(&annexb).expect("should succeed");
assert!(mp4.is_empty());
}
}