use cpal::traits::{DeviceTrait, HostTrait};
use serde::{Deserialize, Serialize};
use crate::errors::CameraError;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioDevice {
pub id: String,
pub name: String,
pub sample_rate: u32,
pub channels: u16,
pub is_default: bool,
}
pub fn list_audio_devices() -> Result<Vec<AudioDevice>, CameraError> {
let host = cpal::default_host();
let default_device_name = host.default_input_device().and_then(|d| d.name().ok());
let mut devices: Vec<AudioDevice> = host
.input_devices()
.map_err(|e| CameraError::AudioError(format!("Failed to enumerate audio devices: {}", e)))?
.enumerate()
.filter_map(|(index, device)| {
let name = device.name().ok()?;
let config = device.default_input_config().ok()?;
let name_hash = {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
name.hash(&mut hasher);
format!("{:08x}", hasher.finish() & 0xFFFFFFFF)
};
let id = format!("audio_{}_{}", index, name_hash);
Some(AudioDevice {
id,
name: name.clone(),
sample_rate: config.sample_rate().0,
channels: config.channels(),
is_default: default_device_name.as_ref() == Some(&name),
})
})
.collect();
devices.sort_by(|a, b| match (a.is_default, b.is_default) {
(true, false) => std::cmp::Ordering::Less,
(false, true) => std::cmp::Ordering::Greater,
_ => a.name.cmp(&b.name),
});
Ok(devices)
}
pub fn get_default_audio_device() -> Result<AudioDevice, CameraError> {
let host = cpal::default_host();
let device = host
.default_input_device()
.ok_or_else(|| CameraError::AudioError("No default audio input device".to_string()))?;
let name = device
.name()
.map_err(|e| CameraError::AudioError(format!("Failed to get device name: {}", e)))?;
let config = device
.default_input_config()
.map_err(|e| CameraError::AudioError(format!("Failed to get device config: {}", e)))?;
let name_hash = {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
name.hash(&mut hasher);
format!("{:08x}", hasher.finish() & 0xFFFFFFFF)
};
let id = format!("audio_0_{}", name_hash);
Ok(AudioDevice {
id,
name,
sample_rate: config.sample_rate().0,
channels: config.channels(),
is_default: true,
})
}
pub fn find_audio_device(device_id: &str) -> Result<AudioDevice, CameraError> {
if device_id.is_empty() || device_id == "default" {
return get_default_audio_device();
}
let devices = list_audio_devices()?;
devices
.into_iter()
.find(|d| d.id == device_id || d.name == device_id)
.ok_or_else(|| CameraError::AudioError(format!("Audio device not found: {}", device_id)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_list_audio_devices_no_panic() {
let _ = list_audio_devices();
}
#[test]
fn test_default_device_is_first() {
if let Ok(devices) = list_audio_devices() {
if !devices.is_empty() {
let has_default = devices.iter().any(|d| d.is_default);
if has_default {
assert!(devices[0].is_default);
}
}
}
}
#[test]
fn test_find_device_default() {
if let Ok(device) = find_audio_device("default") {
assert!(device.is_default);
}
}
#[test]
fn test_find_device_empty_string() {
if let Ok(device) = find_audio_device("") {
assert!(device.is_default);
}
}
}