Skip to content

Commit 5d43a86

Browse files
committed
[add] tests for loading ogg/wav files with various sampling rates and output formats.
1 parent 850b648 commit 5d43a86

10 files changed

Lines changed: 381 additions & 24 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:fb635f26da26ae2dadd8560ed3e0451381b90e7b5abfbe3303ec33f27f340e43
3+
size 14370
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:7241cee48cae37e67abcac3aa06fbf1c478828c896fc92d594605d9e44721476
3+
size 35336
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:5dfea227c9ae756cf4176f2a15ce702be6e1e3bc53985997e962c3fc3c5a3d1f
3+
size 8864
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:4fe4cb948618e2945b70edd203639092b134bcbd6d841d44488a0bb07c7a7af4
3+
size 17684
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:d2f1da451b14d054360935d78127ebc29723b7aed12809f82f259a5870034eb5
3+
size 13274

crates/lambda-rs-platform/src/audio/symphonia/mod.rs

Lines changed: 136 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ use std::{
1212

1313
#[cfg(feature = "audio-decode-vorbis")]
1414
use symphonia::core::codecs::CODEC_TYPE_VORBIS;
15-
#[cfg(feature = "audio-decode-wav")]
16-
use symphonia::core::sample::SampleFormat;
1715
use symphonia::core::{
18-
audio::SampleBuffer,
16+
audio::{
17+
AudioBufferRef,
18+
SampleBuffer,
19+
},
1920
codecs::{
2021
Decoder,
2122
DecoderOptions,
@@ -193,6 +194,7 @@ fn decode_track_to_interleaved_f32(
193194

194195
let mut sample_rate: Option<u32> = None;
195196
let mut channel_count: Option<u16> = None;
197+
let mut wav_sample_format_validated = false;
196198

197199
loop {
198200
let packet = match format.next_packet() {
@@ -269,6 +271,15 @@ fn decode_track_to_interleaved_f32(
269271
}
270272

271273
let frames = decoded.frames();
274+
if frames == 0 {
275+
continue;
276+
}
277+
278+
if source_description == "WAV" && !wav_sample_format_validated {
279+
validate_wav_decoded_sample_format(&decoded)?;
280+
wav_sample_format_validated = true;
281+
}
282+
272283
let mut sample_buffer =
273284
SampleBuffer::<f32>::new(frames as u64, *decoded.spec());
274285
sample_buffer.copy_interleaved_ref(decoded);
@@ -299,6 +310,43 @@ fn decode_track_to_interleaved_f32(
299310
});
300311
}
301312

313+
fn validate_wav_decoded_sample_format(
314+
decoded: &AudioBufferRef<'_>,
315+
) -> Result<(), AudioDecodeError> {
316+
match decoded {
317+
AudioBufferRef::S16(_)
318+
| AudioBufferRef::S24(_)
319+
| AudioBufferRef::F32(_) => {
320+
return Ok(());
321+
}
322+
other => {
323+
return Err(AudioDecodeError::UnsupportedFormat {
324+
details: format!(
325+
"unsupported WAV decoded sample format: {}",
326+
wav_decoded_sample_format_name(other)
327+
),
328+
});
329+
}
330+
}
331+
}
332+
333+
fn wav_decoded_sample_format_name(
334+
decoded: &AudioBufferRef<'_>,
335+
) -> &'static str {
336+
match decoded {
337+
AudioBufferRef::U8(_) => "U8",
338+
AudioBufferRef::U16(_) => "U16",
339+
AudioBufferRef::U24(_) => "U24",
340+
AudioBufferRef::U32(_) => "U32",
341+
AudioBufferRef::S8(_) => "S8",
342+
AudioBufferRef::S16(_) => "S16",
343+
AudioBufferRef::S24(_) => "S24",
344+
AudioBufferRef::S32(_) => "S32",
345+
AudioBufferRef::F32(_) => "F32",
346+
AudioBufferRef::F64(_) => "F64",
347+
}
348+
}
349+
302350
/// Decode WAV bytes into interleaved `f32` samples.
303351
#[cfg(feature = "audio-decode-wav")]
304352
pub fn decode_wav_bytes(
@@ -314,22 +362,6 @@ pub fn decode_wav_bytes(
314362
}
315363
};
316364

317-
let sample_format =
318-
codec_params
319-
.sample_format
320-
.ok_or(AudioDecodeError::UnsupportedFormat {
321-
details: "WAV sample format is unspecified".to_string(),
322-
})?;
323-
324-
match sample_format {
325-
SampleFormat::S16 | SampleFormat::S24 | SampleFormat::F32 => {}
326-
other => {
327-
return Err(AudioDecodeError::UnsupportedFormat {
328-
details: format!("unsupported WAV sample format: {other:?}"),
329-
});
330-
}
331-
}
332-
333365
let mut decoder = symphonia::default::get_codecs()
334366
.make(&codec_params, &DecoderOptions::default())
335367
.map_err(|error| map_read_or_decode_error("WAV", error))?;
@@ -387,6 +419,36 @@ pub fn decode_ogg_vorbis_bytes(
387419
mod tests {
388420
use super::*;
389421

422+
#[cfg(feature = "audio-decode-wav")]
423+
const TONE_S16_MONO_44100_WAV: &[u8] = include_bytes!(concat!(
424+
env!("CARGO_MANIFEST_DIR"),
425+
"/assets/audio/tone_s16_mono_44100.wav"
426+
));
427+
428+
#[cfg(feature = "audio-decode-wav")]
429+
const TONE_S16_STEREO_44100_WAV: &[u8] = include_bytes!(concat!(
430+
env!("CARGO_MANIFEST_DIR"),
431+
"/assets/audio/tone_s16_stereo_44100.wav"
432+
));
433+
434+
#[cfg(feature = "audio-decode-wav")]
435+
const TONE_S24_MONO_44100_WAV: &[u8] = include_bytes!(concat!(
436+
env!("CARGO_MANIFEST_DIR"),
437+
"/assets/audio/tone_s24_mono_44100.wav"
438+
));
439+
440+
#[cfg(feature = "audio-decode-wav")]
441+
const TONE_F32_STEREO_44100_WAV: &[u8] = include_bytes!(concat!(
442+
env!("CARGO_MANIFEST_DIR"),
443+
"/assets/audio/tone_f32_stereo_44100.wav"
444+
));
445+
446+
#[cfg(feature = "audio-decode-vorbis")]
447+
const SLASH_VORBIS_STEREO_48000_OGG: &[u8] = include_bytes!(concat!(
448+
env!("CARGO_MANIFEST_DIR"),
449+
"/assets/audio/slash_vorbis_stereo_48000.ogg"
450+
));
451+
390452
#[cfg(feature = "audio-decode-wav")]
391453
#[test]
392454
fn wav_decode_rejects_invalid_bytes() {
@@ -400,6 +462,50 @@ mod tests {
400462
return;
401463
}
402464

465+
#[cfg(feature = "audio-decode-wav")]
466+
#[test]
467+
fn wav_decode_s16_mono_fixture_decodes() {
468+
let decoded =
469+
decode_wav_bytes(TONE_S16_MONO_44100_WAV).expect("decode failed");
470+
assert_eq!(decoded.sample_rate, 44100);
471+
assert_eq!(decoded.channels, 1);
472+
assert_eq!(decoded.samples.len(), 4410);
473+
return;
474+
}
475+
476+
#[cfg(feature = "audio-decode-wav")]
477+
#[test]
478+
fn wav_decode_s16_stereo_fixture_decodes() {
479+
let decoded =
480+
decode_wav_bytes(TONE_S16_STEREO_44100_WAV).expect("decode failed");
481+
assert_eq!(decoded.sample_rate, 44100);
482+
assert_eq!(decoded.channels, 2);
483+
assert_eq!(decoded.samples.len(), 4410 * 2);
484+
return;
485+
}
486+
487+
#[cfg(feature = "audio-decode-wav")]
488+
#[test]
489+
fn wav_decode_s24_mono_fixture_decodes() {
490+
let decoded =
491+
decode_wav_bytes(TONE_S24_MONO_44100_WAV).expect("decode failed");
492+
assert_eq!(decoded.sample_rate, 44100);
493+
assert_eq!(decoded.channels, 1);
494+
assert_eq!(decoded.samples.len(), 4410);
495+
return;
496+
}
497+
498+
#[cfg(feature = "audio-decode-wav")]
499+
#[test]
500+
fn wav_decode_f32_stereo_fixture_decodes() {
501+
let decoded =
502+
decode_wav_bytes(TONE_F32_STEREO_44100_WAV).expect("decode failed");
503+
assert_eq!(decoded.sample_rate, 44100);
504+
assert_eq!(decoded.channels, 2);
505+
assert_eq!(decoded.samples.len(), 4410 * 2);
506+
return;
507+
}
508+
403509
#[cfg(feature = "audio-decode-vorbis")]
404510
#[test]
405511
fn ogg_vorbis_decode_rejects_invalid_bytes() {
@@ -412,4 +518,15 @@ mod tests {
412518
));
413519
return;
414520
}
521+
522+
#[cfg(feature = "audio-decode-vorbis")]
523+
#[test]
524+
fn ogg_vorbis_decode_fixture_decodes() {
525+
let decoded = decode_ogg_vorbis_bytes(SLASH_VORBIS_STEREO_48000_OGG)
526+
.expect("decode failed");
527+
assert_eq!(decoded.sample_rate, 48000);
528+
assert_eq!(decoded.channels, 2);
529+
assert!(!decoded.samples.is_empty());
530+
return;
531+
}
415532
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#![allow(clippy::needless_return)]
2+
3+
use std::path::{
4+
Path,
5+
PathBuf,
6+
};
7+
8+
use lambda::audio::{
9+
AudioError,
10+
SoundBuffer,
11+
};
12+
13+
fn main() {
14+
let path = match parse_path_argument() {
15+
Ok(path) => path,
16+
Err(message) => {
17+
eprintln!("{message}");
18+
std::process::exit(2);
19+
}
20+
};
21+
22+
let buffer = match load_sound_buffer(&path) {
23+
Ok(buffer) => buffer,
24+
Err(error) => {
25+
eprintln!("failed to load sound buffer: {error}");
26+
std::process::exit(1);
27+
}
28+
};
29+
30+
println!("path: {}", path.display());
31+
println!("sample_rate: {}", buffer.sample_rate());
32+
println!("channels: {}", buffer.channels());
33+
println!("duration_seconds: {:.3}", buffer.duration_seconds());
34+
return;
35+
}
36+
37+
fn parse_path_argument() -> Result<PathBuf, String> {
38+
let mut args = std::env::args_os();
39+
let program_name = args
40+
.next()
41+
.and_then(|value| value.into_string().ok())
42+
.unwrap_or_else(|| "sound_buffer_load".to_string());
43+
44+
let path = args.next().ok_or_else(|| {
45+
return format!("usage: {program_name} <path-to-wav-or-ogg>");
46+
})?;
47+
48+
return Ok(PathBuf::from(path));
49+
}
50+
51+
fn load_sound_buffer(path: &Path) -> Result<SoundBuffer, AudioError> {
52+
let extension = path
53+
.extension()
54+
.and_then(|value| value.to_str())
55+
.map(|value| value.to_ascii_lowercase())
56+
.unwrap_or_else(|| "".to_string());
57+
58+
match extension.as_str() {
59+
#[cfg(feature = "audio-sound-buffer-wav")]
60+
"wav" => {
61+
return SoundBuffer::from_wav_file(path);
62+
}
63+
#[cfg(not(feature = "audio-sound-buffer-wav"))]
64+
"wav" => {
65+
return Err(AudioError::UnsupportedFormat {
66+
details: "WAV support is disabled (enable `audio-sound-buffer-wav`)"
67+
.to_string(),
68+
});
69+
}
70+
#[cfg(feature = "audio-sound-buffer-vorbis")]
71+
"ogg" | "oga" => {
72+
return SoundBuffer::from_ogg_file(path);
73+
}
74+
#[cfg(not(feature = "audio-sound-buffer-vorbis"))]
75+
"ogg" | "oga" => {
76+
return Err(AudioError::UnsupportedFormat {
77+
details:
78+
"OGG Vorbis support is disabled (enable `audio-sound-buffer-vorbis`)"
79+
.to_string(),
80+
});
81+
}
82+
_ => {
83+
return Err(AudioError::UnsupportedFormat {
84+
details: format!("unsupported file extension: {extension:?}"),
85+
});
86+
}
87+
}
88+
}

crates/lambda-rs/src/audio/buffer.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,60 @@ mod tests {
131131
assert_eq!(buffer.duration_seconds(), 1.0);
132132
return;
133133
}
134+
135+
#[cfg(feature = "audio-sound-buffer-wav")]
136+
#[test]
137+
fn from_wav_bytes_decodes_fixture() {
138+
let bytes = include_bytes!(concat!(
139+
env!("CARGO_MANIFEST_DIR"),
140+
"/../lambda-rs-platform/assets/audio/tone_s16_mono_44100.wav"
141+
));
142+
143+
let buffer = SoundBuffer::from_wav_bytes(bytes).expect("decode failed");
144+
assert_eq!(buffer.sample_rate(), 44100);
145+
assert_eq!(buffer.channels(), 1);
146+
assert!(buffer.duration_seconds() > 0.0);
147+
return;
148+
}
149+
150+
#[cfg(feature = "audio-sound-buffer-wav")]
151+
#[test]
152+
fn from_wav_file_decodes_fixture() {
153+
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
154+
.join("../lambda-rs-platform/assets/audio/tone_s16_mono_44100.wav");
155+
156+
let buffer = SoundBuffer::from_wav_file(&path).expect("decode failed");
157+
assert_eq!(buffer.sample_rate(), 44100);
158+
assert_eq!(buffer.channels(), 1);
159+
assert!(buffer.duration_seconds() > 0.0);
160+
return;
161+
}
162+
163+
#[cfg(feature = "audio-sound-buffer-vorbis")]
164+
#[test]
165+
fn from_ogg_bytes_decodes_fixture() {
166+
let bytes = include_bytes!(concat!(
167+
env!("CARGO_MANIFEST_DIR"),
168+
"/../lambda-rs-platform/assets/audio/slash_vorbis_stereo_48000.ogg"
169+
));
170+
171+
let buffer = SoundBuffer::from_ogg_bytes(bytes).expect("decode failed");
172+
assert_eq!(buffer.sample_rate(), 48000);
173+
assert_eq!(buffer.channels(), 2);
174+
assert!(buffer.duration_seconds() > 0.0);
175+
return;
176+
}
177+
178+
#[cfg(feature = "audio-sound-buffer-vorbis")]
179+
#[test]
180+
fn from_ogg_file_decodes_fixture() {
181+
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
182+
.join("../lambda-rs-platform/assets/audio/slash_vorbis_stereo_48000.ogg");
183+
184+
let buffer = SoundBuffer::from_ogg_file(&path).expect("decode failed");
185+
assert_eq!(buffer.sample_rate(), 48000);
186+
assert_eq!(buffer.channels(), 2);
187+
assert!(buffer.duration_seconds() > 0.0);
188+
return;
189+
}
134190
}

0 commit comments

Comments
 (0)