char-rtc-obs/rtc-backend/src/ffi/output.rs

175 lines
4.9 KiB
Rust

use crate::output::{EncodedPacket, EncodedPacketType, OutputStream, OutputStreamError};
use anyhow::Result;
use log::{error, info};
use std::{
os::raw::{c_char, c_void},
slice,
time::Duration,
};
use tokio::runtime::Runtime;
pub struct RTCOutput {
stream: OutputStream,
runtime: Runtime,
}
/// cbindgen:prefix-with-name
#[repr(C)]
pub enum RTCOutputError {
ConnectFailed,
NetworkError,
}
impl From<OutputStreamError> for RTCOutputError {
fn from(ose: OutputStreamError) -> Self {
match ose {
OutputStreamError::ConnectFailed => RTCOutputError::ConnectFailed,
OutputStreamError::NetworkError => RTCOutputError::NetworkError,
OutputStreamError::WriteError(_) => RTCOutputError::NetworkError,
}
}
}
// You must call `char_rtc_output_free` on the returned value
#[no_mangle]
pub extern "C" fn char_rtc_output_new() -> *mut RTCOutput {
(|| -> Result<*mut RTCOutput> {
let runtime = tokio::runtime::Runtime::new()?;
let stream = runtime.block_on(OutputStream::new())?;
Ok(Box::into_raw(Box::new(RTCOutput { stream, runtime })))
})()
.unwrap_or_else(|e| {
error!("Unable to create whip output: {e:?}");
std::ptr::null_mut::<RTCOutput>()
})
}
#[no_mangle]
pub unsafe extern "C" fn char_rtc_output_free(output: *mut RTCOutput) {
info!("Freeing whip output");
if !output.is_null() {
drop(Box::from_raw(output));
}
}
#[no_mangle]
pub unsafe extern "C" fn char_rtc_output_bytes_sent(output: *const RTCOutput) -> u64 {
let output = output.as_ref().unwrap();
output.stream.bytes_sent()
}
#[no_mangle]
pub unsafe extern "C" fn char_rtc_output_dropped_frames(output: *const RTCOutput) -> i32 {
let output = output.as_ref().unwrap();
output.stream.dropped_frames()
}
#[no_mangle]
pub unsafe extern "C" fn char_rtc_output_congestion(output: *const RTCOutput) -> f64 {
let output = output.as_ref().unwrap();
output.stream.congestion()
}
#[no_mangle]
pub unsafe extern "C" fn char_rtc_output_connect_time_ms(output: *const RTCOutput) -> i32 {
let output = output.as_ref().unwrap();
output.stream.connect_time().as_millis() as i32
}
#[no_mangle]
pub unsafe extern "C" fn char_rtc_output_connect(
output: *const RTCOutput,
url: *const c_char,
bearer_token: *const c_char,
) {
let output = output.as_ref().unwrap();
let url = std::ffi::CStr::from_ptr(url).to_str().unwrap().to_owned();
let bearer_token = if !bearer_token.is_null() {
Some(
std::ffi::CStr::from_ptr(bearer_token)
.to_str()
.unwrap()
.to_owned(),
)
} else {
None
};
output.runtime.spawn(async move {
output.stream.connect(&url, bearer_token.as_deref()).await;
});
}
// Once closed, you cannot call `char_rtc_output_connect` again
#[no_mangle]
pub unsafe extern "C" fn char_rtc_output_close(output: *const RTCOutput) {
let output = output.as_ref().unwrap();
info!("Closing whip output");
output
.runtime
.block_on(async {
info!("I'm on a thread!");
output.stream.close().await
})
.unwrap_or_else(|e| error!("Failed closing whip output: {e:?}"))
}
/// Write an audio or video packet to the whip output
#[no_mangle]
pub unsafe extern "C" fn char_rtc_output_write(
output: *const RTCOutput,
data: *const u8,
size: usize,
duration: u64,
is_audio: bool,
) -> bool {
let output = output.as_ref().unwrap();
let slice: &[u8] = slice::from_raw_parts(data, size);
let encoded_packet = EncodedPacket {
data: slice.to_owned(),
duration: Duration::from_micros(duration),
typ: if is_audio {
EncodedPacketType::Audio
} else {
EncodedPacketType::Video
},
};
output
.stream
.write(encoded_packet)
.map(|_| true)
.unwrap_or_else(|e| {
error!("Failed to write packets to whip output: {e:?}");
false
})
}
pub struct ErrorCallbackUserdata(*mut c_void);
unsafe impl Send for ErrorCallbackUserdata {}
unsafe impl Sync for ErrorCallbackUserdata {}
impl ErrorCallbackUserdata {
fn as_ptr(&self) -> *mut c_void {
self.0
}
}
type RTCOutputErrorCallback = unsafe extern "C" fn(user_data: *mut c_void, error: RTCOutputError);
#[no_mangle]
pub unsafe extern "C" fn char_rtc_output_set_error_callback(
output: *const RTCOutput,
cb: RTCOutputErrorCallback,
user_data: *mut c_void,
) {
let output = output.as_ref().unwrap();
let user_data = ErrorCallbackUserdata(user_data);
output
.stream
.set_error_callback(Box::new(move |error| cb(user_data.as_ptr(), error.into())))
}