1use std::fmt;
4use std::mem::size_of;
5use std::sync::Arc;
6use std::time::Duration;
7
8use crate::AudioData;
9
10#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
11mod web;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct DecodeOptions {
16 max_decoded_bytes: u64,
17}
18
19impl DecodeOptions {
20 pub const DEFAULT_MAX_DECODED_BYTES: u64 = 128 * 1024 * 1024;
22
23 #[must_use]
28 pub fn with_max_decoded_bytes(mut self, max_decoded_bytes: u64) -> Self {
29 self.max_decoded_bytes = max_decoded_bytes;
30 self
31 }
32
33 pub fn max_decoded_bytes(self) -> u64 {
34 self.max_decoded_bytes
35 }
36
37 pub fn check_decoded_size(
43 self,
44 channel_count: u64,
45 frame_count: u64,
46 ) -> Result<u64, DecodeError> {
47 if channel_count == 0 || frame_count == 0 {
48 return Err(DecodeError::backend(
49 "decoded channel and frame counts must be positive",
50 ));
51 }
52
53 let required_bytes = channel_count
54 .checked_mul(frame_count)
55 .and_then(|samples| samples.checked_mul(size_of::<f32>() as u64))
56 .ok_or_else(|| DecodeError::backend("decoded byte count overflowed"))?;
57 if required_bytes > self.max_decoded_bytes {
58 return Err(DecodeError::resource_limit(
59 required_bytes,
60 self.max_decoded_bytes,
61 ));
62 }
63
64 Ok(required_bytes)
65 }
66}
67
68impl Default for DecodeOptions {
69 fn default() -> Self {
70 Self {
71 max_decoded_bytes: Self::DEFAULT_MAX_DECODED_BYTES,
72 }
73 }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78#[non_exhaustive]
79pub enum DecodeErrorKind {
80 UnsupportedPlatform,
81 ResourceLimit,
82 AllocationFailure,
83 DecodeRejected,
84 Backend,
85}
86
87#[derive(Clone, Debug, PartialEq, Eq)]
89pub struct DecodeError {
90 kind: DecodeErrorKind,
91 message: String,
92 required_bytes: Option<u64>,
93 configured_bytes: Option<u64>,
94}
95
96impl DecodeError {
97 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
98 fn unsupported() -> Self {
99 Self {
100 kind: DecodeErrorKind::UnsupportedPlatform,
101 message: "audio decoding requires the wasm32 web backend".into(),
102 required_bytes: None,
103 configured_bytes: None,
104 }
105 }
106
107 fn resource_limit(required_bytes: u64, configured_bytes: u64) -> Self {
108 Self {
109 kind: DecodeErrorKind::ResourceLimit,
110 message: format!(
111 "decoded PCM requires {required_bytes} Rust-owned bytes, exceeding the configured {configured_bytes}-byte limit"
112 ),
113 required_bytes: Some(required_bytes),
114 configured_bytes: Some(configured_bytes),
115 }
116 }
117
118 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
119 fn allocation_failure(required_bytes: u64) -> Self {
120 Self {
121 kind: DecodeErrorKind::AllocationFailure,
122 message: format!(
123 "could not allocate {required_bytes} bytes for decoded planar samples"
124 ),
125 required_bytes: Some(required_bytes),
126 configured_bytes: None,
127 }
128 }
129
130 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
131 fn decode_rejected() -> Self {
132 Self {
133 kind: DecodeErrorKind::DecodeRejected,
134 message: "browser rejected the complete audio data".into(),
135 required_bytes: None,
136 configured_bytes: None,
137 }
138 }
139
140 fn backend(message: impl Into<String>) -> Self {
141 Self {
142 kind: DecodeErrorKind::Backend,
143 message: message.into(),
144 required_bytes: None,
145 configured_bytes: None,
146 }
147 }
148
149 pub fn kind(&self) -> DecodeErrorKind {
150 self.kind
151 }
152
153 pub fn message(&self) -> &str {
154 &self.message
155 }
156
157 pub fn required_bytes(&self) -> Option<u64> {
159 self.required_bytes
160 }
161
162 pub fn configured_bytes(&self) -> Option<u64> {
164 self.configured_bytes
165 }
166}
167
168impl fmt::Display for DecodeError {
169 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
170 formatter.write_str(&self.message)
171 }
172}
173
174impl std::error::Error for DecodeError {}
175
176pub async fn decode_audio_data(
183 audio: AudioData,
184 options: DecodeOptions,
185) -> Result<DecodedAudio, DecodeError> {
186 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
187 {
188 web::decode_audio_data(audio, options).await
189 }
190
191 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
192 {
193 let _ = (audio, options);
194 Err(DecodeError::unsupported())
195 }
196}
197
198#[derive(Clone, Debug, PartialEq, Eq)]
200#[non_exhaustive]
201pub enum DecodedAudioError {
202 NoChannels,
203 NoFrames,
204 MisalignedSamples { samples: usize, channels: usize },
205 InvalidSampleRate,
206}
207
208impl fmt::Display for DecodedAudioError {
209 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
210 match self {
211 Self::NoChannels => formatter.write_str("channel count must be positive"),
212 Self::NoFrames => formatter.write_str("frame count must be positive"),
213 Self::MisalignedSamples { samples, channels } => write!(
214 formatter,
215 "{samples} planar samples cannot form {channels} equal channels"
216 ),
217 Self::InvalidSampleRate => {
218 formatter.write_str("sample rate must be positive and finite")
219 }
220 }
221 }
222}
223
224impl std::error::Error for DecodedAudioError {}
225
226#[derive(Clone, Debug)]
230pub struct DecodedAudio {
231 inner: Arc<DecodedAudioInner>,
232}
233
234#[derive(Debug)]
235struct DecodedAudioInner {
236 samples: Vec<f32>,
237 channels: usize,
238 frames: usize,
239 sample_rate: f32,
240 duration: Duration,
241}
242
243impl DecodedAudio {
244 pub fn from_planar(
246 samples: Vec<f32>,
247 channels: usize,
248 sample_rate: f32,
249 ) -> Result<Self, DecodedAudioError> {
250 if channels == 0 {
251 return Err(DecodedAudioError::NoChannels);
252 }
253 if samples.is_empty() {
254 return Err(DecodedAudioError::NoFrames);
255 }
256 if !samples.len().is_multiple_of(channels) {
257 return Err(DecodedAudioError::MisalignedSamples {
258 samples: samples.len(),
259 channels,
260 });
261 }
262 if !sample_rate.is_finite() || sample_rate <= 0.0 {
263 return Err(DecodedAudioError::InvalidSampleRate);
264 }
265
266 let frames = samples.len() / channels;
267 let duration = Duration::try_from_secs_f64(frames as f64 / f64::from(sample_rate))
268 .map_err(|_| DecodedAudioError::InvalidSampleRate)?;
269
270 Ok(Self {
271 inner: Arc::new(DecodedAudioInner {
272 samples,
273 channels,
274 frames,
275 sample_rate,
276 duration,
277 }),
278 })
279 }
280
281 pub fn channel_count(&self) -> usize {
282 self.inner.channels
283 }
284
285 pub fn frame_count(&self) -> usize {
286 self.inner.frames
287 }
288
289 pub fn sample_rate(&self) -> f32 {
291 self.inner.sample_rate
292 }
293
294 pub fn duration(&self) -> Duration {
296 self.inner.duration
297 }
298
299 pub fn channel(&self, index: usize) -> Option<&[f32]> {
300 let start = index.checked_mul(self.frame_count())?;
301 let end = start.checked_add(self.frame_count())?;
302 self.inner.samples.get(start..end)
303 }
304
305 pub fn channels(&self) -> impl ExactSizeIterator<Item = &[f32]> {
306 self.inner.samples.chunks_exact(self.frame_count())
307 }
308}