use super::{
asset_handler::{AssetHandler, BakeContext, LoadErrors},
audio_utils::{bytes_per_sample, push_pcm_sample, sample_count_from_pcm_len},
ResourceId,
};
use crate::{processors::audio_processor::process_audio_in, resources::audio::Audio, types::BitDepths};
impl Default for OGGAssetHandler {
fn default() -> Self {
Self::new()
}
}
impl OGGAssetHandler {
fn decode_ogg<'a>(
data: &[u8],
bit_depth: BitDepths,
allocator: &'a dyn std::alloc::Allocator,
) -> Result<(Audio, Box<[u8], &'a dyn std::alloc::Allocator>), String> {
use std::io::Cursor;
let mut decoder = vorbis_rs::VorbisDecoder::new(Cursor::new(data))
.map_err(|_| "Failed to decode OGG data. The file is likely corrupt or not Vorbis encoded.".to_string())?;
let sample_rate = decoder.sampling_frequency().get();
let channel_count = decoder.channels().get();
let bytes_per_sample = bytes_per_sample(bit_depth);
let mut data = Vec::with_capacity_in(channel_count as usize * sample_rate as usize * bytes_per_sample, allocator);
while let Some(block) = decoder
.decode_audio_block()
.map_err(|_| "Failed to decode OGG data. The stream is likely corrupt.".to_string())?
{
let samples = block.samples();
for &channel in samples {
for sample in channel {
push_pcm_sample(&mut data, *sample, bit_depth);
}
}
}
let sample_count = sample_count_from_pcm_len(data.len(), channel_count as u16, bit_depth);
let channel_count = channel_count as u16;
let audio_resource = Audio {
bit_depth,
channel_count,
sample_rate,
sample_count,
};
Ok((audio_resource, data.into_boxed_slice()))
}
pub fn new() -> OGGAssetHandler {
OGGAssetHandler {
bit_depth: BitDepths::Sixteen,
}
}
pub fn with_bit_depth(bit_depth: BitDepths) -> OGGAssetHandler {
OGGAssetHandler { bit_depth }
}
}
pub struct OGGAssetHandler {
bit_depth: BitDepths,
}
impl AssetHandler for OGGAssetHandler {
fn can_handle(&self, r#type: &str) -> bool {
r#type == "ogg"
}
async fn bake<'a>(&'a self, context: BakeContext<'a>, url: ResourceId<'a>) -> Result<(), LoadErrors> {
if let Some(dt) = context.resource_type(url) {
if !self.can_handle(dt) {
return Err(LoadErrors::UnsupportedType);
}
}
let (data, _, dt) = context.resolve(url).await?;
let allocator = context.allocator();
if !self.can_handle(&dt) {
return Err(LoadErrors::UnsupportedType);
}
let (audio_resource, data) =
Self::decode_ogg(&data, self.bit_depth, allocator).map_err(|_| LoadErrors::FailedToProcess)?;
let (asset, data) = process_audio_in(url, audio_resource, data)?;
context.store_primary(asset, &data)
}
}
#[cfg(test)]
mod tests {
use crate::{
asset::{self, asset_manager::AssetManager, ogg_asset_handler::OGGAssetHandler, ResourceId},
r#async, resource,
resources::audio::Audio,
types::BitDepths,
AssetHandler,
};
#[r#async::test]
async fn test_audio_asset_handler() {
let asset_storage_backend = asset::storage_backend::tests::TestStorageBackend::new();
let resource_storage_backend = resource::storage_backend::tests::TestStorageBackend::new();
asset_storage_backend.add_file("test-tone.ogg", &make_test_ogg());
let mut asset_manager = AssetManager::new(asset_storage_backend);
asset_manager.add_asset_handler(OGGAssetHandler::new());
asset_manager
.bake("test-tone.ogg", &resource_storage_backend)
.await
.expect("Audio asset handler failed to load asset");
let generated_resources = resource_storage_backend.get_resources();
assert_eq!(generated_resources.len(), 1);
let resource = &generated_resources[0];
assert_eq!(resource.id, "test-tone.ogg");
assert_eq!(resource.class, "Audio");
let resource: Audio = crate::from_slice(&resource.resource).unwrap();
assert_eq!(resource.bit_depth, BitDepths::Sixteen);
assert_eq!(resource.channel_count, 1);
assert_eq!(resource.sample_rate, 48_000);
assert_eq!(resource.sample_count, 1024);
let data = resource_storage_backend
.get_resource_data_by_name(ResourceId::new("test-tone.ogg"))
.expect("Audio resource data should exist");
assert_eq!(data.len(), 1024 * 2);
}
#[test]
fn decode_ogg_supports_configured_output_bit_depths() {
let ogg = make_test_ogg();
for (bit_depth, bytes_per_sample) in [
(BitDepths::Eight, 1),
(BitDepths::Sixteen, 2),
(BitDepths::TwentyFour, 3),
(BitDepths::ThirtyTwo, 4),
] {
let (audio, data) =
OGGAssetHandler::decode_ogg(&ogg, bit_depth, &std::alloc::Global).expect("Generated OGG should decode");
assert_eq!(audio.bit_depth, bit_depth);
assert_eq!(audio.channel_count, 1);
assert_eq!(audio.sample_rate, 48_000);
assert_eq!(audio.sample_count, 1024);
assert_eq!(data.len(), 1024 * bytes_per_sample);
}
}
fn make_test_ogg() -> Vec<u8> {
use std::num::{NonZeroU32, NonZeroU8};
let sample_rate = NonZeroU32::new(48_000).unwrap();
let channels = NonZeroU8::new(1).unwrap();
let sink = Vec::new();
let mut builder = vorbis_rs::VorbisEncoderBuilder::new_with_serial(sample_rate, channels, sink, 1);
let mut encoder = builder.build().expect("Test OGG encoder should initialize");
let samples: Vec<f32> = (0..1024)
.map(|index| ((index as f32 / 48_000.0) * 440.0 * std::f32::consts::TAU).sin() * 0.25)
.collect();
encoder.encode_audio_block([samples]).expect("Test OGG samples should encode");
encoder.finish().expect("Test OGG stream should finish")
}
}