Skip to content

Commit 6ec90c4

Browse files
committed
Using SDL3 audio stream API better
1 parent f6b25d0 commit 6ec90c4

File tree

4 files changed

+113
-100
lines changed

4 files changed

+113
-100
lines changed

.cargo/config.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[env]
22
# use pipewire for audio
3-
SDL_AUDIODRIVER = "pipewire"
3+
#SDL_AUDIODRIVER = "pipewire"

Cargo.lock

-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ rust-version = "1.68.2"
88
libc = "*"
99
#projectm = { path = "../projectm-rs", version = "2.0.2", features = ["playlist"] }
1010
projectm = { version = "2.0.2", features = ['playlist'] }
11-
sdl3 = { version = "0.10.8", features = ["build-from-source"] }
12-
#sdl3 = { path = "../../sdl3-rs", version = "0", features = ["build-from-source"] }
11+
#sdl3 = { version = "0.10.8", features = ["build-from-source"] }
12+
sdl3 = { path = "../../sdl3-rs", version = "0", features = ["build-from-source"] }
1313
rand = "0.8.5"
1414
include_dir = "0.7"
1515

src/app/audio.rs

+110-95
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,24 @@
1-
use projectm::core::ProjectM;
2-
use sdl3::audio::{
3-
AudioDevice, AudioDeviceID, AudioRecordingCallback, AudioSpec, AudioStreamWithCallback,
4-
};
1+
use sdl3::audio::{AudioDevice, AudioDeviceID, AudioSpec, AudioStream};
2+
use std::io::Read;
53

64
use super::config::FrameRate;
75
use super::ProjectMWrapped;
86

9-
use std::rc::Rc;
10-
117
// let the user cycle through audio devices
128
type AudioDeviceIndex = usize;
139

1410
type SampleFormat = f32; // format of audio samples
1511

1612
pub struct Audio {
1713
audio_subsystem: sdl3::AudioSubsystem,
18-
19-
current_recording_device: Option<AudioDevice>,
20-
capture_stream: Option<Box<AudioStreamWithCallback<AudioCaptureCallback>>>,
14+
recording_stream: Option<Box<AudioStream>>,
2115
is_capturing: bool,
22-
2316
frame_rate: Option<FrameRate>,
2417
projectm: ProjectMWrapped,
18+
current_device_id: Option<AudioDeviceID>,
2519
}
2620

27-
/// Wrapper around the audio subsystem to capture audio and pass it to projectM.
21+
/// Wrapper around the audio subsystem to record audio and pass it to projectM.
2822
impl Audio {
2923
pub fn new(sdl_context: &sdl3::Sdl, projectm: ProjectMWrapped) -> Self {
3024
let audio_subsystem = sdl_context.audio().unwrap();
@@ -36,9 +30,9 @@ impl Audio {
3630
Self {
3731
is_capturing: false,
3832
audio_subsystem,
39-
current_recording_device: None,
4033
frame_rate: None,
41-
capture_stream: None,
34+
current_device_id: None,
35+
recording_stream: None,
4236
projectm,
4337
}
4438
}
@@ -49,7 +43,7 @@ impl Audio {
4943
self.frame_rate = frame_rate.into();
5044

5145
#[cfg(not(feature = "dummy_audio"))]
52-
self.begin_audio_capture(self.get_default_recording_device().id());
46+
self.begin_audio_capture(None);
5347
}
5448

5549
pub fn list_devices(&self) {
@@ -62,124 +56,145 @@ impl Audio {
6256
}
6357

6458
/// Start capturing audio from device_id.
65-
pub fn capture_device(&mut self, device_id: AudioDeviceID) {
59+
pub fn begin_audio_capture(&mut self, device_id: Option<AudioDeviceID>) {
60+
// stop capturing from current stream/device
6661
self.stop_audio_capture();
67-
self.begin_audio_capture(device_id);
68-
}
69-
70-
fn get_default_recording_device(&self) -> AudioDevice {
71-
self.audio_subsystem.default_recording_device()
72-
}
7362

74-
/// Select a new audio device and start capturing audio from it.
75-
pub fn open_next_device(&mut self) {
76-
let device_list = self.get_device_list();
77-
let current_device = self.current_recording_device.as_ref().unwrap();
78-
let current_device_id = current_device.id();
79-
let current_device_index = device_list
80-
.iter()
81-
.position(|d| d.eq(&current_device_id))
82-
.unwrap();
83-
let next_device_index = (current_device_index + 1) % device_list.len();
84-
let next_device_id = device_list[next_device_index];
85-
self.capture_device(next_device_id);
86-
}
87-
88-
fn get_device_list(&self) -> Vec<AudioDeviceID> {
89-
let audio_subsystem = &self.audio_subsystem;
90-
91-
audio_subsystem.audio_recording_device_ids().unwrap()
92-
}
93-
94-
pub fn begin_audio_capture(&mut self, device_id: AudioDeviceID) {
9563
let sample_rate: u32 = 44100;
9664
let frame_rate = self.frame_rate.unwrap();
9765

9866
// how many samples to capture at a time
9967
// should be enough for 1 frame or less
10068
// should not be larger than max_samples / channels
101-
let max_samples: usize = ProjectM::pcm_get_max_samples().try_into().unwrap();
102-
let samples_per_frame = (sample_rate / frame_rate) as usize;
103-
let buffer_size = std::cmp::min(max_samples / 2, samples_per_frame);
104-
println!("Capturing audio from device {}", device_id.name());
105-
println!("Buffer size: {}", buffer_size);
69+
// let max_samples: usize = ProjectM::pcm_get_max_samples().try_into().unwrap();
70+
// let samples_per_frame = (sample_rate / frame_rate) as usize;
71+
// let buffer_size = std::cmp::min(max_samples / 2, samples_per_frame);
72+
// println!("Buffer size: {}", buffer_size);
10673

10774
let desired_spec = AudioSpec {
10875
freq: Some(sample_rate.try_into().unwrap()),
10976
channels: Some(2),
11077
format: Some(sdl3::audio::AudioFormat::f32_sys()),
11178
};
11279

113-
// open audio device for capture
114-
let device = AudioDevice::new(device_id);
115-
let audio_stream = device
116-
// move this to open_recording_device
117-
.open_recording_stream_with_callback(
118-
&desired_spec,
119-
AudioCaptureCallback {
120-
pm: Rc::clone(&self.projectm),
121-
},
122-
)
123-
.unwrap();
80+
// open audio device for capture (use default device if none specified)
81+
let device_id = (device_id
82+
.or_else(|| Some(self.get_default_recording_device().id()))
83+
.unwrap());
84+
85+
let audio_stream = match AudioStream::open_device_stream(device_id, Some(&desired_spec)) {
86+
Ok(stream) => stream,
87+
Err(e) => {
88+
println!("Failed to open audio stream: {}", e);
89+
return;
90+
}
91+
};
92+
println!("Capturing audio from device {:?}", audio_stream);
93+
94+
let device_id = audio_stream.device_id();
95+
if device_id.is_none() {
96+
println!("Failed to get begin audio capture: {:?}", audio_stream);
97+
return;
98+
}
12499

125100
// start capturing
126101
audio_stream
127102
.resume()
128103
.expect("Failed to start audio capture");
129104

130105
// take ownership of device
131-
self.capture_stream = Some(Box::new(audio_stream));
132-
self.current_recording_device = Some(device);
106+
self.recording_stream = Some(Box::new(audio_stream));
107+
self.current_device_id = device_id;
133108
self.is_capturing = true;
134109
}
135110

111+
fn get_default_recording_device(&self) -> AudioDevice {
112+
self.audio_subsystem.default_recording_device()
113+
}
114+
115+
/// Select a new audio device and start capturing audio from it.
116+
pub fn open_next_device(&mut self) {
117+
let current_device_id = self.current_device_id;
118+
if current_device_id.is_none() {
119+
self.begin_audio_capture(None);
120+
return;
121+
}
122+
let current_device_id = current_device_id.unwrap();
123+
124+
// get list of devices
125+
let device_list = self.get_device_list();
126+
127+
println!("Device list: {:?}", device_list);
128+
println!("Current device: {:?}", current_device_id);
129+
130+
// find current device in list
131+
let mut current_device_index = device_list.iter().position(|d| d.eq(&current_device_id));
132+
133+
// if current device not found, start from the beginning
134+
if current_device_index.is_none() {
135+
println!("Current device not found in device list");
136+
self.begin_audio_capture(None);
137+
return;
138+
}
139+
140+
// select next device
141+
let next_device_index = (current_device_index.unwrap() + 1) % device_list.len();
142+
let next_device_id = device_list[next_device_index];
143+
144+
// start capturing from next device
145+
self.begin_audio_capture(Some(next_device_id));
146+
}
147+
148+
fn get_device_list(&self) -> Vec<AudioDeviceID> {
149+
let audio_subsystem = &self.audio_subsystem;
150+
151+
audio_subsystem.audio_recording_device_ids().unwrap()
152+
}
153+
136154
pub fn recording_device_name(&self) -> Option<String> {
137-
self.current_recording_device
138-
.as_ref()
139-
.map(|device| device.name())
155+
self.current_device_id.and_then(|id| Some(id.name()))
140156
}
141157

142158
pub fn stop_audio_capture(&mut self) {
143-
if self.current_recording_device.is_none() {
159+
let mut recording_stream = &mut self.recording_stream;
160+
if recording_stream.is_none() {
144161
return;
145162
}
146163

147-
let current_device_name = self.recording_device_name();
164+
// take ownership of stream
165+
let stream = recording_stream.take().unwrap();
166+
167+
let current_device_name = stream.device_name();
148168
println!(
149169
"Stopping audio capture for device {}",
150-
current_device_name.unwrap_or("unknown".to_string())
170+
current_device_name.unwrap_or_else(|| "unknown".to_string())
151171
);
152172

153-
// take ownership of device
154-
// capture device will be dropped when this function returns
155-
// and the audio callback will stop being called
156-
let device = self.capture_stream.take().unwrap();
157-
device.pause().expect("Failed to stop audio capture");
158-
173+
// the recording device will be closed when the stream is dropped
159174
self.is_capturing = false;
160-
drop(device);
175+
drop(stream);
161176
}
162177
}
163178

164-
struct AudioCaptureCallback {
165-
// we need to keep a reference to the projectm instance to
166-
// add the audio data to it
167-
pm: ProjectMWrapped,
168-
}
169-
unsafe impl Send for AudioCaptureCallback {}
170-
unsafe impl Sync for AudioCaptureCallback {}
171-
172-
impl AudioRecordingCallback<SampleFormat> for AudioCaptureCallback {
173-
// we are receiving some chunk of audio data
174-
// we need to pass it to projectm
175-
fn callback(&mut self, out: &[SampleFormat]) {
176-
println!("Received {} samples", out.len());
177-
let mut out = out;
178-
let max_samples = ProjectM::pcm_get_max_samples() as usize;
179-
if (out.len() > max_samples) {
180-
// remove some samples
181-
out = &out[..max_samples];
182-
}
183-
self.pm.pcm_add_float(out.to_vec(), 2);
184-
}
185-
}
179+
// struct AudioCaptureCallback {
180+
// // we need to keep a reference to the projectm instance to
181+
// // add the audio data to it
182+
// pm: ProjectMWrapped,
183+
// }
184+
// unsafe impl Send for AudioCaptureCallback {}
185+
// unsafe impl Sync for AudioCaptureCallback {}
186+
//
187+
// impl AudioRecordingCallback<SampleFormat> for AudioCaptureCallback {
188+
// // we are receiving some chunk of audio data
189+
// // we need to pass it to projectm
190+
// fn callback(&mut self, out: &[SampleFormat]) {
191+
// println!("Received {} samples", out.len());
192+
// let mut out = out;
193+
// let max_samples = ProjectM::pcm_get_max_samples() as usize;
194+
// if (out.len() > max_samples) {
195+
// // remove some samples
196+
// out = &out[..max_samples];
197+
// }
198+
// self.pm.pcm_add_float(out.to_vec(), 2);
199+
// }
200+
// }

0 commit comments

Comments
 (0)