Skip to content

Commit 7f181b3

Browse files
committed
wip: duplex mode API + implementation for ALSA
1 parent 8e93242 commit 7f181b3

File tree

8 files changed

+379
-9
lines changed

8 files changed

+379
-9
lines changed

examples/duplex.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ use interflow::{duplex::DuplexStreamConfig, prelude::*};
44

55
mod util;
66

7+
#[cfg(os_alsa)]
8+
fn main() -> Result<()> {
9+
env_logger::init();
10+
11+
let device = default_duplex_device();
12+
let mut config = device.default_duplex_config().unwrap();
13+
config.buffer_size_range = (Some(128), Some(512));
14+
let stream = device.create_duplex_stream(config, RingMod::new()).unwrap();
15+
println!("Press Enter to stop");
16+
std::io::stdin().read_line(&mut String::new())?;
17+
stream.eject().unwrap();
18+
Ok(())
19+
}
20+
21+
#[cfg(not(os_alsa))]
722
fn main() -> Result<()> {
823
let input = default_input_device();
924
let output = default_output_device();
@@ -39,8 +54,12 @@ impl AudioDuplexCallback for RingMod {
3954
input: AudioInput<f32>,
4055
mut output: AudioOutput<f32>,
4156
) {
57+
if input.buffer.num_samples() < output.buffer.num_samples() {
58+
log::error!("Input underrun");
59+
}
4260
let sr = context.stream_config.samplerate as f32;
43-
for i in 0..output.buffer.num_samples() {
61+
let num_samples = output.buffer.num_samples().min(input.buffer.num_samples());
62+
for i in 0..num_samples {
4463
let inp = input.buffer.get_frame(i)[0];
4564
let c = self.carrier.next_sample(sr);
4665
output.buffer.set_mono(i, inp * c);

examples/enumerate_alsa.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::util::enumerate::enumerate_duplex_devices;
2+
13
mod util;
24

35
#[cfg(os_alsa)]
@@ -7,7 +9,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
79

810
env_logger::init();
911

10-
enumerate_devices(AlsaDriver)
12+
enumerate_devices(AlsaDriver)?;
13+
enumerate_duplex_devices(AlsaDriver)?;
14+
Ok(())
1115
}
1216

1317
#[cfg(not(os_alsa))]

examples/loopback.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@ use std::sync::Arc;
66

77
mod util;
88

9+
#[cfg(os_alsa)]
10+
fn main() -> Result<()> {
11+
env_logger::init();
12+
13+
let device = default_duplex_device();
14+
let mut config = device.default_duplex_config().unwrap();
15+
config.buffer_size_range = (Some(128), Some(512));
16+
let value = Arc::new(AtomicF32::new(0.0));
17+
let stream = device
18+
.create_duplex_stream(config, Loopback::new(44100., value.clone()))
19+
.unwrap();
20+
util::display_peakmeter(value)?;
21+
stream.eject().unwrap();
22+
Ok(())
23+
}
24+
25+
#[cfg(not(os_alsa))]
926
fn main() -> Result<()> {
1027
env_logger::init();
1128

examples/util/enumerate.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,24 @@ where
2323
}
2424
Ok(())
2525
}
26+
27+
pub fn enumerate_duplex_devices<Driver: AudioDuplexDriver>(
28+
driver: Driver,
29+
) -> Result<(), Box<dyn Error>>
30+
where
31+
<Driver as AudioDriver>::Error: 'static,
32+
{
33+
eprintln!("Driver name : {}", Driver::DISPLAY_NAME);
34+
eprintln!("Driver version: {}", driver.version()?);
35+
if let Some(device) = driver.default_duplex_device()? {
36+
eprintln!("Default duplex device: {}", device.name());
37+
} else {
38+
eprintln!("No default duplex device");
39+
}
40+
41+
eprintln!("All duplex devices");
42+
for device in driver.list_duplex_devices()? {
43+
eprintln!("\t{}", device.name());
44+
}
45+
Ok(())
46+
}

src/backends/alsa/device.rs

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::backends::alsa::stream::AlsaStream;
1+
use crate::{backends::alsa::stream::AlsaStream, device::AudioDuplexDevice, duplex::AudioDuplexCallback, SendEverywhereButOnWeb};
22
use crate::backends::alsa::AlsaError;
33
use crate::device::Channel;
44
use crate::device::{AudioDevice, AudioInputDevice, AudioOutputDevice, DeviceType};
@@ -16,6 +16,23 @@ pub struct AlsaDevice {
1616
pub(super) direction: alsa::Direction,
1717
}
1818

19+
impl AlsaDevice {
20+
fn channel_map(&self, requested_direction: alsa::Direction) -> impl Iterator<Item = Channel> {
21+
let max_channels = if self.direction == requested_direction {
22+
self.pcm
23+
.hw_params_current()
24+
.and_then(|hwp| hwp.get_channels_max())
25+
.unwrap_or(0)
26+
} else {
27+
0
28+
};
29+
(0..max_channels as usize).map(|i| Channel {
30+
index: i,
31+
name: Cow::Owned(format!("Channel {}", i)),
32+
})
33+
}
34+
}
35+
1936
impl fmt::Debug for AlsaDevice {
2037
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2138
f.debug_struct("AlsaDevice")
@@ -111,8 +128,8 @@ impl AlsaDevice {
111128
}
112129

113130
pub(super) fn new(name: &str, direction: alsa::Direction) -> Result<Self, alsa::Error> {
114-
let pcm = PCM::new(name, direction, true)?;
115-
let pcm = Rc::new(pcm);
131+
log::info!("Opening device: {name}, direction {direction:?}");
132+
let pcm = Rc::new(PCM::new(name, direction, true)?);
116133
Ok(Self {
117134
name: name.to_string(),
118135
direction,
@@ -125,11 +142,12 @@ impl AlsaDevice {
125142
hwp.set_channels(config.channels as _)?;
126143
hwp.set_rate(config.samplerate as _, alsa::ValueOr::Nearest)?;
127144
if let Some(min) = config.buffer_size_range.0 {
128-
hwp.set_buffer_size_min(min as _)?;
145+
hwp.set_buffer_size_min(min as pcm::Frames * 2)?;
129146
}
130147
if let Some(max) = config.buffer_size_range.1 {
131-
hwp.set_buffer_size_max(max as _)?;
148+
hwp.set_buffer_size_max(max as pcm::Frames * 2)?;
132149
}
150+
hwp.set_periods(2, alsa::ValueOr::Nearest)?;
133151
hwp.set_format(pcm::Format::float())?;
134152
hwp.set_access(pcm::Access::RWInterleaved)?;
135153
Ok(hwp)
@@ -147,13 +165,24 @@ impl AlsaDevice {
147165

148166
log::debug!("Apply config: hwp {hwp:#?}");
149167

168+
swp.set_avail_min(hwp.get_period_size()?)?;
150169
swp.set_start_threshold(hwp.get_buffer_size()?)?;
151170
self.pcm.sw_params(&swp)?;
152171
log::debug!("Apply config: swp {swp:#?}");
153172

154173
Ok((hwp, swp, io))
155174
}
156175

176+
pub(super) fn ensure_state(&self, hwp: &pcm::HwParams) -> Result<bool, AlsaError> {
177+
match self.pcm.state() {
178+
pcm::State::Suspended if hwp.can_resume() => self.pcm.resume()?,
179+
pcm::State::Suspended => self.pcm.prepare()?,
180+
pcm::State::Paused => return Ok(true),
181+
_ => {}
182+
}
183+
Ok(false)
184+
}
185+
157186
fn default_config(&self) -> Result<StreamConfig, AlsaError> {
158187
let samplerate = 48e3; // Default ALSA sample rate
159188
let channel_count = 2; // Stereo stream
@@ -166,3 +195,81 @@ impl AlsaDevice {
166195
})
167196
}
168197
}
198+
199+
pub struct AlsaDuplexDevice {
200+
pub(super) input: AlsaDevice,
201+
pub(super) output: AlsaDevice,
202+
}
203+
204+
impl AudioDevice for AlsaDuplexDevice {
205+
type Error = AlsaError;
206+
207+
fn name(&self) -> Cow<str> {
208+
Cow::Owned(format!("{} / {}", self.input.name(), self.output.name()))
209+
}
210+
211+
fn device_type(&self) -> DeviceType {
212+
DeviceType::Duplex
213+
}
214+
215+
fn is_config_supported(&self, config: &StreamConfig) -> bool {
216+
let Ok((hwp, _, _)) = self.output.apply_config(config) else {
217+
return false;
218+
};
219+
let Ok(period) = hwp.get_period_size() else {
220+
return false;
221+
};
222+
let period = period as usize;
223+
self.input
224+
.apply_config(&StreamConfig {
225+
buffer_size_range: (Some(period), Some(period)),
226+
..*config
227+
})
228+
.is_ok()
229+
}
230+
231+
fn enumerate_configurations(&self) -> Option<impl IntoIterator<Item = StreamConfig>> {
232+
Some(
233+
self.output
234+
.enumerate_configurations()?
235+
.into_iter()
236+
.filter(|config| self.is_config_supported(config)),
237+
)
238+
}
239+
}
240+
241+
impl AudioDuplexDevice for AlsaDuplexDevice {
242+
type StreamHandle<Callback: AudioDuplexCallback> = AlsaStream<Callback>;
243+
244+
fn default_duplex_config(&self) -> Result<StreamConfig, Self::Error> {
245+
self.output.default_output_config()
246+
}
247+
248+
fn create_duplex_stream<Callback: SendEverywhereButOnWeb + AudioDuplexCallback>(
249+
&self,
250+
config: StreamConfig,
251+
callback: Callback,
252+
) -> Result<<Self as AudioDuplexDevice>::StreamHandle<Callback>, Self::Error> {
253+
AlsaStream::new_duplex(
254+
config,
255+
self.input.name.clone(),
256+
self.output.name.clone(),
257+
callback,
258+
)
259+
}
260+
}
261+
262+
impl AlsaDuplexDevice {
263+
/// Create a new duplex device from an input and output device.
264+
pub fn new(input: AlsaDevice, output: AlsaDevice) -> Self {
265+
Self { input, output }
266+
}
267+
268+
/// Create a full-duplex device from the given name.
269+
pub fn full_duplex(name: &str) -> Result<Self, AlsaError> {
270+
Ok(Self::new(
271+
AlsaDevice::new(name, alsa::Direction::Capture)?,
272+
AlsaDevice::new(name, alsa::Direction::Playback)?,
273+
))
274+
}
275+
}

0 commit comments

Comments
 (0)