use std::ffi::{CStr, c_char, c_void};
use std::os::raw::c_int;
use std::ptr;
use std::sync::{Arc, Mutex};
use audio_cpp_sys::*;
use crate::error::Error;
use crate::ffi;
use crate::model::Model;
use crate::request::IntoRequest;
use crate::types::{Backend, RunMode, StreamEvent, StreamingPolicy, TaskKind, TaskResult};
struct EventSinkInner {
cb: Mutex<Box<dyn FnMut(StreamEvent) + Send>>,
}
unsafe extern "C" fn stream_event_cb(
user_data: *mut c_void,
event_json: *const c_char,
_is_final: c_int,
) {
if user_data.is_null() || event_json.is_null() {
return;
}
let inner = user_data.cast::<EventSinkInner>();
let json = unsafe { CStr::from_ptr(event_json) }
.to_string_lossy()
.into_owned();
let Ok(event) = serde_json::from_str::<StreamEvent>(&json) else {
return; };
let mut guard =
unsafe { (*inner).cb.lock() }.unwrap_or_else(std::sync::PoisonError::into_inner);
(guard)(event);
}
pub struct Session {
raw: *mut audiocpp_session,
event_sink: Mutex<Option<*mut EventSinkInner>>,
}
unsafe impl Send for Session {}
impl Session {
pub(crate) fn from_raw(raw: *mut audiocpp_session) -> Self {
Self {
raw,
event_sink: Mutex::new(None),
}
}
pub fn family(&self) -> String {
unsafe {
let p = audiocpp_session_family(self.raw);
if p.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
}
}
}
pub fn task_kind(&self) -> String {
unsafe {
let p = audiocpp_session_task_kind(self.raw);
if p.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
}
}
}
pub fn run_mode(&self) -> String {
unsafe {
let p = audiocpp_session_run_mode(self.raw);
if p.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
}
}
}
pub fn streaming_policy(&self) -> Result<StreamingPolicy, Error> {
let mut out: *mut c_char = ptr::null_mut();
ffi::check_rc(unsafe { audiocpp_session_streaming_policy_json(self.raw, &mut out) })?;
let json = unsafe { ffi::take_string(out)? };
serde_json::from_str(&json).map_err(Error::from)
}
pub fn prepare<R: IntoRequest>(&self, request: R) -> Result<(), Error> {
let json = request.into_request()?.to_json()?;
let req_c = ffi::cstring(&json)?;
ffi::check_rc(unsafe {
audiocpp_session_prepare(self.raw, req_c.as_ptr() as *const c_char)
})
}
pub fn set_event_callback<F>(&self, cb: Option<F>)
where
F: FnMut(StreamEvent) + Send + 'static,
{
let old = self.event_sink_take();
if let Some(old) = old {
unsafe {
audiocpp_session_set_event_sink(self.raw, None, ptr::null_mut());
drop(Box::from_raw(old));
}
}
if let Some(cb) = cb {
let inner = Box::into_raw(Box::new(EventSinkInner {
cb: Mutex::new(Box::new(cb)),
}));
unsafe {
audiocpp_session_set_event_sink(
self.raw,
Some(stream_event_cb),
inner.cast::<c_void>(),
);
}
*self
.event_sink
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(inner);
}
}
pub fn start<R: IntoRequest>(&self, request: R) -> Result<(), Error> {
let json = request.into_request()?.to_json()?;
let req_c = ffi::cstring(&json)?;
ffi::check_rc(unsafe { audiocpp_session_start(self.raw, req_c.as_ptr() as *const c_char) })
}
pub fn process_audio(
&self,
samples: &[f32],
sample_rate: i32,
channels: i32,
start_sample: i64,
) -> Result<Option<StreamEvent>, Error> {
let mut out: *mut c_char = ptr::null_mut();
ffi::check_rc(unsafe {
audiocpp_session_process_audio(
self.raw,
samples.as_ptr(),
samples.len(),
sample_rate,
channels,
start_sample,
&mut out,
)
})?;
if out.is_null() {
return Ok(None);
}
let json = unsafe { ffi::take_string(out)? };
Ok(Some(serde_json::from_str(&json).map_err(Error::from)?))
}
pub fn finish(&self) -> Result<TaskResult, Error> {
let mut out: *mut c_char = ptr::null_mut();
ffi::check_rc(unsafe { audiocpp_session_finish(self.raw, &mut out) })?;
if out.is_null() {
return Ok(TaskResult {
speech_segments: Vec::new(),
speaker_turns: Vec::new(),
text_output: None,
audio_output: None,
named_audio_outputs: Vec::new(),
});
}
let json = unsafe { ffi::take_string(out)? };
serde_json::from_str(&json).map_err(Error::from)
}
pub fn run_offline<R: IntoRequest>(&self, request: R) -> Result<TaskResult, Error> {
let json = request.into_request()?.to_json()?;
let req_c = ffi::cstring(&json)?;
let mut out: *mut c_char = ptr::null_mut();
ffi::check_rc(unsafe {
audiocpp_session_run_offline(self.raw, req_c.as_ptr() as *const c_char, &mut out)
})?;
if out.is_null() {
return Err(Error::Ffi(ffi::last_error()));
}
let json = unsafe { ffi::take_string(out)? };
serde_json::from_str(&json).map_err(Error::from)
}
pub fn reset(&self) {
unsafe {
audiocpp_session_reset(self.raw);
}
}
fn event_sink_take(&self) -> Option<*mut EventSinkInner> {
self.event_sink
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take()
}
}
impl Drop for Session {
fn drop(&mut self) {
if let Some(inner) = self.event_sink_take() {
unsafe {
audiocpp_session_set_event_sink(self.raw, None, ptr::null_mut());
drop(Box::from_raw(inner));
}
}
unsafe {
audiocpp_session_free(self.raw);
}
}
}
pub struct StreamingSession {
session: Session,
events: Arc<Mutex<Vec<StreamEvent>>>,
}
unsafe impl Send for StreamingSession {}
impl StreamingSession {
pub fn from_session(session: Session) -> Self {
let events: Arc<Mutex<Vec<StreamEvent>>> = Arc::new(Mutex::new(Vec::new()));
let sink = events.clone();
session.set_event_callback(Some(move |ev| {
if let Ok(mut v) = sink.lock() {
v.push(ev);
}
}));
Self { session, events }
}
pub fn from_model(
model: &Model,
task: TaskKind,
backend: Backend,
device: i32,
threads: i32,
session_options: Option<&str>,
) -> Result<Self, Error> {
let session = model.create_task_session(
task,
RunMode::Streaming,
backend,
device,
threads,
session_options,
)?;
Ok(Self::from_session(session))
}
pub fn start<R: IntoRequest>(&self, request: R) -> Result<(), Error> {
self.session.start(request)
}
pub fn push_audio(
&self,
samples: &[f32],
sample_rate: i32,
channels: i32,
start_sample: i64,
) -> Result<Vec<StreamEvent>, Error> {
self.events
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clear();
self.session
.process_audio(samples, sample_rate, channels, start_sample)?;
Ok(self
.events
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.drain(..)
.collect())
}
pub fn finish(&self) -> Result<TaskResult, Error> {
self.session.finish()
}
pub fn reset(&self) {
self.session.reset();
self.events
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clear();
}
pub fn session(&self) -> &Session {
&self.session
}
pub fn session_mut(&mut self) -> &mut Session {
&mut self.session
}
}