|
4 | 4 | //! |
5 | 5 | //! This module will provide WAV and OGG Vorbis decode helpers for `lambda-rs`. |
6 | 6 | //! It is intentionally internal and MAY change between releases. |
| 7 | +
|
| 8 | +use std::{ |
| 9 | + fmt, |
| 10 | + io::Cursor, |
| 11 | +}; |
| 12 | + |
| 13 | +use symphonia::core::{ |
| 14 | + errors::Error, |
| 15 | + formats::FormatOptions, |
| 16 | + io::MediaSourceStream, |
| 17 | + meta::MetadataOptions, |
| 18 | + probe::Hint, |
| 19 | +}; |
| 20 | + |
| 21 | +/// Fully decoded, interleaved `f32` samples with associated metadata. |
| 22 | +#[derive(Clone, Debug, PartialEq)] |
| 23 | +pub struct DecodedAudio { |
| 24 | + pub samples: Vec<f32>, |
| 25 | + pub sample_rate: u32, |
| 26 | + pub channels: u16, |
| 27 | +} |
| 28 | + |
| 29 | +/// Vendor-free errors produced by audio decoding helpers. |
| 30 | +#[derive(Clone, Debug)] |
| 31 | +pub enum AudioDecodeError { |
| 32 | + UnsupportedFormat { details: String }, |
| 33 | + InvalidData { details: String }, |
| 34 | + DecodeFailed { details: String }, |
| 35 | +} |
| 36 | + |
| 37 | +impl fmt::Display for AudioDecodeError { |
| 38 | + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 39 | + match self { |
| 40 | + Self::UnsupportedFormat { details } => { |
| 41 | + return write!(formatter, "unsupported audio format: {details}"); |
| 42 | + } |
| 43 | + Self::InvalidData { details } => { |
| 44 | + return write!(formatter, "invalid audio data: {details}"); |
| 45 | + } |
| 46 | + Self::DecodeFailed { details } => { |
| 47 | + return write!(formatter, "audio decode failed: {details}"); |
| 48 | + } |
| 49 | + } |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +impl std::error::Error for AudioDecodeError {} |
| 54 | + |
| 55 | +fn hint_for_decode(extensions: &[&str]) -> Hint { |
| 56 | + let mut hint_value = Hint::new(); |
| 57 | + for extension in extensions { |
| 58 | + hint_value.with_extension(extension); |
| 59 | + } |
| 60 | + return hint_value; |
| 61 | +} |
| 62 | + |
| 63 | +fn map_probe_error(source_description: &str, error: Error) -> AudioDecodeError { |
| 64 | + match error { |
| 65 | + Error::Unsupported(_) => { |
| 66 | + return AudioDecodeError::UnsupportedFormat { |
| 67 | + details: format!("unsupported or unrecognized {source_description}"), |
| 68 | + }; |
| 69 | + } |
| 70 | + Error::IoError(_) => { |
| 71 | + return AudioDecodeError::InvalidData { |
| 72 | + details: format!("failed to read {source_description} bytes"), |
| 73 | + }; |
| 74 | + } |
| 75 | + other => { |
| 76 | + return AudioDecodeError::DecodeFailed { |
| 77 | + details: format!("{source_description} probe error: {other}"), |
| 78 | + }; |
| 79 | + } |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +fn probe_bytes( |
| 84 | + bytes: &[u8], |
| 85 | + source_description: &str, |
| 86 | + extensions: &[&str], |
| 87 | +) -> Result<(), AudioDecodeError> { |
| 88 | + let hint_value = hint_for_decode(extensions); |
| 89 | + |
| 90 | + let cursor = Cursor::new(bytes.to_vec()); |
| 91 | + let media_source = |
| 92 | + MediaSourceStream::new(Box::new(cursor), Default::default()); |
| 93 | + |
| 94 | + let probe_result = symphonia::default::get_probe() |
| 95 | + .format( |
| 96 | + &hint_value, |
| 97 | + media_source, |
| 98 | + &FormatOptions::default(), |
| 99 | + &MetadataOptions::default(), |
| 100 | + ) |
| 101 | + .map_err(|error| map_probe_error(source_description, error))?; |
| 102 | + |
| 103 | + if probe_result.format.tracks().is_empty() { |
| 104 | + return Err(AudioDecodeError::InvalidData { |
| 105 | + details: "no audio tracks found".to_string(), |
| 106 | + }); |
| 107 | + } |
| 108 | + |
| 109 | + return Ok(()); |
| 110 | +} |
| 111 | + |
| 112 | +/// Decode WAV bytes into interleaved `f32` samples. |
| 113 | +#[cfg(feature = "audio-decode-wav")] |
| 114 | +pub fn decode_wav_bytes( |
| 115 | + bytes: &[u8], |
| 116 | +) -> Result<DecodedAudio, AudioDecodeError> { |
| 117 | + probe_bytes(bytes, "WAV", &["wav"])?; |
| 118 | + return Err(AudioDecodeError::DecodeFailed { |
| 119 | + details: "WAV decoding not implemented yet".to_string(), |
| 120 | + }); |
| 121 | +} |
| 122 | + |
| 123 | +/// Decode OGG Vorbis bytes into interleaved `f32` samples. |
| 124 | +#[cfg(feature = "audio-decode-vorbis")] |
| 125 | +pub fn decode_ogg_vorbis_bytes( |
| 126 | + bytes: &[u8], |
| 127 | +) -> Result<DecodedAudio, AudioDecodeError> { |
| 128 | + probe_bytes(bytes, "OGG Vorbis", &["ogg", "oga"])?; |
| 129 | + return Err(AudioDecodeError::DecodeFailed { |
| 130 | + details: "OGG Vorbis decoding not implemented yet".to_string(), |
| 131 | + }); |
| 132 | +} |
0 commit comments