Skip to content

Fix: Audio not playing on Windows#34

Merged
teancom merged 1 commit intomusic-assistant:mainfrom
RobinDadswell:audio-fix
Apr 21, 2026
Merged

Fix: Audio not playing on Windows#34
teancom merged 1 commit intomusic-assistant:mainfrom
RobinDadswell:audio-fix

Conversation

@RobinDadswell
Copy link
Copy Markdown
Contributor

This may also fix issues on other devices, however this is not confirmed as I do not have alternative Operating Systems to check on

It also allows for changing the audio device to output through without needing to restart the whole application

@RobinDadswell
Copy link
Copy Markdown
Contributor Author

Fixes #19
possibly fixes #33

@RobinDadswell
Copy link
Copy Markdown
Contributor Author

@teancom @marcelveldt can I get a review of this one as it's a major issue for users

@Jesstr8803
Copy link
Copy Markdown

Tested on Windows 11 with a TOPPING USB DAC and ran into a hi-res-DAC edge case the current fix doesn't cover. Posting in case it's useful.

👋 Heads up: I'm not a Rust dev — I used Claude Code to clone the fork, build, debug the stderr output, and write the patch below. So I can answer questions about the symptoms / what I tested, but for deep questions about the diff itself you may want to verify it independently.

Repro: DAC default format set to 384000 Hz / 24-bit in Windows Sound → Properties → Advanced (common for audiophile DACs). With the patch applied, audio still doesn't play.

Diagnostic from stderr:

[Sendspin] Using default output device: Speakers (TOPPING USB DAC)
[Sendspin] No reliable device format capabilities found; using conservative fallback formats: 2ch/48000Hz/16bit, 2ch/44100Hz/16bit
[Sendspin] StreamStart format from server: codec=pcm, channels=2, sample_rate=48000, bit_depth=16
[Sendspin] Failed to create SyncedPlayer for channels=2, sample_rate=48000, bit_depth=16: Audio output error: The requested stream configuration is not supported by the device.
Root cause:

On Windows shared-mode WASAPI, cpal::Device::supported_output_configs() returns only configs at the device's currently configured shared-mode rate. For my DAC that's [ch=2, rate=384000..384000Hz, fmt={U8|I16|I32|F32}]. The current derive_supported_pcm_formats checks [48000, 44100, 96000] against that range, all fail (rate < min_rate), the function returns empty, and fallback_supported_formats() (48k/44.1k 16-bit) is advertised — which the device then rejects when cpal tries to open the stream at 48k against a 384k mix format.

Workaround for users hitting this: change Windows default format to 48000 Hz. But that defeats the point of a hi-res DAC.

Suggested fix:

Anchor on device.default_output_config() so the device's native rate is always advertised, and add common rates only when supported_output_configs() actually covers them. Full replacement function I've been running locally:

pub fn derive_supported_pcm_formats(device: Option<&cpal::Device>) -> Vec {
let Some(device) = device else {
return vec![];
};

let default_cfg = device.default_output_config().ok();
let native_rate = default_cfg.as_ref().map(|c| c.sample_rate().0);
let native_supports_24bit = default_cfg
    .as_ref()
    .map(|c| {
        let s = format!("{:?}", c.sample_format());
        s.contains("I24") || s.contains("I32") || s.contains("F32")
    })
    .unwrap_or(false);

let mut collected = BTreeSet::new();

// Always include the device's native rate so negotiation succeeds on
// Windows shared-mode WASAPI.
if let Some(rate) = native_rate {
    collected.insert(SupportedPcmFormat { channels: 2, sample_rate: rate, bit_depth: 16 });
    if native_supports_24bit {
        collected.insert(SupportedPcmFormat { channels: 2, sample_rate: rate, bit_depth: 24 });
    }
}

// Add common rates only when supported_output_configs explicitly covers them.
if let Ok(configs) = device.supported_output_configs() {
    let common_rates = [48_000u32, 44_100u32, 96_000u32];
    for cfg in configs {
        if cfg.channels() < 2 {
            continue;
        }
        let min_rate = cfg.min_sample_rate().0;
        let max_rate = cfg.max_sample_rate().0;
        let supports_24bit = format!("{:?}", cfg.sample_format()).contains("I24");

        for rate in common_rates {
            if rate < min_rate || rate > max_rate {
                continue;
            }
            collected.insert(SupportedPcmFormat { channels: 2, sample_rate: rate, bit_depth: 16 });
            if supports_24bit {
                collected.insert(SupportedPcmFormat { channels: 2, sample_rate: rate, bit_depth: 24 });
            }
        }
    }
}

let mut result: Vec<_> = collected.into_iter().collect();
result.sort_by_key(|f| {
    let rate_rank = if Some(f.sample_rate) == native_rate {
        0
    } else {
        match f.sample_rate {
            48_000 => 1,
            44_100 => 2,
            96_000 => 3,
            _ => 4,
        }
    };
    let depth_rank = i32::from(f.bit_depth != 16);
    (rate_rank, depth_rank, f.sample_rate, f.bit_depth)
});
result

}
Verified locally: with this patch the DAC plays at native 384kHz without changing Windows sound settings.

Copy link
Copy Markdown
Contributor

@teancom teancom left a comment

Choose a reason for hiding this comment

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

This looks lovely, thank you! A few things to tweak but I think this will be a great change.

Comment thread src-tauri/src/sendspin/devices.rs Outdated
Comment thread src-tauri/src/sendspin/devices.rs
@RobinDadswell RobinDadswell force-pushed the audio-fix branch 2 times, most recently from e619ac3 to b664686 Compare April 20, 2026 10:47
@RobinDadswell
Copy link
Copy Markdown
Contributor Author

Tested on Windows 11 with a TOPPING USB DAC and ran into a hi-res-DAC edge case the current fix doesn't cover. Posting in case it's useful.

👋 Heads up: I'm not a Rust dev — I used Claude Code to clone the fork, build, debug the stderr output, and write the patch below. So I can answer questions about the symptoms / what I tested, but for deep questions about the diff itself you may want to verify it independently.

Repro: DAC default format set to 384000 Hz / 24-bit in Windows Sound → Properties → Advanced (common for audiophile DACs). With the patch applied, audio still doesn't play.

Diagnostic from stderr:

[Sendspin] Using default output device: Speakers (TOPPING USB DAC) [Sendspin] No reliable device format capabilities found; using conservative fallback formats: 2ch/48000Hz/16bit, 2ch/44100Hz/16bit [Sendspin] StreamStart format from server: codec=pcm, channels=2, sample_rate=48000, bit_depth=16 [Sendspin] Failed to create SyncedPlayer for channels=2, sample_rate=48000, bit_depth=16: Audio output error: The requested stream configuration is not supported by the device. Root cause:

On Windows shared-mode WASAPI, cpal::Device::supported_output_configs() returns only configs at the device's currently configured shared-mode rate. For my DAC that's [ch=2, rate=384000..384000Hz, fmt={U8|I16|I32|F32}]. The current derive_supported_pcm_formats checks [48000, 44100, 96000] against that range, all fail (rate < min_rate), the function returns empty, and fallback_supported_formats() (48k/44.1k 16-bit) is advertised — which the device then rejects when cpal tries to open the stream at 48k against a 384k mix format.

Workaround for users hitting this: change Windows default format to 48000 Hz. But that defeats the point of a hi-res DAC.

Suggested fix:

Anchor on device.default_output_config() so the device's native rate is always advertised, and add common rates only when supported_output_configs() actually covers them. Full replacement function I've been running locally:

pub fn derive_supported_pcm_formats(device: Option<&cpal::Device>) -> Vec { let Some(device) = device else { return vec![]; };

let default_cfg = device.default_output_config().ok();
let native_rate = default_cfg.as_ref().map(|c| c.sample_rate().0);
let native_supports_24bit = default_cfg
    .as_ref()
    .map(|c| {
        let s = format!("{:?}", c.sample_format());
        s.contains("I24") || s.contains("I32") || s.contains("F32")
    })
    .unwrap_or(false);

let mut collected = BTreeSet::new();

// Always include the device's native rate so negotiation succeeds on
// Windows shared-mode WASAPI.
if let Some(rate) = native_rate {
    collected.insert(SupportedPcmFormat { channels: 2, sample_rate: rate, bit_depth: 16 });
    if native_supports_24bit {
        collected.insert(SupportedPcmFormat { channels: 2, sample_rate: rate, bit_depth: 24 });
    }
}

// Add common rates only when supported_output_configs explicitly covers them.
if let Ok(configs) = device.supported_output_configs() {
    let common_rates = [48_000u32, 44_100u32, 96_000u32];
    for cfg in configs {
        if cfg.channels() < 2 {
            continue;
        }
        let min_rate = cfg.min_sample_rate().0;
        let max_rate = cfg.max_sample_rate().0;
        let supports_24bit = format!("{:?}", cfg.sample_format()).contains("I24");

        for rate in common_rates {
            if rate < min_rate || rate > max_rate {
                continue;
            }
            collected.insert(SupportedPcmFormat { channels: 2, sample_rate: rate, bit_depth: 16 });
            if supports_24bit {
                collected.insert(SupportedPcmFormat { channels: 2, sample_rate: rate, bit_depth: 24 });
            }
        }
    }
}

let mut result: Vec<_> = collected.into_iter().collect();
result.sort_by_key(|f| {
    let rate_rank = if Some(f.sample_rate) == native_rate {
        0
    } else {
        match f.sample_rate {
            48_000 => 1,
            44_100 => 2,
            96_000 => 3,
            _ => 4,
        }
    };
    let depth_rank = i32::from(f.bit_depth != 16);
    (rate_rank, depth_rank, f.sample_rate, f.bit_depth)
});
result

} Verified locally: with this patch the DAC plays at native 384kHz without changing Windows sound settings.

can you have a pull of the latest version and see if it works for you now please :D

@RobinDadswell RobinDadswell requested a review from teancom April 20, 2026 10:50
Copy link
Copy Markdown
Contributor

@teancom teancom left a comment

Choose a reason for hiding this comment

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

One final tweak and we're good to go!

Comment thread src-tauri/src/sendspin/mod.rs
@Jesstr8803
Copy link
Copy Markdown

@RobinDadswell Confirmed working at 384kHz on the TOPPING USB DAC with the latest commit — no Windows audio config changes needed. 🎉

Thanks for the quick turnaround on the fix!

This may also fix issues on other devices, however this is not confirmed as I do not have alternative Operating Systems to check on
@RobinDadswell RobinDadswell requested a review from teancom April 21, 2026 11:32
@teancom teancom merged commit 2b7c1a6 into music-assistant:main Apr 21, 2026
1 check passed
@teancom
Copy link
Copy Markdown
Contributor

teancom commented Apr 21, 2026

Thanks, @RobinDadswell !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants