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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
# Unreleased

- Add `Sample::bits_per_sample` method.
- ALSA(process_output): Pass `silent=true` to `PCM.try_recover`, so it doesn't write to stderr.
- ALSA: Fix `BufferSize::Fixed` by selecting the nearest supported frame count.
- ALSA: Change `BufferSize::Default` to use the device defaults.
- ALSA: Change card enumeration to work like `aplay -L` does.
- ALSA: Add `I24` and `U24` sample format support (24-bit samples stored in 4 bytes).
- ALSA(process_output): Pass `silent=true` to `PCM.try_recover`, so it doesn't write to stderr.
- ASIO: Fix linker flags for MinGW cross-compilation.
- CoreAudio: Change `Device::supported_configs` to return a single element containing the available sample rate range when all elements have the same `mMinimum` and `mMaximum` values.
- CoreAudio: Change default audio device detection to be lazy when building a stream, instead of during device enumeration.
- CoreAudio: Add `i8`, `i32` and `I24` sample format support (24-bit samples stored in 4 bytes).
- iOS: Fix example by properly activating audio session.
- WASAPI: Expose `IMMDevice` from WASAPI host Device.
- WASAPI: Add `I24` and `U24` sample format support (24-bit samples stored in 4 bytes).

# Version 0.16.0 (2025-06-07)

Expand Down
3 changes: 2 additions & 1 deletion examples/synth_tones.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ extern crate cpal;

use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
SizedSample, I24,
SizedSample, I24, U24,
};
use cpal::{FromSample, Sample};

Expand Down Expand Up @@ -103,6 +103,7 @@ where
cpal::SampleFormat::I64 => make_stream::<i64>(&device, &config.into()),
cpal::SampleFormat::U8 => make_stream::<u8>(&device, &config.into()),
cpal::SampleFormat::U16 => make_stream::<u16>(&device, &config.into()),
cpal::SampleFormat::U24 => make_stream::<U24>(&device, &config.into()),
cpal::SampleFormat::U32 => make_stream::<u32>(&device, &config.into()),
cpal::SampleFormat::U64 => make_stream::<u64>(&device, &config.into()),
cpal::SampleFormat::F32 => make_stream::<f32>(&device, &config.into()),
Expand Down
14 changes: 7 additions & 7 deletions src/host/alsa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,16 +328,16 @@ impl Device {
let hw_params = alsa::pcm::HwParams::any(handle)?;

// TODO: check endianness
const FORMATS: [(SampleFormat, alsa::pcm::Format); 8] = [
const FORMATS: [(SampleFormat, alsa::pcm::Format); 10] = [
(SampleFormat::I8, alsa::pcm::Format::S8),
(SampleFormat::U8, alsa::pcm::Format::U8),
(SampleFormat::I16, alsa::pcm::Format::S16LE),
//SND_PCM_FORMAT_S16_BE,
(SampleFormat::U16, alsa::pcm::Format::U16LE),
//SND_PCM_FORMAT_U16_BE,
//SND_PCM_FORMAT_S24_LE,
(SampleFormat::I24, alsa::pcm::Format::S24LE),
//SND_PCM_FORMAT_S24_BE,
//SND_PCM_FORMAT_U24_LE,
(SampleFormat::U24, alsa::pcm::Format::U24LE),
//SND_PCM_FORMAT_U24_BE,
(SampleFormat::I32, alsa::pcm::Format::S32LE),
//SND_PCM_FORMAT_S32_BE,
Expand Down Expand Up @@ -1072,13 +1072,13 @@ fn set_hw_params_from_format(
match sample_format {
SampleFormat::I8 => alsa::pcm::Format::S8,
SampleFormat::I16 => alsa::pcm::Format::S16BE,
// SampleFormat::I24 => alsa::pcm::Format::S24BE,
SampleFormat::I24 => alsa::pcm::Format::S24BE,
SampleFormat::I32 => alsa::pcm::Format::S32BE,
// SampleFormat::I48 => alsa::pcm::Format::S48BE,
// SampleFormat::I64 => alsa::pcm::Format::S64BE,
SampleFormat::U8 => alsa::pcm::Format::U8,
SampleFormat::U16 => alsa::pcm::Format::U16BE,
// SampleFormat::U24 => alsa::pcm::Format::U24BE,
SampleFormat::U24 => alsa::pcm::Format::U24BE,
SampleFormat::U32 => alsa::pcm::Format::U32BE,
// SampleFormat::U48 => alsa::pcm::Format::U48BE,
// SampleFormat::U64 => alsa::pcm::Format::U64BE,
Expand All @@ -1096,13 +1096,13 @@ fn set_hw_params_from_format(
match sample_format {
SampleFormat::I8 => alsa::pcm::Format::S8,
SampleFormat::I16 => alsa::pcm::Format::S16LE,
// SampleFormat::I24 => alsa::pcm::Format::S24LE,
SampleFormat::I24 => alsa::pcm::Format::S24LE,
SampleFormat::I32 => alsa::pcm::Format::S32LE,
// SampleFormat::I48 => alsa::pcm::Format::S48LE,
// SampleFormat::I64 => alsa::pcm::Format::S64LE,
SampleFormat::U8 => alsa::pcm::Format::U8,
SampleFormat::U16 => alsa::pcm::Format::U16LE,
// SampleFormat::U24 => alsa::pcm::Format::U24LE,
SampleFormat::U24 => alsa::pcm::Format::U24LE,
SampleFormat::U32 => alsa::pcm::Format::U32LE,
// SampleFormat::U48 => alsa::pcm::Format::U48LE,
// SampleFormat::U64 => alsa::pcm::Format::U64LE,
Expand Down
7 changes: 5 additions & 2 deletions src/host/coreaudio/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ impl Device {
n_channels += buffer.mNumberChannels as usize;
}

// TODO: macOS should support U8, I16, I32, F32 and F64. This should allow for using
// I16 but just use F32 for now as it's the default anyway.
// TODO: macOS should support I8, I16, I24, I32, F32 and F64. This should allow for
// using I16 but just use F32 for now as it's the default anyway.
let sample_format = SampleFormat::F32;

// Get available sample rate ranges.
Expand Down Expand Up @@ -404,7 +404,10 @@ impl Device {
);
match maybe_sample_format {
Some(coreaudio::audio_unit::SampleFormat::F32) => SampleFormat::F32,
Some(coreaudio::audio_unit::SampleFormat::I8) => SampleFormat::I8,
Some(coreaudio::audio_unit::SampleFormat::I16) => SampleFormat::I16,
Some(coreaudio::audio_unit::SampleFormat::I24) => SampleFormat::I24,
Some(coreaudio::audio_unit::SampleFormat::I32) => SampleFormat::I32,
_ => return Err(DefaultStreamConfigError::StreamTypeNotSupported),
}
};
Expand Down
8 changes: 5 additions & 3 deletions src/host/coreaudio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ fn asbd_from_config(
let bytes_per_packet = frames_per_packet * bytes_per_frame;
let format_flags = match sample_format {
SampleFormat::F32 | SampleFormat::F64 => kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked,
SampleFormat::I16 | SampleFormat::I32 | SampleFormat::I64 => {
kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked
}
SampleFormat::I8
| SampleFormat::I16
| SampleFormat::I24
| SampleFormat::I32
| SampleFormat::I64 => kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked,
_ => kAudioFormatFlagIsPacked,
};
AudioStreamBasicDescription {
Expand Down
26 changes: 19 additions & 7 deletions src/host/wasapi/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ unsafe fn format_from_waveformatex_ptr(
match n_bits {
8 => SampleFormat::U8,
16 => SampleFormat::I16,
24 => SampleFormat::I24,
32 => SampleFormat::I32,
64 => SampleFormat::I64,
_ => return None,
Expand Down Expand Up @@ -426,6 +427,8 @@ impl Device {
for sample_format in [
SampleFormat::U8,
SampleFormat::I16,
SampleFormat::I24,
SampleFormat::U24,
SampleFormat::I32,
SampleFormat::I64,
SampleFormat::F32,
Expand Down Expand Up @@ -948,9 +951,11 @@ fn config_to_waveformatextensible(
let format_tag = match sample_format {
SampleFormat::U8 | SampleFormat::I16 => Audio::WAVE_FORMAT_PCM,

SampleFormat::I32 | SampleFormat::I64 | SampleFormat::F32 => {
KernelStreaming::WAVE_FORMAT_EXTENSIBLE
}
SampleFormat::I24
| SampleFormat::U24
| SampleFormat::I32
| SampleFormat::I64
| SampleFormat::F32 => KernelStreaming::WAVE_FORMAT_EXTENSIBLE,

_ => return None,
};
Expand All @@ -959,7 +964,11 @@ fn config_to_waveformatextensible(
let sample_bytes = sample_format.sample_size() as u16;
let avg_bytes_per_sec = u32::from(channels) * sample_rate * u32::from(sample_bytes);
let block_align = channels * sample_bytes;
let bits_per_sample = 8 * sample_bytes;
let bits_per_sample = match sample_format {
// 24-bit formats use 32-bit storage but only 24 valid bits
SampleFormat::I24 | SampleFormat::U24 => 24,
_ => 8 * sample_bytes,
};

let cb_size = if format_tag == Audio::WAVE_FORMAT_PCM {
0
Expand All @@ -983,9 +992,12 @@ fn config_to_waveformatextensible(
let channel_mask = KernelStreaming::KSAUDIO_SPEAKER_DIRECTOUT;

let sub_format = match sample_format {
SampleFormat::U8 | SampleFormat::I16 | SampleFormat::I32 | SampleFormat::I64 => {
KernelStreaming::KSDATAFORMAT_SUBTYPE_PCM
}
SampleFormat::U8
| SampleFormat::I16
| SampleFormat::I24
| SampleFormat::U24
| SampleFormat::I32
| SampleFormat::I64 => KernelStreaming::KSDATAFORMAT_SUBTYPE_PCM,

SampleFormat::F32 => Multimedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
_ => return None,
Expand Down
22 changes: 21 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,7 @@ impl SupportedStreamConfigRange {
/// - Max sample rate
pub fn cmp_default_heuristics(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering::Equal;
use SampleFormat::{F32, I16, U16};
use SampleFormat::{F32, I16, I24, I32, U16, U24, U32};

let cmp_stereo = (self.channels == 2).cmp(&(other.channels == 2));
if cmp_stereo != Equal {
Expand All @@ -726,6 +726,26 @@ impl SupportedStreamConfigRange {
return cmp_f32;
}

let cmp_i32 = (self.sample_format == I32).cmp(&(other.sample_format == I32));
if cmp_i32 != Equal {
return cmp_i32;
}

let cmp_u32 = (self.sample_format == U32).cmp(&(other.sample_format == U32));
if cmp_u32 != Equal {
return cmp_u32;
}

let cmp_i24 = (self.sample_format == I24).cmp(&(other.sample_format == I24));
if cmp_i24 != Equal {
return cmp_i24;
}

let cmp_u24 = (self.sample_format == U24).cmp(&(other.sample_format == U24));
if cmp_u24 != Equal {
return cmp_u24;
}

let cmp_i16 = (self.sample_format == I16).cmp(&(other.sample_format == I16));
if cmp_i16 != Equal {
return cmp_i16;
Expand Down
16 changes: 8 additions & 8 deletions src/samples_formats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub enum SampleFormat {
U16,

/// `U24` with a valid range of '0..16777216' with `1 << 23 == 8388608` being the origin
// U24,
U24,
/// `u32` with a valid range of `u32::MIN..=u32::MAX` with `1 << 31` being the origin.
U32,

Expand Down Expand Up @@ -76,7 +76,7 @@ impl SampleFormat {
SampleFormat::I16 => mem::size_of::<i16>(),
SampleFormat::U16 => mem::size_of::<u16>(),
SampleFormat::I24 => mem::size_of::<i32>(),
// SampleFormat::U24 => mem::size_of::<i32>(),
SampleFormat::U24 => mem::size_of::<i32>(),
SampleFormat::I32 => mem::size_of::<i32>(),
SampleFormat::U32 => mem::size_of::<u32>(),
// SampleFormat::I48 => mem::size_of::<i64>(),
Expand All @@ -100,7 +100,7 @@ impl SampleFormat {
SampleFormat::I16 => i16::BITS,
SampleFormat::U16 => u16::BITS,
SampleFormat::I24 => 24,
// SampleFormat::U24 => 24,
SampleFormat::U24 => 24,
SampleFormat::I32 => i32::BITS,
SampleFormat::U32 => u32::BITS,
// SampleFormat::I48 => 48,
Expand Down Expand Up @@ -133,7 +133,7 @@ impl SampleFormat {
*self,
SampleFormat::U8
| SampleFormat::U16
// | SampleFormat::U24
| SampleFormat::U24
| SampleFormat::U32
// | SampleFormat::U48
| SampleFormat::U64
Expand All @@ -158,7 +158,7 @@ impl Display for SampleFormat {
SampleFormat::I64 => "i64",
SampleFormat::U8 => "u8",
SampleFormat::U16 => "u16",
// SampleFormat::U24 => "u24",
SampleFormat::U24 => "u24",
SampleFormat::U32 => "u32",
// SampleFormat::U48 => "u48",
SampleFormat::U64 => "u64",
Expand Down Expand Up @@ -205,9 +205,9 @@ impl SizedSample for u16 {
const FORMAT: SampleFormat = SampleFormat::U16;
}

// impl SizedSample for U24 {
// const FORMAT: SampleFormat = SampleFormat::U24;
// }
impl SizedSample for U24 {
const FORMAT: SampleFormat = SampleFormat::U24;
}

impl SizedSample for u32 {
const FORMAT: SampleFormat = SampleFormat::U32;
Expand Down
Loading