quad_snd/
alsa_snd.rs

1// roughly based on http://equalarea.com/paul/alsa-audio.html
2
3use crate::{error::Error, PlaySoundParams};
4
5use quad_alsa_sys as sys;
6
7use std::sync::mpsc;
8
9pub use crate::mixer::Playback;
10
11mod consts {
12    pub const DEVICES: &[&str] = &["default\0", "pipewire\0"];
13    pub const RATE: u32 = 44100;
14    pub const CHANNELS: u32 = 2;
15    pub const PCM_BUFFER_SIZE: ::std::os::raw::c_ulong = 4096;
16}
17
18unsafe fn setup_pcm_device() -> *mut sys::snd_pcm_t {
19    let mut pcm_handle = std::ptr::null_mut();
20
21    // Open the PCM device in playback mode
22    if !consts::DEVICES.iter().any(|device| {
23        sys::snd_pcm_open(
24            &mut pcm_handle,
25            device.as_ptr() as _,
26            sys::SND_PCM_STREAM_PLAYBACK,
27            0,
28        ) >= 0
29    }) {
30        panic!("Can't open PCM device.");
31    }
32
33    let mut hw_params: *mut sys::snd_pcm_hw_params_t = std::ptr::null_mut();
34    sys::snd_pcm_hw_params_malloc(&mut hw_params);
35    sys::snd_pcm_hw_params_any(pcm_handle, hw_params);
36
37    if sys::snd_pcm_hw_params_set_access(pcm_handle, hw_params, sys::SND_PCM_ACCESS_RW_INTERLEAVED)
38        < 0
39    {
40        panic!("Can't set interleaved mode");
41    }
42
43    if sys::snd_pcm_hw_params_set_format(pcm_handle, hw_params, sys::SND_PCM_FORMAT_FLOAT_LE) < 0 {
44        panic!("Can't set SND_PCM_FORMAT_FLOAT_LE format");
45    }
46    if sys::snd_pcm_hw_params_set_buffer_size(pcm_handle, hw_params, consts::PCM_BUFFER_SIZE) < 0 {
47        panic!("Cant's set buffer size");
48    }
49    if sys::snd_pcm_hw_params_set_channels(pcm_handle, hw_params, consts::CHANNELS) < 0 {
50        panic!("Can't set channels number.");
51    }
52
53    let mut rate = consts::RATE;
54    if sys::snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &mut rate, std::ptr::null_mut())
55        < 0
56    {
57        panic!("Can't set rate.");
58    }
59
60    // Write parameters
61    if sys::snd_pcm_hw_params(pcm_handle, hw_params) < 0 {
62        panic!("Can't set harware parameters.");
63    }
64    sys::snd_pcm_hw_params_free(hw_params);
65
66    // tell ALSA to wake us up whenever AudioContext::PCM_BUFFER_SIZE or more frames
67    //   of playback data can be delivered. Also, tell
68    //   ALSA that we'll start the device ourselves.
69    let mut sw_params: *mut sys::snd_pcm_sw_params_t = std::ptr::null_mut();
70
71    if sys::snd_pcm_sw_params_malloc(&mut sw_params) < 0 {
72        panic!("cannot allocate software parameters structure");
73    }
74    if sys::snd_pcm_sw_params_current(pcm_handle, sw_params) < 0 {
75        panic!("cannot initialize software parameters structure");
76    }
77
78    // if sys::snd_pcm_sw_params_set_avail_min(
79    //     pcm_handle,
80    //     sw_params,
81    //     AudioContext::PCM_BUFFER_SIZE,
82    // ) < 0
83    // {
84    //     panic!("cannot set minimum available count");
85    // }
86    if sys::snd_pcm_sw_params_set_start_threshold(pcm_handle, sw_params, 0) < 0 {
87        panic!("cannot set start mode");
88    }
89    if sys::snd_pcm_sw_params(pcm_handle, sw_params) < 0 {
90        panic!("cannot set software parameters");
91    }
92    sys::snd_pcm_sw_params_free(sw_params);
93
94    if sys::snd_pcm_prepare(pcm_handle) < 0 {
95        panic!("cannot prepare audio interface for use");
96    }
97
98    pcm_handle
99}
100
101unsafe fn audio_thread(mut mixer: crate::mixer::Mixer) {
102    let mut buffer: Vec<f32> = vec![0.0; consts::PCM_BUFFER_SIZE as usize * 2];
103
104    let pcm_handle = setup_pcm_device();
105
106    loop {
107        // Wait for PCM to be ready for next write (no timeout)
108        if sys::snd_pcm_wait(pcm_handle, -1) < 0 {
109            panic!("PCM device is not ready");
110        }
111
112        // // find out how much space is available for playback data
113        // teoretically it should reduce latency - we will fill a minimum amount of
114        // frames just to keep alsa busy and will be able to mix some fresh sounds
115        // it does, but also randmly panics sometimes
116
117        // let frames_to_deliver = sys::snd_pcm_avail_update(pcm_handle);
118        // println!("{}", frames_to_deliver);
119        // let frames_to_deliver = if frames_to_deliver > consts::PCM_BUFFER_SIZE as _ {
120        //     consts::PCM_BUFFER_SIZE as i64
121        // } else {
122        //     frames_to_deliver
123        // };
124
125        let frames_to_deliver = consts::PCM_BUFFER_SIZE as i64;
126
127        // ask mixer to fill the buffer
128        mixer.fill_audio_buffer(&mut buffer, frames_to_deliver as usize);
129
130        // send filled buffer back to alsa
131        let frames_writen = sys::snd_pcm_writei(
132            pcm_handle,
133            buffer.as_ptr() as *const _,
134            frames_to_deliver as _,
135        );
136        if frames_writen == -libc::EPIPE as ::std::os::raw::c_long {
137            println!("Underrun occured: -EPIPE, attempting recover");
138
139            sys::snd_pcm_recover(pcm_handle, frames_writen as _, 0);
140        }
141
142        if frames_writen > 0 && frames_writen != frames_to_deliver as _ {
143            println!("Underrun occured: frames_writen != frames_to_deliver, attempting recover");
144
145            sys::snd_pcm_recover(pcm_handle, frames_writen as _, 0);
146        }
147    }
148}
149
150pub struct AudioContext {
151    pub(crate) mixer_ctrl: crate::mixer::MixerControl,
152}
153
154impl AudioContext {
155    pub fn new() -> AudioContext {
156        use crate::mixer::Mixer;
157
158        let (mixer_builder, mixer_ctrl) = Mixer::new();
159        std::thread::spawn(move || unsafe {
160            audio_thread(mixer_builder.build());
161        });
162
163        AudioContext { mixer_ctrl }
164    }
165}
166
167pub struct Sound {
168    sound_id: u32,
169}
170
171impl Sound {
172    pub fn load(ctx: &AudioContext, data: &[u8]) -> Sound {
173        let sound_id = ctx.mixer_ctrl.load(data);
174
175        Sound { sound_id }
176    }
177
178    pub fn play(&self, ctx: &AudioContext, params: PlaySoundParams) -> Playback {
179        ctx.mixer_ctrl.play(self.sound_id, params)
180    }
181
182    pub fn stop(&self, ctx: &AudioContext) {
183        ctx.mixer_ctrl.stop_all(self.sound_id);
184    }
185
186    pub fn set_volume(&self, ctx: &AudioContext, volume: f32) {
187        ctx.mixer_ctrl.set_volume_all(self.sound_id, volume);
188    }
189
190    pub fn delete(&self, ctx: &AudioContext) {
191        ctx.mixer_ctrl.delete(self.sound_id);
192    }
193}