// 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, } static G_CUBEB_CONTEXT_PARAMS: Lazy> = 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) { if let Some(func) = callback { let name = CString::new(thread::current().name().unwrap()).unwrap(); func(name.as_ptr()); } } fn unregister_thread(callback: Option) { if let Some(func) = callback { func(); } } fn init_threads( thread_create_callback: Option, thread_destroy_callback: Option, ) -> Result { 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, pub thread_destroy_callback: Option, } #[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::::from_raw(p as *mut _) }; drop(wrapper); }