#[cfg(target_os = "linux")]
mod vulkan;
#[cfg(target_os = "windows")]
mod media_foundation;
#[cfg(target_os = "macos")]
mod video_toolbox;
use std::
{
os::raw::c_int,
time::Instant,
};
use openh264::
{
OpenH264API,
Timestamp,
formats::YUVSlices,
encoder::
{
Encoder,
EncoderConfig,
BitRate,
FrameRate,
IntraFramePeriod,
Complexity,
UsageType,
RateControlMode,
},
};
const ENCODER_OPTION_BITRATE: c_int = 5;
const ENCODER_OPTION_MAX_BITRATE: c_int = 6;
const SPATIAL_LAYER_0: c_int = 0;
const SPATIAL_LAYER_ALL: c_int = 4;
pub struct Planes<'a> {
pub width: u32,
pub height: u32,
pub y: &'a [u8],
pub u: &'a [u8],
pub v: &'a [u8],
}
#[derive(Clone, Copy)]
pub struct Settings {
pub width: u32,
pub height: u32,
pub fps: u32,
pub bitrate: u32,
pub keyframe_interval: u32,
}
impl Settings
{
pub fn new(width: u32, height: u32, fps: u32, bitrate: u32) -> Self
{
Self { width, height, fps, bitrate, keyframe_interval: fps * 2 }
}
}
pub struct Budget {
rate: f64,
balance: f64,
refilled: Instant,
}
impl Budget
{
pub fn new(bitrate: u32) -> Self
{
Self { rate: f64::from(bitrate), balance: f64::from(bitrate), refilled: Instant::now() }
}
pub fn allows(&mut self, keyframe: bool) -> bool {
let now = Instant::now();
self.balance = (self.balance + self.rate * (now - self.refilled).as_secs_f64()).min(self.rate);
self.refilled = now;
keyframe || self.balance >= 0.0
}
pub fn spend(&mut self, bytes: usize)
{
self.balance -= bytes as f64 * 8.0;
}
pub fn set_rate(&mut self, bitrate: u32)
{
self.rate = f64::from(bitrate);
self.balance = self.balance.min(self.rate);
}
}
#[derive(Default)]
pub struct ParameterSets {
sps: Vec<u8>,
pps: Vec<u8>,
}
impl ParameterSets
{
pub fn complete(&mut self, frame: Vec<u8>) -> Vec<u8> {
let mut idr = false;
let mut sps = false;
for unit in nal_units(&frame)
{
match unit.first().map(|header| header & 0x1f)
{
Some(5) => idr = true,
Some(7) =>
{
sps = true;
self.sps = annex_b(unit);
},
Some(8) => self.pps = annex_b(unit),
_ => {},
}
}
if !idr || sps || self.sps.is_empty() { return frame; }
let mut complete = Vec::with_capacity(self.sps.len() + self.pps.len() + frame.len());
complete.extend_from_slice(&self.sps);
complete.extend_from_slice(&self.pps);
complete.extend_from_slice(&frame);
complete
}
}
struct Software {
encoder: Encoder,
started: bool, bitrate: Option<u32>, epoch: Instant, applied: u32, }
#[repr(C)]
struct BitrateInfo {
layer: c_int,
bitrate: c_int,
}
impl Software
{
fn new(settings: Settings) -> Result<Self, String>
{
let config = EncoderConfig::new()
.max_frame_rate(FrameRate::from_hz(settings.fps as f32))
.rate_control_mode(RateControlMode::Bitrate)
.bitrate(BitRate::from_bps(settings.bitrate))
.intra_frame_period(IntraFramePeriod::from_num_frames(settings.keyframe_interval))
.complexity(Complexity::Low)
.usage_type(UsageType::CameraVideoRealTime)
.skip_frames(true)
.adaptive_quantization(false)
.background_detection(false);
let encoder = Encoder::with_api_config(OpenH264API::from_source(), config).map_err(|error| error.to_string())?;
Ok(Self { encoder, started: false, bitrate: None, epoch: Instant::now(), applied: settings.bitrate })
}
fn apply_bitrate(&mut self, bitrate: u32)
{
let options = if bitrate > self.applied
{
[ENCODER_OPTION_MAX_BITRATE, ENCODER_OPTION_BITRATE]
} else
{
[ENCODER_OPTION_BITRATE, ENCODER_OPTION_MAX_BITRATE]
};
for option in options
{
let layer = if option == ENCODER_OPTION_MAX_BITRATE { SPATIAL_LAYER_0 } else { SPATIAL_LAYER_ALL };
let mut info = BitrateInfo { layer, bitrate: bitrate as c_int };
unsafe { self.encoder.raw_api().set_option(option, (&raw mut info).cast()) };
}
self.applied = bitrate;
}
}
impl Backend for Software
{
fn encode(&mut self, planes: &Planes, keyframe: bool) -> Result<Vec<u8>, String>
{
if keyframe { self.encoder.force_intra_frame(); }
if self.started && let Some(bitrate) = self.bitrate.take() { self.apply_bitrate(bitrate); }
let (width, height) = (planes.width as usize, planes.height as usize);
let source = YUVSlices::new((planes.y, planes.u, planes.v), (width, height), (width, width / 2, width / 2));
let timestamp = Timestamp::from_millis(self.epoch.elapsed().as_millis() as u64 + 1);
let frame = self.encoder.encode_at(&source, timestamp)
.map(|bitstream| bitstream.to_vec())
.map_err(|error| error.to_string());
self.started = true;
frame
}
fn set_bitrate(&mut self, bitrate: u32)
{
if self.started { self.apply_bitrate(bitrate) } else { self.bitrate = Some(bitrate) }
}
fn hardware(&self) -> bool
{
false
}
}
pub trait Backend {
fn encode(&mut self, planes: &Planes, keyframe: bool) -> Result<Vec<u8>, String>;
fn set_bitrate(&mut self, bitrate: u32);
fn hardware(&self) -> bool;
}
#[allow(unreachable_code)]
fn open_hardware(settings: Settings) -> Result<Box<dyn Backend>, String> {
#[cfg(target_os = "linux")]
return vulkan::VulkanEncoder::new(settings).map(|encoder| Box::new(encoder) as Box<dyn Backend>);
#[cfg(target_os = "windows")]
return media_foundation::MediaFoundationEncoder::new(settings).map(|encoder| Box::new(encoder) as Box<dyn Backend>);
#[cfg(target_os = "macos")]
return video_toolbox::VideoToolboxEncoder::new(settings).map(|encoder| Box::new(encoder) as Box<dyn Backend>);
let _ = settings;
Err("no hardware encoder on this platform".to_owned())
}
fn annex_b(unit: &[u8]) -> Vec<u8> {
let mut framed = Vec::with_capacity(unit.len() + 4);
framed.extend_from_slice(&[0, 0, 0, 1]);
framed.extend_from_slice(unit);
framed
}
pub fn open(settings: Settings, hardware: &mut bool) -> Result<Box<dyn Backend>, String> {
if *hardware
{
match open_hardware(settings)
{
Ok(encoder) => return Ok(encoder),
Err(_) => *hardware = false,
}
}
Software::new(settings).map(|encoder| Box::new(encoder) as Box<dyn Backend>)
}
pub fn nal_units(data: &[u8]) -> impl Iterator<Item = &[u8]> {
let mut rest = data;
std::iter::from_fn(move ||
{
let start = rest.windows(3).position(|window| window == [0, 0, 1])? + 3;
rest = &rest[start..];
let end = rest.windows(3).position(|window| window == [0, 0, 1]).unwrap_or(rest.len());
let unit = &rest[..end];
rest = &rest[end..];
Some(unit.strip_suffix(&[0]).unwrap_or(unit))
})
}
pub fn write_nv12
(
planes: &Planes,
luma: &mut [u8],
luma_stride: usize,
chroma: &mut [u8],
chroma_stride: usize,
coded: (usize, usize),
)
{
let (width, height) = (planes.width as usize, planes.height as usize);
let (coded_width, coded_height) = coded;
for (row, line) in luma.chunks_mut(luma_stride).take(coded_height).enumerate()
{
let source = &planes.y[row.min(height - 1) * width..][..width];
line[..width].copy_from_slice(source);
line[width..coded_width].fill(source[width - 1]);
}
let half = width / 2;
for (row, line) in chroma.chunks_mut(chroma_stride).take(coded_height / 2).enumerate()
{
let offset = row.min(height / 2 - 1) * half;
let (u, v) = (&planes.u[offset..][..half], &planes.v[offset..][..half]);
for (pair, (u, v)) in line[..half * 2].chunks_exact_mut(2).zip(u.iter().zip(v))
{
pair[0] = *u;
pair[1] = *v;
}
for pair in line[half * 2..coded_width].chunks_exact_mut(2)
{
pair[0] = u[half - 1];
pair[1] = v[half - 1];
}
}
}