#![allow(unsafe_code)]
use crate::backend::AudioSink;
use crate::mixer::{Mixer, MixerSeed, RenderStatus};
use cranpose_services::AudioError;
use ndk::audio::{
AudioCallbackResult, AudioDirection, AudioError as AAudioError, AudioFormat,
AudioPerformanceMode, AudioSharingMode, AudioStream, AudioStreamBuilder, AudioStreamState,
};
use std::ffi::c_void;
const NOMINAL_SAMPLE_RATE: f32 = 48_000.0;
const REQUESTED_CHANNELS: i32 = 2;
pub(crate) fn open(seed: MixerSeed) -> Result<Box<dyn AudioSink>, AudioError> {
let mut mixer = Mixer::new(seed, NOMINAL_SAMPLE_RATE, REQUESTED_CHANNELS as usize);
let stream = AudioStreamBuilder::new()
.map_err(backend_error("failed to create an AAudio stream builder"))?
.direction(AudioDirection::Output)
.format(AudioFormat::PCM_Float)
.channel_count(REQUESTED_CHANNELS)
.performance_mode(AudioPerformanceMode::LowLatency)
.sharing_mode(AudioSharingMode::Shared)
.data_callback(Box::new(
move |stream: &AudioStream, audio_data: *mut c_void, frames: i32| {
let channels = stream.channel_count().max(1) as usize;
mixer.set_device_format(stream.sample_rate() as f32, channels);
let samples = frames.max(0) as usize * channels;
if samples == 0 || audio_data.is_null() {
return AudioCallbackResult::Continue;
}
let data = audio_data.cast::<f32>();
let out = unsafe { std::slice::from_raw_parts_mut(data, samples) };
match mixer.render(out) {
RenderStatus::Continue => AudioCallbackResult::Continue,
RenderStatus::Idle => AudioCallbackResult::Stop,
}
},
))
.error_callback(Box::new(|_stream, error| {
log::warn!("AAudio stream error: {error:?}");
}))
.open_stream()
.map_err(backend_error("failed to open the AAudio output stream"))?;
if stream.format() != AudioFormat::PCM_Float {
return Err(AudioError::Backend(format!(
"AAudio opened the output stream as {:?} rather than 32-bit float",
stream.format()
)));
}
stream
.request_start()
.map_err(backend_error("failed to start the AAudio output stream"))?;
log::debug!(
"cranpose audio: AAudio stream at {} Hz, {} channels, {} frames per burst",
stream.sample_rate(),
stream.channel_count(),
stream.frames_per_burst()
);
Ok(Box::new(AAudioSink { stream }))
}
fn backend_error(context: &'static str) -> impl Fn(AAudioError) -> AudioError {
move |error| AudioError::Backend(format!("{context}: {error}"))
}
struct AAudioSink {
stream: AudioStream,
}
impl AudioSink for AAudioSink {
fn suspend(&self) {
if let Err(error) = self.stream.request_pause() {
log::debug!("failed to pause the AAudio stream: {error}");
}
}
fn resume(&self) {
if self.stream.state() == AudioStreamState::Started {
if let Err(error) = self.stream.request_stop() {
log::debug!("failed to settle the AAudio stream before starting it: {error}");
}
}
if let Err(error) = self.stream.request_start() {
log::debug!("failed to restart the AAudio stream: {error}");
}
}
fn park(&self) {
if let Err(error) = self.stream.request_stop() {
log::debug!("failed to release the idle AAudio stream: {error}");
}
}
}
impl Drop for AAudioSink {
fn drop(&mut self) {
if let Err(error) = self.stream.request_stop() {
log::debug!("failed to stop the AAudio stream: {error}");
}
}
}