Fix PipeWire backend to respect requested sample rate

PipeWire backend was using generic OutputStream::try_from_device() which
lets PipeWire decide the sample rate, causing resampling even when
DAC Passthrough is enabled.

Now uses OutputStream::try_from_device_config() with custom StreamConfig
that forces the exact sample rate requested (e.g. 192kHz), just like
the legacy CPAL path.

Changes:
- Import CPAL config types (StreamConfig, SampleRate, etc.)
- Create StreamConfig with requested sample_rate and channels
- Use try_from_device_config() instead of try_from_device()
- Verify device supports the configuration
- Support exclusive mode with lower buffer size (512 samples)

This ensures bit-perfect playback:
- 192kHz track -> 192kHz hardware output
- 44.1kHz track -> 44.1kHz hardware output
- No resampling by PipeWire
This commit is contained in:
vicrodh committed 2026-01-17 16:25:40 -06:00
1 parent b3f5c43031
commit e934894b6e
1 file changed
+67 -5
+67 -5
View File
@@ -7,7 +7,13 @@
//! - Does NOT change system default (only affects QBZ)
use super::backend::{AlsaPlugin, AudioBackend, AudioBackendType, AudioDevice, BackendConfig, BackendResult};
use rodio::{cpal::traits::{DeviceTrait, HostTrait}, OutputStream, OutputStreamHandle};
use rodio::{
cpal::{
traits::{DeviceTrait, HostTrait},
BufferSize, SampleFormat, SampleRate, StreamConfig, SupportedBufferSize, SupportedStreamConfig,
},
OutputStream, OutputStreamHandle,
};
use std::process::Command;
pub struct PipeWireBackend {
@@ -172,11 +178,67 @@ impl AudioBackend for PipeWireBackend {
let device_name = device.name().unwrap_or_else(|_| "unknown".to_string());
log::info!("[PipeWire Backend] Using CPAL device: {}", device_name);
// Create output stream (will route to current default sink)
let stream = OutputStream::try_from_device(&device)
.map_err(|e| format!("Failed to create output stream: {}", e))?;
// Create output stream with custom sample rate configuration
log::info!(
"[PipeWire Backend] Creating stream: {}Hz, {} channels, exclusive: {}",
config.sample_rate,
config.channels,
config.exclusive_mode
);
log::info!("[PipeWire Backend] ✓ Output stream created successfully");
// Create StreamConfig with desired sample rate
let stream_config = StreamConfig {
channels: config.channels,
sample_rate: SampleRate(config.sample_rate),
buffer_size: if config.exclusive_mode {
BufferSize::Fixed(512) // Lower latency for exclusive mode
} else {
BufferSize::Default
},
};
// Check if device supports this configuration
let supported_configs = device
.supported_output_configs()
.map_err(|e| format!("Failed to get supported configs: {}", e))?;
let mut found_matching = false;
for range in supported_configs {
if range.channels() == config.channels
&& config.sample_rate >= range.min_sample_rate().0
&& config.sample_rate <= range.max_sample_rate().0
{
found_matching = true;
log::info!(
"[PipeWire Backend] Device supports {}Hz (range: {}-{}Hz)",
config.sample_rate,
range.min_sample_rate().0,
range.max_sample_rate().0
);
break;
}
}
if !found_matching {
log::warn!(
"[PipeWire Backend] Device may not support {}Hz, attempting anyway",
config.sample_rate
);
}
// Create SupportedStreamConfig
let supported_config = SupportedStreamConfig::new(
stream_config.channels,
stream_config.sample_rate,
SupportedBufferSize::Range { min: 64, max: 8192 },
SampleFormat::F32,
);
// Create OutputStream with custom config
let stream = OutputStream::try_from_device_config(&device, supported_config)
.map_err(|e| format!("Failed to create output stream at {}Hz: {}", config.sample_rate, e))?;
log::info!("[PipeWire Backend] ✓ Output stream created successfully at {}Hz", config.sample_rate);
Ok(stream)
}