aboutsummaryrefslogtreecommitdiff
path: root/pulse
diff options
context:
space:
mode:
authorAndreas Grois <andi@grois.info>2021-06-04 21:48:01 +0200
committerAndreas Grois <andi@grois.info>2021-06-04 21:48:01 +0200
commit15ae48210535358ad90dc85c4bc8809a71627d1d (patch)
treeb62e55887cce7ec7d90d12952b9968630e4565ba /pulse
parent6457d779ec253ade253fc49f5891b5479b270a6d (diff)
Pulse now displays volume correctly.
Still missing actual functionality beyond a numeric display, and error cases have not been tested, but hey, it's there!
Diffstat (limited to 'pulse')
-rw-r--r--pulse/Cargo.toml1
-rw-r--r--pulse/src/runnable/mod.rs192
-rw-r--r--pulse/src/runnable/pulse.rs290
3 files changed, 395 insertions, 88 deletions
diff --git a/pulse/Cargo.toml b/pulse/Cargo.toml
index f1d8925..9aa4520 100644
--- a/pulse/Cargo.toml
+++ b/pulse/Cargo.toml
@@ -10,7 +10,6 @@ edition = "2018"
swaystatus-plugin = { path = '../swaystatus-plugin', version = '*'}
serde = { version = "1.0", features = ["derive"] }
erased-serde = "0.3"
-libc = "0.2.0"
[lib]
crate-type = ["cdylib"]
diff --git a/pulse/src/runnable/mod.rs b/pulse/src/runnable/mod.rs
index 7286c31..352a10a 100644
--- a/pulse/src/runnable/mod.rs
+++ b/pulse/src/runnable/mod.rs
@@ -31,103 +31,135 @@ impl<'p : 's, 's> PulseVolumeRunnable<'p> {
self.to_main.send_update(Err(PluginError::PrintToStdErr(err.to_string()))).expect("Tried to tell main thread that an error occured. Main thread isn't listening any more.");
}
+ fn send_updated_volume_to_main(&self, volume : &pulse::Volume) -> Result<(),PluginCommunicationError> {
+ self.to_main.send_update(Ok(self.format_volume(volume)))
+ }
+
+ fn format_volume(&self, volume : &pulse::Volume) -> String {
+ format!("{}",volume.volume)
+ }
}
impl<'p> SwayStatusModuleRunnable for PulseVolumeRunnable<'p> {
fn run(&self) {
- let pulse = match &self.pulse {
- Err(x) => {
- self.send_error_to_main(x);
- return;
- }
- Ok(x) => x
- };
- let context = match PulseContext::create(pulse) {
- Err(x) => {
- self.send_error_to_main(x);
- return;
- }
- Ok(x) => x
- };
- let mut context_state = PaContextState::Unconnected;
- let mut curr_default_sink = None;
- let mut curr_volume = None;
- let mut sink_we_care_about = match self.config.sink {
- crate::config::Sink::Default => { None }
- crate::config::Sink::Specific { sink_name } => {
- Some(match SinkHandle::try_from(&*sink_name) {
- Ok(x) => {x}
- Err(e) => {
- self.send_error_to_main(e);
- return;
- }
- })
- }
- };
- loop {
- match context_state {
- PaContextState::Unconnected => {
- if let crate::config::Sink::Default = self.config.sink {
- sink_we_care_about = None;
- }
- context.connect();
+ 'outer : loop {
+ let pulse = match &self.pulse {
+ Err(x) => {
+ self.send_error_to_main(x);
+ return;
+ }
+ Ok(x) => x
+ };
+ let mut scratch = pulse::ContextScratch::default();
+ let mut context = match PulseContext::create(pulse, &mut scratch) {
+ Err(x) => {
+ self.send_error_to_main(x);
+ return;
+ }
+ Ok(x) => x
+ };
+ let mut curr_default_sink = None;
+ let mut curr_volume = None;
+ let mut sink_we_care_about = match &self.config.sink {
+ crate::config::Sink::Default => { None }
+ crate::config::Sink::Specific { sink_name } => {
+ Some(match SinkHandle::try_from(sink_name as &str) {
+ Ok(x) => {x}
+ Err(e) => {
+ self.send_error_to_main(e);
+ return;
+ }
+ })
}
- PaContextState::Failed | PaContextState::Terminated => {
- match self.from_main.recv_timeout(std::time::Duration::from_secs(1)) {
- Ok(x) => {
- if let MessagesFromMain::Quit = x {
- break;
+ };
+ loop {
+ match context.get_state() {
+ PaContextState::Unconnected => {
+ if let crate::config::Sink::Default = &self.config.sink {
+ sink_we_care_about = None;
+ }
+ if let Err(e) = context.connect_and_set_callbacks() {
+ self.send_error_to_main(e);
+ match self.from_main.recv_timeout(std::time::Duration::from_secs(1)) {
+ Ok(x) => {
+ if let MessagesFromMain::Quit = x {
+ break 'outer;
+ }
+ }
+ Err(e) => {
+ if let RecvTimeoutError::Disconnected = e {
+ break 'outer;
+ }
+ }
}
}
- Err(e) => {
- if let RecvTimeoutError::Disconnected = e {
- break;
+ }
+ PaContextState::Failed | PaContextState::Terminated => {
+ //context is dead. Wait a second, and start over.
+ self.to_main.send_update(Err(PluginError::ShowInsteadOfText(String::from("Context died")))).expect("Tried to tell main thread that pulse context died. Main thread isn't listening.");
+ self.to_main.send_update(Err(PluginError::PrintToStdErr(String::from("Pulseaudio context entered either the Terminated or Failed state. Creating a new context and retrying")))).expect("Tried to tell main thread that pulse context died. Main thread isn't listening.");
+ match self.from_main.recv_timeout(std::time::Duration::from_secs(1)) {
+ Ok(x) => {
+ if let MessagesFromMain::Quit = x {
+ break 'outer;
+ }
+ }
+ Err(e) => {
+ if let RecvTimeoutError::Disconnected = e {
+ break 'outer;
+ }
}
}
+ continue 'outer;
}
- if let crate::config::Sink::Default = self.config.sink {
- sink_we_care_about = None;
+ PaContextState::Ready => {
+ if sink_we_care_about.is_none() {
+ //this may trigger several redundant refreshes, but it _should_ only happen
+ //during startup, so we don't really care.
+ context.refresh_default_sink();
+ }
}
- context.connect();
+ _ => {}
+ }
+
+ let iteration_result = context.iterate(&sink_we_care_about);
+ if let Err(e) = iteration_result {
+ self.send_error_to_main(e);
+ break 'outer;
}
- PaContextState::Ready => {
- if sink_we_care_about.is_none() {
- //this may trigger several redundant refreshes, but it _should_ only happen
- //during startup, so we don't really care.
- context.refresh_default_sink();
+
+ let pulse::IterationResult { default_sink, volume } = iteration_result.unwrap();
+ if default_sink.is_some() && default_sink != curr_default_sink {
+ curr_default_sink = default_sink;
+ if let crate::config::Sink::Default = self.config.sink {
+ sink_we_care_about = curr_default_sink.clone();
+ if let Some(s) = &sink_we_care_about {
+ context.refresh_volume(s);
+ }
}
}
- _ => {}
- }
-
- let Pulse::IterationResult { default_sink, volume, state } = context.iterate(sink_we_care_about);
- context_state = state.unwrap_or(context_state);
- if default_sink.is_some() && default_sink != curr_default_sink {
- curr_default_sink = default_sink;
- if let crate::config::Sink::Default = self.config.sink {
- sink_we_care_about = curr_default_sink;
- context.refresh_volume();
+ if volume.is_some() && volume != curr_volume {
+ curr_volume = volume;
+ self.send_updated_volume_to_main(curr_volume.as_ref().unwrap()).expect("Tried to inform main thread about volume update. Main thread isn't listening.");
}
- }
- if volume.is_some() && volume != curr_volume {
- curr_volume = volume;
- self.send_updated_volume_to_main(curr_volume);
- }
- match self.from_main.try_recv() {
- Ok(x) => match x {
- MessagesFromMain::Quit => {
- break;
- }
- MessagesFromMain::Refresh => {
- context.refresh_volume();
- if let crate::config::Sink::Default = self.config.sink {
- context.refresh_default_sink();
+ match self.from_main.try_recv() {
+ Ok(x) => match x {
+ MessagesFromMain::Quit => {
+ break 'outer;
+ }
+ MessagesFromMain::Refresh => {
+ if let Some(s) = &sink_we_care_about {
+ context.refresh_volume(s);
+ }
+ if let crate::config::Sink::Default = self.config.sink {
+ context.refresh_default_sink();
+ }
}
}
- }
- Err(e) => {
- if let TryRecvError::Disconnected = e {
- break;
+ Err(e) => {
+ if let TryRecvError::Disconnected = e {
+ break 'outer;
+ }
}
}
}
diff --git a/pulse/src/runnable/pulse.rs b/pulse/src/runnable/pulse.rs
index 64a7d33..d92a41f 100644
--- a/pulse/src/runnable/pulse.rs
+++ b/pulse/src/runnable/pulse.rs
@@ -2,9 +2,8 @@
//! It is the only place in this plugin that explicitly includes unsafe code.
use std::sync::{Arc, Weak};
-use std::ffi::{c_void, CString};
-use libc::{c_int, size_t};
-use std::os::raw::c_char;
+use std::ffi::{CString, CStr};
+use std::os::raw::{c_int, c_char};
use std::convert::TryFrom;
use std::marker::PhantomData;
@@ -38,11 +37,12 @@ impl Pulse {
pub(super) struct PulseContext<'c> {
context : *mut PaContext, //Can actually never be null, the nullptr case is handled by create, which returns an Err instead of a PulseContext then.
+ scratch : &'c mut ContextScratch, //This scratch space is needed, because user data pointers in pulse need to survive between iterations. Yes, that's insane. No, I can't do anything about it...
main : &'c Pulse
}
impl<'c> PulseContext<'c> {
- pub(super) fn create<'m>(pulse : &'m Pulse) -> Result<PulseContext<'m>, PulseContextCreationError> {
+ pub(super) fn create<'m>(pulse : &'m Pulse, scratch : &'m mut ContextScratch) -> Result<PulseContext<'m>, PulseContextCreationError> {
let api = pulse.main_loop.get_api();
if api.is_null() {
Err(PulseContextCreationError::FailedToGetPulseApi)
@@ -54,10 +54,102 @@ impl<'c> PulseContext<'c> {
Err(PulseContextCreationError::ContextNewFailed)
}
else {
- Ok(PulseContext { context, main : pulse})
+ Ok(PulseContext { context, scratch, main : pulse})
}
}
}
+ pub(super) fn iterate(&mut self, relevant_sink : &Option<SinkHandle>) -> Result<IterationResult,MainLoopIterationError> {
+ self.scratch.sink_we_care_about = relevant_sink.clone();
+ self.scratch.volume = None;
+ self.scratch.default_sink = None;
+ self.main.main_loop.iterate()?;
+ return Ok(IterationResult {
+ default_sink : self.scratch.default_sink.clone(),
+ volume : self.scratch.volume.clone()
+ });
+ }
+ pub(super) fn get_state(&self) -> PaContextState {
+ unsafe { pa_context_get_state(self.context) }
+ }
+ pub(super) fn connect_and_set_callbacks(&mut self) -> Result<(), PulseContextConnectError> {
+ unsafe {
+ pa_context_set_state_callback(self.context,Some(Self::on_context_state_change),self.scratch);
+ let connect_result = pa_context_connect(self.context,std::ptr::null(),PaContextFlags::NoAutoSpawnNoFail,std::ptr::null());
+ if connect_result == 0 {
+ Ok(())
+ }
+ else {
+ Err(PulseContextConnectError(connect_result))
+ }
+ }
+ }
+
+ pub(super) fn refresh_default_sink(&mut self) {
+ unsafe {pa_operation_unref(pa_context_get_server_info(self.context,Some(Self::on_server_info_received),self.scratch)); }
+ }
+
+ pub(super) fn refresh_volume(&mut self, sink : &SinkHandle) {
+ unsafe {pa_operation_unref(pa_context_get_sink_info_by_name(self.context,sink.sink.as_ptr(),Some(Self::on_sink_info_received),self.scratch));}
+ }
+
+ fn on_context_state_change(context : *mut PaContext, scratch : *mut ContextScratch) {
+ unsafe {
+ match pa_context_get_state(context) {
+ PaContextState::Ready => {
+ pa_context_set_subscribe_callback(context,Some(Self::on_context_event),scratch);
+ pa_operation_unref(pa_context_subscribe(context,0x80 /* SERVER */ | 0x01 /* SINK */, None, std::ptr::null_mut()));
+ }
+ _ => {}
+ }
+ }
+ }
+
+ fn on_context_event(context : *mut PaContext,event_type : c_int,index: u32,scratch : *mut ContextScratch) {
+ assert!(!context.is_null());
+ assert!(!scratch.is_null());
+ unsafe {
+ let facility = 0x000f & event_type;
+ match facility {
+ 0x0 /* SINK */ => {
+ pa_operation_unref(pa_context_get_sink_info_by_index(context,index,Some(Self::on_sink_info_received),scratch));
+ }
+ 0x7 /* SERVER */ => {
+ pa_operation_unref(pa_context_get_server_info(context,Some(Self::on_server_info_received),scratch));
+ }
+ _ /* should not happen */ => {
+ assert!(false);
+ }
+ }
+ }
+ }
+
+ fn on_sink_info_received(_context : *mut PaContext, sink_info : *const PaSinkInfo, _eol : c_int, scratch : *mut ContextScratch) {
+ if sink_info.is_null() {
+ return;
+ }
+ unsafe {
+ if let Some(s) = &(*scratch).sink_we_care_about {
+ if s.sink.as_c_str() == CStr::from_ptr((*sink_info).sink_name) {
+ let avg_volume = pa_cvolume_avg(&(*sink_info).volume);
+ const norm : u32 = 0x10000;
+ let volume = (avg_volume as f32) / (norm as f32);
+ let balance = pa_cvolume_get_balance(&(*sink_info).volume, &(*sink_info).channel_map);
+ let muted = (*sink_info).mute != 0;
+
+ (*scratch).volume = Some(Volume {volume, balance, muted});
+ }
+ }
+ }
+ }
+
+ fn on_server_info_received(_context : *mut PaContext, server_info : *const PaServerInfo, scratch : *mut ContextScratch) {
+ if server_info.is_null() {
+ return;
+ }
+ unsafe {
+ (*scratch).default_sink= Some(SinkHandle::from(CStr::from_ptr((*server_info).default_sink_name)));
+ }
+ }
}
impl<'c> Drop for PulseContext<'c> {
@@ -67,6 +159,46 @@ impl<'c> Drop for PulseContext<'c> {
}
#[derive(Debug)]
+pub struct PulseContextConnectError(i32);
+impl std::fmt::Display for PulseContextConnectError {
+ fn fmt(&self, f : &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "Pulse Context connect error: ")?;
+ match self.0 {
+ 0 => { write!(f,"No error occured.") }
+ 1 => { write!(f,"Access failure") }
+ 2 => { write!(f,"Unknown command") }
+ 3 => { write!(f,"Invalid argument") }
+ 4 => { write!(f,"Entity exists") }
+ 5 => { write!(f,"No such entity") }
+ 6 => { write!(f,"Connection refused") }
+ 7 => { write!(f,"Protocol error") }
+ 8 => { write!(f,"Timeout") }
+ 9 => { write!(f,"No authentication key") }
+ 10 => { write!(f,"Internal error") }
+ 11 => { write!(f,"Connection terminated") }
+ 12 => { write!(f,"Entity killed") }
+ 13 => { write!(f,"Invalid server") }
+ 14 => { write!(f,"Module initialization failed") }
+ 15 => { write!(f,"Bad state") }
+ 16 => { write!(f,"No data") }
+ 17 => { write!(f,"Incompatible protocol version") }
+ 18 => { write!(f,"Data too large") }
+ 19 => { write!(f,"Operation not supported") }
+ 20 => { write!(f,"The error code was unknown to the client") }
+ 21 => { write!(f,"Extension does not exist.") }
+ 22 => { write!(f,"Obsolete functionality.") }
+ 23 => { write!(f,"Missing implementation.") }
+ 24 => { write!(f,"The caller forked without calling execve() and tried to reuse the context.") }
+ 25 => { write!(f,"An IO error happened.") }
+ 26 => { write!(f,"Device or resource busy.") }
+ _ => { write!(f,"Unknown error. Not documented at time of writing.") }
+ }
+ }
+}
+impl std::error::Error for PulseContextConnectError {}
+
+
+#[derive(Debug)]
pub enum PulseContextCreationError {
SettingNameFailed,
FailedToGetPulseApi,
@@ -144,6 +276,15 @@ impl PulseMainLoop {
fn get_api(&self) -> *mut PaMainloopApi {
unsafe { pa_mainloop_get_api(self.main_loop) }
}
+ fn iterate(&self) -> Result<(),MainLoopIterationError> {
+ let i = unsafe { pa_mainloop_iterate(self.main_loop, 1, std::ptr::null_mut()) };
+ if i >= 0 {
+ Ok(())
+ }
+ else {
+ Err(MainLoopIterationError(i))
+ }
+ }
}
impl Drop for PulseMainLoop {
fn drop(&mut self) {
@@ -156,6 +297,15 @@ impl Drop for PulseMainLoop {
}
#[derive(Debug)]
+pub struct MainLoopIterationError(i32);
+impl std::fmt::Display for MainLoopIterationError {
+ fn fmt(&self, f : &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "Failed to iterate pulseaudio main loop. Error {}", self.0)
+ }
+}
+impl std::error::Error for MainLoopIterationError {}
+
+#[derive(Debug)]
pub struct MainLoopCreationError {}
impl std::fmt::Display for MainLoopCreationError {
fn fmt(&self, f : &mut std::fmt::Formatter) -> std::fmt::Result {
@@ -164,6 +314,7 @@ impl std::fmt::Display for MainLoopCreationError {
}
impl std::error::Error for MainLoopCreationError {}
+#[derive(Clone, PartialEq, Eq, Debug)]
pub(super) struct SinkHandle {
sink: CString
}
@@ -178,15 +329,41 @@ impl TryFrom<&str> for SinkHandle {
}
}
+impl From<&CStr> for SinkHandle {
+ fn from(s : &CStr) -> SinkHandle {
+ SinkHandle { sink : CString::from(s) }
+ }
+}
+
pub(super) struct IterationResult {
pub default_sink : Option<SinkHandle>,
pub volume : Option<Volume>,
- pub state : Option<PaContextState>
}
+#[derive(Clone, PartialEq)]
pub(super) struct Volume {
pub volume : f32,
- pub balance : f32
+ pub balance : f32,
+ pub muted : bool
+}
+
+pub(super) struct ContextScratch {
+ /* output */
+ volume : Option<Volume>,
+ default_sink : Option<SinkHandle>,
+
+ /* input */
+ sink_we_care_about : Option<SinkHandle>
+}
+
+impl Default for ContextScratch {
+ fn default() -> Self {
+ ContextScratch {
+ volume : None,
+ default_sink : None,
+ sink_we_care_about : None
+ }
+ }
}
#[repr(C)] pub(super) enum PaContextState {
@@ -199,21 +376,120 @@ pub(super) struct Volume {
Terminated
}
+#[repr(C)] enum PaContextFlags {
+ NoFlags = 0,
+ NoAutoSpawn = 1,
+ NoFail = 2,
+ NoAutoSpawnNoFail = 3
+}
+
+#[repr(C)] struct PaSinkInfo {
+ sink_name : *const c_char,
+ index : u32,
+ description : *const c_char,
+ sample_spec : PaSampleSpec,
+ channel_map : PaChannelMap,
+ owner_module : u32,
+ volume : PaCVolume,
+ mute : c_int,
+ monitor_source : u32,
+ monitor_source_name : *const c_char,
+ latency : u64,
+ driver : *const c_char,
+ flags : c_int,
+ proplist : *mut PaPropList,
+ configured_latency : u64,
+ base_volume : u32,
+ state : c_int,
+ n_volume_steps : u32,
+ card : u32,
+ n_ports : u32,
+ ports : *mut *mut PaSinkPortInfo,
+ active_port : *mut PaSinkPortInfo,
+ n_formats : u8,
+ formats : *mut *mut PaFormatInfo
+}
+
+#[repr(C)] struct PaSampleSpec {
+ format : c_int,
+ rate : u32,
+ channels : u8
+}
+
+#[repr(C)] struct PaChannelMap {
+ channels : u8,
+ map : [c_int; 32]
+}
+
+#[repr(C)] struct PaCVolume {
+ channels : u8,
+ values : [u32; 32]
+}
+
+#[repr(C)] struct PaServerInfo {
+ user_name : *const c_char,
+ host_name : *const c_char,
+ server_version : *const c_char,
+ server_name : *const c_char,
+ sample_spec : PaSampleSpec,
+ default_sink_name : *const c_char,
+ default_source_name : *const c_char,
+ cookie : u32,
+ channel_map : PaChannelMap
+}
+
+#[repr(C)] struct PaPropList { _private: [u8; 0]}
+
+///This actually holds meaningful data, but we don't care about it for now.
+#[repr(C)] struct PaSinkPortInfo { _private: [u8; 0]}
+
+///Similar to PaSinkPortInfo, we could interpret this, but won't need to.
+#[repr(C)] struct PaFormatInfo { _private: [u8;0]}
+
#[repr(C)] struct PaMainloop { _private: [u8; 0] }
#[repr(C)] struct PaContext { _private: [u8; 0] }
+#[repr(C)] struct PaOperation { _private: [u8; 0] }
///While we could in theory wrap the whole API, there's no need for it. We can treat it as an
///opaque type, because we never call any functions on it.
#[repr(C)] struct PaMainloopApi { _private: [u8; 0] }
+///If we were to allow auto-spawning, we would need to actually implement this...
+#[repr(C)] struct PaSpawnApi { _private: [u8; 0] }
+
+type PaContextSuccessCb = fn(*mut PaContext, c_int, *mut ContextScratch);
+type PaContextSubscribeCb = fn(*mut PaContext,c_int,u32,*mut ContextScratch);
+type PaContextStateCb = fn(*mut PaContext, *mut ContextScratch);
+type PaSinkInfoCb = fn(*mut PaContext, *const PaSinkInfo, c_int, *mut ContextScratch);
+type PaServerInfoCb = fn(*mut PaContext, *const PaServerInfo, *mut ContextScratch);
+
#[link(name = "pulse")]
extern {
+ #[must_use]
fn pa_mainloop_new() -> *mut PaMainloop;
fn pa_mainloop_wakeup(_ : *mut PaMainloop);
fn pa_mainloop_quit(_ : *mut PaMainloop, retval : c_int);
fn pa_mainloop_free(_ : *mut PaMainloop);
+ fn pa_mainloop_iterate(_ : *mut PaMainloop, block : c_int, return_value : *mut c_int) -> c_int;
fn pa_mainloop_get_api(_ : *mut PaMainloop) -> *mut PaMainloopApi;
+ #[must_use]
fn pa_context_new(_ : *mut PaMainloopApi, name :*const c_char) -> *mut PaContext;
fn pa_context_unref(_ : *mut PaContext);
+ fn pa_context_get_state(_ : *mut PaContext) -> PaContextState;
+ fn pa_context_connect(_: *mut PaContext, server : *const c_char, flags : PaContextFlags, api : *const PaSpawnApi) -> c_int;
+ fn pa_context_set_state_callback(_: *mut PaContext, callback: Option<PaContextStateCb>, scratch : *mut ContextScratch);
+ fn pa_context_set_subscribe_callback(_: *mut PaContext,callback : Option<PaContextSubscribeCb>,scratch : *mut ContextScratch);
+ #[must_use]
+ fn pa_context_subscribe(_: *mut PaContext, subscription_mask : c_int, callback : Option<PaContextSuccessCb>, scratch : *mut ContextScratch) -> *mut PaOperation;
+ fn pa_operation_unref(operation : *mut PaOperation);
+
+ #[must_use]
+ fn pa_context_get_sink_info_by_index(_: *mut PaContext, sink_index : u32, callback : Option<PaSinkInfoCb>, scratch : *mut ContextScratch) -> *mut PaOperation;
+ fn pa_context_get_sink_info_by_name(_: *mut PaContext, sink : *const c_char, callback : Option<PaSinkInfoCb>, scratch : *mut ContextScratch) -> *mut PaOperation;
+ #[must_use]
+ fn pa_context_get_server_info(_: *mut PaContext, callback : Option<PaServerInfoCb>, scratch : *mut ContextScratch) -> *mut PaOperation;
+
+ fn pa_cvolume_avg(volume : *const PaCVolume) -> u32;
+ fn pa_cvolume_get_balance(volume : *const PaCVolume, channel_map : *const PaChannelMap) -> f32;
}