Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/windows-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Windows CI

on:
push:
branches:
- master
pull_request:
release:
types:
- published

jobs:
check-and-test:
if: ${{ github.event_name != 'release' }}
runs-on: windows-latest

steps:
- name: Checkout
uses: actions/checkout@v5

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Cargo check (all targets)
run: cargo check --workspace --all-targets

- name: Cargo check (examples)
if: ${{ always() }}
run: cargo check --workspace --examples

- name: Cargo fmt (check)
if: ${{ always() }}
run: cargo fmt --all -- --check

- name: Cargo clippy
if: ${{ always() }}
run: cargo clippy --workspace --all-targets -- -D warnings

- name: Cargo test
if: ${{ always() }}
run: cargo test --workspace --all-targets

- name: Cargo publish (dry run)
if: ${{ always() }}
run: cargo publish --dry-run

publish:
if: ${{ github.event_name == 'release' && github.event.action == 'published' }}
runs-on: windows-latest

steps:
- name: Checkout
uses: actions/checkout@v5

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish
14 changes: 7 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "wasapi"
version = "0.22.0"
version = "0.23.0"
edition = "2021"
rust-version = "1.74"
rust-version = "1.76"
authors = ["HEnquist <henrik.enquist@gmail.com>"]
description = "Bindings for the Wasapi API on Windows"
license = "MIT"
Expand All @@ -29,15 +29,15 @@ features = ["Foundation",
"Win32_Security",]

[dependencies]
log = "0.4.22"
log = "0.4"
num-integer = "0.1"
windows-core = "0.62"
thiserror = "2.0.9"
thiserror = "2.0"

[dev-dependencies]
simplelog = "0.12.2"
rand = "0.9.1"
sysinfo = "0.37.1"
simplelog = "0.12"
rand = "0.10"
sysinfo = "0.38"

[package.metadata.docs.rs]
default-target = "x86_64-pc-windows-msvc"
Expand Down
36 changes: 20 additions & 16 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,10 +531,10 @@ impl Device {
/// that flows between the audio engine and the audio endpoint device when the device operates in shared mode.
pub fn get_device_format(&self) -> WasapiRes<WaveFormat> {
let data = self.get_blob_property(&PKEY_AudioEngine_DeviceFormat)?;
// SAFETY: PKEY_AudioEngine_DeviceFormat is guaranteed to be a WAVEFORMATEX structure based on MSFT docs:
// PKEY_AudioEngine_DeviceFormat contains a WAVEFORMATEX/WAVEFORMATEXTENSIBLE blob:
// https://learn.microsoft.com/en-us/windows/win32/coreaudio/pkey-audioengine-deviceformat
let waveformatex: &WAVEFORMATEX = unsafe { &*(data.as_ptr() as *const _) };
WaveFormat::parse(waveformatex)
// The blob can be byte-aligned only, so parse without creating aligned references.
WaveFormat::parse_from_blob_bytes(&data)
}

/// Read a string property from an [IMMDevice]
Expand Down Expand Up @@ -564,7 +564,7 @@ impl Device {
fn parse_string_property(prop: &PROPVARIANT) -> WasapiRes<String> {
let propstr = unsafe { PropVariantToStringAlloc(prop)? };
let name = unsafe { propstr.to_string()? };
unsafe { CoTaskMemFree(Some(propstr.0 as _)) };
unsafe { CoTaskMemFree(Some(propstr.0.cast())) };
trace!("name: {name}");
Ok(name)
}
Expand All @@ -585,7 +585,7 @@ impl Device {
let idstr = unsafe { self.device.GetId()? };
//let wide_id = unsafe { U16CString::from_ptr_str(idstr.0) };
let id = unsafe { idstr.to_string()? };
unsafe { CoTaskMemFree(Some(idstr.0 as _)) };
unsafe { CoTaskMemFree(Some(idstr.0.cast())) };
//let id = wide_id.to_string_lossy();
trace!("id: {id}");
Ok(id)
Expand Down Expand Up @@ -706,7 +706,7 @@ impl AudioClient {
Anonymous: PROPVARIANT_0_0_0 {
blob: BLOB {
cbSize: size_of::<AUDIOCLIENT_ACTIVATION_PARAMS>() as u32,
pBlobData: pinned_params.get_mut() as *const _ as *mut _,
pBlobData: std::ptr::from_mut(pinned_params.get_mut()).cast(),
},
},
}),
Expand All @@ -715,7 +715,7 @@ impl AudioClient {

let activation_prop = ManuallyDrop::new(raw_prop);
let pinned_prop = Pin::new(activation_prop.deref());
let activation_params = Some(pinned_prop.get_ref() as *const _);
let activation_params = Some(std::ptr::from_ref(pinned_prop.get_ref()));

// Create completion handler
let setup = Arc::new((Mutex::new(false), Condvar::new()));
Expand Down Expand Up @@ -767,7 +767,7 @@ impl AudioClient {
if temp_fmt.cbSize == 22 && temp_fmt.wFormatTag as u32 == WAVE_FORMAT_EXTENSIBLE {
let format = unsafe {
WaveFormat {
wave_fmt: (temp_fmt_ptr as *const _ as *const WAVEFORMATEXTENSIBLE).read(),
wave_fmt: temp_fmt_ptr.cast::<WAVEFORMATEXTENSIBLE>().read(),
}
};

Expand Down Expand Up @@ -840,9 +840,8 @@ impl AudioClient {
&& temp_fmt.wFormatTag as u32 == WAVE_FORMAT_EXTENSIBLE
{
debug!("got the nearest matching format as a WAVEFORMATEXTENSIBLE");
let temp_fmt_ext: WAVEFORMATEXTENSIBLE = unsafe {
(supported_format as *const _ as *const WAVEFORMATEXTENSIBLE).read()
};
let temp_fmt_ext: WAVEFORMATEXTENSIBLE =
unsafe { supported_format.cast::<WAVEFORMATEXTENSIBLE>().read() };

unsafe { CoTaskMemFree(Some(supported_format.cast())) };

Expand Down Expand Up @@ -1863,16 +1862,21 @@ impl AudioEffectsManager {
.GetAudioEffects(&mut audio_effects, &mut num_effects)?;
}

if num_effects > 0 {
let effects = if num_effects > 0 {
let effects_slice =
unsafe { slice::from_raw_parts(audio_effects, num_effects as usize) };
let effects_vec = effects_slice.to_vec();
// Free the memory allocated for the audio effects.
unsafe { CoTaskMemFree(Some(audio_effects as *mut _)) };
Ok(Some(effects_vec))
Some(effects_vec)
} else {
Ok(None)
None
};

if !audio_effects.is_null() {
// Free the memory allocated for the audio effects.
unsafe { CoTaskMemFree(Some(audio_effects.cast())) };
}

Ok(effects)
}
}

Expand Down
30 changes: 29 additions & 1 deletion src/waveformat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,34 @@ impl fmt::Debug for WaveFormat {
}

impl WaveFormat {
/// Parse a potentially unaligned byte slice containing a WAVEFORMATEX or WAVEFORMATEXTENSIBLE.
pub fn parse_from_blob_bytes(blob: &[u8]) -> WasapiRes<Self> {
if blob.len() < size_of::<WAVEFORMATEX>() {
return Err(WasapiError::UnsupportedFormat);
}

let waveformatex: WAVEFORMATEX = unsafe { std::ptr::read_unaligned(blob.as_ptr().cast()) };

if waveformatex.wFormatTag == WAVE_FORMAT_EXTENSIBLE as u16 {
const ATLEAST_SIZE: usize =
size_of::<WAVEFORMATEXTENSIBLE>() - size_of::<WAVEFORMATEX>();
if waveformatex.cbSize < ATLEAST_SIZE as u16 {
return Err(WasapiError::UnsupportedFormat);
}

let declared_size = size_of::<WAVEFORMATEX>() + waveformatex.cbSize as usize;
if blob.len() < declared_size {
return Err(WasapiError::UnsupportedFormat);
}

let waveformatextensible: WAVEFORMATEXTENSIBLE =
unsafe { std::ptr::read_unaligned(blob.as_ptr().cast()) };
return Ok(waveformatextensible.into());
}

Self::from_waveformatex(waveformatex)
}

/// Parse a [WAVEFORMATEX](https://docs.microsoft.com/en-us/previous-versions/dd757713(v=vs.85)) structure and
/// return a [WaveFormat] instance. If the underlying structure is a WAVEFORMATEXTENSIBLE, as specified by
/// wFormatTag, then use as-is. If not, assume it is only a WAVEFORMATEX structure.
Expand All @@ -101,7 +129,7 @@ impl WaveFormat {
}
// SAFETY: Both wFormatTag and size check passed, so the pointed-to memory is a full WAVEFORMATEXTENSIBLE.
let waveformatextensible: WAVEFORMATEXTENSIBLE = unsafe {
std::ptr::read(waveformatex as *const WAVEFORMATEX as *const WAVEFORMATEXTENSIBLE)
std::ptr::read(std::ptr::from_ref(waveformatex).cast::<WAVEFORMATEXTENSIBLE>())
};
return Ok(waveformatextensible.into());
}
Expand Down