Skip to content

Commit 2acb959

Browse files
authored
Merge pull request #18 from HEnquist/win048
Win048
2 parents bf15059 + 2613e97 commit 2acb959

File tree

11 files changed

+690
-159
lines changed

11 files changed

+690
-159
lines changed

Cargo.toml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "wasapi"
3-
version = "0.12.0"
3+
version = "0.13.0"
44
edition = "2021"
55
rust-version = "1.59"
66
authors = ["HEnquist <henrik.enquist@gmail.com>"]
@@ -13,11 +13,12 @@ readme = "README.md"
1313
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1414

1515
[dependencies.windows]
16-
version = "0.34.0"
16+
version = "0.48.0"
1717
features = ["Foundation",
1818
"implement",
1919
"Win32_Media_Audio",
2020
"Win32_Foundation",
21+
"Win32_Devices_FunctionDiscovery",
2122
"Win32_Devices_Properties",
2223
"Win32_UI_Shell_PropertiesSystem",
2324
"Win32_System_Com",
@@ -28,11 +29,13 @@ features = ["Foundation",
2829
"Win32_Security",]
2930

3031
[dependencies]
31-
widestring = "0.5.1"
32-
log = "0.4.14"
32+
widestring = "1.0.2"
33+
log = "0.4.18"
34+
num-integer = "0.1"
3335

3436
[dev-dependencies]
35-
simplelog = "0.11.2"
37+
simplelog = "0.12.1"
38+
rand = "0.8.5"
3639

3740
[package.metadata.docs.rs]
3841
default-target = "x86_64-pc-windows-msvc"

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@ These things have been implemented so far:
1414

1515
- Audio playback and capture
1616
- Shared and exclusive modes
17+
- Event-driven buffering
1718
- Loopback capture
1819
- Notifications for volume change, device disconnect etc
1920

2021
## Included examples
2122

22-
| Example | Description |
23-
| ---------- | ---------------------------------------------------------------|
24-
| `playsine` | Plays a sine wave in shared mode on the default output device. |
25-
| `playsine_events` | Similar to `playsine` but also listens to notifications. |
26-
| `loopback` | Shows how to simultaneously capture and render sound, with separate threads for capture and render. |
27-
| `record` | Records audio from the default device, and saves the raw samples to a file. |
23+
| Example | Description |
24+
| --------------------- | ------------------------------------------------------------------------------------------------------ |
25+
| `playsine` | Plays a sine wave in shared mode on the default output device. |
26+
| `playsine_events` | Similar to `playsine` but also listens to notifications. |
27+
| `playnoise_exclusive` | Plays white noise in exclusive mode on the default output device. Shows how to handle HRESULT errors. |
28+
| `loopback` | Shows how to simultaneously capture and render sound, with separate threads for capture and render. |
29+
| `record` | Records audio from the default device, and saves the raw samples to a file. |
2830

examples/loopback.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type Res<T> = Result<T, Box<dyn error::Error>>;
1414
fn playback_loop(rx_play: std::sync::mpsc::Receiver<Vec<u8>>) -> Res<()> {
1515
let device = get_default_device(&Direction::Render)?;
1616
let mut audio_client = device.get_iaudioclient()?;
17-
let desired_format = WaveFormat::new(32, 32, &SampleType::Float, 44100, 2);
17+
let desired_format = WaveFormat::new(32, 32, &SampleType::Float, 44100, 2, None);
1818

1919
let blockalign = desired_format.get_blockalign();
2020
debug!("Desired playback format: {:?}", desired_format);
@@ -89,7 +89,7 @@ fn capture_loop(tx_capt: std::sync::mpsc::SyncSender<Vec<u8>>, chunksize: usize)
8989
let device = get_default_device(&Direction::Capture)?;
9090
let mut audio_client = device.get_iaudioclient()?;
9191

92-
let desired_format = WaveFormat::new(32, 32, &SampleType::Float, 44100, 2);
92+
let desired_format = WaveFormat::new(32, 32, &SampleType::Float, 44100, 2, None);
9393

9494
let blockalign = desired_format.get_blockalign();
9595
debug!("Desired capture format: {:?}", desired_format);
@@ -140,7 +140,9 @@ fn main() -> Res<()> {
140140
let _ = SimpleLogger::init(
141141
LevelFilter::Trace,
142142
ConfigBuilder::new()
143-
.set_time_format_str("%H:%M:%S%.3f")
143+
.set_time_format_rfc3339()
144+
.set_time_offset_to_local()
145+
.unwrap()
144146
.build(),
145147
);
146148

examples/playnoise_exclusive.rs

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
use rand::prelude::*;
2+
use wasapi::*;
3+
4+
#[macro_use]
5+
extern crate log;
6+
use simplelog::*;
7+
use windows::core::Error;
8+
9+
// A selection of the possible errors
10+
use windows::Win32::Foundation::E_INVALIDARG;
11+
use windows::Win32::Media::Audio::{
12+
AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED, AUDCLNT_E_DEVICE_IN_USE, AUDCLNT_E_ENDPOINT_CREATE_FAILED,
13+
AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED, AUDCLNT_E_UNSUPPORTED_FORMAT,
14+
};
15+
16+
// Main loop
17+
fn main() {
18+
let _ = SimpleLogger::init(
19+
LevelFilter::Debug,
20+
ConfigBuilder::new()
21+
.set_time_format_rfc3339()
22+
.set_time_offset_to_local()
23+
.unwrap()
24+
.build(),
25+
);
26+
27+
initialize_mta().unwrap();
28+
29+
let channels = 2;
30+
let device = get_default_device(&Direction::Render).unwrap();
31+
let mut audio_client = device.get_iaudioclient().unwrap();
32+
let desired_format = WaveFormat::new(24, 24, &SampleType::Int, 44100, channels, None);
33+
34+
// Make sure the format is supported, panic if not.
35+
let desired_format = audio_client
36+
.is_supported_exclusive_with_quirks(&desired_format)
37+
.unwrap();
38+
39+
// Blockalign is the number of bytes per frame
40+
let blockalign = desired_format.get_blockalign();
41+
debug!("Desired playback format: {:?}", desired_format);
42+
43+
let (def_period, min_period) = audio_client.get_periods().unwrap();
44+
45+
// Set some period as an example, using 128 byte alignment to satisfy for example Intel HDA devices.
46+
let desired_period = audio_client
47+
.calculate_aligned_period_near(3 * min_period / 2, Some(128), &desired_format)
48+
.unwrap();
49+
50+
debug!(
51+
"periods in 100ns units {}, minimum {}, wanted {}",
52+
def_period, min_period, desired_period
53+
);
54+
55+
let init_result = audio_client.initialize_client(
56+
&desired_format,
57+
desired_period as i64,
58+
&Direction::Render,
59+
&ShareMode::Exclusive,
60+
false,
61+
);
62+
match init_result {
63+
Ok(()) => debug!("IAudioClient::Initialize ok"),
64+
Err(e) => {
65+
if let Some(werr) = e.downcast_ref::<Error>() {
66+
// Some of the possible errors. See the documentation for the full list and descriptions.
67+
// https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize
68+
match werr.code() {
69+
E_INVALIDARG => error!("IAudioClient::Initialize: Invalid argument"),
70+
AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED => {
71+
warn!("IAudioClient::Initialize: Unaligned buffer, trying to adjust the period.");
72+
// Try to recover following the example in the docs.
73+
// https://learn.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize#examples
74+
// Just panic on errors to keep it short and simple.
75+
// 1. Call IAudioClient::GetBufferSize and receive the next-highest-aligned buffer size (in frames).
76+
let buffersize = audio_client.get_bufferframecount().unwrap();
77+
info!(
78+
"Client next-highest-aligned buffer size: {} frames",
79+
buffersize
80+
);
81+
// 2. Call IAudioClient::Release, skipped since this will happen automatically when we drop the client.
82+
// 3. Calculate the aligned buffer size in 100-nanosecond units.
83+
let aligned_period = calculate_period_100ns(
84+
buffersize as i64,
85+
desired_format.get_samplespersec() as i64,
86+
);
87+
info!("Aligned period in 100ns units: {}", aligned_period);
88+
// 4. Get a new IAudioClient
89+
audio_client = device.get_iaudioclient().unwrap();
90+
// 5. Call Initialize again on the created audio client.
91+
audio_client
92+
.initialize_client(
93+
&desired_format,
94+
aligned_period as i64,
95+
&Direction::Render,
96+
&ShareMode::Exclusive,
97+
false,
98+
)
99+
.unwrap();
100+
debug!("IAudioClient::Initialize ok");
101+
}
102+
AUDCLNT_E_DEVICE_IN_USE => {
103+
error!("IAudioClient::Initialize: The device is already in use");
104+
panic!("IAudioClient::Initialize failed");
105+
}
106+
AUDCLNT_E_UNSUPPORTED_FORMAT => {
107+
error!(
108+
"IAudioClient::Initialize The device does not support the audio format"
109+
);
110+
panic!("IAudioClient::Initialize failed");
111+
}
112+
AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED => {
113+
error!("IAudioClient::Initialize: Exclusive mode is not allowed");
114+
panic!("IAudioClient::Initialize failed");
115+
}
116+
AUDCLNT_E_ENDPOINT_CREATE_FAILED => {
117+
error!("IAudioClient::Initialize: Failed to create endpoint");
118+
panic!("IAudioClient::Initialize failed");
119+
}
120+
_ => {
121+
error!(
122+
"IAudioClient::Initialize: Other error, HRESULT: {:#010x}, info: {:?}",
123+
werr.code().0,
124+
werr.message()
125+
);
126+
panic!("IAudioClient::Initialize failed");
127+
}
128+
};
129+
} else {
130+
panic!("IAudioClient::Initialize: Other error {:?}", e);
131+
}
132+
}
133+
};
134+
135+
let mut rng = rand::thread_rng();
136+
137+
let h_event = audio_client.set_get_eventhandle().unwrap();
138+
139+
let render_client = audio_client.get_audiorenderclient().unwrap();
140+
141+
audio_client.start_stream().unwrap();
142+
loop {
143+
let buffer_frame_count = audio_client.get_available_space_in_frames().unwrap();
144+
145+
let mut data = vec![0u8; buffer_frame_count as usize * blockalign as usize];
146+
for frame in data.chunks_exact_mut(blockalign as usize) {
147+
let sample: u32 = rng.gen();
148+
let sample_bytes = sample.to_le_bytes();
149+
for value in frame.chunks_exact_mut(blockalign as usize / channels as usize) {
150+
for (bufbyte, samplebyte) in value.iter_mut().zip(sample_bytes.iter()) {
151+
*bufbyte = *samplebyte;
152+
}
153+
}
154+
}
155+
156+
trace!("write");
157+
render_client
158+
.write_to_device(
159+
buffer_frame_count as usize,
160+
blockalign as usize,
161+
&data,
162+
None,
163+
)
164+
.unwrap();
165+
trace!("write ok");
166+
if h_event.wait_for_event(1000).is_err() {
167+
error!("error, stopping playback");
168+
audio_client.stop_stream().unwrap();
169+
break;
170+
}
171+
}
172+
}

examples/playsine.rs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ fn main() {
3737
let _ = SimpleLogger::init(
3838
LevelFilter::Debug,
3939
ConfigBuilder::new()
40-
.set_time_format_str("%H:%M:%S%.3f")
40+
.set_time_format_rfc3339()
41+
.set_time_offset_to_local()
42+
.unwrap()
4143
.build(),
4244
);
4345

@@ -48,28 +50,50 @@ fn main() {
4850
let channels = 2;
4951
let device = get_default_device(&Direction::Render).unwrap();
5052
let mut audio_client = device.get_iaudioclient().unwrap();
51-
let desired_format = WaveFormat::new(32, 32, &SampleType::Float, 44100, channels);
53+
let desired_format = WaveFormat::new(32, 32, &SampleType::Float, 44100, channels, None);
5254

5355
// Check if the desired format is supported.
54-
// Since we have convert = true in the initialize_client call later,
55-
// it's ok to run with an unsupported format.
56-
match audio_client.is_supported(&desired_format, &ShareMode::Shared) {
56+
let needs_convert = match audio_client.is_supported(&desired_format, &ShareMode::Shared) {
5757
Ok(None) => {
5858
debug!("Device supports format {:?}", desired_format);
59+
false
5960
}
6061
Ok(Some(modified)) => {
6162
debug!(
6263
"Device doesn't support format:\n{:#?}\nClosest match is:\n{:#?}",
6364
desired_format, modified
64-
)
65+
);
66+
true
6567
}
6668
Err(err) => {
6769
debug!(
6870
"Device doesn't support format:\n{:#?}\nError: {}",
6971
desired_format, err
7072
);
73+
debug!("Repeating query with format as WAVEFORMATEX");
74+
let desired_formatex = desired_format.to_waveformatex().unwrap();
75+
match audio_client.is_supported(&desired_formatex, &ShareMode::Shared) {
76+
Ok(None) => {
77+
debug!("Device supports format {:?}", desired_formatex);
78+
false
79+
}
80+
Ok(Some(modified)) => {
81+
debug!(
82+
"Device doesn't support format:\n{:#?}\nClosest match is:\n{:#?}",
83+
desired_formatex, modified
84+
);
85+
true
86+
}
87+
Err(err) => {
88+
debug!(
89+
"Device doesn't support format:\n{:#?}\nError: {}",
90+
desired_formatex, err
91+
);
92+
true
93+
}
94+
}
7195
}
72-
}
96+
};
7397

7498
// Blockalign is the number of bytes per frame
7599
let blockalign = desired_format.get_blockalign();
@@ -78,13 +102,14 @@ fn main() {
78102
let (def_time, min_time) = audio_client.get_periods().unwrap();
79103
debug!("default period {}, min period {}", def_time, min_time);
80104

105+
debug!("Initializing device with convert={}", needs_convert);
81106
audio_client
82107
.initialize_client(
83108
&desired_format,
84109
def_time as i64,
85110
&Direction::Render,
86111
&ShareMode::Shared,
87-
true,
112+
needs_convert,
88113
)
89114
.unwrap();
90115
debug!("initialized playback");

examples/playsine_events.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ fn main() {
3838
let _ = SimpleLogger::init(
3939
LevelFilter::Debug,
4040
ConfigBuilder::new()
41-
.set_time_format_str("%H:%M:%S%.3f")
41+
.set_time_format_rfc3339()
42+
.set_time_offset_to_local()
43+
.unwrap()
4244
.build(),
4345
);
4446

@@ -49,7 +51,7 @@ fn main() {
4951
let channels = 2;
5052
let device = get_default_device(&Direction::Render).unwrap();
5153
let mut audio_client = device.get_iaudioclient().unwrap();
52-
let desired_format = WaveFormat::new(32, 32, &SampleType::Float, 44100, channels);
54+
let desired_format = WaveFormat::new(32, 32, &SampleType::Float, 44100, channels, None);
5355

5456
// Blockalign is the number of bytes per frame
5557
let blockalign = desired_format.get_blockalign();

examples/record.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fn capture_loop(tx_capt: std::sync::mpsc::SyncSender<Vec<u8>>, chunksize: usize)
2020

2121
let mut audio_client = device.get_iaudioclient()?;
2222

23-
let desired_format = WaveFormat::new(32, 32, &SampleType::Float, 44100, 2);
23+
let desired_format = WaveFormat::new(32, 32, &SampleType::Float, 44100, 2, None);
2424

2525
let blockalign = desired_format.get_blockalign();
2626
debug!("Desired capture format: {:?}", desired_format);
@@ -76,7 +76,9 @@ fn main() -> Res<()> {
7676
let _ = SimpleLogger::init(
7777
LevelFilter::Trace,
7878
ConfigBuilder::new()
79-
.set_time_format_str("%H:%M:%S%.3f")
79+
.set_time_format_rfc3339()
80+
.set_time_offset_to_local()
81+
.unwrap()
8082
.build(),
8183
);
8284

0 commit comments

Comments
 (0)