use bytes::Bytes;
use openh264::OpenH264API;
use openh264::encoder::{BitRate, Encoder, EncoderConfig, FrameRate, IntraFramePeriod, RateControlMode, UsageType};
use openh264::formats::YUVSlices;
use openh264_sys2::{ENCODER_OPTION_BITRATE, SBitrateInfo, SPATIAL_LAYER_ALL};
use super::super::encoder::Config;
use super::Backend;
use crate::Error;
use crate::frame::Frame;
pub(crate) const NAME: &str = "openh264";
pub(crate) struct Openh264 {
encoder: Encoder,
pending: Option<u64>,
started: bool,
}
impl Openh264 {
pub(crate) fn open(config: &Config) -> Result<Box<dyn Backend>, Error> {
Ok(Box::new(Self::new(config)?))
}
fn new(config: &Config) -> Result<Self, Error> {
let cfg = EncoderConfig::new()
.bitrate(BitRate::from_bps(config.resolved_bitrate().min(u32::MAX as u64) as u32))
.max_frame_rate(FrameRate::from_hz(config.framerate as f32))
.rate_control_mode(RateControlMode::Bitrate)
.usage_type(UsageType::CameraVideoRealTime)
.intra_frame_period(IntraFramePeriod::from_num_frames(config.gop));
let encoder = Encoder::with_api_config(OpenH264API::from_source(), cfg)
.map_err(|e| Error::Codec(anyhow::anyhow!("openh264 init: {e}")))?;
tracing::info!(
encoder = NAME,
width = config.width,
height = config.height,
"opened H.264 encoder"
);
Ok(Self {
encoder,
pending: None,
started: false,
})
}
#[cfg(test)]
fn read_bitrate(&mut self) -> i64 {
let mut info = SBitrateInfo {
iLayer: SPATIAL_LAYER_ALL,
iBitrate: 0,
};
let status = unsafe {
let api = self.encoder.raw_api();
api.get_option(ENCODER_OPTION_BITRATE, std::ptr::from_mut(&mut info).cast())
};
assert_eq!(status, 0, "openh264 get bitrate failed");
info.iBitrate as i64
}
fn apply_bitrate(&mut self, bitrate: u64) -> Result<(), Error> {
let mut info = SBitrateInfo {
iLayer: SPATIAL_LAYER_ALL,
iBitrate: bitrate.min(i32::MAX as u64) as i32,
};
let status = unsafe {
let api = self.encoder.raw_api();
api.set_option(ENCODER_OPTION_BITRATE, std::ptr::from_mut(&mut info).cast())
};
if status != 0 {
return Err(Error::Codec(anyhow::anyhow!(
"openh264 set bitrate to {bitrate}: status {status}"
)));
}
Ok(())
}
}
impl Backend for Openh264 {
fn encode(&mut self, frame: &Frame, keyframe: bool) -> Result<Vec<Bytes>, Error> {
if self.started {
if let Some(bitrate) = self.pending.take() {
self.apply_bitrate(bitrate)?;
}
}
if keyframe {
self.encoder.force_intra_frame();
}
let i420 = frame.to_i420()?;
let (w, h) = (i420.width as usize, i420.height as usize);
let yuv = YUVSlices::new((i420.y(), i420.u(), i420.v()), (w, h), (w, w / 2, w / 2));
let bitstream = self
.encoder
.encode(&yuv)
.map_err(|e| Error::Codec(anyhow::anyhow!("openh264 encode: {e}")))?;
let bytes = bitstream.to_vec();
self.started = true;
Ok(if bytes.is_empty() {
Vec::new()
} else {
vec![Bytes::from(bytes)]
})
}
fn finish(&mut self) -> Result<Vec<Bytes>, Error> {
Ok(Vec::new())
}
fn set_bitrate(&mut self, bitrate: u64) -> Result<(), Error> {
if !self.started {
self.pending = Some(bitrate);
return Ok(());
}
self.pending = None;
self.apply_bitrate(bitrate)
}
fn name(&self) -> &str {
NAME
}
}
#[cfg(test)]
mod tests {
use super::super::super::encoder::Kind;
use super::*;
use crate::frame::I420;
fn config() -> Config {
Config {
kind: Kind::Software,
..Config::new(320, 240, 30)
}
}
fn gray() -> Frame {
Frame::I420(I420 {
width: 320,
height: 240,
data: vec![0x80u8; I420::len(320, 240)],
})
}
#[test]
fn set_bitrate_reaches_the_encoder() {
let mut enc = Openh264::new(&config()).unwrap();
enc.encode(&gray(), true).unwrap();
let lower = config().resolved_bitrate() / 2;
enc.set_bitrate(lower).unwrap();
assert_eq!(enc.read_bitrate(), lower as i64);
}
#[test]
fn set_bitrate_above_the_opening_rate_is_rejected() {
let mut enc = Openh264::new(&config()).unwrap();
enc.encode(&gray(), true).unwrap();
let higher = config().resolved_bitrate() * 4;
assert!(enc.set_bitrate(higher).is_err());
}
#[test]
fn set_bitrate_at_the_opening_rate_is_accepted() {
let mut enc = Openh264::new(&config()).unwrap();
enc.encode(&gray(), true).unwrap();
let opened = config().resolved_bitrate();
enc.set_bitrate(opened / 2).unwrap();
enc.set_bitrate(opened).unwrap();
assert_eq!(enc.read_bitrate(), opened as i64);
}
#[test]
fn a_live_set_supersedes_a_deferred_one() {
let mut enc = Openh264::new(&config()).unwrap();
let opened = config().resolved_bitrate();
enc.set_bitrate(opened / 2).unwrap();
enc.encode(&gray(), true).unwrap();
enc.set_bitrate(opened / 4).unwrap();
enc.encode(&gray(), false).unwrap();
assert_eq!(enc.read_bitrate(), (opened / 4) as i64, "the deferred rate resurrected");
}
}