Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
38 changes: 32 additions & 6 deletions src/host/alsa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ impl Device {
Ok(handle) => handle,
};
let can_pause = set_hw_params_from_format(&handle, conf, sample_format)?;
let period_samples = set_sw_params_from_format(&handle, conf, stream_type)?;
let period_samples = set_sw_params_from_format(&handle, conf, sample_format, stream_type)?;

handle.prepare()?;

Expand Down Expand Up @@ -482,7 +482,7 @@ impl Device {
// Test both LE and BE formats to detect what the hardware actually supports.
// LE is listed first as it's the common case for most audio hardware.
// Hardware reports its supported formats regardless of CPU endianness.
const FORMATS: [(SampleFormat, alsa::pcm::Format); 18] = [
const FORMATS: [(SampleFormat, alsa::pcm::Format); 23] = [
(SampleFormat::I8, alsa::pcm::Format::S8),
(SampleFormat::U8, alsa::pcm::Format::U8),
(SampleFormat::I16, alsa::pcm::Format::S16LE),
Expand All @@ -501,6 +501,11 @@ impl Device {
(SampleFormat::F32, alsa::pcm::Format::FloatBE),
(SampleFormat::F64, alsa::pcm::Format::Float64LE),
(SampleFormat::F64, alsa::pcm::Format::Float64BE),
(SampleFormat::DsdU8, alsa::pcm::Format::DSDU8),
(SampleFormat::DsdU16, alsa::pcm::Format::DSDU16LE),
(SampleFormat::DsdU16, alsa::pcm::Format::DSDU16BE),
(SampleFormat::DsdU32, alsa::pcm::Format::DSDU32LE),
(SampleFormat::DsdU32, alsa::pcm::Format::DSDU32BE),
//SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
//SND_PCM_FORMAT_IEC958_SUBFRAME_BE,
//SND_PCM_FORMAT_MU_LAW,
Expand Down Expand Up @@ -1284,6 +1289,9 @@ fn fill_with_equilibrium(buffer: &mut [u8], sample_format: SampleFormat) {
SampleFormat::U64 => fill_typed!(u64),
SampleFormat::F32 => fill_typed!(f32),
SampleFormat::F64 => fill_typed!(f64),
SampleFormat::DsdU8 => fill_typed!(u8),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be fill_typed!(DsdU8)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now when I removed structs I am not sure what to use here. any suggestion?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this?

const DSD_SILENCE_BYTE: u8 = 0x69;

SampleFormat::DsdU8 | SampleFormat::DsdU16 | SampleFormat::DsdU32 => buffer.fill(DSD_SILENCE_BYTE),

SampleFormat::DsdU16 => fill_typed!(u16),
SampleFormat::DsdU32 => fill_typed!(u32),
}
}

Expand Down Expand Up @@ -1350,6 +1358,15 @@ fn sample_format_to_alsa_format(
SampleFormat::F64 => (Format::Float64LE, Format::Float64BE),
#[cfg(target_endian = "big")]
SampleFormat::F64 => (Format::Float64BE, Format::Float64LE),
SampleFormat::DsdU8 => return Ok(Format::DSDU8),
#[cfg(target_endian = "little")]
SampleFormat::DsdU16 => (Format::DSDU16LE, Format::DSDU16BE),
#[cfg(target_endian = "big")]
SampleFormat::DsdU16 => (Format::DSDU16BE, Format::DSDU16LE),
#[cfg(target_endian = "little")]
SampleFormat::DsdU32 => (Format::DSDU32LE, Format::DSDU32BE),
#[cfg(target_endian = "big")]
SampleFormat::DsdU32 => (Format::DSDU32BE, Format::DSDU32LE),
_ => {
return Err(BackendSpecificError {
description: format!("Sample format '{sample_format}' is not supported"),
Expand Down Expand Up @@ -1416,6 +1433,7 @@ fn set_hw_params_from_format(
fn set_sw_params_from_format(
pcm_handle: &alsa::pcm::PCM,
config: &StreamConfig,
sample_format: SampleFormat,
stream_type: alsa::Direction,
) -> Result<usize, BackendSpecificError> {
let sw_params = pcm_handle.sw_params_current()?;
Expand All @@ -1429,10 +1447,18 @@ fn set_sw_params_from_format(
}
let start_threshold = match stream_type {
alsa::Direction::Playback => {
// Always use 2-period double-buffering: one period playing from hardware, one
// period queued in the software buffer. This ensures consistent low latency
// regardless of the total buffer size.
2 * period
// For playback, we want to start only when the buffer is full for DSD content
// to avoid underruns. For PCM, we keep the default low-latency behavior.
let is_dsd = matches!(
sample_format,
SampleFormat::DsdU8 | SampleFormat::DsdU16 | SampleFormat::DsdU32
);

if is_dsd {
buffer
} else {
2 * period
}
}
alsa::Direction::Capture => 1,
};
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ pub use platform::{
available_hosts, default_host, host_from_id, Device, Devices, Host, HostId, Stream,
SupportedInputConfigs, SupportedOutputConfigs, ALL_HOSTS,
};
pub use samples_formats::{FromSample, Sample, SampleFormat, SizedSample, I24, U24};
pub use samples_formats::{FromSample, Sample, SampleFormat, SizedSample, I24, U24, DsdU8, DsdU16, DsdU32};
use std::convert::TryInto;
use std::time::Duration;

Expand Down
142 changes: 142 additions & 0 deletions src/samples_formats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ pub enum SampleFormat {

/// `f64` with a valid range of `-1.0..=1.0` with `0.0` being the origin.
F64,

// DSD Formats
// -----------

/// DSD stream (U8)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency I suggest /// DSD 1-bit stream in u8 container (8 samples per byte) with 0x69 being the silence pattern.

DsdU8,
/// DSD stream (U16)
DsdU16,
/// DSD stream (U32)
DsdU32,
}

impl SampleFormat {
Expand All @@ -129,6 +139,9 @@ impl SampleFormat {
SampleFormat::U64 => mem::size_of::<u64>(),
SampleFormat::F32 => mem::size_of::<f32>(),
SampleFormat::F64 => mem::size_of::<f64>(),
SampleFormat::DsdU8 => mem::size_of::<u8>(),
SampleFormat::DsdU16 => mem::size_of::<u16>(),
SampleFormat::DsdU32 => mem::size_of::<u32>(),
}
}

Expand All @@ -153,6 +166,9 @@ impl SampleFormat {
SampleFormat::U64 => u64::BITS,
SampleFormat::F32 => 32,
SampleFormat::F64 => 64,
SampleFormat::DsdU8 => 8,
SampleFormat::DsdU16 => 16,
SampleFormat::DsdU32 => 32,
}
}

Expand Down Expand Up @@ -181,6 +197,9 @@ impl SampleFormat {
| SampleFormat::U32
// | SampleFormat::U48
| SampleFormat::U64
| SampleFormat::DsdU8
| SampleFormat::DsdU16
| SampleFormat::DsdU32
)
}

Expand Down Expand Up @@ -208,6 +227,9 @@ impl Display for SampleFormat {
SampleFormat::U64 => "u64",
SampleFormat::F32 => "f32",
SampleFormat::F64 => "f64",
SampleFormat::DsdU8 => "DsdU8",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, change into lowercase dsdu8.

SampleFormat::DsdU16 => "DsdU16",
SampleFormat::DsdU32 => "DsdU32",
}
.fmt(f)
}
Expand Down Expand Up @@ -286,3 +308,123 @@ impl SizedSample for f32 {
impl SizedSample for f64 {
const FORMAT: SampleFormat = SampleFormat::F64;
}

/// DSD stream sample (U8).
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct DsdU8(pub u8);

impl Sample for DsdU8 {
type Float = f32;
type Signed = i8;

const EQUILIBRIUM: Self = DsdU8(0x69);
}

// Conflict with blanket impl in dasp_sample
// impl FromSample<DsdU8> for DsdU8 { ... }

impl FromSample<f32> for DsdU8 {
fn from_sample_(_sample: f32) -> Self {
DsdU8(0x69)
}
}

impl FromSample<i8> for DsdU8 {
fn from_sample_(_sample: i8) -> Self {
DsdU8(0x69)
}
}

impl FromSample<DsdU8> for i8 {
fn from_sample_(_sample: DsdU8) -> Self {
0
}
}

impl FromSample<DsdU8> for f32 {
fn from_sample_(_sample: DsdU8) -> Self {
0.0
}
}

impl SizedSample for DsdU8 {
const FORMAT: SampleFormat = SampleFormat::DsdU8;
}

/// DSD stream sample (U16).
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct DsdU16(pub u16);

impl Sample for DsdU16 {
type Float = f32;
type Signed = i16;

const EQUILIBRIUM: Self = DsdU16(0x6969);
}

impl FromSample<f32> for DsdU16 {
fn from_sample_(_sample: f32) -> Self {
DsdU16(0x6969)
}
}

impl FromSample<DsdU16> for f32 {
fn from_sample_(_sample: DsdU16) -> Self {
0.0
}
}

impl FromSample<i16> for DsdU16 {
fn from_sample_(_sample: i16) -> Self {
DsdU16(0x6969)
}
}

impl FromSample<DsdU16> for i16 {
fn from_sample_(_sample: DsdU16) -> Self {
0
}
}

impl SizedSample for DsdU16 {
const FORMAT: SampleFormat = SampleFormat::DsdU16;
}

/// DSD stream sample (U32).
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct DsdU32(pub u32);

impl Sample for DsdU32 {
type Float = f32;
type Signed = i32;

const EQUILIBRIUM: Self = DsdU32(0x69696969);
}

impl FromSample<f32> for DsdU32 {
fn from_sample_(_sample: f32) -> Self {
DsdU32(0x69696969)
}
}

impl FromSample<DsdU32> for f32 {
fn from_sample_(_sample: DsdU32) -> Self {
0.0
}
}

impl FromSample<i32> for DsdU32 {
fn from_sample_(_sample: i32) -> Self {
DsdU32(0x69696969)
}
}

impl FromSample<DsdU32> for i32 {
fn from_sample_(_sample: DsdU32) -> Self {
0
}
}

impl SizedSample for DsdU32 {
const FORMAT: SampleFormat = SampleFormat::DsdU32;
}
Loading