Skip to content

Commit 36ae2f0

Browse files
authored
Merge pull request #523 from alexmoon/examples
Examples improvements
2 parents f6c1225 + e76e1de commit 36ae2f0

File tree

7 files changed

+306
-58
lines changed

7 files changed

+306
-58
lines changed

.github/workflows/cpal.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ jobs:
262262
toolchain: stable
263263
override: true
264264
target: armv7-linux-androideabi
265+
- name: Check android
266+
run: cargo check --example android --target armv7-linux-androideabi --verbose
265267
- name: Check beep
266268
run: cargo check --example beep --target armv7-linux-androideabi --verbose
267269
- name: Check enumerate
@@ -284,4 +286,4 @@ jobs:
284286
- name: Install Cargo APK
285287
run: cargo install cargo-apk
286288
- name: Build APK
287-
run: cargo apk build --example beep
289+
run: cargo apk build --example android

Cargo.toml

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ thiserror = "1.0.2"
1818
anyhow = "1.0.12"
1919
hound = "3.4"
2020
ringbuf = "0.2"
21+
clap = { version = "2.33.3", default-features = false }
2122

2223
[target.'cfg(target_os = "windows")'.dependencies]
2324
winapi = { version = "0.3", features = ["audiosessiontypes", "audioclient", "coml2api", "combaseapi", "debug", "devpkey", "handleapi", "ksmedia", "mmdeviceapi", "objbase", "profileapi", "std", "synchapi", "winbase", "winuser"] }
@@ -53,21 +54,18 @@ ndk-glue = "0.2"
5354
jni = "0.17"
5455

5556
[[example]]
56-
name = "beep"
57-
path = "examples/beep.rs"
57+
name = "android"
58+
path = "examples/android.rs"
5859
crate-type = ["cdylib"]
5960

61+
[[example]]
62+
name = "beep"
63+
6064
[[example]]
6165
name = "enumerate"
62-
path = "examples/enumerate.rs"
63-
crate-type = ["cdylib"]
6466

6567
[[example]]
6668
name = "feedback"
67-
path = "examples/feedback.rs"
68-
crate-type = ["cdylib"]
6969

7070
[[example]]
7171
name = "record_wav"
72-
path = "examples/record_wav.rs"
73-
crate-type = ["cdylib"]

examples/android.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#![allow(dead_code)]
2+
3+
extern crate anyhow;
4+
extern crate cpal;
5+
6+
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
7+
8+
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "full"))]
9+
fn main() {
10+
let host = cpal::default_host();
11+
12+
let device = host
13+
.default_output_device()
14+
.expect("failed to find output device");
15+
16+
let config = device.default_output_config().unwrap();
17+
18+
match config.sample_format() {
19+
cpal::SampleFormat::F32 => run::<f32>(&device, &config.into()).unwrap(),
20+
cpal::SampleFormat::I16 => run::<i16>(&device, &config.into()).unwrap(),
21+
cpal::SampleFormat::U16 => run::<u16>(&device, &config.into()).unwrap(),
22+
}
23+
}
24+
25+
fn run<T>(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error>
26+
where
27+
T: cpal::Sample,
28+
{
29+
let sample_rate = config.sample_rate.0 as f32;
30+
let channels = config.channels as usize;
31+
32+
// Produce a sinusoid of maximum amplitude.
33+
let mut sample_clock = 0f32;
34+
let mut next_value = move || {
35+
sample_clock = (sample_clock + 1.0) % sample_rate;
36+
(sample_clock * 440.0 * 2.0 * std::f32::consts::PI / sample_rate).sin()
37+
};
38+
39+
let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
40+
41+
let stream = device.build_output_stream(
42+
config,
43+
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
44+
write_data(data, channels, &mut next_value)
45+
},
46+
err_fn,
47+
)?;
48+
stream.play()?;
49+
50+
std::thread::sleep(std::time::Duration::from_millis(1000));
51+
52+
Ok(())
53+
}
54+
55+
fn write_data<T>(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32)
56+
where
57+
T: cpal::Sample,
58+
{
59+
for frame in output.chunks_mut(channels) {
60+
let value: T = cpal::Sample::from::<f32>(&next_sample());
61+
for sample in frame.iter_mut() {
62+
*sample = value;
63+
}
64+
}
65+
}

examples/beep.rs

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,59 @@
11
extern crate anyhow;
2+
extern crate clap;
23
extern crate cpal;
34

45
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
56

6-
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "full"))]
7-
fn main() {
7+
#[derive(Debug)]
8+
struct Opt {
9+
#[cfg(all(
10+
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
11+
feature = "jack"
12+
))]
13+
jack: bool,
14+
15+
device: String,
16+
}
17+
18+
impl Opt {
19+
fn from_args() -> Self {
20+
let app = clap::App::new("beep").arg_from_usage("[DEVICE] 'The audio device to use'");
21+
#[cfg(all(
22+
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
23+
feature = "jack"
24+
))]
25+
let app = app.arg_from_usage("-j, --jack 'Use the JACK host");
26+
let matches = app.get_matches();
27+
let device = matches.value_of("DEVICE").unwrap_or("default").to_string();
28+
29+
#[cfg(all(
30+
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
31+
feature = "jack"
32+
))]
33+
return Opt {
34+
jack: matches.is_present("jack"),
35+
device,
36+
};
37+
38+
#[cfg(any(
39+
not(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")),
40+
not(feature = "jack")
41+
))]
42+
Opt { device }
43+
}
44+
}
45+
46+
fn main() -> anyhow::Result<()> {
47+
let opt = Opt::from_args();
48+
849
// Conditionally compile with jack if the feature is specified.
950
#[cfg(all(
1051
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
1152
feature = "jack"
1253
))]
1354
// Manually check for flags. Can be passed through cargo with -- e.g.
1455
// cargo run --release --example beep --features jack -- --jack
15-
let host = if std::env::args()
16-
.collect::<String>()
17-
.contains(&String::from("--jack"))
18-
{
56+
let host = if opt.jack {
1957
cpal::host_from_id(cpal::available_hosts()
2058
.into_iter()
2159
.find(|id| *id == cpal::HostId::Jack)
@@ -32,19 +70,26 @@ fn main() {
3270
))]
3371
let host = cpal::default_host();
3472

35-
let device = host
36-
.default_output_device()
37-
.expect("failed to find a default output device");
73+
let device = if opt.device == "default" {
74+
host.default_output_device()
75+
} else {
76+
host.output_devices()?
77+
.find(|x| x.name().map(|y| y == opt.device).unwrap_or(false))
78+
}
79+
.expect("failed to find output device");
80+
println!("Output device: {}", device.name()?);
81+
3882
let config = device.default_output_config().unwrap();
83+
println!("Default output config: {:?}", config);
3984

4085
match config.sample_format() {
41-
cpal::SampleFormat::F32 => run::<f32>(&device, &config.into()).unwrap(),
42-
cpal::SampleFormat::I16 => run::<i16>(&device, &config.into()).unwrap(),
43-
cpal::SampleFormat::U16 => run::<u16>(&device, &config.into()).unwrap(),
86+
cpal::SampleFormat::F32 => run::<f32>(&device, &config.into()),
87+
cpal::SampleFormat::I16 => run::<i16>(&device, &config.into()),
88+
cpal::SampleFormat::U16 => run::<u16>(&device, &config.into()),
4489
}
4590
}
4691

47-
fn run<T>(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error>
92+
pub fn run<T>(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error>
4893
where
4994
T: cpal::Sample,
5095
{
@@ -55,7 +100,7 @@ where
55100
let mut sample_clock = 0f32;
56101
let mut next_value = move || {
57102
sample_clock = (sample_clock + 1.0) % sample_rate;
58-
(sample_clock * 440.0 * 2.0 * 3.141592 / sample_rate).sin()
103+
(sample_clock * 440.0 * 2.0 * std::f32::consts::PI / sample_rate).sin()
59104
};
60105

61106
let err_fn = |err| eprintln!("an error occurred on stream: {}", err);

examples/enumerate.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,16 @@ fn main() -> Result<(), anyhow::Error> {
2626
if let Ok(conf) = device.default_input_config() {
2727
println!(" Default input stream config:\n {:?}", conf);
2828
}
29-
let mut input_configs = match device.supported_input_configs() {
30-
Ok(f) => f.peekable(),
29+
let input_configs = match device.supported_input_configs() {
30+
Ok(f) => f.collect(),
3131
Err(e) => {
32-
println!("Error: {:?}", e);
33-
continue;
32+
println!(" Error getting supported input configs: {:?}", e);
33+
Vec::new()
3434
}
3535
};
36-
if input_configs.peek().is_some() {
36+
if !input_configs.is_empty() {
3737
println!(" All supported input stream configs:");
38-
for (config_index, config) in input_configs.enumerate() {
38+
for (config_index, config) in input_configs.into_iter().enumerate() {
3939
println!(
4040
" {}.{}. {:?}",
4141
device_index + 1,
@@ -49,16 +49,16 @@ fn main() -> Result<(), anyhow::Error> {
4949
if let Ok(conf) = device.default_output_config() {
5050
println!(" Default output stream config:\n {:?}", conf);
5151
}
52-
let mut output_configs = match device.supported_output_configs() {
53-
Ok(f) => f.peekable(),
52+
let output_configs = match device.supported_output_configs() {
53+
Ok(f) => f.collect(),
5454
Err(e) => {
55-
println!("Error: {:?}", e);
56-
continue;
55+
println!(" Error getting supported output configs: {:?}", e);
56+
Vec::new()
5757
}
5858
};
59-
if output_configs.peek().is_some() {
59+
if !output_configs.is_empty() {
6060
println!(" All supported output stream configs:");
61-
for (config_index, config) in output_configs.enumerate() {
61+
for (config_index, config) in output_configs.into_iter().enumerate() {
6262
println!(
6363
" {}.{}. {:?}",
6464
device_index + 1,

0 commit comments

Comments
 (0)