veridian_kernel/audio/
pipeline.rs1#![allow(dead_code)]
8
9use alloc::vec::Vec;
10use core::sync::atomic::{AtomicU64, Ordering};
11
12use crate::{audio::AudioConfig, error::KernelError};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum PipelineState {
21 Idle,
23 Running,
25 Draining,
27}
28
29#[derive(Debug, Clone, Copy)]
31pub struct PipelineStats {
32 pub frames_processed: u64,
34 pub underruns: u64,
36 pub state: PipelineState,
38}
39
40pub struct AudioPipeline {
49 state: PipelineState,
51 output_buffer: Vec<i16>,
53 output_config: AudioConfig,
55 frames_processed: AtomicU64,
57 underruns: AtomicU64,
59}
60
61impl AudioPipeline {
62 pub fn new(config: AudioConfig) -> Self {
64 let buffer_size = config.buffer_frames as usize * config.channels as usize;
65 Self {
66 state: PipelineState::Idle,
67 output_buffer: alloc::vec![0i16; buffer_size],
68 output_config: config,
69 frames_processed: AtomicU64::new(0),
70 underruns: AtomicU64::new(0),
71 }
72 }
73
74 pub fn process_frame(&mut self) -> &[i16] {
79 if self.state != PipelineState::Running {
80 for sample in self.output_buffer.iter_mut() {
82 *sample = 0;
83 }
84 return &self.output_buffer;
85 }
86
87 let result = crate::audio::mixer::with_mixer(|mixer| {
89 mixer.mix_to_output(&mut self.output_buffer);
90 });
91
92 match result {
93 Ok(()) => {
94 let all_silence = self.output_buffer.iter().all(|&s| s == 0);
96 if all_silence {
97 self.underruns.fetch_add(1, Ordering::Relaxed);
98 }
99
100 if !all_silence {
102 let _ = crate::audio::virtio_sound::with_device(|dev| {
103 let _ = dev.write_pcm(0, &self.output_buffer);
104 });
105 }
106
107 let frames = self.output_config.buffer_frames as u64;
108 self.frames_processed.fetch_add(frames, Ordering::Relaxed);
109 }
110 Err(_) => {
111 for sample in self.output_buffer.iter_mut() {
113 *sample = 0;
114 }
115 self.underruns.fetch_add(1, Ordering::Relaxed);
116 }
117 }
118
119 &self.output_buffer
120 }
121
122 pub fn start(&mut self) {
124 if self.state == PipelineState::Idle {
125 self.state = PipelineState::Running;
126 self.frames_processed.store(0, Ordering::Relaxed);
127 self.underruns.store(0, Ordering::Relaxed);
128 println!("[AUDIO] Pipeline started");
129 }
130 }
131
132 pub fn stop(&mut self) {
134 self.state = PipelineState::Idle;
135 for sample in self.output_buffer.iter_mut() {
137 *sample = 0;
138 }
139 println!("[AUDIO] Pipeline stopped");
140 }
141
142 pub fn drain(&mut self) {
144 if self.state == PipelineState::Running {
145 self.state = PipelineState::Draining;
146 println!("[AUDIO] Pipeline draining...");
147
148 self.process_frame_internal();
150
151 self.state = PipelineState::Idle;
152 println!("[AUDIO] Pipeline drain complete");
153 }
154 }
155
156 fn process_frame_internal(&mut self) {
158 let _ = crate::audio::mixer::with_mixer(|mixer| {
159 mixer.mix_to_output(&mut self.output_buffer);
160 });
161
162 let frames = self.output_config.buffer_frames as u64;
163 self.frames_processed.fetch_add(frames, Ordering::Relaxed);
164 }
165
166 pub fn stats(&self) -> PipelineStats {
168 PipelineStats {
169 frames_processed: self.frames_processed.load(Ordering::Relaxed),
170 underruns: self.underruns.load(Ordering::Relaxed),
171 state: self.state,
172 }
173 }
174
175 pub fn state(&self) -> PipelineState {
177 self.state
178 }
179
180 pub fn config(&self) -> &AudioConfig {
182 &self.output_config
183 }
184}
185
186static PIPELINE: spin::Mutex<Option<AudioPipeline>> = spin::Mutex::new(None);
191
192pub fn init(config: AudioConfig) -> Result<(), KernelError> {
194 let mut pipeline = PIPELINE.lock();
195 if pipeline.is_some() {
196 return Err(KernelError::InvalidState {
197 expected: "uninitialized",
198 actual: "already initialized",
199 });
200 }
201
202 *pipeline = Some(AudioPipeline::new(config));
203 println!(
204 "[AUDIO] Pipeline initialized: {} Hz, {} ch, {} frames/period",
205 config.sample_rate, config.channels, config.buffer_frames
206 );
207 Ok(())
208}
209
210pub fn with_pipeline<R, F: FnOnce(&mut AudioPipeline) -> R>(f: F) -> Result<R, KernelError> {
212 let mut guard = PIPELINE.lock();
213 match guard.as_mut() {
214 Some(pipeline) => Ok(f(pipeline)),
215 None => Err(KernelError::NotInitialized {
216 subsystem: "audio pipeline",
217 }),
218 }
219}
220
221#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::audio::SampleFormat;
229
230 fn test_config() -> AudioConfig {
231 AudioConfig {
232 sample_rate: 48000,
233 channels: 2,
234 format: SampleFormat::S16Le,
235 buffer_frames: 256,
236 }
237 }
238
239 #[test]
240 fn test_pipeline_creation() {
241 let config = test_config();
242 let pipeline = AudioPipeline::new(config);
243 assert_eq!(pipeline.state(), PipelineState::Idle);
244 assert_eq!(pipeline.output_buffer.len(), 256 * 2); }
247
248 #[test]
249 fn test_pipeline_start_stop() {
250 let config = test_config();
251 let mut pipeline = AudioPipeline::new(config);
252
253 assert_eq!(pipeline.state(), PipelineState::Idle);
254
255 pipeline.start();
256 assert_eq!(pipeline.state(), PipelineState::Running);
257
258 pipeline.stop();
259 assert_eq!(pipeline.state(), PipelineState::Idle);
260 }
261
262 #[test]
263 fn test_pipeline_stats_initial() {
264 let config = test_config();
265 let pipeline = AudioPipeline::new(config);
266 let stats = pipeline.stats();
267
268 assert_eq!(stats.frames_processed, 0);
269 assert_eq!(stats.underruns, 0);
270 assert_eq!(stats.state, PipelineState::Idle);
271 }
272
273 #[test]
274 fn test_pipeline_idle_silence() {
275 let config = test_config();
276 let mut pipeline = AudioPipeline::new(config);
277
278 let output = pipeline.process_frame();
280 for &sample in output {
281 assert_eq!(sample, 0);
282 }
283 }
284}