use crate::error::{MppError, MppResult};
use crate::ffi::{self, MppLib};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct MppEncoderConfig {
pub width: u32,
pub height: u32,
pub bitrate_kbps: u32,
pub fps: u32,
pub gop: u32,
pub profile: u32,
}
impl Default for MppEncoderConfig {
fn default() -> Self {
Self {
width: 1280,
height: 720,
bitrate_kbps: 4000,
fps: 30,
gop: 30,
profile: 66,
}
}
}
pub struct MppH264Encoder {
lib: Arc<MppLib>,
ctx: ffi::MppCtx,
api: *const ffi::MppApi,
cfg: MppEncoderConfig,
}
unsafe impl Send for MppH264Encoder {}
impl MppH264Encoder {
pub fn new(cfg: MppEncoderConfig) -> MppResult<Self> {
if cfg.width == 0 || cfg.height == 0 {
return Err(MppError::InvalidConfig("width/height must be > 0".into()));
}
if !cfg.width.is_multiple_of(16) || !cfg.height.is_multiple_of(16) {
return Err(MppError::InvalidConfig(
"width/height must be multiples of 16 for H.264 macroblock alignment".into(),
));
}
if cfg.bitrate_kbps == 0 {
return Err(MppError::InvalidConfig("bitrate_kbps must be > 0".into()));
}
if cfg.fps == 0 {
return Err(MppError::InvalidConfig("fps must be > 0".into()));
}
let lib = ffi::load_library()?;
let mut ctx: ffi::MppCtx = std::ptr::null_mut();
let mut api_raw: *mut std::ffi::c_void = std::ptr::null_mut();
let ret = unsafe { (lib.mpp_create)(&mut ctx, &mut api_raw as *mut _ as *mut _) };
if ret != 0 {
return Err(MppError::CallFailed {
op: "mpp_create",
status: ret,
});
}
if api_raw.is_null() {
unsafe { (lib.mpp_destroy)(ctx) };
return Err(MppError::CallFailed {
op: "mpp_create",
status: -1,
});
}
let api = api_raw as *const ffi::MppApi;
let ret = unsafe { (lib.mpp_init)(ctx, ffi::MPP_CTX_ENC, ffi::MPP_VIDEO_CODING_AVC) };
if ret != 0 {
unsafe { (lib.mpp_destroy)(ctx) };
return Err(MppError::CallFailed {
op: "mpp_init",
status: ret,
});
}
let enc = Self { lib, ctx, api, cfg };
enc.apply_cfg()?;
Ok(enc)
}
fn apply_cfg(&self) -> MppResult<()> {
let mut cfg: ffi::MppEncCfg = std::ptr::null_mut();
let ret = unsafe { (self.lib.enc_cfg_init)(&mut cfg) };
if ret != 0 {
return Err(MppError::CallFailed {
op: "mpp_enc_cfg_init",
status: ret,
});
}
let set = |name: &[u8], val: i32| -> MppResult<()> {
let r = unsafe { (self.lib.enc_cfg_set_s32)(cfg, name.as_ptr(), val) };
if r != 0 {
return Err(MppError::CallFailed {
op: "mpp_enc_cfg_set_s32",
status: r,
});
}
Ok(())
};
set(b"prep:width\0", self.cfg.width as i32)?;
set(b"prep:height\0", self.cfg.height as i32)?;
set(b"prep:hor_stride\0", self.cfg.width as i32)?;
set(b"prep:ver_stride\0", self.cfg.height as i32)?;
set(b"prep:format\0", ffi::MPP_FMT_YUV420SP as i32)?; set(b"rc:mode\0", 1 )?;
set(b"rc:bps_target\0", (self.cfg.bitrate_kbps * 1000) as i32)?;
set(b"rc:fps_in_num\0", self.cfg.fps as i32)?;
set(b"rc:fps_in_denorm\0", 1)?;
set(b"rc:fps_out_num\0", self.cfg.fps as i32)?;
set(b"rc:fps_out_denorm\0", 1)?;
set(b"rc:gop\0", self.cfg.gop as i32)?;
set(b"h264:profile\0", self.cfg.profile as i32)?;
set(b"h264:level\0", 40 )?;
set(
b"h264:cabac_en\0",
if self.cfg.profile == 66 { 0 } else { 1 },
)?;
let ret = unsafe {
let api_ref = &*self.api;
(api_ref.control)(self.ctx, ffi::MPP_ENC_SET_CFG, cfg)
};
let _ = unsafe { (self.lib.enc_cfg_deinit)(cfg) };
if ret != 0 {
return Err(MppError::CallFailed {
op: "MPP_ENC_SET_CFG",
status: ret,
});
}
Ok(())
}
pub fn encode_nv12_dmabuf(&mut self, fd: i32, len_bytes: usize) -> MppResult<Vec<u8>> {
let mut buf: ffi::MppBuffer = std::ptr::null_mut();
let ret = unsafe { (self.lib.buffer_import_from_fd)(&mut buf, fd, len_bytes) };
if ret != 0 {
return Err(MppError::CallFailed {
op: "mpp_buffer_import",
status: ret,
});
}
let result = self.encode_with_buffer(buf);
let _ = unsafe { (self.lib.buffer_put)(buf, c"yscv-video-mpp".as_ptr().cast()) };
result
}
fn encode_with_buffer(&mut self, buf: ffi::MppBuffer) -> MppResult<Vec<u8>> {
let mut frame: ffi::MppFrame = std::ptr::null_mut();
let ret = unsafe { (self.lib.frame_init)(&mut frame) };
if ret != 0 {
return Err(MppError::CallFailed {
op: "mpp_frame_init",
status: ret,
});
}
unsafe {
(self.lib.frame_set_width)(frame, self.cfg.width);
(self.lib.frame_set_height)(frame, self.cfg.height);
(self.lib.frame_set_hor_stride)(frame, self.cfg.width);
(self.lib.frame_set_ver_stride)(frame, self.cfg.height);
(self.lib.frame_set_fmt)(frame, ffi::MPP_FMT_YUV420SP);
(self.lib.frame_set_buffer)(frame, buf);
}
let put_ret = unsafe {
let api_ref = &*self.api;
(api_ref.encode_put_frame)(self.ctx, frame)
};
if put_ret != 0 {
let _ = unsafe { (self.lib.frame_deinit)(&mut frame) };
return Err(MppError::CallFailed {
op: "encode_put_frame",
status: put_ret,
});
}
let mut packet: ffi::MppPacket = std::ptr::null_mut();
let pkt_ret = unsafe {
let api_ref = &*self.api;
(api_ref.encode_get_packet)(self.ctx, &mut packet)
};
if pkt_ret != 0 || packet.is_null() {
return Err(MppError::CallFailed {
op: "encode_get_packet",
status: pkt_ret,
});
}
let nal: Vec<u8> = unsafe {
let data = (self.lib.packet_get_data)(packet);
let len = (self.lib.packet_get_length)(packet);
if data.is_null() || len == 0 {
Vec::new()
} else {
std::slice::from_raw_parts(data, len).to_vec()
}
};
let _ = unsafe { (self.lib.packet_deinit)(&mut packet) };
Ok(nal)
}
pub fn cfg(&self) -> &MppEncoderConfig {
&self.cfg
}
}
impl Drop for MppH264Encoder {
fn drop(&mut self) {
if !self.ctx.is_null() {
unsafe { (self.lib.mpp_destroy)(self.ctx) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_validates_alignment() {
let cfg = MppEncoderConfig {
width: 1281, height: 720,
..Default::default()
};
let res = MppH264Encoder::new(cfg);
match res {
Err(MppError::InvalidConfig(msg)) => assert!(msg.contains("multiples of 16")),
Err(MppError::LibraryNotFound) => {}
Ok(_) => panic!("encoder constructed unexpectedly without librockchip_mpp.so"),
Err(other) => panic!("unexpected error: {other}"),
}
}
#[test]
fn config_validates_zero_dims() {
let cfg = MppEncoderConfig {
width: 0,
..Default::default()
};
let res = MppH264Encoder::new(cfg);
match res {
Err(MppError::InvalidConfig(_)) => {}
Err(MppError::LibraryNotFound) => {}
Ok(_) => panic!("encoder constructed unexpectedly without librockchip_mpp.so"),
Err(other) => panic!("unexpected error: {other}"),
}
}
#[test]
fn config_default_is_valid_shape() {
let cfg = MppEncoderConfig::default();
assert_eq!(cfg.width % 16, 0);
assert_eq!(cfg.height % 16, 0);
assert!(cfg.bitrate_kbps > 0);
assert!(cfg.fps > 0);
}
#[test]
fn mpp_api_struct_size_matches_sdk() {
assert_eq!(std::mem::size_of::<ffi::MppApi>(), 168);
}
}