orinium_browser/platform/audio/
mod.rs1use crate::engine::bridge::audio::{AudioError, AudioSink, AudioSinkFactory};
4use crate::platform::io as platform_io;
5use anyhow::{Context, Result};
6use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
7use cpal::{SampleFormat, StreamConfig};
8use std::io::Cursor;
9use std::sync::{Arc, Mutex, MutexGuard};
10use std::time::Duration;
11use symphonia::core::audio::sample::Sample;
12use symphonia::core::codecs::audio::AudioDecoderOptions;
13use symphonia::core::errors::Error;
14use symphonia::core::formats::FormatOptions;
15use symphonia::core::formats::TrackType;
16use symphonia::core::formats::probe::Hint;
17use symphonia::core::io::MediaSourceStream;
18use symphonia::core::meta::MetadataOptions;
19use symphonia::default::{get_codecs, get_probe};
20
21const MAX_AUDIO_INPUT_BYTES: usize = 64 * 1024 * 1024;
24const MAX_DECODED_SAMPLES: usize = 50_000_000;
28
29pub struct SoundManager {
31 samples: Arc<Mutex<Vec<f32>>>,
33 play_pos: Arc<Mutex<usize>>,
35 src_channels: usize,
37 src_sample_rate: u32,
40 stream: Option<cpal::Stream>,
42}
43
44impl SoundManager {
45 pub fn init() -> Result<Arc<Mutex<Self>>> {
47 Ok(Arc::new(Mutex::new(Self::new())))
48 }
49
50 pub fn new() -> Self {
55 SoundManager {
56 samples: Arc::new(Mutex::new(Vec::new())),
57 play_pos: Arc::new(Mutex::new(0)),
58 src_channels: 0,
59 src_sample_rate: 0,
60 stream: None,
61 }
62 }
63
64 fn ensure_stream(&mut self) -> Result<()> {
66 if self.stream.is_some() {
67 return Ok(());
68 }
69
70 let host = cpal::default_host();
71 let device = host
72 .default_output_device()
73 .context("No default output device available")?;
74 let supported_cfg = device
75 .default_output_config()
76 .context("Failed to get default output config")?;
77 let config: StreamConfig = supported_cfg.into();
78 let sample_format = supported_cfg.sample_format();
79 let output_channels = config.channels as usize;
80
81 let samples = Arc::clone(&self.samples);
82 let play_pos = Arc::clone(&self.play_pos);
83 let src_channels = self.src_channels;
84
85 let err_fn = |err| log::error!("cpal stream error: {}", err);
86
87 let latency = Some(Duration::from_millis(100));
88
89 let stream = match sample_format {
90 SampleFormat::F32 => device.build_output_stream(
91 config,
92 move |data: &mut [f32], _| {
93 write_output_f32(data, src_channels, output_channels, &samples, &play_pos)
94 },
95 err_fn,
96 latency,
97 )?,
98 SampleFormat::I16 => device.build_output_stream(
99 config,
100 move |data: &mut [i16], _| {
101 write_output_i16(data, src_channels, output_channels, &samples, &play_pos)
102 },
103 err_fn,
104 latency,
105 )?,
106 SampleFormat::U16 => device.build_output_stream(
107 config,
108 move |data: &mut [u16], _| {
109 write_output_u16(data, src_channels, output_channels, &samples, &play_pos)
110 },
111 err_fn,
112 latency,
113 )?,
114 _ => {
115 return Err(anyhow::anyhow!(
116 "Unsupported sample format from output device"
117 ));
118 }
119 };
120
121 stream.play()?;
122 self.stream = Some(stream);
123 Ok(())
124 }
125
126 pub fn load_from_bytes(&mut self, data: &[u8]) -> Result<()> {
128 let (samples, channels, sample_rate) = decode(data)?;
129 {
131 let mut buf = self.samples.lock().unwrap_or_else(|e| e.into_inner());
132 *buf = samples;
133 }
134 {
136 let mut pos = self.play_pos.lock().unwrap_or_else(|e| e.into_inner());
137 *pos = 0;
138 }
139 self.src_channels = channels;
140 self.src_sample_rate = sample_rate;
141
142 Ok(())
143 }
144
145 pub fn play_from_bytes(&mut self, data: &[u8]) -> Result<()> {
147 self.load_from_bytes(data)?;
148
149 self.ensure_stream()?;
150
151 Ok(())
152 }
153
154 pub fn play_from_file(&mut self, path: &str) -> Result<()> {
156 let data = platform_io::load_local_file(path)
157 .with_context(|| format!("Failed to read local file: {}", path))?;
158 self.play_from_bytes(&data)
159 }
160
161 pub fn play_from_local_uri(&mut self, uri: &str) -> Result<()> {
166 if uri.starts_with("resource:") {
167 let rel = uri
168 .trim_start_matches("resource:///")
169 .trim_start_matches("resource://")
170 .trim_start_matches("resource:/")
171 .trim_start_matches("resource:");
172 let rel = rel.trim_start_matches('/');
173 let data = platform_io::load_resource(rel)
174 .with_context(|| format!("Failed to load resource: {}", rel))?;
175 return self.play_from_bytes(&data);
176 }
177 if uri.starts_with("file://") {
178 let p = uri.trim_start_matches("file://");
179 let data = platform_io::load_local_file(p)
180 .with_context(|| format!("Failed to read local file from URI: {}", p))?;
181 return self.play_from_bytes(&data);
182 }
183 let data = platform_io::load_local_file(uri)
184 .with_context(|| format!("Failed to read local file: {}", uri))?;
185 self.play_from_bytes(&data)
186 }
187
188 pub fn pause(&mut self) -> Result<()> {
190 if let Some(stream) = &self.stream {
191 stream.pause()?;
192 }
193 Ok(())
194 }
195
196 pub fn resume(&mut self) -> Result<()> {
198 if self.src_channels == 0 {
199 anyhow::bail!("Cannot resume before audio is loaded");
200 }
201 self.ensure_stream()?;
202 if let Some(stream) = &self.stream {
203 stream.play()?;
204 }
205 Ok(())
206 }
207
208 pub fn current_seconds(&self) -> f32 {
210 if self.src_sample_rate == 0 {
211 return 0.0;
212 }
213 let frame = *self.play_pos.lock().unwrap_or_else(|e| e.into_inner());
214 frame as f32 / self.src_sample_rate as f32
215 }
216
217 pub fn duration_seconds(&self) -> f32 {
219 if self.src_channels == 0 || self.src_sample_rate == 0 {
220 return 0.0;
221 }
222 let samples = self.samples.lock().unwrap_or_else(|e| e.into_inner());
223 let frames = samples.len() / self.src_channels;
224 frames as f32 / self.src_sample_rate as f32
225 }
226
227 pub fn is_finished(&self) -> bool {
229 let frame = *self.play_pos.lock().unwrap_or_else(|e| e.into_inner());
230 let samples = self.samples.lock().unwrap_or_else(|e| e.into_inner());
231 self.src_channels > 0 && frame >= samples.len() / self.src_channels
232 }
233}
234
235impl Default for SoundManager {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241pub struct PlatformAudioSink {
250 manager: Mutex<SoundManager>,
251}
252
253impl PlatformAudioSink {
254 pub fn new() -> Self {
256 Self {
257 manager: Mutex::new(SoundManager::new()),
258 }
259 }
260
261 fn lock(&self) -> MutexGuard<'_, SoundManager> {
263 self.manager.lock().unwrap_or_else(|e| e.into_inner())
264 }
265}
266
267impl Default for PlatformAudioSink {
268 fn default() -> Self {
269 Self::new()
270 }
271}
272
273impl AudioSink for PlatformAudioSink {
274 fn preload(&self, data: &[u8]) -> Result<(), AudioError> {
275 self.lock()
276 .load_from_bytes(data)
277 .map_err(|e| AudioError::Decode(format!("{e:#}")))
278 }
279
280 fn play(&self, source: &str, data: Option<&[u8]>) -> Result<(), AudioError> {
281 let mut manager = self.lock();
282 let result = match data {
283 Some(bytes) => manager.play_from_bytes(bytes),
284 None => manager.play_from_local_uri(source),
286 };
287 result.map_err(|e| AudioError::Device(format!("{e:#}")))
288 }
289
290 fn pause(&self) -> Result<(), AudioError> {
291 self.lock()
292 .pause()
293 .map_err(|e| AudioError::Device(format!("{e:#}")))
294 }
295
296 fn resume(&self) -> Result<(), AudioError> {
297 self.lock()
298 .resume()
299 .map_err(|e| AudioError::Device(format!("{e:#}")))
300 }
301
302 fn current_seconds(&self) -> f32 {
303 self.lock().current_seconds()
304 }
305
306 fn duration_seconds(&self) -> f32 {
307 self.lock().duration_seconds()
308 }
309
310 fn is_finished(&self) -> bool {
311 self.lock().is_finished()
312 }
313}
314
315#[derive(Debug, Clone, Copy, Default)]
320pub struct PlatformAudioSinkFactory;
321
322impl AudioSinkFactory for PlatformAudioSinkFactory {
323 fn create(&self) -> Arc<dyn AudioSink> {
324 Arc::new(PlatformAudioSink::new())
325 }
326}
327
328pub fn default_audio_sink_factory() -> Arc<dyn AudioSinkFactory> {
330 Arc::new(PlatformAudioSinkFactory)
331}
332
333fn decode(data: &[u8]) -> Result<(Vec<f32>, usize, u32)> {
335 if data.len() > MAX_AUDIO_INPUT_BYTES {
336 anyhow::bail!(
337 "audio payload of {} bytes exceeds the {MAX_AUDIO_INPUT_BYTES}-byte limit",
338 data.len()
339 );
340 }
341 let cursor = Cursor::new(data.to_vec());
342 let mss = MediaSourceStream::new(Box::new(cursor), Default::default());
343
344 let hint = Hint::new();
345 let fmt_opts: FormatOptions = Default::default();
346 let meta_opts: MetadataOptions = Default::default();
347 let dec_opts: AudioDecoderOptions = Default::default();
348
349 let mut format = get_probe()
350 .probe(&hint, mss, fmt_opts, meta_opts)
351 .context("Failed to probe media format")?;
352
353 let track = format
354 .default_track(TrackType::Audio)
355 .ok_or_else(|| anyhow::anyhow!("No default audio track found"))?;
356
357 let track_id = track.id;
358
359 let audio_params = track
360 .codec_params
361 .as_ref()
362 .and_then(|p| p.audio())
363 .ok_or_else(|| anyhow::anyhow!("Default track has no audio codec parameters"))?;
364
365 let mut decoder = get_codecs()
366 .make_audio_decoder(audio_params, &dec_opts)
367 .context("Failed to create audio decoder")?;
368
369 let mut samples = Vec::<f32>::new();
370 let mut channels = audio_params.channels.as_ref().map_or(1, |c| c.count());
371
372 let mut sample_rate = audio_params.sample_rate.unwrap_or(44100);
373
374 loop {
375 let packet = match format.next_packet() {
376 Ok(Some(packet)) => packet,
377 Ok(None) => break,
378 Err(err) => return Err(err).context("Failed to read media packet"),
379 };
380
381 if packet.track_id != track_id {
382 continue;
383 }
384
385 match decoder.decode(&packet) {
386 Ok(audio_buf) => {
387 channels = audio_buf.spec().channels().count();
388 sample_rate = audio_buf.spec().rate();
389
390 let decoded_len = audio_buf.samples_interleaved();
391 if samples.len().saturating_add(decoded_len) > MAX_DECODED_SAMPLES {
392 anyhow::bail!("decoded audio exceeds the {MAX_DECODED_SAMPLES}-sample limit");
393 }
394 let mut decoded = vec![f32::MID; decoded_len];
395 audio_buf.copy_to_slice_interleaved(&mut decoded);
396 samples.extend_from_slice(&decoded);
397 }
398 Err(Error::DecodeError(_)) => {
399 continue;
400 }
401 Err(err) => {
402 return Err(err).context("Failed to decode audio packet");
403 }
404 }
405 }
406
407 Ok((samples, channels, sample_rate))
408}
409
410fn write_output_f32(
412 output: &mut [f32],
413 src_channels: usize,
414 out_channels: usize,
415 samples: &Arc<Mutex<Vec<f32>>>,
416 pos: &Arc<Mutex<usize>>,
417) {
418 let mut p = pos.lock().unwrap();
419 let buf = samples.lock().unwrap();
420 let total_frames = buf.len().checked_div(src_channels).unwrap_or(0);
421
422 if out_channels == 0 {
423 return;
424 }
425 let frames_to_write = output.len() / out_channels;
426
427 for frame in 0..frames_to_write {
428 if total_frames == 0 || *p >= total_frames {
429 for ch in 0..out_channels {
431 output[frame * out_channels + ch] = 0.0;
432 }
433 continue;
434 }
435 for ch in 0..out_channels {
436 let src_index = (*p * src_channels) + (ch % src_channels);
437 if src_index < buf.len() {
438 output[frame * out_channels + ch] = buf[src_index];
439 } else {
440 output[frame * out_channels + ch] = 0.0;
441 }
442 }
443 *p += 1;
444 }
445}
446
447fn write_output_i16(
449 output: &mut [i16],
450 src_channels: usize,
451 out_channels: usize,
452 samples: &Arc<Mutex<Vec<f32>>>,
453 pos: &Arc<Mutex<usize>>,
454) {
455 let mut p = pos.lock().unwrap();
456 let buf = samples.lock().unwrap();
457 let total_frames = buf.len().checked_div(src_channels).unwrap_or(0);
458
459 if out_channels == 0 {
460 return;
461 }
462 let frames_to_write = output.len() / out_channels;
463
464 for frame in 0..frames_to_write {
465 if total_frames == 0 || *p >= total_frames {
466 for ch in 0..out_channels {
467 output[frame * out_channels + ch] = 0;
468 }
469 continue;
470 }
471 for ch in 0..out_channels {
472 let src_index = (*p * src_channels) + (ch % src_channels);
473 if src_index < buf.len() {
474 let v = buf[src_index].clamp(-1.0, 1.0);
475 output[frame * out_channels + ch] = (v * i16::MAX as f32) as i16;
476 } else {
477 output[frame * out_channels + ch] = 0;
478 }
479 }
480 *p += 1;
481 }
482}
483
484fn write_output_u16(
486 output: &mut [u16],
487 src_channels: usize,
488 out_channels: usize,
489 samples: &Arc<Mutex<Vec<f32>>>,
490 pos: &Arc<Mutex<usize>>,
491) {
492 let mut p = pos.lock().unwrap();
493 let buf = samples.lock().unwrap();
494 let total_frames = buf.len().checked_div(src_channels).unwrap_or(0);
495
496 if out_channels == 0 {
497 return;
498 }
499 let frames_to_write = output.len() / out_channels;
500
501 for frame in 0..frames_to_write {
502 if total_frames == 0 || *p >= total_frames {
503 for ch in 0..out_channels {
504 output[frame * out_channels + ch] = 0;
505 }
506 continue;
507 }
508 for ch in 0..out_channels {
509 let src_index = (*p * src_channels) + (ch % src_channels);
510 if src_index < buf.len() {
511 let v = buf[src_index].clamp(-1.0, 1.0);
512 output[frame * out_channels + ch] = ((v * 0.5 + 0.5) * u16::MAX as f32) as u16;
513 } else {
514 output[frame * out_channels + ch] = 0;
515 }
516 }
517 *p += 1;
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 fn wav(samples: &[i16]) -> Vec<u8> {
527 let data_len = (samples.len() * 2) as u32;
528 let mut bytes = Vec::new();
529 bytes.extend_from_slice(b"RIFF");
530 bytes.extend_from_slice(&(36 + data_len).to_le_bytes());
531 bytes.extend_from_slice(b"WAVE");
532 bytes.extend_from_slice(b"fmt ");
533 bytes.extend_from_slice(&16u32.to_le_bytes());
534 bytes.extend_from_slice(&1u16.to_le_bytes()); bytes.extend_from_slice(&1u16.to_le_bytes()); bytes.extend_from_slice(&8000u32.to_le_bytes()); bytes.extend_from_slice(&16000u32.to_le_bytes()); bytes.extend_from_slice(&2u16.to_le_bytes()); bytes.extend_from_slice(&16u16.to_le_bytes()); bytes.extend_from_slice(b"data");
541 bytes.extend_from_slice(&data_len.to_le_bytes());
542 for sample in samples {
543 bytes.extend_from_slice(&sample.to_le_bytes());
544 }
545 bytes
546 }
547
548 #[test]
549 fn decodes_a_small_wav() {
550 let (samples, channels, sample_rate) =
551 decode(&wav(&[0, 1000, -1000, 2000])).expect("decodes wav");
552 assert!(!samples.is_empty());
553 assert_eq!(channels, 1);
554 assert_eq!(sample_rate, 8000);
555 }
556
557 #[test]
558 fn rejects_oversized_input_before_decoding() {
559 let oversized = vec![0_u8; MAX_AUDIO_INPUT_BYTES + 1];
560 let error = decode(&oversized).expect_err("oversized input must be rejected");
561 assert!(error.to_string().contains("exceeds"));
562 }
563}