use std::process::Command;
use tokio::task;
use tracing::{debug, error, info};
use crate::error::AudioError;
pub struct PipeWireController {
original_rate: Option<u32>,
current_rate: Option<u32>,
}
impl PipeWireController {
pub fn new() -> Self {
let original_rate = task::block_in_place(|| Self::get_current_rate_blocking().ok());
debug!("Original PipeWire sample rate: {:?}", original_rate);
Self {
original_rate,
current_rate: None,
}
}
fn get_current_rate_blocking() -> Result<u32, AudioError> {
let output = Command::new("pw-metadata")
.arg("-n")
.arg("settings")
.arg("0")
.arg("clock.force-rate")
.output()
.map_err(|e| AudioError::PipeWire(format!("Failed to run pw-metadata: {}", e)))?;
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if line.contains("clock.force-rate") && line.contains("value:") {
if let Some(start) = line.find("value:'") {
let rest = &line[start + 7..];
if let Some(end) = rest.find('\'') {
let rate_str = &rest[..end];
if let Ok(rate) = rate_str.parse::<u32>() {
return Ok(rate);
}
}
}
}
}
Ok(0)
}
pub fn get_current_rate(&self) -> Option<u32> {
self.current_rate
}
fn set_rate_blocking(rate: u32) -> Result<(), AudioError> {
info!("Setting PipeWire sample rate to {} Hz", rate);
let output = Command::new("pw-metadata")
.arg("-n")
.arg("settings")
.arg("0")
.arg("clock.force-rate")
.arg(rate.to_string())
.output()
.map_err(|e| AudioError::PipeWire(format!("Failed to run pw-metadata: {}", e)))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(AudioError::PipeWire(format!(
"pw-metadata failed: {}",
stderr
)));
}
Ok(())
}
pub fn set_rate(&mut self, rate: u32) -> Result<(), AudioError> {
if self.current_rate == Some(rate) {
debug!("Sample rate already set to {}", rate);
return Ok(());
}
task::block_in_place(|| Self::set_rate_blocking(rate))?;
self.current_rate = Some(rate);
Ok(())
}
pub fn restore_original(&mut self) -> Result<(), AudioError> {
if let Some(rate) = self.original_rate {
if rate > 0 {
info!("Restoring original sample rate: {} Hz", rate);
self.set_rate(rate)?;
} else {
info!("Clearing forced sample rate");
self.clear_forced_rate()?;
}
}
Ok(())
}
fn clear_forced_rate_blocking() -> Result<(), AudioError> {
info!("Clearing PipeWire forced sample rate");
let output = Command::new("pw-metadata")
.arg("-n")
.arg("settings")
.arg("0")
.arg("clock.force-rate")
.arg("0")
.output()
.map_err(|e| AudioError::PipeWire(format!("Failed to run pw-metadata: {}", e)))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(AudioError::PipeWire(format!(
"pw-metadata failed: {}",
stderr
)));
}
Ok(())
}
pub fn clear_forced_rate(&mut self) -> Result<(), AudioError> {
task::block_in_place(|| Self::clear_forced_rate_blocking())?;
self.current_rate = None;
Ok(())
}
}
impl Default for PipeWireController {
fn default() -> Self {
Self::new()
}
}
impl Drop for PipeWireController {
fn drop(&mut self) {
if let Some(rate) = self.original_rate {
std::thread::spawn(move || {
if rate > 0 {
if let Err(e) = Self::set_rate_blocking(rate) {
error!("Failed to restore sample rate: {}", e);
}
} else if let Err(e) = Self::clear_forced_rate_blocking() {
error!("Failed to clear forced sample rate: {}", e);
}
});
}
}
}