use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::ptr::NonNull;
use crate::error::{Error, Result, last_ffi_error};
use crate::session::Protocol;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum VideoBackend {
#[default]
Auto,
GStreamer,
HwPipeline,
}
#[derive(Debug, Clone)]
pub struct VideoOptions {
pub width: u32,
pub height: u32,
pub pixel_format: Option<String>,
pub codec: String,
pub encoder: Option<String>,
pub bitrate_kbps: u32,
pub adaptive_bitrate: bool,
pub min_bitrate_kbps: Option<u32>,
pub max_bitrate_kbps: Option<u32>,
pub bitrate_priority: f32,
pub fps: u32,
pub keyframe_distance: f64,
pub stereo: bool,
pub backend: VideoBackend,
pub shm_publish: Option<String>,
pub passthrough: bool,
}
impl Default for VideoOptions {
fn default() -> Self {
Self {
width: 1280,
height: 720,
pixel_format: None,
codec: "h264".to_string(),
encoder: None,
bitrate_kbps: 2000,
adaptive_bitrate: true,
min_bitrate_kbps: None,
max_bitrate_kbps: None,
bitrate_priority: 1.0,
fps: 30,
keyframe_distance: 2.0,
stereo: false,
backend: VideoBackend::Auto,
shm_publish: None,
passthrough: false,
}
}
}
impl VideoOptions {
pub fn with_pixel_format(mut self, pixel_format: impl Into<String>) -> Self {
self.pixel_format = Some(pixel_format.into());
self
}
pub fn with_source_format(self, source_format: impl Into<String>) -> Self {
self.with_pixel_format(source_format)
}
pub fn with_encoder(mut self, encoder: impl Into<String>) -> Self {
self.encoder = Some(encoder.into());
self
}
pub fn with_backend(mut self, backend: VideoBackend) -> Self {
self.backend = backend;
self
}
pub fn with_shm_publish(mut self, service: impl Into<String>) -> Self {
self.shm_publish = Some(service.into());
self
}
pub fn with_passthrough(mut self, passthrough: bool) -> Self {
self.passthrough = passthrough;
self
}
}
struct RawVideoOptions {
raw: adamo_sys::adamo_video_options_t,
_pixel_format: Option<CString>,
_codec: CString,
_encoder: Option<CString>,
_shm_publish: Option<CString>,
}
impl RawVideoOptions {
fn new(options: &VideoOptions) -> Result<Self> {
let pixel_format = options
.pixel_format
.as_deref()
.map(CString::new)
.transpose()?;
let codec = CString::new(options.codec.as_str())?;
let encoder = options.encoder.as_deref().map(CString::new).transpose()?;
let shm_publish = options
.shm_publish
.as_deref()
.map(CString::new)
.transpose()?;
let mut raw = unsafe { adamo_sys::adamo_video_options_default() };
raw.width = options.width;
raw.height = options.height;
raw.pixel_format = pixel_format
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(std::ptr::null());
raw.codec = codec.as_ptr();
raw.encoder = encoder
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(std::ptr::null());
raw.bitrate_kbps = options.bitrate_kbps;
raw.adaptive_bitrate = if options.adaptive_bitrate { 1 } else { 0 };
raw.min_bitrate_kbps = options.min_bitrate_kbps.unwrap_or(0);
raw.max_bitrate_kbps = options.max_bitrate_kbps.unwrap_or(0);
raw.bitrate_priority = options.bitrate_priority;
raw.fps = options.fps;
raw.keyframe_distance = options.keyframe_distance;
raw.stereo = if options.stereo { 1 } else { 0 };
raw.backend = backend_raw(options.backend);
raw.shm_publish = shm_publish
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(std::ptr::null());
raw.passthrough = if options.passthrough { 1 } else { 0 };
Ok(Self {
raw,
_pixel_format: pixel_format,
_codec: codec,
_encoder: encoder,
_shm_publish: shm_publish,
})
}
fn as_ptr(&self) -> *const adamo_sys::adamo_video_options_t {
&self.raw
}
}
pub struct Robot {
raw: NonNull<adamo_sys::adamo_robot_t>,
}
unsafe impl Send for Robot {}
impl Robot {
pub fn new(api_key: &str, name: Option<&str>, protocol: Protocol) -> Result<Self> {
let api_key = CString::new(api_key)?;
let name_c = match name {
Some(n) => Some(CString::new(n)?),
None => None,
};
let name_ptr = name_c
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(std::ptr::null());
let raw = unsafe {
adamo_sys::adamo_robot_new(api_key.as_ptr(), name_ptr, protocol_raw(protocol))
};
NonNull::new(raw)
.map(|raw| Robot { raw })
.ok_or_else(last_ffi_error)
}
pub fn new_default(api_key: &str, name: Option<&str>) -> Result<Self> {
let api_key = CString::new(api_key)?;
let name_c = match name {
Some(n) => Some(CString::new(n)?),
None => None,
};
let name_ptr = name_c
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(std::ptr::null());
let raw = unsafe { adamo_sys::adamo_robot_new_default(api_key.as_ptr(), name_ptr) };
NonNull::new(raw)
.map(|raw| Robot { raw })
.ok_or_else(last_ffi_error)
}
pub fn video(
&mut self,
name: &str,
width: u32,
height: u32,
pixel_format: &str,
fps: u32,
bitrate_kbps: u32,
stereo: bool,
) -> Result<VideoTrack<'_>> {
let name = CString::new(name)?;
let fmt = CString::new(pixel_format)?;
let raw = unsafe {
adamo_sys::adamo_robot_video(
self.raw.as_ptr(),
name.as_ptr(),
width,
height,
fmt.as_ptr(),
fps,
bitrate_kbps,
stereo,
)
};
NonNull::new(raw)
.map(|raw| VideoTrack {
raw,
_robot: PhantomData,
})
.ok_or_else(last_ffi_error)
}
pub fn video_with_options(
&mut self,
name: &str,
options: &VideoOptions,
) -> Result<VideoTrack<'_>> {
let name = CString::new(name)?;
let options = RawVideoOptions::new(options)?;
let raw = unsafe {
adamo_sys::adamo_robot_video_configured(
self.raw.as_ptr(),
name.as_ptr(),
options.as_ptr(),
)
};
NonNull::new(raw)
.map(|raw| VideoTrack {
raw,
_robot: PhantomData,
})
.ok_or_else(last_ffi_error)
}
pub fn video_with_encoder(
&mut self,
name: &str,
width: u32,
height: u32,
pixel_format: &str,
fps: u32,
bitrate_kbps: u32,
stereo: bool,
encoder: &str,
hw_pipeline: bool,
) -> Result<VideoTrack<'_>> {
let name = CString::new(name)?;
let fmt = CString::new(pixel_format)?;
let encoder = CString::new(encoder)?;
let raw = unsafe {
adamo_sys::adamo_robot_video_with_options(
self.raw.as_ptr(),
name.as_ptr(),
width,
height,
fmt.as_ptr(),
fps,
bitrate_kbps,
stereo,
encoder.as_ptr(),
hw_pipeline,
)
};
NonNull::new(raw)
.map(|raw| VideoTrack {
raw,
_robot: PhantomData,
})
.ok_or_else(last_ffi_error)
}
pub fn attach_v4l2(
&mut self,
name: &str,
device: &str,
width: u32,
height: u32,
fps: u32,
bitrate_kbps: u32,
stereo: bool,
) -> Result<()> {
let name = CString::new(name)?;
let device = CString::new(device)?;
let rc = unsafe {
adamo_sys::adamo_robot_attach_video_v4l2(
self.raw.as_ptr(),
name.as_ptr(),
device.as_ptr(),
width,
height,
fps,
bitrate_kbps,
stereo,
)
};
if rc == 0 { Ok(()) } else { Err(last_ffi_error()) }
}
pub fn attach_v4l2_with_options(
&mut self,
name: &str,
device: &str,
options: &VideoOptions,
) -> Result<()> {
let name = CString::new(name)?;
let device = CString::new(device)?;
let options = RawVideoOptions::new(options)?;
let rc = unsafe {
adamo_sys::adamo_robot_attach_video_v4l2_configured(
self.raw.as_ptr(),
name.as_ptr(),
device.as_ptr(),
options.as_ptr(),
)
};
if rc == 0 { Ok(()) } else { Err(last_ffi_error()) }
}
pub fn attach_gst(
&mut self,
name: &str,
pipeline: &str,
width: u32,
height: u32,
fps: u32,
bitrate_kbps: u32,
stereo: bool,
) -> Result<()> {
let name = CString::new(name)?;
let pipeline = CString::new(pipeline)?;
let rc = unsafe {
adamo_sys::adamo_robot_attach_video_gst(
self.raw.as_ptr(),
name.as_ptr(),
pipeline.as_ptr(),
width,
height,
fps,
bitrate_kbps,
stereo,
)
};
if rc == 0 { Ok(()) } else { Err(last_ffi_error()) }
}
pub fn attach_gst_with_options(
&mut self,
name: &str,
pipeline: &str,
options: &VideoOptions,
) -> Result<()> {
let name = CString::new(name)?;
let pipeline = CString::new(pipeline)?;
let options = RawVideoOptions::new(options)?;
let rc = unsafe {
adamo_sys::adamo_robot_attach_video_gst_configured(
self.raw.as_ptr(),
name.as_ptr(),
pipeline.as_ptr(),
options.as_ptr(),
)
};
if rc == 0 { Ok(()) } else { Err(last_ffi_error()) }
}
pub fn attach_shm(
&mut self,
name: &str,
service: &str,
width: u32,
height: u32,
pixel_format: &str,
fps: u32,
bitrate_kbps: u32,
stereo: bool,
) -> Result<()> {
let name = CString::new(name)?;
let service = CString::new(service)?;
let pixel_format = CString::new(pixel_format)?;
let rc = unsafe {
adamo_sys::adamo_robot_attach_video_shm(
self.raw.as_ptr(),
name.as_ptr(),
service.as_ptr(),
width,
height,
pixel_format.as_ptr(),
fps,
bitrate_kbps,
stereo,
)
};
if rc == 0 { Ok(()) } else { Err(last_ffi_error()) }
}
pub fn attach_shm_with_options(
&mut self,
name: &str,
service: &str,
options: &VideoOptions,
) -> Result<()> {
let name = CString::new(name)?;
let service = CString::new(service)?;
let options = RawVideoOptions::new(options)?;
let rc = unsafe {
adamo_sys::adamo_robot_attach_video_shm_configured(
self.raw.as_ptr(),
name.as_ptr(),
service.as_ptr(),
options.as_ptr(),
)
};
if rc == 0 { Ok(()) } else { Err(last_ffi_error()) }
}
pub fn run(self) -> Result<()> {
let raw = self.raw.as_ptr();
std::mem::forget(self);
let rc = unsafe { adamo_sys::adamo_robot_run(raw) };
if rc == 0 { Ok(()) } else { Err(last_ffi_error()) }
}
}
impl Drop for Robot {
fn drop(&mut self) {
unsafe { adamo_sys::adamo_robot_free(self.raw.as_ptr()) };
}
}
pub struct VideoTrack<'a> {
raw: NonNull<adamo_sys::adamo_video_track_t>,
_robot: PhantomData<&'a mut Robot>,
}
unsafe impl Send for VideoTrack<'_> {}
impl VideoTrack<'_> {
pub fn send(&mut self, frame: &[u8]) -> Result<()> {
let rc = unsafe {
adamo_sys::adamo_video_track_send(self.raw.as_ptr(), frame.as_ptr(), frame.len())
};
if rc == 0 { Ok(()) } else { Err(last_ffi_error()) }
}
}
impl Drop for VideoTrack<'_> {
fn drop(&mut self) {
unsafe { adamo_sys::adamo_video_track_free(self.raw.as_ptr()) };
}
}
pub fn detect_encoder() -> Result<&'static str> {
let ptr = unsafe { adamo_sys::adamo_detect_encoder() };
if ptr.is_null() {
return Err(last_ffi_error());
}
unsafe { CStr::from_ptr(ptr) }
.to_str()
.map_err(|_| Error::InvalidUtf8)
}
fn protocol_raw(p: Protocol) -> adamo_sys::adamo_protocol_t {
match p {
Protocol::Udp => adamo_sys::ADAMO_PROTOCOL_UDP,
Protocol::Quic => adamo_sys::ADAMO_PROTOCOL_QUIC,
Protocol::Tcp => adamo_sys::ADAMO_PROTOCOL_TCP,
}
}
fn backend_raw(backend: VideoBackend) -> adamo_sys::adamo_video_backend_t {
match backend {
VideoBackend::Auto => adamo_sys::ADAMO_VIDEO_BACKEND_AUTO,
VideoBackend::GStreamer => adamo_sys::ADAMO_VIDEO_BACKEND_GSTREAMER,
VideoBackend::HwPipeline => adamo_sys::ADAMO_VIDEO_BACKEND_HW_PIPELINE,
}
}