forked from RustAudio/cpal
-
Notifications
You must be signed in to change notification settings - Fork 0
/
feedback.rs
198 lines (174 loc) · 6.38 KB
/
feedback.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//! Feeds back the input stream directly into the output stream.
//!
//! Assumes that the input and output devices can use the same stream configuration and that they
//! support the f32 sample format.
//!
//! Uses a delay of `LATENCY_MS` milliseconds in case the default input and output streams are not
//! precisely synchronised.
extern crate anyhow;
extern crate clap;
extern crate cpal;
extern crate ringbuf;
use anyhow::Context;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use ringbuf::RingBuffer;
#[derive(Debug)]
struct Opt {
#[cfg(all(
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
feature = "jack"
))]
jack: bool,
latency: f32,
input_device: String,
output_device: String,
}
impl Opt {
fn from_args() -> anyhow::Result<Self> {
let app = clap::App::new("beep")
.arg_from_usage(
"-l, --latency [DELAY_MS] 'Specify the delay between input and output [default: 150]'",
)
.arg_from_usage("[IN] 'The input audio device to use'")
.arg_from_usage("[OUT] 'The output audio device to use'");
#[cfg(all(
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
feature = "jack"
))]
let app = app.arg_from_usage("-j, --jack 'Use the JACK host");
let matches = app.get_matches();
let latency: f32 = matches
.value_of("latency")
.unwrap_or("150")
.parse()
.context("parsing latency option")?;
let input_device = matches.value_of("IN").unwrap_or("default").to_string();
let output_device = matches.value_of("OUT").unwrap_or("default").to_string();
#[cfg(all(
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
feature = "jack"
))]
return Ok(Opt {
jack: matches.is_present("jack"),
latency,
input_device,
output_device,
});
#[cfg(any(
not(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")),
not(feature = "jack")
))]
Ok(Opt {
latency,
input_device,
output_device,
})
}
}
fn main() -> anyhow::Result<()> {
let opt = Opt::from_args()?;
// Conditionally compile with jack if the feature is specified.
#[cfg(all(
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
feature = "jack"
))]
// Manually check for flags. Can be passed through cargo with -- e.g.
// cargo run --release --example beep --features jack -- --jack
let host = if opt.jack {
cpal::host_from_id(cpal::available_hosts()
.into_iter()
.find(|id| *id == cpal::HostId::Jack)
.expect(
"make sure --features jack is specified. only works on OSes where jack is available",
)).expect("jack host unavailable")
} else {
cpal::default_host()
};
#[cfg(any(
not(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")),
not(feature = "jack")
))]
let host = cpal::default_host();
// Find devices.
let input_device = if opt.input_device == "default" {
host.default_input_device()
} else {
host.input_devices()?
.find(|x| x.name().map(|y| y == opt.input_device).unwrap_or(false))
}
.expect("failed to find input device");
let output_device = if opt.output_device == "default" {
host.default_output_device()
} else {
host.output_devices()?
.find(|x| x.name().map(|y| y == opt.output_device).unwrap_or(false))
}
.expect("failed to find output device");
println!("Using input device: \"{}\"", input_device.name()?);
println!("Using output device: \"{}\"", output_device.name()?);
// We'll try and use the same configuration between streams to keep it simple.
let config: cpal::StreamConfig = input_device.default_input_config()?.into();
// Create a delay in case the input and output devices aren't synced.
let latency_frames = (opt.latency / 1_000.0) * config.sample_rate.0 as f32;
let latency_samples = latency_frames as usize * config.channels as usize;
// The buffer to share samples
let ring = RingBuffer::new(latency_samples * 2);
let (mut producer, mut consumer) = ring.split();
// Fill the samples with 0.0 equal to the length of the delay.
for _ in 0..latency_samples {
// The ring buffer has twice as much space as necessary to add latency here,
// so this should never fail
producer.push(0.0).unwrap();
}
let input_data_fn = move |data: &[f32], _: &cpal::InputCallbackInfo| {
let mut output_fell_behind = false;
for &sample in data {
if producer.push(sample).is_err() {
output_fell_behind = true;
}
}
if output_fell_behind {
eprintln!("output stream fell behind: try increasing latency");
}
};
let output_data_fn = move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
let mut input_fell_behind = false;
for sample in data {
*sample = match consumer.pop() {
Some(s) => s,
None => {
input_fell_behind = true;
0.0
}
};
}
if input_fell_behind {
eprintln!("input stream fell behind: try increasing latency");
}
};
// Build streams.
println!(
"Attempting to build both streams with f32 samples and `{:?}`.",
config
);
let input_stream = input_device.build_input_stream(&config, input_data_fn, err_fn)?;
let output_stream = output_device.build_output_stream(&config, output_data_fn, err_fn)?;
println!("Successfully built streams.");
// Play the streams.
println!(
"Starting the input and output streams with `{}` milliseconds of latency.",
opt.latency
);
input_stream.play()?;
output_stream.play()?;
// Run for 3 seconds before closing.
println!("Playing for 3 seconds... ");
std::thread::sleep(std::time::Duration::from_secs(3));
drop(input_stream);
drop(output_stream);
println!("Done!");
Ok(())
}
fn err_fn(err: cpal::StreamError) {
eprintln!("an error occurred on stream: {}", err);
}