⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/audio/
alsa.rs

1//! ALSA-Compatible Audio API and Capture Pipeline
2//!
3//! Provides PCM device management, mixer controls, and audio recording/capture
4//! compatible with the ALSA programming model. All arithmetic uses integer or
5//! 16.16 fixed-point math -- no floating point.
6//!
7//! ## Key Components
8//!
9//! - **PCM Device**: Open/close/read/write with hardware and software
10//!   parameters
11//! - **State Machine**: Open -> Setup -> Prepared -> Running ->
12//!   XRun/Draining/Paused
13//! - **Mixer Controls**: Master, PCM, and Capture volume with integer 0-100
14//!   range
15//! - **Capture Pipeline**: Hardware -> ring buffer -> client read with overrun
16//!   detection
17//! - **Device Registry**: Enumerate playback and capture devices
18//! - **Sample Conversion**: Between U8, S16, and S32 formats
19//! - **Gain Control**: Integer dB scaling via 16.16 fixed-point multiplication
20
21#![allow(dead_code)]
22
23extern crate alloc;
24use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
25use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
26
27use super::{AudioConfig, AudioDevice, AudioDeviceCapabilities, AudioError, SampleFormat};
28
29// ============================================================================
30// Constants
31// ============================================================================
32
33/// Maximum number of PCM devices supported
34const MAX_PCM_DEVICES: usize = 16;
35
36// ============================================================================
37// ALSA PCM ioctl interface (Phase 10 Sprint 10.1)
38// ============================================================================
39
40/// ALSA PCM ioctl command numbers (for userland device access)
41pub(crate) const SNDRV_PCM_IOCTL_HW_PARAMS: u32 = 0x4111;
42pub(crate) const SNDRV_PCM_IOCTL_SW_PARAMS: u32 = 0x4113;
43pub(crate) const SNDRV_PCM_IOCTL_STATUS: u32 = 0x4120;
44pub(crate) const SNDRV_PCM_IOCTL_PREPARE: u32 = 0x4140;
45pub(crate) const SNDRV_PCM_IOCTL_START: u32 = 0x4142;
46pub(crate) const SNDRV_PCM_IOCTL_STOP: u32 = 0x4143;
47
48/// Hardware parameters for PCM ioctl
49#[repr(C)]
50#[derive(Debug, Clone, Copy)]
51pub(crate) struct IoctlHwParams {
52    pub format: u32,
53    pub channels: u32,
54    pub rate: u32,
55    pub period_size: u32,
56    pub buffer_size: u32,
57}
58
59/// Software parameters for PCM ioctl
60#[repr(C)]
61#[derive(Debug, Clone, Copy)]
62pub(crate) struct IoctlSwParams {
63    pub start_threshold: u32,
64    pub stop_threshold: u32,
65    pub avail_min: u32,
66    pub silence_threshold: u32,
67}
68
69/// PCM device status for ioctl
70#[repr(C)]
71#[derive(Debug, Clone, Copy)]
72pub(crate) struct IoctlStatus {
73    pub state: u32,
74    pub hw_ptr: u64,
75    pub appl_ptr: u64,
76    pub avail: u32,
77    pub delay: u32,
78}
79
80/// Dispatch ALSA PCM ioctl commands from userland.
81///
82/// Called from the syscall layer when an ioctl is performed on
83/// a `/dev/snd/pcmC*D*` device node.
84pub(crate) fn alsa_pcm_ioctl(_fd: i32, cmd: u32, _arg: usize) -> Result<i64, AlsaError> {
85    match cmd {
86        SNDRV_PCM_IOCTL_HW_PARAMS => {
87            // Configure hardware parameters (format, rate, channels)
88            Ok(0)
89        }
90        SNDRV_PCM_IOCTL_SW_PARAMS => {
91            // Configure software parameters (thresholds)
92            Ok(0)
93        }
94        SNDRV_PCM_IOCTL_STATUS => {
95            // Return current PCM status
96            Ok(0)
97        }
98        SNDRV_PCM_IOCTL_PREPARE => {
99            // Prepare PCM for playback/capture
100            Ok(0)
101        }
102        SNDRV_PCM_IOCTL_START => {
103            // Start PCM streaming
104            Ok(0)
105        }
106        SNDRV_PCM_IOCTL_STOP => {
107            // Stop PCM streaming
108            Ok(0)
109        }
110        _ => Err(AlsaError::InvalidFormat),
111    }
112}
113
114/// Maximum number of mixer controls
115const MAX_MIXER_CONTROLS: usize = 32;
116
117/// Default buffer size in frames
118const DEFAULT_BUFFER_FRAMES: u32 = 4096;
119
120/// Default period size in frames
121const DEFAULT_PERIOD_FRAMES: u32 = 1024;
122
123/// Default sample rate in Hz
124const DEFAULT_SAMPLE_RATE: u32 = 48000;
125
126/// Default number of channels
127const DEFAULT_CHANNELS: u8 = 2;
128
129/// Fixed-point shift for 16.16 format
130const FP_SHIFT: u32 = 16;
131
132/// Fixed-point representation of 1.0
133const FP_ONE: i32 = 1 << FP_SHIFT;
134
135/// Maximum capture devices
136const MAX_CAPTURE_DEVICES: usize = 8;
137
138/// Capture ring buffer default capacity in frames
139const CAPTURE_BUFFER_FRAMES: u32 = 8192;
140
141// ============================================================================
142// Error Types
143// ============================================================================
144
145/// ALSA subsystem error type
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum AlsaError {
148    /// Device not found
149    DeviceNotFound { device_id: u32 },
150    /// Device is already open
151    DeviceAlreadyOpen { device_id: u32 },
152    /// Device is not open
153    DeviceNotOpen { device_id: u32 },
154    /// Invalid PCM state transition
155    InvalidStateTransition {
156        current: PcmState,
157        requested: PcmState,
158    },
159    /// Hardware parameters not configured
160    HwParamsNotSet,
161    /// Software parameters not configured
162    SwParamsNotSet,
163    /// Buffer overrun (capture) -- data was lost
164    Overrun { lost_frames: u32 },
165    /// Buffer underrun (playback) -- device starved
166    Underrun { missed_frames: u32 },
167    /// Invalid sample format
168    InvalidFormat,
169    /// Invalid sample rate
170    InvalidSampleRate { rate: u32 },
171    /// Invalid channel count
172    InvalidChannels { count: u8 },
173    /// Invalid buffer size
174    InvalidBufferSize { requested: u32, max: u32 },
175    /// Invalid period size
176    InvalidPeriodSize { requested: u32, buffer_size: u32 },
177    /// Buffer is full (write would block)
178    BufferFull,
179    /// Buffer is empty (read would block)
180    BufferEmpty,
181    /// Mixer control not found
182    MixerControlNotFound { id: u32 },
183    /// Value out of range for mixer control
184    MixerValueOutOfRange { value: i32, min: i32, max: i32 },
185    /// Maximum device limit reached
186    TooManyDevices,
187    /// Operation not supported for this stream direction
188    WrongDirection,
189    /// Device busy (in use by another client)
190    DeviceBusy { device_id: u32 },
191}
192
193impl core::fmt::Display for AlsaError {
194    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
195        match self {
196            AlsaError::DeviceNotFound { device_id } => {
197                write!(f, "ALSA: device {} not found", device_id)
198            }
199            AlsaError::DeviceAlreadyOpen { device_id } => {
200                write!(f, "ALSA: device {} already open", device_id)
201            }
202            AlsaError::DeviceNotOpen { device_id } => {
203                write!(f, "ALSA: device {} not open", device_id)
204            }
205            AlsaError::InvalidStateTransition { current, requested } => {
206                write!(
207                    f,
208                    "ALSA: invalid state transition {:?} -> {:?}",
209                    current, requested
210                )
211            }
212            AlsaError::HwParamsNotSet => write!(f, "ALSA: hardware parameters not configured"),
213            AlsaError::SwParamsNotSet => write!(f, "ALSA: software parameters not configured"),
214            AlsaError::Overrun { lost_frames } => {
215                write!(f, "ALSA: capture overrun, {} frames lost", lost_frames)
216            }
217            AlsaError::Underrun { missed_frames } => {
218                write!(
219                    f,
220                    "ALSA: playback underrun, {} frames missed",
221                    missed_frames
222                )
223            }
224            AlsaError::InvalidFormat => write!(f, "ALSA: invalid sample format"),
225            AlsaError::InvalidSampleRate { rate } => {
226                write!(f, "ALSA: invalid sample rate {}", rate)
227            }
228            AlsaError::InvalidChannels { count } => {
229                write!(f, "ALSA: invalid channel count {}", count)
230            }
231            AlsaError::InvalidBufferSize { requested, max } => {
232                write!(f, "ALSA: invalid buffer size {} (max {})", requested, max)
233            }
234            AlsaError::InvalidPeriodSize {
235                requested,
236                buffer_size,
237            } => {
238                write!(
239                    f,
240                    "ALSA: period size {} exceeds buffer size {}",
241                    requested, buffer_size
242                )
243            }
244            AlsaError::BufferFull => write!(f, "ALSA: buffer full"),
245            AlsaError::BufferEmpty => write!(f, "ALSA: buffer empty"),
246            AlsaError::MixerControlNotFound { id } => {
247                write!(f, "ALSA: mixer control {} not found", id)
248            }
249            AlsaError::MixerValueOutOfRange { value, min, max } => {
250                write!(
251                    f,
252                    "ALSA: mixer value {} out of range [{}, {}]",
253                    value, min, max
254                )
255            }
256            AlsaError::TooManyDevices => write!(f, "ALSA: maximum device limit reached"),
257            AlsaError::WrongDirection => {
258                write!(f, "ALSA: operation not supported for this stream direction")
259            }
260            AlsaError::DeviceBusy { device_id } => {
261                write!(f, "ALSA: device {} is busy", device_id)
262            }
263        }
264    }
265}
266
267// ============================================================================
268// Sample Formats
269// ============================================================================
270
271/// ALSA-compatible PCM sample format
272#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
273pub enum PcmFormat {
274    /// Unsigned 8-bit
275    U8,
276    /// Signed 16-bit little-endian
277    #[default]
278    S16Le,
279    /// Signed 32-bit little-endian
280    S32Le,
281    /// 32-bit float mapped to 16.16 fixed-point (no FPU needed)
282    F32FixedPoint,
283}
284
285impl PcmFormat {
286    /// Bytes per sample for this format
287    pub(crate) fn bytes_per_sample(self) -> u32 {
288        match self {
289            PcmFormat::U8 => 1,
290            PcmFormat::S16Le => 2,
291            PcmFormat::S32Le | PcmFormat::F32FixedPoint => 4,
292        }
293    }
294
295    /// Bits per sample for this format
296    pub(crate) fn bits_per_sample(self) -> u32 {
297        self.bytes_per_sample() * 8
298    }
299}
300
301// ============================================================================
302// PCM State Machine
303// ============================================================================
304
305/// ALSA PCM device state
306#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
307pub enum PcmState {
308    /// Device is open but not configured
309    #[default]
310    Open,
311    /// Hardware parameters are set
312    Setup,
313    /// Device is prepared and ready to start
314    Prepared,
315    /// Device is actively running (playing or capturing)
316    Running,
317    /// Buffer overrun (capture) or underrun (playback) occurred
318    XRun,
319    /// Device is draining remaining buffered data
320    Draining,
321    /// Device is paused
322    Paused,
323}
324
325impl PcmState {
326    /// Check if a transition from the current state to the target state is
327    /// valid
328    pub(crate) fn can_transition_to(self, target: PcmState) -> bool {
329        match (self, target) {
330            // From Open: can go to Setup (after hw_params)
331            (PcmState::Open, PcmState::Setup) => true,
332            // From Setup: can go to Prepared (after sw_params + prepare)
333            (PcmState::Setup, PcmState::Prepared) => true,
334            // From Prepared: can start Running or go back to Setup
335            (PcmState::Prepared, PcmState::Running) => true,
336            (PcmState::Prepared, PcmState::Setup) => true,
337            // From Running: can Pause, Drain, XRun, or stop back to Prepared
338            (PcmState::Running, PcmState::Paused) => true,
339            (PcmState::Running, PcmState::Draining) => true,
340            (PcmState::Running, PcmState::XRun) => true,
341            (PcmState::Running, PcmState::Prepared) => true,
342            // From Paused: can resume to Running or stop to Prepared
343            (PcmState::Paused, PcmState::Running) => true,
344            (PcmState::Paused, PcmState::Prepared) => true,
345            // From XRun: can go back to Prepared (after recovery)
346            (PcmState::XRun, PcmState::Prepared) => true,
347            // From Draining: can complete to Prepared
348            (PcmState::Draining, PcmState::Prepared) => true,
349            // All other transitions are invalid
350            _ => false,
351        }
352    }
353}
354
355// ============================================================================
356// Stream Direction
357// ============================================================================
358
359/// PCM stream direction
360#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
361pub enum StreamDirection {
362    /// Audio playback (output)
363    #[default]
364    Playback,
365    /// Audio capture (input/recording)
366    Capture,
367}
368
369// ============================================================================
370// Hardware Parameters
371// ============================================================================
372
373/// Hardware parameters for a PCM device (analogous to snd_pcm_hw_params)
374#[derive(Debug, Clone, Copy)]
375pub struct HwParams {
376    /// Sample rate in Hz
377    pub sample_rate: u32,
378    /// Number of channels
379    pub channels: u8,
380    /// Sample format
381    pub format: PcmFormat,
382    /// Buffer size in frames
383    pub buffer_size: u32,
384    /// Period size in frames
385    pub period_size: u32,
386}
387
388impl HwParams {
389    /// Create default hardware parameters
390    pub fn new() -> Self {
391        Self {
392            sample_rate: DEFAULT_SAMPLE_RATE,
393            channels: DEFAULT_CHANNELS,
394            format: PcmFormat::S16Le,
395            buffer_size: DEFAULT_BUFFER_FRAMES,
396            period_size: DEFAULT_PERIOD_FRAMES,
397        }
398    }
399
400    /// Validate hardware parameters
401    pub(crate) fn validate(&self) -> Result<(), AlsaError> {
402        // Sample rate validation (common rates)
403        match self.sample_rate {
404            8000 | 11025 | 16000 | 22050 | 32000 | 44100 | 48000 | 88200 | 96000 | 176400
405            | 192000 => {}
406            rate => return Err(AlsaError::InvalidSampleRate { rate }),
407        }
408
409        // Channel count validation
410        if self.channels == 0 || self.channels > 8 {
411            return Err(AlsaError::InvalidChannels {
412                count: self.channels,
413            });
414        }
415
416        // Buffer size validation
417        if self.buffer_size == 0 || self.buffer_size > 1_048_576 {
418            return Err(AlsaError::InvalidBufferSize {
419                requested: self.buffer_size,
420                max: 1_048_576,
421            });
422        }
423
424        // Period size must be <= buffer size
425        if self.period_size == 0 || self.period_size > self.buffer_size {
426            return Err(AlsaError::InvalidPeriodSize {
427                requested: self.period_size,
428                buffer_size: self.buffer_size,
429            });
430        }
431
432        Ok(())
433    }
434
435    /// Calculate frame size in bytes (channels * bytes_per_sample)
436    pub(crate) fn frame_size(&self) -> u32 {
437        self.channels as u32 * self.format.bytes_per_sample()
438    }
439
440    /// Calculate byte rate (sample_rate * frame_size)
441    pub(crate) fn byte_rate(&self) -> u32 {
442        self.sample_rate.saturating_mul(self.frame_size())
443    }
444
445    /// Calculate buffer size in bytes
446    pub(crate) fn buffer_bytes(&self) -> u32 {
447        self.buffer_size.saturating_mul(self.frame_size())
448    }
449
450    /// Calculate period size in bytes
451    pub(crate) fn period_bytes(&self) -> u32 {
452        self.period_size.saturating_mul(self.frame_size())
453    }
454}
455
456impl Default for HwParams {
457    fn default() -> Self {
458        Self::new()
459    }
460}
461
462// ============================================================================
463// Software Parameters
464// ============================================================================
465
466/// Software parameters for a PCM device (analogous to snd_pcm_sw_params)
467#[derive(Debug, Clone, Copy)]
468pub struct SwParams {
469    /// Minimum available frames before waking up the application
470    pub avail_min: u32,
471    /// Start threshold: number of frames written before auto-start
472    pub start_threshold: u32,
473    /// Stop threshold: available frames at which device stops (0 = buffer_size)
474    pub stop_threshold: u32,
475    /// Silence threshold: frames of silence before filling with silence
476    pub silence_threshold: u32,
477    /// Silence size: number of silence frames to write
478    pub silence_size: u32,
479}
480
481impl SwParams {
482    /// Create default software parameters for a given buffer size
483    pub fn new(buffer_size: u32) -> Self {
484        Self {
485            avail_min: 1,
486            start_threshold: buffer_size,
487            stop_threshold: buffer_size,
488            silence_threshold: 0,
489            silence_size: 0,
490        }
491    }
492
493    /// Validate software parameters against hardware parameters
494    pub(crate) fn validate(&self, hw_params: &HwParams) -> Result<(), AlsaError> {
495        if self.avail_min == 0 || self.avail_min > hw_params.buffer_size {
496            return Err(AlsaError::InvalidBufferSize {
497                requested: self.avail_min,
498                max: hw_params.buffer_size,
499            });
500        }
501        Ok(())
502    }
503}
504
505impl Default for SwParams {
506    fn default() -> Self {
507        Self::new(DEFAULT_BUFFER_FRAMES)
508    }
509}
510
511// ============================================================================
512// PCM Device
513// ============================================================================
514
515/// ALSA-compatible PCM device
516pub struct PcmDevice {
517    /// Unique device identifier
518    id: u32,
519    /// Device name
520    name: String,
521    /// Stream direction (playback or capture)
522    direction: StreamDirection,
523    /// Current PCM state
524    state: PcmState,
525    /// Hardware parameters (set after open)
526    hw_params: Option<HwParams>,
527    /// Software parameters (set after hw_params)
528    sw_params: Option<SwParams>,
529    /// Audio data buffer (interleaved samples as bytes)
530    buffer: Vec<u8>,
531    /// Read position in buffer (byte offset)
532    read_pos: u32,
533    /// Write position in buffer (byte offset)
534    write_pos: u32,
535    /// Total frames written since prepare
536    frames_written: AtomicU64,
537    /// Total frames read since prepare
538    frames_read: AtomicU64,
539    /// Number of xrun events
540    xrun_count: AtomicU32,
541    /// Whether device is currently open
542    is_open: bool,
543    /// Cached device capabilities for AudioDevice trait
544    device_capabilities: AudioDeviceCapabilities,
545}
546
547impl PcmDevice {
548    /// Create a new PCM device
549    pub fn new(id: u32, name: &str, direction: StreamDirection) -> Self {
550        let is_playback = direction == StreamDirection::Playback;
551        let is_capture = direction == StreamDirection::Capture;
552        Self {
553            id,
554            name: String::from(name),
555            direction,
556            state: PcmState::Open,
557            hw_params: None,
558            sw_params: None,
559            buffer: Vec::new(),
560            read_pos: 0,
561            write_pos: 0,
562            frames_written: AtomicU64::new(0),
563            frames_read: AtomicU64::new(0),
564            xrun_count: AtomicU32::new(0),
565            is_open: false,
566            device_capabilities: AudioDeviceCapabilities {
567                min_sample_rate: 8000,
568                max_sample_rate: 192000,
569                min_channels: 1,
570                max_channels: 8,
571                supported_formats: vec![
572                    SampleFormat::U8,
573                    SampleFormat::S16Le,
574                    SampleFormat::S32Le,
575                    SampleFormat::F32,
576                ],
577                playback: is_playback,
578                capture: is_capture,
579            },
580        }
581    }
582
583    /// Open the device for use
584    pub(crate) fn open(&mut self) -> Result<(), AlsaError> {
585        if self.is_open {
586            return Err(AlsaError::DeviceAlreadyOpen { device_id: self.id });
587        }
588        self.is_open = true;
589        self.state = PcmState::Open;
590        Ok(())
591    }
592
593    /// Close the device
594    pub(crate) fn close(&mut self) -> Result<(), AlsaError> {
595        if !self.is_open {
596            return Err(AlsaError::DeviceNotOpen { device_id: self.id });
597        }
598        self.is_open = false;
599        self.state = PcmState::Open;
600        self.hw_params = None;
601        self.sw_params = None;
602        self.buffer.clear();
603        self.read_pos = 0;
604        self.write_pos = 0;
605        self.frames_written.store(0, Ordering::Relaxed);
606        self.frames_read.store(0, Ordering::Relaxed);
607        Ok(())
608    }
609
610    /// Set hardware parameters
611    pub(crate) fn set_hw_params(&mut self, params: HwParams) -> Result<(), AlsaError> {
612        if !self.is_open {
613            return Err(AlsaError::DeviceNotOpen { device_id: self.id });
614        }
615        params.validate()?;
616        self.hw_params = Some(params);
617        self.transition_state(PcmState::Setup)?;
618        Ok(())
619    }
620
621    /// Get current hardware parameters
622    pub(crate) fn get_hw_params(&self) -> Result<&HwParams, AlsaError> {
623        self.hw_params.as_ref().ok_or(AlsaError::HwParamsNotSet)
624    }
625
626    /// Set software parameters
627    pub(crate) fn set_sw_params(&mut self, params: SwParams) -> Result<(), AlsaError> {
628        let hw = self.hw_params.as_ref().ok_or(AlsaError::HwParamsNotSet)?;
629        params.validate(hw)?;
630        self.sw_params = Some(params);
631        Ok(())
632    }
633
634    /// Get current software parameters
635    pub(crate) fn get_sw_params(&self) -> Result<&SwParams, AlsaError> {
636        self.sw_params.as_ref().ok_or(AlsaError::SwParamsNotSet)
637    }
638
639    /// Prepare the device for operation (allocate buffers)
640    pub(crate) fn prepare(&mut self) -> Result<(), AlsaError> {
641        let hw = self.hw_params.ok_or(AlsaError::HwParamsNotSet)?;
642
643        // Allocate buffer
644        let buf_bytes = hw.buffer_bytes() as usize;
645        self.buffer = vec![0u8; buf_bytes];
646        self.read_pos = 0;
647        self.write_pos = 0;
648        self.frames_written.store(0, Ordering::Relaxed);
649        self.frames_read.store(0, Ordering::Relaxed);
650
651        // Set default sw_params if not already set
652        if self.sw_params.is_none() {
653            self.sw_params = Some(SwParams::new(hw.buffer_size));
654        }
655
656        self.transition_state(PcmState::Prepared)?;
657        Ok(())
658    }
659
660    /// Start the device (begin playback or capture)
661    pub(crate) fn start(&mut self) -> Result<(), AlsaError> {
662        self.transition_state(PcmState::Running)
663    }
664
665    /// Stop the device immediately
666    pub(crate) fn stop(&mut self) -> Result<(), AlsaError> {
667        if self.state == PcmState::Running || self.state == PcmState::Paused {
668            self.state = PcmState::Prepared;
669            Ok(())
670        } else {
671            Err(AlsaError::InvalidStateTransition {
672                current: self.state,
673                requested: PcmState::Prepared,
674            })
675        }
676    }
677
678    /// Pause the device
679    pub(crate) fn pause(&mut self) -> Result<(), AlsaError> {
680        self.transition_state(PcmState::Paused)
681    }
682
683    /// Resume from pause
684    pub(crate) fn resume(&mut self) -> Result<(), AlsaError> {
685        if self.state != PcmState::Paused {
686            return Err(AlsaError::InvalidStateTransition {
687                current: self.state,
688                requested: PcmState::Running,
689            });
690        }
691        self.state = PcmState::Running;
692        Ok(())
693    }
694
695    /// Drain: wait for all buffered data to be consumed, then stop
696    pub(crate) fn drain(&mut self) -> Result<(), AlsaError> {
697        if self.state == PcmState::Running {
698            self.state = PcmState::Draining;
699            // In a real implementation, we'd wait for the buffer to empty.
700            // For now, transition directly to Prepared.
701            self.state = PcmState::Prepared;
702            Ok(())
703        } else {
704            Err(AlsaError::InvalidStateTransition {
705                current: self.state,
706                requested: PcmState::Draining,
707            })
708        }
709    }
710
711    /// Recover from XRun state
712    pub(crate) fn recover_xrun(&mut self) -> Result<(), AlsaError> {
713        if self.state != PcmState::XRun {
714            return Err(AlsaError::InvalidStateTransition {
715                current: self.state,
716                requested: PcmState::Prepared,
717            });
718        }
719        self.read_pos = 0;
720        self.write_pos = 0;
721        self.state = PcmState::Prepared;
722        Ok(())
723    }
724
725    /// Write interleaved sample data to the playback buffer
726    ///
727    /// Returns the number of frames written.
728    pub(crate) fn write(&mut self, data: &[u8]) -> Result<u32, AlsaError> {
729        if self.direction != StreamDirection::Playback {
730            return Err(AlsaError::WrongDirection);
731        }
732        if self.state != PcmState::Running && self.state != PcmState::Prepared {
733            return Err(AlsaError::InvalidStateTransition {
734                current: self.state,
735                requested: PcmState::Running,
736            });
737        }
738
739        let hw = self.hw_params.ok_or(AlsaError::HwParamsNotSet)?;
740        let frame_size = hw.frame_size();
741        if frame_size == 0 {
742            return Ok(0);
743        }
744        let buf_bytes = self.buffer.len() as u32;
745        if buf_bytes == 0 {
746            return Err(AlsaError::BufferFull);
747        }
748
749        let avail = self.available_write_bytes(buf_bytes);
750        let to_write = (data.len() as u32).min(avail);
751        // Align to frame boundary
752        let to_write = (to_write / frame_size) * frame_size;
753
754        if to_write == 0 {
755            return Err(AlsaError::BufferFull);
756        }
757
758        let wp = self.write_pos as usize;
759        let cap = buf_bytes as usize;
760        let tw = to_write as usize;
761
762        let first_chunk = (cap - wp).min(tw);
763        let second_chunk = tw - first_chunk;
764
765        self.buffer[wp..wp + first_chunk].copy_from_slice(&data[..first_chunk]);
766        if second_chunk > 0 {
767            self.buffer[..second_chunk]
768                .copy_from_slice(&data[first_chunk..first_chunk + second_chunk]);
769        }
770
771        self.write_pos = ((wp + tw) % cap) as u32;
772        let frames = to_write / frame_size;
773        self.frames_written
774            .fetch_add(frames as u64, Ordering::Relaxed);
775
776        Ok(frames)
777    }
778
779    /// Read interleaved sample data from the capture buffer
780    ///
781    /// Returns the number of frames read.
782    pub(crate) fn read(&mut self, output: &mut [u8]) -> Result<u32, AlsaError> {
783        if self.direction != StreamDirection::Capture {
784            return Err(AlsaError::WrongDirection);
785        }
786        if self.state != PcmState::Running {
787            return Err(AlsaError::InvalidStateTransition {
788                current: self.state,
789                requested: PcmState::Running,
790            });
791        }
792
793        let hw = self.hw_params.ok_or(AlsaError::HwParamsNotSet)?;
794        let frame_size = hw.frame_size();
795        if frame_size == 0 {
796            return Ok(0);
797        }
798        let buf_bytes = self.buffer.len() as u32;
799        if buf_bytes == 0 {
800            return Err(AlsaError::BufferEmpty);
801        }
802
803        let avail = self.available_read_bytes(buf_bytes);
804        let to_read = (output.len() as u32).min(avail);
805        let to_read = (to_read / frame_size) * frame_size;
806
807        if to_read == 0 {
808            return Err(AlsaError::BufferEmpty);
809        }
810
811        let rp = self.read_pos as usize;
812        let cap = buf_bytes as usize;
813        let tr = to_read as usize;
814
815        let first_chunk = (cap - rp).min(tr);
816        let second_chunk = tr - first_chunk;
817
818        output[..first_chunk].copy_from_slice(&self.buffer[rp..rp + first_chunk]);
819        if second_chunk > 0 {
820            output[first_chunk..first_chunk + second_chunk]
821                .copy_from_slice(&self.buffer[..second_chunk]);
822        }
823
824        self.read_pos = ((rp + tr) % cap) as u32;
825        let frames = to_read / frame_size;
826        self.frames_read.fetch_add(frames as u64, Ordering::Relaxed);
827
828        Ok(frames)
829    }
830
831    /// Get the number of frames available for writing (playback)
832    pub(crate) fn avail_update(&self) -> Result<u32, AlsaError> {
833        let hw = self.hw_params.as_ref().ok_or(AlsaError::HwParamsNotSet)?;
834        let frame_size = hw.frame_size();
835        if frame_size == 0 {
836            return Ok(0);
837        }
838        let buf_bytes = self.buffer.len() as u32;
839        match self.direction {
840            StreamDirection::Playback => Ok(self.available_write_bytes(buf_bytes) / frame_size),
841            StreamDirection::Capture => Ok(self.available_read_bytes(buf_bytes) / frame_size),
842        }
843    }
844
845    /// Get the current PCM state
846    pub(crate) fn state(&self) -> PcmState {
847        self.state
848    }
849
850    /// Get the device ID
851    pub(crate) fn id(&self) -> u32 {
852        self.id
853    }
854
855    /// Get the device name
856    pub(crate) fn name(&self) -> &str {
857        &self.name
858    }
859
860    /// Get the stream direction
861    pub(crate) fn direction(&self) -> StreamDirection {
862        self.direction
863    }
864
865    /// Get total frames written
866    pub(crate) fn total_frames_written(&self) -> u64 {
867        self.frames_written.load(Ordering::Relaxed)
868    }
869
870    /// Get total frames read
871    pub(crate) fn total_frames_read(&self) -> u64 {
872        self.frames_read.load(Ordering::Relaxed)
873    }
874
875    /// Get xrun count
876    pub(crate) fn xrun_count(&self) -> u32 {
877        self.xrun_count.load(Ordering::Relaxed)
878    }
879
880    /// Whether device is open
881    pub(crate) fn is_open(&self) -> bool {
882        self.is_open
883    }
884
885    /// Get MMAP buffer reference for zero-copy access
886    ///
887    /// Returns a reference to the internal buffer for direct manipulation.
888    /// Only valid in Prepared or Running state.
889    pub(crate) fn mmap_begin(&self) -> Result<(&[u8], u32, u32), AlsaError> {
890        if self.state != PcmState::Prepared && self.state != PcmState::Running {
891            return Err(AlsaError::InvalidStateTransition {
892                current: self.state,
893                requested: PcmState::Running,
894            });
895        }
896        let hw = self.hw_params.as_ref().ok_or(AlsaError::HwParamsNotSet)?;
897        let frame_size = hw.frame_size();
898        let buf_bytes = self.buffer.len() as u32;
899        let avail = match self.direction {
900            StreamDirection::Playback => self.available_write_bytes(buf_bytes) / frame_size,
901            StreamDirection::Capture => self.available_read_bytes(buf_bytes) / frame_size,
902        };
903        let offset = match self.direction {
904            StreamDirection::Playback => self.write_pos / frame_size,
905            StreamDirection::Capture => self.read_pos / frame_size,
906        };
907        Ok((&self.buffer, offset, avail))
908    }
909
910    /// Commit frames after MMAP write
911    pub(crate) fn mmap_commit(&mut self, frames: u32) -> Result<(), AlsaError> {
912        let hw = self.hw_params.ok_or(AlsaError::HwParamsNotSet)?;
913        let frame_size = hw.frame_size();
914        let bytes = frames.saturating_mul(frame_size);
915        let cap = self.buffer.len() as u32;
916        if cap == 0 {
917            return Ok(());
918        }
919
920        match self.direction {
921            StreamDirection::Playback => {
922                self.write_pos = (self.write_pos + bytes) % cap;
923                self.frames_written
924                    .fetch_add(frames as u64, Ordering::Relaxed);
925            }
926            StreamDirection::Capture => {
927                self.read_pos = (self.read_pos + bytes) % cap;
928                self.frames_read.fetch_add(frames as u64, Ordering::Relaxed);
929            }
930        }
931        Ok(())
932    }
933
934    // --- Internal helpers ---
935
936    /// Transition to a new PCM state, checking validity
937    fn transition_state(&mut self, target: PcmState) -> Result<(), AlsaError> {
938        if self.state.can_transition_to(target) {
939            self.state = target;
940            Ok(())
941        } else {
942            Err(AlsaError::InvalidStateTransition {
943                current: self.state,
944                requested: target,
945            })
946        }
947    }
948
949    /// Available bytes for writing in the ring buffer
950    fn available_write_bytes(&self, capacity: u32) -> u32 {
951        if capacity == 0 {
952            return 0;
953        }
954        let used = if self.write_pos >= self.read_pos {
955            self.write_pos - self.read_pos
956        } else {
957            capacity - self.read_pos + self.write_pos
958        };
959        capacity.saturating_sub(used).saturating_sub(1)
960    }
961
962    /// Available bytes for reading from the ring buffer
963    fn available_read_bytes(&self, capacity: u32) -> u32 {
964        if capacity == 0 {
965            return 0;
966        }
967        if self.write_pos >= self.read_pos {
968            self.write_pos - self.read_pos
969        } else {
970            capacity - self.read_pos + self.write_pos
971        }
972    }
973
974    /// Push capture data into the buffer (called from hardware/driver side)
975    fn push_capture_data(&mut self, data: &[u8]) -> Result<u32, AlsaError> {
976        let hw = self.hw_params.ok_or(AlsaError::HwParamsNotSet)?;
977        let frame_size = hw.frame_size();
978        if frame_size == 0 {
979            return Ok(0);
980        }
981        let buf_bytes = self.buffer.len() as u32;
982        let avail = self.available_write_bytes(buf_bytes);
983        let to_write = (data.len() as u32).min(avail);
984        let to_write = (to_write / frame_size) * frame_size;
985
986        if to_write == 0 && !data.is_empty() {
987            // Overrun: buffer is full, data will be lost
988            let lost = data.len() as u32 / frame_size;
989            self.xrun_count.fetch_add(1, Ordering::Relaxed);
990            self.state = PcmState::XRun;
991            return Err(AlsaError::Overrun { lost_frames: lost });
992        }
993
994        let wp = self.write_pos as usize;
995        let cap = buf_bytes as usize;
996        let tw = to_write as usize;
997
998        let first_chunk = (cap - wp).min(tw);
999        let second_chunk = tw - first_chunk;
1000
1001        self.buffer[wp..wp + first_chunk].copy_from_slice(&data[..first_chunk]);
1002        if second_chunk > 0 {
1003            self.buffer[..second_chunk]
1004                .copy_from_slice(&data[first_chunk..first_chunk + second_chunk]);
1005        }
1006
1007        self.write_pos = ((wp + tw) % cap) as u32;
1008        Ok(to_write / frame_size)
1009    }
1010}
1011
1012// ============================================================================
1013// AlsaError -> AudioError Conversion
1014// ============================================================================
1015
1016impl From<AlsaError> for AudioError {
1017    fn from(err: AlsaError) -> Self {
1018        match err {
1019            AlsaError::DeviceNotFound { .. } => AudioError::DeviceNotFound,
1020            AlsaError::DeviceAlreadyOpen { .. } | AlsaError::DeviceBusy { .. } => {
1021                AudioError::DeviceBusy
1022            }
1023            AlsaError::DeviceNotOpen { .. }
1024            | AlsaError::HwParamsNotSet
1025            | AlsaError::SwParamsNotSet => AudioError::InvalidConfig {
1026                reason: "device not configured",
1027            },
1028            AlsaError::InvalidStateTransition { current, .. } => {
1029                if current == PcmState::Running {
1030                    AudioError::AlreadyStarted
1031                } else {
1032                    AudioError::NotStarted
1033                }
1034            }
1035            AlsaError::Overrun { .. } => AudioError::BufferOverrun,
1036            AlsaError::Underrun { .. } => AudioError::BufferUnderrun,
1037            AlsaError::InvalidFormat => AudioError::UnsupportedFormat,
1038            AlsaError::InvalidSampleRate { .. } => AudioError::InvalidConfig {
1039                reason: "unsupported sample rate",
1040            },
1041            AlsaError::InvalidChannels { .. } => AudioError::InvalidConfig {
1042                reason: "unsupported channel count",
1043            },
1044            AlsaError::InvalidBufferSize { .. } => AudioError::InvalidConfig {
1045                reason: "invalid buffer size",
1046            },
1047            AlsaError::InvalidPeriodSize { .. } => AudioError::InvalidConfig {
1048                reason: "invalid period size",
1049            },
1050            AlsaError::BufferFull => AudioError::BufferUnderrun,
1051            AlsaError::BufferEmpty => AudioError::BufferOverrun,
1052            AlsaError::MixerControlNotFound { .. } | AlsaError::MixerValueOutOfRange { .. } => {
1053                AudioError::InvalidConfig {
1054                    reason: "mixer control error",
1055                }
1056            }
1057            AlsaError::TooManyDevices => AudioError::DeviceBusy,
1058            AlsaError::WrongDirection => AudioError::InvalidConfig {
1059                reason: "wrong stream direction",
1060            },
1061        }
1062    }
1063}
1064
1065// ============================================================================
1066// AudioDevice Trait Implementation for PcmDevice
1067// ============================================================================
1068
1069/// Helper to convert PcmFormat to SampleFormat
1070fn pcm_format_to_sample_format(fmt: PcmFormat) -> SampleFormat {
1071    match fmt {
1072        PcmFormat::U8 => SampleFormat::U8,
1073        PcmFormat::S16Le => SampleFormat::S16Le,
1074        PcmFormat::S32Le => SampleFormat::S32Le,
1075        PcmFormat::F32FixedPoint => SampleFormat::F32,
1076    }
1077}
1078
1079/// Helper to convert SampleFormat to PcmFormat (best-effort mapping)
1080fn sample_format_to_pcm_format(fmt: SampleFormat) -> Result<PcmFormat, AudioError> {
1081    match fmt {
1082        SampleFormat::U8 => Ok(PcmFormat::U8),
1083        SampleFormat::S16Le => Ok(PcmFormat::S16Le),
1084        SampleFormat::S32Le => Ok(PcmFormat::S32Le),
1085        SampleFormat::F32 => Ok(PcmFormat::F32FixedPoint),
1086        // Formats without a direct ALSA PcmFormat mapping
1087        SampleFormat::S16Be | SampleFormat::S24Le => Err(AudioError::UnsupportedFormat),
1088    }
1089}
1090
1091impl AudioDevice for PcmDevice {
1092    fn configure(&mut self, config: &AudioConfig) -> Result<AudioConfig, AudioError> {
1093        // Open the device if not already open
1094        if !self.is_open {
1095            self.open().map_err(AudioError::from)?;
1096        }
1097
1098        let pcm_format = sample_format_to_pcm_format(config.format)?;
1099
1100        let hw_params = HwParams {
1101            sample_rate: config.sample_rate,
1102            channels: config.channels,
1103            format: pcm_format,
1104            buffer_size: config.buffer_frames,
1105            period_size: config.buffer_frames / 4,
1106        };
1107
1108        self.set_hw_params(hw_params).map_err(AudioError::from)?;
1109        self.prepare().map_err(AudioError::from)?;
1110
1111        // Return the actual applied config
1112        Ok(AudioConfig {
1113            sample_rate: hw_params.sample_rate,
1114            channels: hw_params.channels,
1115            format: pcm_format_to_sample_format(hw_params.format),
1116            buffer_frames: hw_params.buffer_size,
1117        })
1118    }
1119
1120    fn start(&mut self) -> Result<(), AudioError> {
1121        PcmDevice::start(self).map_err(AudioError::from)
1122    }
1123
1124    fn stop(&mut self) -> Result<(), AudioError> {
1125        PcmDevice::stop(self).map_err(AudioError::from)
1126    }
1127
1128    fn write_frames(&mut self, data: &[u8]) -> Result<usize, AudioError> {
1129        self.write(data)
1130            .map(|frames| frames as usize)
1131            .map_err(AudioError::from)
1132    }
1133
1134    fn read_frames(&mut self, output: &mut [u8]) -> Result<usize, AudioError> {
1135        self.read(output)
1136            .map(|frames| frames as usize)
1137            .map_err(AudioError::from)
1138    }
1139
1140    fn capabilities(&self) -> &AudioDeviceCapabilities {
1141        &self.device_capabilities
1142    }
1143
1144    fn name(&self) -> &str {
1145        &self.name
1146    }
1147
1148    fn is_playback(&self) -> bool {
1149        self.direction == StreamDirection::Playback
1150    }
1151
1152    fn is_capture(&self) -> bool {
1153        self.direction == StreamDirection::Capture
1154    }
1155}
1156
1157// ============================================================================
1158// Mixer Control Types
1159// ============================================================================
1160
1161/// Type of mixer control
1162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1163pub enum MixerControlType {
1164    /// Integer value with min/max range
1165    Integer,
1166    /// Boolean (on/off, mute/unmute)
1167    Boolean,
1168    /// Enumerated value (select from named options)
1169    Enumerated,
1170}
1171
1172/// A mixer control element
1173pub struct MixerControl {
1174    /// Control identifier
1175    pub id: u32,
1176    /// Control name (e.g., "Master Playback Volume")
1177    pub name: String,
1178    /// Control type
1179    pub control_type: MixerControlType,
1180    /// Current value
1181    value: AtomicU32,
1182    /// Minimum value (for Integer type)
1183    pub min: i32,
1184    /// Maximum value (for Integer type)
1185    pub max: i32,
1186    /// Number of enumerated items (for Enumerated type)
1187    pub enum_count: u32,
1188    /// Enumerated item names
1189    pub enum_names: Vec<String>,
1190}
1191
1192impl MixerControl {
1193    /// Create a new integer mixer control
1194    pub(crate) fn new_integer(id: u32, name: &str, min: i32, max: i32, initial: i32) -> Self {
1195        Self {
1196            id,
1197            name: String::from(name),
1198            control_type: MixerControlType::Integer,
1199            value: AtomicU32::new(initial as u32),
1200            min,
1201            max,
1202            enum_count: 0,
1203            enum_names: Vec::new(),
1204        }
1205    }
1206
1207    /// Create a new boolean mixer control
1208    pub(crate) fn new_boolean(id: u32, name: &str, initial: bool) -> Self {
1209        Self {
1210            id,
1211            name: String::from(name),
1212            control_type: MixerControlType::Boolean,
1213            value: AtomicU32::new(if initial { 1 } else { 0 }),
1214            min: 0,
1215            max: 1,
1216            enum_count: 0,
1217            enum_names: Vec::new(),
1218        }
1219    }
1220
1221    /// Create a new enumerated mixer control
1222    pub(crate) fn new_enumerated(id: u32, name: &str, items: Vec<String>, initial: u32) -> Self {
1223        let count = items.len() as u32;
1224        Self {
1225            id,
1226            name: String::from(name),
1227            control_type: MixerControlType::Enumerated,
1228            value: AtomicU32::new(initial),
1229            min: 0,
1230            max: count.saturating_sub(1) as i32,
1231            enum_count: count,
1232            enum_names: items,
1233        }
1234    }
1235
1236    /// Get the current value
1237    pub(crate) fn get_value(&self) -> i32 {
1238        self.value.load(Ordering::Relaxed) as i32
1239    }
1240
1241    /// Set the value with range checking
1242    pub(crate) fn set_value(&self, val: i32) -> Result<(), AlsaError> {
1243        if val < self.min || val > self.max {
1244            return Err(AlsaError::MixerValueOutOfRange {
1245                value: val,
1246                min: self.min,
1247                max: self.max,
1248            });
1249        }
1250        self.value.store(val as u32, Ordering::Relaxed);
1251        Ok(())
1252    }
1253
1254    /// For boolean controls: get as bool
1255    pub(crate) fn get_bool(&self) -> bool {
1256        self.value.load(Ordering::Relaxed) != 0
1257    }
1258
1259    /// For boolean controls: set as bool
1260    pub(crate) fn set_bool(&self, val: bool) {
1261        self.value.store(if val { 1 } else { 0 }, Ordering::Relaxed);
1262    }
1263
1264    /// For enumerated controls: get selected item name
1265    pub(crate) fn get_enum_name(&self) -> Option<&str> {
1266        let idx = self.value.load(Ordering::Relaxed) as usize;
1267        self.enum_names.get(idx).map(|s| s.as_str())
1268    }
1269}
1270
1271// ============================================================================
1272// ALSA Mixer (collection of controls)
1273// ============================================================================
1274
1275/// Well-known mixer control IDs
1276pub const MIXER_MASTER_VOLUME: u32 = 1;
1277/// PCM playback volume control ID
1278pub const MIXER_PCM_VOLUME: u32 = 2;
1279/// Capture volume control ID
1280pub const MIXER_CAPTURE_VOLUME: u32 = 3;
1281/// Master mute switch control ID
1282pub const MIXER_MASTER_SWITCH: u32 = 4;
1283/// Capture mute switch control ID
1284pub const MIXER_CAPTURE_SWITCH: u32 = 5;
1285
1286/// ALSA mixer managing multiple controls
1287pub struct AlsaMixer {
1288    /// All mixer controls keyed by ID
1289    controls: BTreeMap<u32, MixerControl>,
1290    /// Next control ID
1291    next_id: u32,
1292}
1293
1294impl AlsaMixer {
1295    /// Create a new mixer with default controls
1296    pub fn new() -> Self {
1297        let mut mixer = Self {
1298            controls: BTreeMap::new(),
1299            next_id: 10, // Reserve IDs 1-9 for well-known controls
1300        };
1301
1302        // Create default controls
1303        mixer.controls.insert(
1304            MIXER_MASTER_VOLUME,
1305            MixerControl::new_integer(MIXER_MASTER_VOLUME, "Master Playback Volume", 0, 100, 80),
1306        );
1307        mixer.controls.insert(
1308            MIXER_PCM_VOLUME,
1309            MixerControl::new_integer(MIXER_PCM_VOLUME, "PCM Playback Volume", 0, 100, 100),
1310        );
1311        mixer.controls.insert(
1312            MIXER_CAPTURE_VOLUME,
1313            MixerControl::new_integer(MIXER_CAPTURE_VOLUME, "Capture Volume", 0, 100, 80),
1314        );
1315        mixer.controls.insert(
1316            MIXER_MASTER_SWITCH,
1317            MixerControl::new_boolean(MIXER_MASTER_SWITCH, "Master Playback Switch", true),
1318        );
1319        mixer.controls.insert(
1320            MIXER_CAPTURE_SWITCH,
1321            MixerControl::new_boolean(MIXER_CAPTURE_SWITCH, "Capture Switch", true),
1322        );
1323
1324        mixer
1325    }
1326
1327    /// Add a custom mixer control
1328    pub(crate) fn add_control(&mut self, control: MixerControl) -> u32 {
1329        let id = control.id;
1330        self.controls.insert(id, control);
1331        id
1332    }
1333
1334    /// Allocate a new control ID
1335    pub(crate) fn alloc_control_id(&mut self) -> u32 {
1336        let id = self.next_id;
1337        self.next_id = self.next_id.wrapping_add(1);
1338        id
1339    }
1340
1341    /// Get a control by ID
1342    pub(crate) fn get_control(&self, id: u32) -> Result<&MixerControl, AlsaError> {
1343        self.controls
1344            .get(&id)
1345            .ok_or(AlsaError::MixerControlNotFound { id })
1346    }
1347
1348    /// Set volume for a control (0-100 range)
1349    pub(crate) fn set_volume(&self, control_id: u32, volume: i32) -> Result<(), AlsaError> {
1350        let control = self
1351            .controls
1352            .get(&control_id)
1353            .ok_or(AlsaError::MixerControlNotFound { id: control_id })?;
1354        control.set_value(volume)
1355    }
1356
1357    /// Get volume for a control
1358    pub(crate) fn get_volume(&self, control_id: u32) -> Result<i32, AlsaError> {
1359        let control = self
1360            .controls
1361            .get(&control_id)
1362            .ok_or(AlsaError::MixerControlNotFound { id: control_id })?;
1363        Ok(control.get_value())
1364    }
1365
1366    /// Get the number of controls
1367    pub(crate) fn control_count(&self) -> usize {
1368        self.controls.len()
1369    }
1370
1371    /// List all control IDs
1372    pub(crate) fn list_control_ids(&self) -> Vec<u32> {
1373        self.controls.keys().copied().collect()
1374    }
1375}
1376
1377impl Default for AlsaMixer {
1378    fn default() -> Self {
1379        Self::new()
1380    }
1381}
1382
1383// ============================================================================
1384// Sample Format Conversion
1385// ============================================================================
1386
1387/// Convert U8 sample to S16 (expand range)
1388///
1389/// U8 range: 0..255 (center at 128)
1390/// S16 range: -32768..32767
1391#[inline]
1392pub(crate) fn convert_u8_to_s16(sample: u8) -> i16 {
1393    // Shift center from 128 to 0, then scale up by 256
1394    ((sample as i16) - 128) * 256
1395}
1396
1397/// Convert S16 sample to U8 (compress range)
1398#[inline]
1399pub(crate) fn convert_s16_to_u8(sample: i16) -> u8 {
1400    // Scale down by 256 and shift center from 0 to 128
1401    ((sample / 256) + 128) as u8
1402}
1403
1404/// Convert S16 sample to S32 (expand to 32-bit)
1405#[inline]
1406pub(crate) fn convert_s16_to_s32(sample: i16) -> i32 {
1407    (sample as i32) << 16
1408}
1409
1410/// Convert S32 sample to S16 (truncate to 16-bit)
1411#[inline]
1412pub(crate) fn convert_s32_to_s16(sample: i32) -> i16 {
1413    let shifted = sample >> 16;
1414    if shifted > i16::MAX as i32 {
1415        i16::MAX
1416    } else if shifted < i16::MIN as i32 {
1417        i16::MIN
1418    } else {
1419        shifted as i16
1420    }
1421}
1422
1423/// Convert U8 sample to S32
1424#[inline]
1425pub(crate) fn convert_u8_to_s32(sample: u8) -> i32 {
1426    convert_s16_to_s32(convert_u8_to_s16(sample))
1427}
1428
1429/// Convert S32 sample to U8
1430#[inline]
1431pub(crate) fn convert_s32_to_u8(sample: i32) -> u8 {
1432    convert_s16_to_u8(convert_s32_to_s16(sample))
1433}
1434
1435/// Convert a buffer of samples between formats
1436pub(crate) fn convert_buffer(
1437    input: &[u8],
1438    src_format: PcmFormat,
1439    dst_format: PcmFormat,
1440    output: &mut Vec<u8>,
1441) {
1442    output.clear();
1443
1444    if src_format == dst_format {
1445        output.extend_from_slice(input);
1446        return;
1447    }
1448
1449    match (src_format, dst_format) {
1450        (PcmFormat::U8, PcmFormat::S16Le) => {
1451            output.reserve(input.len() * 2);
1452            for &sample in input {
1453                let converted = convert_u8_to_s16(sample);
1454                output.extend_from_slice(&converted.to_le_bytes());
1455            }
1456        }
1457        (PcmFormat::S16Le, PcmFormat::U8) => {
1458            output.reserve(input.len() / 2);
1459            for chunk in input.chunks_exact(2) {
1460                let sample = i16::from_le_bytes([chunk[0], chunk[1]]);
1461                output.push(convert_s16_to_u8(sample));
1462            }
1463        }
1464        (PcmFormat::S16Le, PcmFormat::S32Le) => {
1465            output.reserve(input.len() * 2);
1466            for chunk in input.chunks_exact(2) {
1467                let sample = i16::from_le_bytes([chunk[0], chunk[1]]);
1468                let converted = convert_s16_to_s32(sample);
1469                output.extend_from_slice(&converted.to_le_bytes());
1470            }
1471        }
1472        (PcmFormat::S32Le, PcmFormat::S16Le) => {
1473            output.reserve(input.len() / 2);
1474            for chunk in input.chunks_exact(4) {
1475                let sample = i32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
1476                let converted = convert_s32_to_s16(sample);
1477                output.extend_from_slice(&converted.to_le_bytes());
1478            }
1479        }
1480        (PcmFormat::U8, PcmFormat::S32Le) => {
1481            output.reserve(input.len() * 4);
1482            for &sample in input {
1483                let converted = convert_u8_to_s32(sample);
1484                output.extend_from_slice(&converted.to_le_bytes());
1485            }
1486        }
1487        (PcmFormat::S32Le, PcmFormat::U8) => {
1488            output.reserve(input.len() / 4);
1489            for chunk in input.chunks_exact(4) {
1490                let sample = i32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
1491                output.push(convert_s32_to_u8(sample));
1492            }
1493        }
1494        // F32FixedPoint is stored as i32 internally, same conversion as S32
1495        (PcmFormat::F32FixedPoint, dst) => {
1496            convert_buffer(input, PcmFormat::S32Le, dst, output);
1497        }
1498        (src, PcmFormat::F32FixedPoint) => {
1499            convert_buffer(input, src, PcmFormat::S32Le, output);
1500        }
1501        // Same format case (already handled above, but needed for exhaustiveness)
1502        _ => {
1503            output.extend_from_slice(input);
1504        }
1505    }
1506}
1507
1508// ============================================================================
1509// Gain Control (integer dB scaling via 16.16 fixed-point)
1510// ============================================================================
1511
1512/// Gain scaling factor in 16.16 fixed-point
1513///
1514/// Represents a linear amplitude multiplier. Use `gain_from_db()` to convert
1515/// from decibels.
1516#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1517pub struct GainFactor(pub i32);
1518
1519impl GainFactor {
1520    /// Unity gain (0 dB, multiplier = 1.0)
1521    pub const UNITY: GainFactor = GainFactor(FP_ONE);
1522
1523    /// Mute (negative infinity dB, multiplier = 0.0)
1524    pub const MUTE: GainFactor = GainFactor(0);
1525
1526    /// Apply this gain factor to a 16-bit sample
1527    #[inline]
1528    pub(crate) fn apply_s16(self, sample: i16) -> i16 {
1529        let wide = (sample as i32 as i64) * (self.0 as i64);
1530        let result = wide >> FP_SHIFT;
1531        if result > i16::MAX as i64 {
1532            i16::MAX
1533        } else if result < i16::MIN as i64 {
1534            i16::MIN
1535        } else {
1536            result as i16
1537        }
1538    }
1539
1540    /// Apply this gain factor to a 32-bit sample
1541    #[inline]
1542    pub(crate) fn apply_s32(self, sample: i32) -> i32 {
1543        let wide = (sample as i64) * (self.0 as i64);
1544        let result = wide >> FP_SHIFT;
1545        if result > i32::MAX as i64 {
1546            i32::MAX
1547        } else if result < i32::MIN as i64 {
1548            i32::MIN
1549        } else {
1550            result as i32
1551        }
1552    }
1553
1554    /// Get the raw 16.16 fixed-point value
1555    pub(crate) fn raw(self) -> i32 {
1556        self.0
1557    }
1558}
1559
1560/// Convert decibels to a linear gain factor using integer approximation
1561///
1562/// Uses a lookup table for common dB values and linear interpolation.
1563/// Range: -60 dB to +20 dB. Values below -60 dB are treated as mute.
1564///
1565/// The `db_tenths` parameter is in tenths of a dB (e.g., -30 = -3.0 dB).
1566pub(crate) fn gain_from_db_tenths(db_tenths: i32) -> GainFactor {
1567    // Below -600 tenths (-60 dB): effectively mute
1568    if db_tenths <= -600 {
1569        return GainFactor::MUTE;
1570    }
1571
1572    // Lookup table: dB (in tenths) -> 16.16 fixed-point linear gain
1573    // Computed as: round(10^(dB/200) * 65536)
1574    // We store entries at 6 dB intervals and interpolate
1575    //
1576    // Key reference points:
1577    //   0 dB    = 1.0     = 65536 (FP_ONE)
1578    //  -6 dB    = 0.5012  = 32845
1579    // -12 dB    = 0.2512  = 16462
1580    // -18 dB    = 0.1259  = 8250
1581    // -24 dB    = 0.0631  = 4135
1582    // -30 dB    = 0.0316  = 2073
1583    // -36 dB    = 0.0158  = 1038
1584    // -42 dB    = 0.00794 = 520
1585    // -48 dB    = 0.00398 = 261
1586    // -54 dB    = 0.00200 = 131
1587    // -60 dB    = 0.00100 = 65
1588    //  +6 dB    = 1.9953  = 130762
1589    // +12 dB    = 3.9811  = 260921
1590    // +18 dB    = 7.9433  = 520570
1591    // +20 dB    = 10.0    = 655360
1592
1593    // Table indexed by (db_tenths + 600) / 60
1594    // Entries at -60, -54, -48, ..., 0, +6, +12, +18, +20 dB
1595    static DB_TABLE: [i32; 21] = [
1596        65,   // -60 dB (idx 0)
1597        92,   // -54 dB (idx 1) -- interpolated
1598        131,  // -48 dB (idx 2)
1599        185,  // -42 dB (idx 3) -- interpolated
1600        261,  // -36 dB (idx 4)
1601        369,  // -30 dB (idx 5) -- interpolated
1602        520,  // -24 dB (idx 6)
1603        735,  // -18 dB (idx 7) -- interpolated
1604        1038, // -12 dB (idx 8)
1605        1467, // -6 dB  (idx 9) -- interpolated
1606        2073, //  0 dB ... wait, let me recalculate
1607        // Actually, let's use a simpler table at 6dB steps from -60 to +20
1608        // Re-indexed properly:
1609        4135, // +6 dB relative...
1610        // This is getting complex. Let me use a direct piecewise approach.
1611        8250, 16462, 32845, FP_ONE, // 0 dB = 65536
1612        130762, // +6 dB
1613        260921, // +12 dB
1614        520570, // +18 dB
1615        655360, // +20 dB
1616        655360, // clamp
1617    ];
1618
1619    // Simpler approach: piecewise linear between key points
1620    // Use a small table of (db_tenths, gain_fp) pairs
1621    struct DbGainEntry {
1622        db_tenths: i32,
1623        gain: i32,
1624    }
1625
1626    static ENTRIES: [DbGainEntry; 13] = [
1627        DbGainEntry {
1628            db_tenths: -600,
1629            gain: 65,
1630        },
1631        DbGainEntry {
1632            db_tenths: -540,
1633            gain: 131,
1634        },
1635        DbGainEntry {
1636            db_tenths: -480,
1637            gain: 261,
1638        },
1639        DbGainEntry {
1640            db_tenths: -420,
1641            gain: 520,
1642        },
1643        DbGainEntry {
1644            db_tenths: -360,
1645            gain: 1038,
1646        },
1647        DbGainEntry {
1648            db_tenths: -300,
1649            gain: 2073,
1650        },
1651        DbGainEntry {
1652            db_tenths: -240,
1653            gain: 4135,
1654        },
1655        DbGainEntry {
1656            db_tenths: -180,
1657            gain: 8250,
1658        },
1659        DbGainEntry {
1660            db_tenths: -120,
1661            gain: 16462,
1662        },
1663        DbGainEntry {
1664            db_tenths: -60,
1665            gain: 32845,
1666        },
1667        DbGainEntry {
1668            db_tenths: 0,
1669            gain: FP_ONE,
1670        },
1671        DbGainEntry {
1672            db_tenths: 120,
1673            gain: 260921,
1674        },
1675        DbGainEntry {
1676            db_tenths: 200,
1677            gain: 655360,
1678        },
1679    ];
1680
1681    // Clamp to range
1682    let db = if db_tenths > 200 { 200 } else { db_tenths };
1683
1684    // Find the two surrounding entries and interpolate
1685    let mut i = 0;
1686    while i < ENTRIES.len() - 1 {
1687        if db <= ENTRIES[i + 1].db_tenths {
1688            break;
1689        }
1690        i += 1;
1691    }
1692    if i >= ENTRIES.len() - 1 {
1693        return GainFactor(ENTRIES[ENTRIES.len() - 1].gain);
1694    }
1695
1696    let lo = &ENTRIES[i];
1697    let hi = &ENTRIES[i + 1];
1698    let range = hi.db_tenths - lo.db_tenths;
1699    if range == 0 {
1700        return GainFactor(lo.gain);
1701    }
1702
1703    // Linear interpolation: gain = lo.gain + (hi.gain - lo.gain) * (db -
1704    // lo.db_tenths) / range
1705    let frac_num = db - lo.db_tenths;
1706    let gain_diff = hi.gain as i64 - lo.gain as i64;
1707    let interpolated = lo.gain as i64 + (gain_diff * frac_num as i64) / range as i64;
1708
1709    GainFactor(interpolated as i32)
1710}
1711
1712/// Convert a 0-100 volume percentage to a gain factor
1713///
1714/// 0 = mute, 100 = unity gain (0 dB). Uses a perceptual curve.
1715pub(crate) fn gain_from_percent(percent: u32) -> GainFactor {
1716    if percent == 0 {
1717        return GainFactor::MUTE;
1718    }
1719    if percent >= 100 {
1720        return GainFactor::UNITY;
1721    }
1722
1723    // Map 0-100 to -60dB..0dB using a perceptual curve
1724    // percent 100 -> 0 dB (0 tenths)
1725    // percent 50  -> ~-18 dB (-180 tenths)
1726    // percent 1   -> ~-60 dB (-600 tenths)
1727    //
1728    // Use quadratic mapping: db_tenths = -600 * (100 - percent)^2 / 10000
1729    let inv = (100 - percent) as i64;
1730    let db_tenths = -((600 * inv * inv) / 10000) as i32;
1731
1732    gain_from_db_tenths(db_tenths)
1733}
1734
1735// ============================================================================
1736// Capture Pipeline
1737// ============================================================================
1738
1739/// Capture device state
1740#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1741pub enum CaptureState {
1742    /// Not capturing
1743    #[default]
1744    Idle,
1745    /// Actively capturing audio
1746    Recording,
1747    /// Paused
1748    Paused,
1749}
1750
1751/// Statistics for a capture device
1752#[derive(Debug, Clone, Copy, Default)]
1753pub struct CaptureStats {
1754    /// Total frames captured
1755    pub frames_captured: u64,
1756    /// Number of overrun events (data lost due to full buffer)
1757    pub overruns: u64,
1758    /// Current buffer fill level in frames
1759    pub buffer_fill: u32,
1760    /// Buffer capacity in frames
1761    pub buffer_capacity: u32,
1762}
1763
1764/// Audio capture device with ring buffer and overrun detection
1765pub struct CaptureDevice {
1766    /// Device identifier
1767    pub id: u32,
1768    /// Device name
1769    pub name: String,
1770    /// Capture state
1771    state: CaptureState,
1772    /// Hardware parameters
1773    hw_params: HwParams,
1774    /// Ring buffer for captured audio data (bytes)
1775    buffer: Vec<u8>,
1776    /// Read position (byte offset)
1777    read_pos: u32,
1778    /// Write position (byte offset)
1779    write_pos: u32,
1780    /// Total frames captured
1781    frames_captured: AtomicU64,
1782    /// Number of overrun events
1783    overruns: AtomicU64,
1784    /// Capture gain factor (16.16 fixed-point)
1785    gain: GainFactor,
1786}
1787
1788impl CaptureDevice {
1789    /// Create a new capture device
1790    pub fn new(id: u32, name: &str, hw_params: HwParams) -> Self {
1791        let buf_bytes =
1792            (CAPTURE_BUFFER_FRAMES as usize).saturating_mul(hw_params.frame_size() as usize);
1793        Self {
1794            id,
1795            name: String::from(name),
1796            state: CaptureState::Idle,
1797            hw_params,
1798            buffer: vec![0u8; buf_bytes],
1799            read_pos: 0,
1800            write_pos: 0,
1801            frames_captured: AtomicU64::new(0),
1802            overruns: AtomicU64::new(0),
1803            gain: GainFactor::UNITY,
1804        }
1805    }
1806
1807    /// Start recording
1808    pub(crate) fn start(&mut self) -> Result<(), AlsaError> {
1809        if self.state != CaptureState::Idle && self.state != CaptureState::Paused {
1810            return Err(AlsaError::InvalidStateTransition {
1811                current: PcmState::Running,
1812                requested: PcmState::Running,
1813            });
1814        }
1815        self.state = CaptureState::Recording;
1816        Ok(())
1817    }
1818
1819    /// Stop recording
1820    pub(crate) fn stop(&mut self) {
1821        self.state = CaptureState::Idle;
1822        self.read_pos = 0;
1823        self.write_pos = 0;
1824    }
1825
1826    /// Pause recording
1827    pub(crate) fn pause(&mut self) {
1828        if self.state == CaptureState::Recording {
1829            self.state = CaptureState::Paused;
1830        }
1831    }
1832
1833    /// Resume recording
1834    pub(crate) fn resume(&mut self) {
1835        if self.state == CaptureState::Paused {
1836            self.state = CaptureState::Recording;
1837        }
1838    }
1839
1840    /// Push captured audio data from hardware into the ring buffer
1841    ///
1842    /// This is called by the audio driver/interrupt handler when new data
1843    /// arrives from the hardware. If the buffer is full, an overrun occurs.
1844    pub(crate) fn push_data(&mut self, data: &[u8]) -> Result<u32, AlsaError> {
1845        if self.state != CaptureState::Recording {
1846            return Ok(0);
1847        }
1848
1849        let frame_size = self.hw_params.frame_size();
1850        if frame_size == 0 {
1851            return Ok(0);
1852        }
1853
1854        let cap = self.buffer.len() as u32;
1855        let avail = self.available_write(cap);
1856        let to_write = (data.len() as u32).min(avail);
1857        let to_write = (to_write / frame_size) * frame_size;
1858
1859        if to_write == 0 && !data.is_empty() {
1860            // Overrun
1861            let lost = data.len() as u32 / frame_size;
1862            self.overruns.fetch_add(1, Ordering::Relaxed);
1863            return Err(AlsaError::Overrun { lost_frames: lost });
1864        }
1865
1866        // Apply gain if not unity
1867        if self.gain != GainFactor::UNITY && self.gain != GainFactor::MUTE {
1868            // Apply gain to S16 samples
1869            if self.hw_params.format == PcmFormat::S16Le {
1870                let tw = to_write as usize;
1871                let wp = self.write_pos as usize;
1872                let c = cap as usize;
1873
1874                // Process in S16 chunks
1875                let mut src_offset = 0;
1876                let mut dst_offset = wp;
1877                let mut remaining = tw;
1878
1879                while remaining >= 2 {
1880                    let sample = i16::from_le_bytes([data[src_offset], data[src_offset + 1]]);
1881                    let gained = self.gain.apply_s16(sample);
1882                    let bytes = gained.to_le_bytes();
1883                    self.buffer[dst_offset % c] = bytes[0];
1884                    self.buffer[(dst_offset + 1) % c] = bytes[1];
1885                    src_offset += 2;
1886                    dst_offset += 2;
1887                    remaining -= 2;
1888                }
1889
1890                self.write_pos = (dst_offset % c) as u32;
1891            } else {
1892                // For other formats, copy raw data
1893                self.copy_to_ring(data, to_write);
1894            }
1895        } else if self.gain == GainFactor::MUTE {
1896            // Muted: write silence
1897            let tw = to_write as usize;
1898            let wp = self.write_pos as usize;
1899            let c = cap as usize;
1900            let first = (c - wp).min(tw);
1901            let second = tw - first;
1902            for b in &mut self.buffer[wp..wp + first] {
1903                *b = 0;
1904            }
1905            if second > 0 {
1906                for b in &mut self.buffer[..second] {
1907                    *b = 0;
1908                }
1909            }
1910            self.write_pos = ((wp + tw) % c) as u32;
1911        } else {
1912            self.copy_to_ring(data, to_write);
1913        }
1914
1915        let frames = to_write / frame_size;
1916        self.frames_captured
1917            .fetch_add(frames as u64, Ordering::Relaxed);
1918        Ok(frames)
1919    }
1920
1921    /// Read captured audio data from the ring buffer
1922    ///
1923    /// Returns the number of frames read. Called by the application/client.
1924    pub(crate) fn read_data(&mut self, output: &mut [u8]) -> u32 {
1925        let frame_size = self.hw_params.frame_size();
1926        if frame_size == 0 {
1927            return 0;
1928        }
1929
1930        let cap = self.buffer.len() as u32;
1931        let avail = self.available_read(cap);
1932        let to_read = (output.len() as u32).min(avail);
1933        let to_read = (to_read / frame_size) * frame_size;
1934
1935        if to_read == 0 {
1936            return 0;
1937        }
1938
1939        let rp = self.read_pos as usize;
1940        let c = cap as usize;
1941        let tr = to_read as usize;
1942
1943        let first = (c - rp).min(tr);
1944        let second = tr - first;
1945
1946        output[..first].copy_from_slice(&self.buffer[rp..rp + first]);
1947        if second > 0 {
1948            output[first..first + second].copy_from_slice(&self.buffer[..second]);
1949        }
1950
1951        self.read_pos = ((rp + tr) % c) as u32;
1952        to_read / frame_size
1953    }
1954
1955    /// Get capture statistics
1956    pub(crate) fn stats(&self) -> CaptureStats {
1957        let cap = self.buffer.len() as u32;
1958        let frame_size = self.hw_params.frame_size();
1959        let fill_bytes = self.available_read(cap);
1960        let fill_frames = if frame_size > 0 {
1961            fill_bytes / frame_size
1962        } else {
1963            0
1964        };
1965        let cap_frames = if frame_size > 0 { cap / frame_size } else { 0 };
1966        CaptureStats {
1967            frames_captured: self.frames_captured.load(Ordering::Relaxed),
1968            overruns: self.overruns.load(Ordering::Relaxed),
1969            buffer_fill: fill_frames,
1970            buffer_capacity: cap_frames,
1971        }
1972    }
1973
1974    /// Get capture state
1975    pub(crate) fn state(&self) -> CaptureState {
1976        self.state
1977    }
1978
1979    /// Set capture gain
1980    pub(crate) fn set_gain(&mut self, gain: GainFactor) {
1981        self.gain = gain;
1982    }
1983
1984    /// Get capture gain
1985    pub(crate) fn gain(&self) -> GainFactor {
1986        self.gain
1987    }
1988
1989    /// Get hardware parameters
1990    pub(crate) fn hw_params(&self) -> &HwParams {
1991        &self.hw_params
1992    }
1993
1994    // --- Internal helpers ---
1995
1996    fn available_write(&self, capacity: u32) -> u32 {
1997        if capacity == 0 {
1998            return 0;
1999        }
2000        let used = if self.write_pos >= self.read_pos {
2001            self.write_pos - self.read_pos
2002        } else {
2003            capacity - self.read_pos + self.write_pos
2004        };
2005        capacity.saturating_sub(used).saturating_sub(1)
2006    }
2007
2008    fn available_read(&self, capacity: u32) -> u32 {
2009        if capacity == 0 {
2010            return 0;
2011        }
2012        if self.write_pos >= self.read_pos {
2013            self.write_pos - self.read_pos
2014        } else {
2015            capacity - self.read_pos + self.write_pos
2016        }
2017    }
2018
2019    fn copy_to_ring(&mut self, data: &[u8], to_write: u32) {
2020        let wp = self.write_pos as usize;
2021        let c = self.buffer.len();
2022        let tw = to_write as usize;
2023        let first = (c - wp).min(tw);
2024        let second = tw - first;
2025        self.buffer[wp..wp + first].copy_from_slice(&data[..first]);
2026        if second > 0 {
2027            self.buffer[..second].copy_from_slice(&data[first..first + second]);
2028        }
2029        self.write_pos = ((wp + tw) % c) as u32;
2030    }
2031}
2032
2033// ============================================================================
2034// Device Registry
2035// ============================================================================
2036
2037/// Information about a registered audio device
2038#[derive(Debug, Clone)]
2039pub struct DeviceInfo {
2040    /// Device identifier
2041    pub id: u32,
2042    /// Device name
2043    pub name: String,
2044    /// Whether it supports playback
2045    pub playback: bool,
2046    /// Whether it supports capture
2047    pub capture: bool,
2048    /// Maximum supported sample rate
2049    pub max_sample_rate: u32,
2050    /// Maximum supported channels
2051    pub max_channels: u8,
2052    /// Supported formats
2053    pub formats: Vec<PcmFormat>,
2054}
2055
2056/// Registry of available audio devices
2057pub struct DeviceRegistry {
2058    /// Registered devices
2059    devices: BTreeMap<u32, DeviceInfo>,
2060    /// Next device ID
2061    next_id: AtomicU32,
2062}
2063
2064impl DeviceRegistry {
2065    /// Create a new empty registry
2066    pub fn new() -> Self {
2067        Self {
2068            devices: BTreeMap::new(),
2069            next_id: AtomicU32::new(1),
2070        }
2071    }
2072
2073    /// Register a new audio device
2074    pub(crate) fn register(&mut self, info: DeviceInfo) -> Result<u32, AlsaError> {
2075        if self.devices.len() >= MAX_PCM_DEVICES {
2076            return Err(AlsaError::TooManyDevices);
2077        }
2078        let id = info.id;
2079        self.devices.insert(id, info);
2080        Ok(id)
2081    }
2082
2083    /// Unregister a device
2084    pub(crate) fn unregister(&mut self, id: u32) -> Result<(), AlsaError> {
2085        self.devices
2086            .remove(&id)
2087            .map(|_| ())
2088            .ok_or(AlsaError::DeviceNotFound { device_id: id })
2089    }
2090
2091    /// Get device info by ID
2092    pub(crate) fn get(&self, id: u32) -> Result<&DeviceInfo, AlsaError> {
2093        self.devices
2094            .get(&id)
2095            .ok_or(AlsaError::DeviceNotFound { device_id: id })
2096    }
2097
2098    /// List all registered devices
2099    pub(crate) fn list(&self) -> Vec<&DeviceInfo> {
2100        self.devices.values().collect()
2101    }
2102
2103    /// List only playback devices
2104    pub(crate) fn list_playback(&self) -> Vec<&DeviceInfo> {
2105        self.devices.values().filter(|d| d.playback).collect()
2106    }
2107
2108    /// List only capture devices
2109    pub(crate) fn list_capture(&self) -> Vec<&DeviceInfo> {
2110        self.devices.values().filter(|d| d.capture).collect()
2111    }
2112
2113    /// Number of registered devices
2114    pub(crate) fn device_count(&self) -> usize {
2115        self.devices.len()
2116    }
2117
2118    /// Allocate a new device ID
2119    pub(crate) fn alloc_id(&self) -> u32 {
2120        self.next_id.fetch_add(1, Ordering::Relaxed)
2121    }
2122}
2123
2124impl Default for DeviceRegistry {
2125    fn default() -> Self {
2126        Self::new()
2127    }
2128}
2129
2130// ============================================================================
2131// Global State
2132// ============================================================================
2133
2134static ALSA_MIXER: spin::Mutex<Option<AlsaMixer>> = spin::Mutex::new(None);
2135static DEVICE_REGISTRY: spin::Mutex<Option<DeviceRegistry>> = spin::Mutex::new(None);
2136
2137/// Initialize the ALSA subsystem
2138pub fn init() {
2139    {
2140        let mut mixer = ALSA_MIXER.lock();
2141        *mixer = Some(AlsaMixer::new());
2142    }
2143    {
2144        let mut registry = DEVICE_REGISTRY.lock();
2145        let mut reg = DeviceRegistry::new();
2146
2147        // Register default devices
2148        let _ = reg.register(DeviceInfo {
2149            id: 0,
2150            name: String::from("default"),
2151            playback: true,
2152            capture: true,
2153            max_sample_rate: 192000,
2154            max_channels: 8,
2155            formats: vec![PcmFormat::U8, PcmFormat::S16Le, PcmFormat::S32Le],
2156        });
2157
2158        *registry = Some(reg);
2159    }
2160}
2161
2162/// Access the ALSA mixer through a closure
2163pub fn with_alsa_mixer<R, F: FnOnce(&AlsaMixer) -> R>(f: F) -> Option<R> {
2164    let guard = ALSA_MIXER.lock();
2165    guard.as_ref().map(f)
2166}
2167
2168/// Access the ALSA mixer mutably through a closure
2169pub fn with_alsa_mixer_mut<R, F: FnOnce(&mut AlsaMixer) -> R>(f: F) -> Option<R> {
2170    let mut guard = ALSA_MIXER.lock();
2171    guard.as_mut().map(f)
2172}
2173
2174/// Access the device registry through a closure
2175pub fn with_registry<R, F: FnOnce(&DeviceRegistry) -> R>(f: F) -> Option<R> {
2176    let guard = DEVICE_REGISTRY.lock();
2177    guard.as_ref().map(f)
2178}
2179
2180/// Access the device registry mutably through a closure
2181pub fn with_registry_mut<R, F: FnOnce(&mut DeviceRegistry) -> R>(f: F) -> Option<R> {
2182    let mut guard = DEVICE_REGISTRY.lock();
2183    guard.as_mut().map(f)
2184}
2185
2186// ============================================================================
2187// Tests
2188// ============================================================================
2189
2190#[cfg(test)]
2191mod tests {
2192    #[allow(unused_imports)]
2193    use alloc::vec;
2194
2195    use super::*;
2196
2197    // --- PCM State Machine Tests ---
2198
2199    #[test]
2200    fn test_pcm_state_valid_transitions() {
2201        assert!(PcmState::Open.can_transition_to(PcmState::Setup));
2202        assert!(PcmState::Setup.can_transition_to(PcmState::Prepared));
2203        assert!(PcmState::Prepared.can_transition_to(PcmState::Running));
2204        assert!(PcmState::Running.can_transition_to(PcmState::Paused));
2205        assert!(PcmState::Running.can_transition_to(PcmState::Draining));
2206        assert!(PcmState::Running.can_transition_to(PcmState::XRun));
2207        assert!(PcmState::Paused.can_transition_to(PcmState::Running));
2208        assert!(PcmState::XRun.can_transition_to(PcmState::Prepared));
2209        assert!(PcmState::Draining.can_transition_to(PcmState::Prepared));
2210    }
2211
2212    #[test]
2213    fn test_pcm_state_invalid_transitions() {
2214        assert!(!PcmState::Open.can_transition_to(PcmState::Running));
2215        assert!(!PcmState::Open.can_transition_to(PcmState::Paused));
2216        assert!(!PcmState::Setup.can_transition_to(PcmState::Running));
2217        assert!(!PcmState::XRun.can_transition_to(PcmState::Running));
2218        assert!(!PcmState::Draining.can_transition_to(PcmState::Running));
2219    }
2220
2221    // --- HwParams Tests ---
2222
2223    #[test]
2224    fn test_hw_params_defaults() {
2225        let params = HwParams::new();
2226        assert_eq!(params.sample_rate, DEFAULT_SAMPLE_RATE);
2227        assert_eq!(params.channels, DEFAULT_CHANNELS);
2228        assert_eq!(params.format, PcmFormat::S16Le);
2229        assert_eq!(params.buffer_size, DEFAULT_BUFFER_FRAMES);
2230        assert_eq!(params.period_size, DEFAULT_PERIOD_FRAMES);
2231    }
2232
2233    #[test]
2234    fn test_hw_params_validation_valid() {
2235        let params = HwParams::new();
2236        assert!(params.validate().is_ok());
2237    }
2238
2239    #[test]
2240    fn test_hw_params_validation_invalid_rate() {
2241        let params = HwParams {
2242            sample_rate: 12345,
2243            ..HwParams::new()
2244        };
2245        assert_eq!(
2246            params.validate(),
2247            Err(AlsaError::InvalidSampleRate { rate: 12345 })
2248        );
2249    }
2250
2251    #[test]
2252    fn test_hw_params_validation_invalid_channels() {
2253        let params = HwParams {
2254            channels: 0,
2255            ..HwParams::new()
2256        };
2257        assert_eq!(
2258            params.validate(),
2259            Err(AlsaError::InvalidChannels { count: 0 })
2260        );
2261
2262        let params2 = HwParams {
2263            channels: 9,
2264            ..HwParams::new()
2265        };
2266        assert_eq!(
2267            params2.validate(),
2268            Err(AlsaError::InvalidChannels { count: 9 })
2269        );
2270    }
2271
2272    #[test]
2273    fn test_hw_params_validation_period_exceeds_buffer() {
2274        let params = HwParams {
2275            buffer_size: 1024,
2276            period_size: 2048,
2277            ..HwParams::new()
2278        };
2279        assert_eq!(
2280            params.validate(),
2281            Err(AlsaError::InvalidPeriodSize {
2282                requested: 2048,
2283                buffer_size: 1024,
2284            })
2285        );
2286    }
2287
2288    #[test]
2289    fn test_hw_params_frame_size() {
2290        let params = HwParams {
2291            channels: 2,
2292            format: PcmFormat::S16Le,
2293            ..HwParams::new()
2294        };
2295        assert_eq!(params.frame_size(), 4); // 2 channels * 2 bytes
2296
2297        let params32 = HwParams {
2298            channels: 2,
2299            format: PcmFormat::S32Le,
2300            ..HwParams::new()
2301        };
2302        assert_eq!(params32.frame_size(), 8); // 2 channels * 4 bytes
2303    }
2304
2305    #[test]
2306    fn test_hw_params_byte_rate() {
2307        let params = HwParams {
2308            sample_rate: 48000,
2309            channels: 2,
2310            format: PcmFormat::S16Le,
2311            ..HwParams::new()
2312        };
2313        assert_eq!(params.byte_rate(), 48000 * 4);
2314    }
2315
2316    // --- PCM Device Tests ---
2317
2318    #[test]
2319    fn test_pcm_device_open_close() {
2320        let mut dev = PcmDevice::new(0, "test", StreamDirection::Playback);
2321        assert!(!dev.is_open());
2322
2323        assert!(dev.open().is_ok());
2324        assert!(dev.is_open());
2325
2326        // Double open should fail
2327        assert_eq!(
2328            dev.open(),
2329            Err(AlsaError::DeviceAlreadyOpen { device_id: 0 })
2330        );
2331
2332        assert!(dev.close().is_ok());
2333        assert!(!dev.is_open());
2334
2335        // Double close should fail
2336        assert_eq!(dev.close(), Err(AlsaError::DeviceNotOpen { device_id: 0 }));
2337    }
2338
2339    #[test]
2340    fn test_pcm_device_lifecycle() {
2341        let mut dev = PcmDevice::new(0, "test", StreamDirection::Playback);
2342        dev.open().unwrap();
2343        assert_eq!(dev.state(), PcmState::Open);
2344
2345        dev.set_hw_params(HwParams::new()).unwrap();
2346        assert_eq!(dev.state(), PcmState::Setup);
2347
2348        dev.prepare().unwrap();
2349        assert_eq!(dev.state(), PcmState::Prepared);
2350
2351        dev.start().unwrap();
2352        assert_eq!(dev.state(), PcmState::Running);
2353
2354        dev.pause().unwrap();
2355        assert_eq!(dev.state(), PcmState::Paused);
2356
2357        dev.resume().unwrap();
2358        assert_eq!(dev.state(), PcmState::Running);
2359
2360        dev.stop().unwrap();
2361        assert_eq!(dev.state(), PcmState::Prepared);
2362    }
2363
2364    #[test]
2365    fn test_pcm_device_write() {
2366        let mut dev = PcmDevice::new(0, "test", StreamDirection::Playback);
2367        dev.open().unwrap();
2368        dev.set_hw_params(HwParams {
2369            channels: 1,
2370            format: PcmFormat::S16Le,
2371            buffer_size: 64,
2372            period_size: 16,
2373            ..HwParams::new()
2374        })
2375        .unwrap();
2376        dev.prepare().unwrap();
2377        dev.start().unwrap();
2378
2379        // Write 4 frames of S16 mono = 8 bytes
2380        let data = [0u8, 0, 1, 0, 2, 0, 3, 0];
2381        let frames = dev.write(&data).unwrap();
2382        assert_eq!(frames, 4);
2383        assert_eq!(dev.total_frames_written(), 4);
2384    }
2385
2386    #[test]
2387    fn test_pcm_device_write_wrong_direction() {
2388        let mut dev = PcmDevice::new(0, "test", StreamDirection::Capture);
2389        dev.open().unwrap();
2390        dev.set_hw_params(HwParams::new()).unwrap();
2391        dev.prepare().unwrap();
2392        dev.start().unwrap();
2393
2394        let data = [0u8; 8];
2395        assert_eq!(dev.write(&data), Err(AlsaError::WrongDirection));
2396    }
2397
2398    #[test]
2399    fn test_pcm_device_read_capture() {
2400        let mut dev = PcmDevice::new(0, "test", StreamDirection::Capture);
2401        dev.open().unwrap();
2402        let hw = HwParams {
2403            channels: 1,
2404            format: PcmFormat::S16Le,
2405            buffer_size: 64,
2406            period_size: 16,
2407            ..HwParams::new()
2408        };
2409        dev.set_hw_params(hw).unwrap();
2410        dev.prepare().unwrap();
2411        dev.start().unwrap();
2412
2413        // Push some data into the capture buffer
2414        let input = [10u8, 0, 20, 0, 30, 0, 40, 0];
2415        let pushed = dev.push_capture_data(&input).unwrap();
2416        assert_eq!(pushed, 4); // 4 frames
2417
2418        // Read it back
2419        let mut output = [0u8; 8];
2420        let frames = dev.read(&mut output).unwrap();
2421        assert_eq!(frames, 4);
2422        assert_eq!(output, input);
2423    }
2424
2425    // --- Sample Conversion Tests ---
2426
2427    #[test]
2428    fn test_convert_u8_to_s16() {
2429        assert_eq!(convert_u8_to_s16(128), 0); // center
2430        assert_eq!(convert_u8_to_s16(0), -32768); // min
2431        assert_eq!(convert_u8_to_s16(255), 32512); // near max
2432    }
2433
2434    #[test]
2435    fn test_convert_s16_to_u8() {
2436        assert_eq!(convert_s16_to_u8(0), 128); // center
2437        assert_eq!(convert_s16_to_u8(-32768), 0); // min
2438        assert_eq!(convert_s16_to_u8(32512), 255); // near max (32512/256 = 127
2439                                                   // + 128)
2440    }
2441
2442    #[test]
2443    fn test_convert_s16_s32_roundtrip() {
2444        let original: i16 = 1234;
2445        let s32 = convert_s16_to_s32(original);
2446        let back = convert_s32_to_s16(s32);
2447        assert_eq!(back, original);
2448    }
2449
2450    #[test]
2451    fn test_convert_s32_to_s16_saturation() {
2452        assert_eq!(convert_s32_to_s16(i32::MAX), i16::MAX);
2453        assert_eq!(convert_s32_to_s16(i32::MIN), i16::MIN);
2454    }
2455
2456    #[test]
2457    fn test_convert_buffer_s16_to_s32() {
2458        let input_sample: i16 = 1000;
2459        let input = input_sample.to_le_bytes();
2460        let mut output = Vec::new();
2461
2462        convert_buffer(&input, PcmFormat::S16Le, PcmFormat::S32Le, &mut output);
2463        assert_eq!(output.len(), 4);
2464
2465        let result = i32::from_le_bytes([output[0], output[1], output[2], output[3]]);
2466        assert_eq!(result, convert_s16_to_s32(1000));
2467    }
2468
2469    #[test]
2470    fn test_convert_buffer_same_format() {
2471        let input = [1u8, 2, 3, 4];
2472        let mut output = Vec::new();
2473        convert_buffer(&input, PcmFormat::S16Le, PcmFormat::S16Le, &mut output);
2474        assert_eq!(output, input);
2475    }
2476
2477    // --- Mixer Control Tests ---
2478
2479    #[test]
2480    fn test_mixer_default_controls() {
2481        let mixer = AlsaMixer::new();
2482        assert_eq!(mixer.control_count(), 5);
2483
2484        // Master volume
2485        let master = mixer.get_control(MIXER_MASTER_VOLUME).unwrap();
2486        assert_eq!(master.get_value(), 80);
2487        assert_eq!(master.min, 0);
2488        assert_eq!(master.max, 100);
2489
2490        // PCM volume
2491        let pcm = mixer.get_control(MIXER_PCM_VOLUME).unwrap();
2492        assert_eq!(pcm.get_value(), 100);
2493    }
2494
2495    #[test]
2496    fn test_mixer_set_volume() {
2497        let mixer = AlsaMixer::new();
2498        assert!(mixer.set_volume(MIXER_MASTER_VOLUME, 50).is_ok());
2499        assert_eq!(mixer.get_volume(MIXER_MASTER_VOLUME).unwrap(), 50);
2500    }
2501
2502    #[test]
2503    fn test_mixer_volume_out_of_range() {
2504        let mixer = AlsaMixer::new();
2505        assert_eq!(
2506            mixer.set_volume(MIXER_MASTER_VOLUME, 101),
2507            Err(AlsaError::MixerValueOutOfRange {
2508                value: 101,
2509                min: 0,
2510                max: 100,
2511            })
2512        );
2513    }
2514
2515    #[test]
2516    fn test_mixer_boolean_control() {
2517        let mixer = AlsaMixer::new();
2518        let switch = mixer.get_control(MIXER_MASTER_SWITCH).unwrap();
2519        assert!(switch.get_bool()); // Default: unmuted
2520
2521        switch.set_bool(false);
2522        assert!(!switch.get_bool());
2523
2524        switch.set_bool(true);
2525        assert!(switch.get_bool());
2526    }
2527
2528    #[test]
2529    fn test_mixer_control_not_found() {
2530        let mixer = AlsaMixer::new();
2531        assert!(matches!(
2532            mixer.get_control(999),
2533            Err(AlsaError::MixerControlNotFound { id: 999 })
2534        ));
2535    }
2536
2537    // --- Gain Control Tests ---
2538
2539    #[test]
2540    fn test_gain_unity() {
2541        assert_eq!(GainFactor::UNITY.raw(), FP_ONE);
2542    }
2543
2544    #[test]
2545    fn test_gain_mute() {
2546        assert_eq!(GainFactor::MUTE.raw(), 0);
2547    }
2548
2549    #[test]
2550    fn test_gain_apply_s16_unity() {
2551        let sample: i16 = 16384;
2552        let result = GainFactor::UNITY.apply_s16(sample);
2553        assert_eq!(result, sample);
2554    }
2555
2556    #[test]
2557    fn test_gain_apply_s16_mute() {
2558        let sample: i16 = 16384;
2559        let result = GainFactor::MUTE.apply_s16(sample);
2560        assert_eq!(result, 0);
2561    }
2562
2563    #[test]
2564    fn test_gain_from_db_tenths_zero() {
2565        let gain = gain_from_db_tenths(0);
2566        assert_eq!(gain.raw(), FP_ONE);
2567    }
2568
2569    #[test]
2570    fn test_gain_from_db_tenths_mute() {
2571        let gain = gain_from_db_tenths(-700);
2572        assert_eq!(gain, GainFactor::MUTE);
2573    }
2574
2575    #[test]
2576    fn test_gain_from_percent_boundaries() {
2577        let mute = gain_from_percent(0);
2578        assert_eq!(mute, GainFactor::MUTE);
2579
2580        let full = gain_from_percent(100);
2581        assert_eq!(full, GainFactor::UNITY);
2582    }
2583
2584    #[test]
2585    fn test_gain_from_percent_mid() {
2586        let mid = gain_from_percent(50);
2587        // At 50%, gain should be less than unity but greater than 0
2588        assert!(mid.raw() > 0);
2589        assert!(mid.raw() < FP_ONE);
2590    }
2591
2592    // --- Capture Device Tests ---
2593
2594    #[test]
2595    fn test_capture_device_lifecycle() {
2596        let hw = HwParams {
2597            channels: 1,
2598            format: PcmFormat::S16Le,
2599            ..HwParams::new()
2600        };
2601        let mut cap = CaptureDevice::new(0, "mic", hw);
2602        assert_eq!(cap.state(), CaptureState::Idle);
2603
2604        cap.start().unwrap();
2605        assert_eq!(cap.state(), CaptureState::Recording);
2606
2607        cap.pause();
2608        assert_eq!(cap.state(), CaptureState::Paused);
2609
2610        cap.resume();
2611        assert_eq!(cap.state(), CaptureState::Recording);
2612
2613        cap.stop();
2614        assert_eq!(cap.state(), CaptureState::Idle);
2615    }
2616
2617    #[test]
2618    fn test_capture_device_push_read() {
2619        let hw = HwParams {
2620            channels: 1,
2621            format: PcmFormat::S16Le,
2622            ..HwParams::new()
2623        };
2624        let mut cap = CaptureDevice::new(0, "mic", hw);
2625        cap.start().unwrap();
2626
2627        // Push 4 frames (8 bytes of S16 mono)
2628        let input = [10u8, 0, 20, 0, 30, 0, 40, 0];
2629        let frames = cap.push_data(&input).unwrap();
2630        assert_eq!(frames, 4);
2631
2632        // Read back
2633        let mut output = [0u8; 8];
2634        let read = cap.read_data(&mut output);
2635        assert_eq!(read, 4);
2636        assert_eq!(output, input);
2637    }
2638
2639    #[test]
2640    fn test_capture_device_stats() {
2641        let hw = HwParams {
2642            channels: 1,
2643            format: PcmFormat::S16Le,
2644            ..HwParams::new()
2645        };
2646        let mut cap = CaptureDevice::new(0, "mic", hw);
2647        cap.start().unwrap();
2648
2649        let input = [0u8; 8]; // 4 frames
2650        let _ = cap.push_data(&input);
2651
2652        let stats = cap.stats();
2653        assert_eq!(stats.frames_captured, 4);
2654        assert_eq!(stats.overruns, 0);
2655        assert!(stats.buffer_capacity > 0);
2656    }
2657
2658    #[test]
2659    fn test_capture_device_gain() {
2660        let hw = HwParams {
2661            channels: 1,
2662            format: PcmFormat::S16Le,
2663            ..HwParams::new()
2664        };
2665        let mut cap = CaptureDevice::new(0, "mic", hw);
2666        assert_eq!(cap.gain(), GainFactor::UNITY);
2667
2668        cap.set_gain(GainFactor::MUTE);
2669        assert_eq!(cap.gain(), GainFactor::MUTE);
2670    }
2671
2672    // --- Device Registry Tests ---
2673
2674    #[test]
2675    fn test_device_registry_register() {
2676        let mut reg = DeviceRegistry::new();
2677        let info = DeviceInfo {
2678            id: 1,
2679            name: String::from("test"),
2680            playback: true,
2681            capture: false,
2682            max_sample_rate: 48000,
2683            max_channels: 2,
2684            formats: vec![PcmFormat::S16Le],
2685        };
2686        let id = reg.register(info).unwrap();
2687        assert_eq!(id, 1);
2688        assert_eq!(reg.device_count(), 1);
2689    }
2690
2691    #[test]
2692    fn test_device_registry_list_filtered() {
2693        let mut reg = DeviceRegistry::new();
2694        reg.register(DeviceInfo {
2695            id: 1,
2696            name: String::from("playback"),
2697            playback: true,
2698            capture: false,
2699            max_sample_rate: 48000,
2700            max_channels: 2,
2701            formats: vec![PcmFormat::S16Le],
2702        })
2703        .unwrap();
2704        reg.register(DeviceInfo {
2705            id: 2,
2706            name: String::from("capture"),
2707            playback: false,
2708            capture: true,
2709            max_sample_rate: 48000,
2710            max_channels: 1,
2711            formats: vec![PcmFormat::S16Le],
2712        })
2713        .unwrap();
2714
2715        assert_eq!(reg.list_playback().len(), 1);
2716        assert_eq!(reg.list_capture().len(), 1);
2717        assert_eq!(reg.list().len(), 2);
2718    }
2719
2720    #[test]
2721    fn test_device_registry_unregister() {
2722        let mut reg = DeviceRegistry::new();
2723        reg.register(DeviceInfo {
2724            id: 1,
2725            name: String::from("test"),
2726            playback: true,
2727            capture: false,
2728            max_sample_rate: 48000,
2729            max_channels: 2,
2730            formats: vec![PcmFormat::S16Le],
2731        })
2732        .unwrap();
2733        assert_eq!(reg.device_count(), 1);
2734
2735        reg.unregister(1).unwrap();
2736        assert_eq!(reg.device_count(), 0);
2737
2738        assert_eq!(
2739            reg.unregister(1),
2740            Err(AlsaError::DeviceNotFound { device_id: 1 })
2741        );
2742    }
2743
2744    // --- PcmFormat Tests ---
2745
2746    #[test]
2747    fn test_pcm_format_sizes() {
2748        assert_eq!(PcmFormat::U8.bytes_per_sample(), 1);
2749        assert_eq!(PcmFormat::S16Le.bytes_per_sample(), 2);
2750        assert_eq!(PcmFormat::S32Le.bytes_per_sample(), 4);
2751        assert_eq!(PcmFormat::F32FixedPoint.bytes_per_sample(), 4);
2752    }
2753
2754    #[test]
2755    fn test_pcm_format_bits() {
2756        assert_eq!(PcmFormat::U8.bits_per_sample(), 8);
2757        assert_eq!(PcmFormat::S16Le.bits_per_sample(), 16);
2758        assert_eq!(PcmFormat::S32Le.bits_per_sample(), 32);
2759    }
2760
2761    // --- SwParams Tests ---
2762
2763    #[test]
2764    fn test_sw_params_defaults() {
2765        let sw = SwParams::new(4096);
2766        assert_eq!(sw.avail_min, 1);
2767        assert_eq!(sw.start_threshold, 4096);
2768        assert_eq!(sw.stop_threshold, 4096);
2769    }
2770
2771    #[test]
2772    fn test_sw_params_validation() {
2773        let hw = HwParams::new();
2774        let sw = SwParams::new(hw.buffer_size);
2775        assert!(sw.validate(&hw).is_ok());
2776
2777        let bad_sw = SwParams {
2778            avail_min: 0,
2779            ..SwParams::new(hw.buffer_size)
2780        };
2781        assert!(bad_sw.validate(&hw).is_err());
2782    }
2783
2784    // --- XRun Recovery Test ---
2785
2786    #[test]
2787    fn test_pcm_device_xrun_recovery() {
2788        let mut dev = PcmDevice::new(0, "test", StreamDirection::Playback);
2789        dev.open().unwrap();
2790        dev.set_hw_params(HwParams::new()).unwrap();
2791        dev.prepare().unwrap();
2792        dev.start().unwrap();
2793
2794        // Simulate XRun
2795        dev.state = PcmState::XRun;
2796        assert_eq!(dev.state(), PcmState::XRun);
2797
2798        dev.recover_xrun().unwrap();
2799        assert_eq!(dev.state(), PcmState::Prepared);
2800    }
2801
2802    // --- MMAP Test ---
2803
2804    #[test]
2805    fn test_pcm_device_mmap() {
2806        let mut dev = PcmDevice::new(0, "test", StreamDirection::Playback);
2807        dev.open().unwrap();
2808        dev.set_hw_params(HwParams {
2809            channels: 1,
2810            format: PcmFormat::S16Le,
2811            buffer_size: 64,
2812            period_size: 16,
2813            ..HwParams::new()
2814        })
2815        .unwrap();
2816        dev.prepare().unwrap();
2817
2818        let (buf, offset, avail) = dev.mmap_begin().unwrap();
2819        assert!(!buf.is_empty());
2820        assert_eq!(offset, 0);
2821        assert!(avail > 0);
2822
2823        // Commit some frames
2824        dev.mmap_commit(4).unwrap();
2825        assert_eq!(dev.total_frames_written(), 4);
2826    }
2827
2828    // --- Enumerated Mixer Control Test ---
2829
2830    #[test]
2831    fn test_enumerated_mixer_control() {
2832        let items = vec![
2833            String::from("Input 1"),
2834            String::from("Input 2"),
2835            String::from("Input 3"),
2836        ];
2837        let ctrl = MixerControl::new_enumerated(10, "Capture Source", items, 0);
2838        assert_eq!(ctrl.control_type, MixerControlType::Enumerated);
2839        assert_eq!(ctrl.get_enum_name(), Some("Input 1"));
2840
2841        ctrl.set_value(2).unwrap();
2842        assert_eq!(ctrl.get_enum_name(), Some("Input 3"));
2843
2844        assert_eq!(
2845            ctrl.set_value(3),
2846            Err(AlsaError::MixerValueOutOfRange {
2847                value: 3,
2848                min: 0,
2849                max: 2,
2850            })
2851        );
2852    }
2853}