Skip to content

Commit 313354a

Browse files
authored
feat(alsa): add native dsd support (#1078)
1 parent d2bdd53 commit 313354a

5 files changed

Lines changed: 49 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7373
- **WASAPI**: `Send` and `Sync` implementations to `Stream`.
7474
- **WebAudio**: `Send` and `Sync` implementations to `Stream`.
7575
- **WebAudio**: `BufferSize::Fixed` validation against supported range.
76+
- **ALSA**: Add support for native DSD playback.
7677

7778
### Changed
7879

examples/record_wav.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ fn main() -> Result<(), anyhow::Error> {
150150
}
151151

152152
fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat {
153-
if format.is_float() {
153+
if format.is_dsd() {
154+
panic!("DSD formats cannot be written to WAV files");
155+
} else if format.is_float() {
154156
hound::SampleFormat::Float
155157
} else {
156158
hound::SampleFormat::Int

src/host/alsa/mod.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ impl Device {
475475
// Test both LE and BE formats to detect what the hardware actually supports.
476476
// LE is listed first as it's the common case for most audio hardware.
477477
// Hardware reports its supported formats regardless of CPU endianness.
478-
const FORMATS: [(SampleFormat, alsa::pcm::Format); 18] = [
478+
const FORMATS: [(SampleFormat, alsa::pcm::Format); 23] = [
479479
(SampleFormat::I8, alsa::pcm::Format::S8),
480480
(SampleFormat::U8, alsa::pcm::Format::U8),
481481
(SampleFormat::I16, alsa::pcm::Format::S16LE),
@@ -494,6 +494,11 @@ impl Device {
494494
(SampleFormat::F32, alsa::pcm::Format::FloatBE),
495495
(SampleFormat::F64, alsa::pcm::Format::Float64LE),
496496
(SampleFormat::F64, alsa::pcm::Format::Float64BE),
497+
(SampleFormat::DsdU8, alsa::pcm::Format::DSDU8),
498+
(SampleFormat::DsdU16, alsa::pcm::Format::DSDU16LE),
499+
(SampleFormat::DsdU16, alsa::pcm::Format::DSDU16BE),
500+
(SampleFormat::DsdU32, alsa::pcm::Format::DSDU32LE),
501+
(SampleFormat::DsdU32, alsa::pcm::Format::DSDU32BE),
497502
//SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
498503
//SND_PCM_FORMAT_IEC958_SUBFRAME_BE,
499504
//SND_PCM_FORMAT_MU_LAW,
@@ -1262,6 +1267,7 @@ fn fill_with_equilibrium(buffer: &mut [u8], sample_format: SampleFormat) {
12621267
}
12631268
}};
12641269
}
1270+
const DSD_SILENCE_BYTE: u8 = 0x69;
12651271

12661272
match sample_format {
12671273
SampleFormat::I8 => fill_typed!(i8),
@@ -1278,6 +1284,9 @@ fn fill_with_equilibrium(buffer: &mut [u8], sample_format: SampleFormat) {
12781284
SampleFormat::U64 => fill_typed!(u64),
12791285
SampleFormat::F32 => fill_typed!(f32),
12801286
SampleFormat::F64 => fill_typed!(f64),
1287+
SampleFormat::DsdU8 | SampleFormat::DsdU16 | SampleFormat::DsdU32 => {
1288+
buffer.fill(DSD_SILENCE_BYTE)
1289+
}
12811290
}
12821291
}
12831292

@@ -1344,6 +1353,15 @@ fn sample_format_to_alsa_format(
13441353
SampleFormat::F64 => (Format::Float64LE, Format::Float64BE),
13451354
#[cfg(target_endian = "big")]
13461355
SampleFormat::F64 => (Format::Float64BE, Format::Float64LE),
1356+
SampleFormat::DsdU8 => return Ok(Format::DSDU8),
1357+
#[cfg(target_endian = "little")]
1358+
SampleFormat::DsdU16 => (Format::DSDU16LE, Format::DSDU16BE),
1359+
#[cfg(target_endian = "big")]
1360+
SampleFormat::DsdU16 => (Format::DSDU16BE, Format::DSDU16LE),
1361+
#[cfg(target_endian = "little")]
1362+
SampleFormat::DsdU32 => (Format::DSDU32LE, Format::DSDU32BE),
1363+
#[cfg(target_endian = "big")]
1364+
SampleFormat::DsdU32 => (Format::DSDU32BE, Format::DSDU32LE),
13471365
_ => {
13481366
return Err(BackendSpecificError {
13491367
description: format!("Sample format '{sample_format}' is not supported"),

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,7 @@ impl From<SupportedStreamConfig> for StreamConfig {
10021002
#[allow(dead_code)]
10031003
pub(crate) const COMMON_SAMPLE_RATES: &[SampleRate] = &[
10041004
5512, 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000,
1005-
176400, 192000, 352800, 384000,
1005+
176400, 192000, 352800, 384000, 705600, 768000, 1411200, 1536000,
10061006
];
10071007

10081008
#[test]

src/samples_formats.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ pub enum SampleFormat {
105105

106106
/// `f64` with a valid range of `-1.0..=1.0` with `0.0` being the origin.
107107
F64,
108+
109+
/// DSD 1-bit stream in u8 container (8 bits = 8 DSD samples) with 0x69 being the silence byte pattern.
110+
DsdU8,
111+
112+
/// DSD 1-bit stream in u16 container (16 bits = 16 DSD samples) with 0x69 being the silence byte pattern.
113+
DsdU16,
114+
115+
/// DSD 1-bit stream in u32 container (32 bits = 32 DSD samples) with 0x69 being the silence byte pattern.
116+
DsdU32,
108117
}
109118

110119
impl SampleFormat {
@@ -129,6 +138,9 @@ impl SampleFormat {
129138
SampleFormat::U64 => mem::size_of::<u64>(),
130139
SampleFormat::F32 => mem::size_of::<f32>(),
131140
SampleFormat::F64 => mem::size_of::<f64>(),
141+
SampleFormat::DsdU8 => mem::size_of::<u8>(),
142+
SampleFormat::DsdU16 => mem::size_of::<u16>(),
143+
SampleFormat::DsdU32 => mem::size_of::<u32>(),
132144
}
133145
}
134146

@@ -153,6 +165,7 @@ impl SampleFormat {
153165
SampleFormat::U64 => u64::BITS,
154166
SampleFormat::F32 => 32,
155167
SampleFormat::F64 => 64,
168+
SampleFormat::DsdU8 | SampleFormat::DsdU16 | SampleFormat::DsdU32 => 1,
156169
}
157170
}
158171

@@ -189,6 +202,15 @@ impl SampleFormat {
189202
pub fn is_float(&self) -> bool {
190203
matches!(*self, SampleFormat::F32 | SampleFormat::F64)
191204
}
205+
206+
#[inline]
207+
#[must_use]
208+
pub fn is_dsd(&self) -> bool {
209+
matches!(
210+
*self,
211+
SampleFormat::DsdU8 | SampleFormat::DsdU16 | SampleFormat::DsdU32
212+
)
213+
}
192214
}
193215

194216
impl Display for SampleFormat {
@@ -208,6 +230,9 @@ impl Display for SampleFormat {
208230
SampleFormat::U64 => "u64",
209231
SampleFormat::F32 => "f32",
210232
SampleFormat::F64 => "f64",
233+
SampleFormat::DsdU8 => "dsdu8",
234+
SampleFormat::DsdU16 => "dsdu16",
235+
SampleFormat::DsdU32 => "dsdu32",
211236
}
212237
.fmt(f)
213238
}

0 commit comments

Comments
 (0)