Revision control
Copy as Markdown
Other Tools
// Copyright © 2017 Mozilla Foundation
//
// This program is made available under an ISC-style license. See the
// accompanying file LICENSE for details
#![warn(unused_extern_crates)]
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate log;
use audio_thread_priority::promote_current_thread_to_real_time;
use audioipc::ipccore;
use audioipc::sys;
use audioipc::PlatformHandleType;
use once_cell::sync::Lazy;
use std::ffi::{CStr, CString};
use std::os::raw::c_void;
use std::ptr;
use std::sync::Mutex;
use std::thread;
mod server;
struct CubebContextParams {
context_name: CString,
backend_name: Option<CString>,
}
static G_CUBEB_CONTEXT_PARAMS: Lazy<Mutex<CubebContextParams>> = Lazy::new(|| {
Mutex::new(CubebContextParams {
context_name: CString::new("AudioIPC Server").unwrap(),
backend_name: None,
})
});
#[allow(deprecated)]
#[allow(clippy::upper_case_acronyms)]
pub mod errors {
error_chain! {
links {
AudioIPC(::audioipc::errors::Error, ::audioipc::errors::ErrorKind);
}
foreign_links {
Cubeb(cubeb_core::Error);
Io(::std::io::Error);
}
}
}
use crate::errors::*;
struct ServerWrapper {
rpc_thread: ipccore::EventLoopThread,
callback_thread: ipccore::EventLoopThread,
device_collection_thread: ipccore::EventLoopThread,
}
fn register_thread(callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>) {
if let Some(func) = callback {
let name = CString::new(thread::current().name().unwrap()).unwrap();
func(name.as_ptr());
}
}
fn unregister_thread(callback: Option<extern "C" fn()>) {
if let Some(func) = callback {
func();
}
}
fn init_threads(
thread_create_callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>,
thread_destroy_callback: Option<extern "C" fn()>,
) -> Result<ServerWrapper> {
let rpc_name = "AudioIPC Server RPC";
let rpc_thread = ipccore::EventLoopThread::new(
rpc_name.to_string(),
None,
move || {
trace!("Starting {} thread", rpc_name);
register_thread(thread_create_callback);
audioipc::server_platform_init();
},
move || {
unregister_thread(thread_destroy_callback);
trace!("Stopping {} thread", rpc_name);
},
)
.map_err(|e| {
debug!("Failed to start {} thread: {:?}", rpc_name, e);
e
})?;
let callback_name = "AudioIPC Server Callback";
let callback_thread = ipccore::EventLoopThread::new(
callback_name.to_string(),
None,
move || {
trace!("Starting {} thread", callback_name);
if let Err(e) = promote_current_thread_to_real_time(0, 48000) {
debug!(
"Failed to promote {} thread to real-time: {:?}",
callback_name, e
);
}
register_thread(thread_create_callback);
},
move || {
unregister_thread(thread_destroy_callback);
trace!("Stopping {} thread", callback_name);
},
)
.map_err(|e| {
debug!("Failed to start {} thread: {:?}", callback_name, e);
e
})?;
let device_collection_name = "AudioIPC DeviceCollection RPC";
let device_collection_thread = ipccore::EventLoopThread::new(
device_collection_name.to_string(),
None,
move || {
trace!("Starting {} thread", device_collection_name);
register_thread(thread_create_callback);
},
move || {
unregister_thread(thread_destroy_callback);
trace!("Stopping {} thread", device_collection_name);
},
)
.map_err(|e| {
debug!("Failed to start {} thread: {:?}", device_collection_name, e);
e
})?;
Ok(ServerWrapper {
rpc_thread,
callback_thread,
device_collection_thread,
})
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct AudioIpcServerInitParams {
// Fields only need to be public for ipctest.
pub thread_create_callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>,
pub thread_destroy_callback: Option<extern "C" fn()>,
}
#[allow(clippy::missing_safety_doc)]
#[no_mangle]
pub unsafe extern "C" fn audioipc2_server_start(
context_name: *const std::os::raw::c_char,
backend_name: *const std::os::raw::c_char,
init_params: *const AudioIpcServerInitParams,
) -> *mut c_void {
assert!(!init_params.is_null());
let mut params = G_CUBEB_CONTEXT_PARAMS.lock().unwrap();
if !context_name.is_null() {
CStr::from_ptr(context_name).clone_into(&mut params.context_name);
}
if !backend_name.is_null() {
let backend_string = CStr::from_ptr(backend_name).to_owned();
params.backend_name = Some(backend_string);
}
match init_threads(
(*init_params).thread_create_callback,
(*init_params).thread_destroy_callback,
) {
Ok(server) => Box::into_raw(Box::new(server)) as *mut _,
Err(_) => ptr::null_mut() as *mut _,
}
}
// A `shm_area_size` of 0 allows the server to calculate an appropriate shm size for each stream.
// A non-zero `shm_area_size` forces all allocations to the specified size.
#[no_mangle]
pub extern "C" fn audioipc2_server_new_client(
p: *mut c_void,
shm_area_size: usize,
) -> PlatformHandleType {
let wrapper: &ServerWrapper = unsafe { &*(p as *mut _) };
// We create a connected pair of anonymous IPC endpoints. One side
// is registered with the event loop core, the other side is returned
// to the caller to be remoted to the client to complete setup.
let (server_pipe, client_pipe) = match sys::make_pipe_pair() {
Ok((server_pipe, client_pipe)) => (server_pipe, client_pipe),
Err(e) => {
error!(
"audioipc_server_new_client - make_pipe_pair failed: {:?}",
e
);
return audioipc::INVALID_HANDLE_VALUE;
}
};
let rpc_thread = wrapper.rpc_thread.handle();
let callback_thread = wrapper.callback_thread.handle();
let device_collection_thread = wrapper.device_collection_thread.handle();
let server = server::CubebServer::new(
callback_thread.clone(),
device_collection_thread.clone(),
shm_area_size,
);
if let Err(e) = rpc_thread.bind_server(server, server_pipe) {
error!("audioipc_server_new_client - bind_server failed: {:?}", e);
return audioipc::INVALID_HANDLE_VALUE;
}
unsafe { client_pipe.into_raw() }
}
#[no_mangle]
pub extern "C" fn audioipc2_server_stop(p: *mut c_void) {
let wrapper = unsafe { Box::<ServerWrapper>::from_raw(p as *mut _) };
drop(wrapper);
}