nandi/jolt-nativepublic Fork 0
2ab40bf193b6a39bc8d1397cc36d17572bbf59cd
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

mod.rs · 737 lines · 25.4 KBRust Blame HistoryRaw
Lift freeq's AV media plane out of sleek 90f8b89 nandi 19d ago1//! AAudio backend implementation.
2//!
3//! Default backend on Android.
4
5use std::cmp;
6use std::convert::TryInto;
7use std::sync::atomic::{AtomicI32, Ordering};
8use std::sync::{Arc, Mutex};
9use std::time::{Duration, Instant};
10use std::vec::IntoIter as VecIntoIter;
11
12extern crate ndk;
13
14use convert::{stream_instant, to_stream_instant};
15use java_interface::{AudioDeviceInfo, AudioManager};
16
17use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
18use crate::{
19 BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError,
20 DeviceDescription, DeviceDescriptionBuilder, DeviceDirection, DeviceId, DeviceIdError,
21 DeviceNameError, DeviceType, DevicesError, InputCallbackInfo, InputStreamTimestamp,
22 InterfaceType, OutputCallbackInfo, OutputStreamTimestamp, PauseStreamError, PlayStreamError,
23 SampleFormat, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig,
24 SupportedStreamConfigRange, SupportedStreamConfigsError,
25};
26
27mod convert;
28mod java_interface;
29
30use self::ndk::audio::AudioStream;
31use java_interface::AudioDeviceType as AndroidDeviceType;
32
33impl From<AndroidDeviceType> for DeviceType {
34 fn from(device_type: AndroidDeviceType) -> Self {
35 match device_type {
36 AndroidDeviceType::BuiltinSpeaker
37 | AndroidDeviceType::BuiltinSpeakerSafe
38 | AndroidDeviceType::BleSpeaker => DeviceType::Speaker,
39
40 AndroidDeviceType::BuiltinMic => DeviceType::Microphone,
41
42 AndroidDeviceType::WiredHeadphones => DeviceType::Headphones,
43
44 AndroidDeviceType::WiredHeadset
45 | AndroidDeviceType::UsbHeadset
46 | AndroidDeviceType::BleHeadset
47 | AndroidDeviceType::BluetoothSCO => DeviceType::Headset,
48
49 AndroidDeviceType::BuiltinEarpiece => DeviceType::Earpiece,
50
51 AndroidDeviceType::HearingAid => DeviceType::HearingAid,
52
53 AndroidDeviceType::Dock => DeviceType::Dock,
54
55 AndroidDeviceType::Fm | AndroidDeviceType::FmTuner | AndroidDeviceType::TvTuner => {
56 DeviceType::Tuner
57 }
58
59 AndroidDeviceType::RemoteSubmix => DeviceType::Virtual,
60
61 _ => DeviceType::Unknown,
62 }
63 }
64}
65
66impl From<AndroidDeviceType> for InterfaceType {
67 fn from(device_type: AndroidDeviceType) -> Self {
68 match device_type {
69 AndroidDeviceType::UsbDevice
70 | AndroidDeviceType::UsbAccessory
71 | AndroidDeviceType::UsbHeadset => InterfaceType::Usb,
72
73 AndroidDeviceType::BluetoothA2DP
74 | AndroidDeviceType::BluetoothSCO
75 | AndroidDeviceType::BleHeadset
76 | AndroidDeviceType::BleSpeaker
77 | AndroidDeviceType::BleBroadcast => InterfaceType::Bluetooth,
78
79 AndroidDeviceType::Hdmi | AndroidDeviceType::HdmiArc | AndroidDeviceType::HdmiEarc => {
80 InterfaceType::Hdmi
81 }
82
83 AndroidDeviceType::LineAnalog
84 | AndroidDeviceType::LineDigital
85 | AndroidDeviceType::AuxLine => InterfaceType::Line,
86
87 AndroidDeviceType::BuiltinEarpiece
88 | AndroidDeviceType::BuiltinMic
89 | AndroidDeviceType::BuiltinSpeaker
90 | AndroidDeviceType::BuiltinSpeakerSafe => InterfaceType::BuiltIn,
91
92 AndroidDeviceType::Ip => InterfaceType::Network,
93
94 AndroidDeviceType::RemoteSubmix => InterfaceType::Virtual,
95
96 _ => InterfaceType::Unknown,
97 }
98 }
99}
100
101// constants from android.media.AudioFormat
102const CHANNEL_OUT_MONO: i32 = 4;
103const CHANNEL_OUT_STEREO: i32 = 12;
104
105// Android Java API supports up to 8 channels
106// TODO: more channels available in native AAudio
107// Maps channel masks to their corresponding channel counts
108const CHANNEL_CONFIGS: [(i32, u16); 2] = [(CHANNEL_OUT_MONO, 1), (CHANNEL_OUT_STEREO, 2)];
109
110const SAMPLE_RATES: [i32; 15] = [
111 5512, 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000,
112 176_400, 192_000,
113];
114
115/// The same default for blocking operations as Oboe uses
116const DEFAULT_TIMEOUT_NANOS: i64 = 2_000_000_000;
117
118pub struct Host;
119#[derive(Clone)]
120pub struct Device(Option<AudioDeviceInfo>);
121
122/// Stream wraps AudioStream in Arc<Mutex<>> to provide Send + Sync semantics.
123///
124/// While the underlying ndk::audio::AudioStream is neither Send nor Sync in ndk 0.9.0
125/// (see https://developer.android.com/ndk/guides/audio/aaudio/aaudio#thread-safety),
126/// we wrap it in a mutex to enable safe concurrent access and manually implement Send + Sync.
127///
128/// # Safety
129///
130/// This is safe because:
131/// - AAudio functions are designed to be called from any thread (the Android docs state
132/// "AAudio is not thread-safe" meaning it lacks internal locking, not that it's unsafe)
133/// - Audio callbacks are called on a dedicated AAudio thread and don't access Stream
134/// - The Mutex ensures exclusive access for control operations (play, pause)
135/// - The pointer in AudioStream (NonNull<AAudioStreamStruct>) is valid for the lifetime
136/// of the stream and AAudio C API functions are thread-safe at the C level
137#[derive(Clone)]
138pub struct Stream {
139 inner: Arc<Mutex<AudioStream>>,
140 direction: DeviceDirection,
141}
142
143// SAFETY: AudioStream can be safely sent between threads. The AAudio C API is thread-safe
144// for moving stream ownership between threads. The NonNull pointer remains valid.
145unsafe impl Send for Stream {}
146
147// SAFETY: AudioStream can be safely shared between threads when protected by a Mutex.
148// All operations on the stream go through the mutex, ensuring exclusive access.
149unsafe impl Sync for Stream {}
150
151// Compile-time assertion that Stream is Send and Sync
152crate::assert_stream_send!(Stream);
153crate::assert_stream_sync!(Stream);
154
155/// State for dynamic buffer tuning on output streams.
156#[derive(Default)]
157struct BufferTuningState {
158 previous_underrun_count: AtomicI32,
159 capacity: AtomicI32,
160 mixer_bursts: AtomicI32,
161}
162
163pub use crate::iter::{SupportedInputConfigs, SupportedOutputConfigs};
164pub type Devices = std::vec::IntoIter<Device>;
165
166impl Host {
167 pub fn new() -> Result<Self, crate::HostUnavailable> {
168 Ok(Host)
169 }
170}
171
172impl HostTrait for Host {
173 type Devices = Devices;
174 type Device = Device;
175
176 fn is_available() -> bool {
177 true
178 }
179
180 fn devices(&self) -> Result<Self::Devices, DevicesError> {
181 if let Ok(devices) = AudioDeviceInfo::request(DeviceDirection::Duplex) {
182 Ok(devices
183 .into_iter()
184 .map(|d| Device(Some(d)))
185 .collect::<Vec<_>>()
186 .into_iter())
187 } else {
188 Ok(vec![Device(None)].into_iter())
189 }
190 }
191
192 fn default_input_device(&self) -> Option<Self::Device> {
193 Some(Device(None))
194 }
195
196 fn default_output_device(&self) -> Option<Self::Device> {
197 Some(Device(None))
198 }
199}
200
201fn buffer_size_range() -> SupportedBufferSize {
202 if let Ok(min_buffer_size) = AudioManager::get_frames_per_buffer() {
203 SupportedBufferSize::Range {
204 min: min_buffer_size as u32,
205 max: i32::MAX as u32,
206 }
207 } else {
208 SupportedBufferSize::Unknown
209 }
210}
211
212fn default_supported_configs() -> VecIntoIter<SupportedStreamConfigRange> {
213 const FORMATS: [SampleFormat; 2] = [SampleFormat::I16, SampleFormat::F32];
214
215 let buffer_size = buffer_size_range();
216 let mut output = Vec::with_capacity(SAMPLE_RATES.len() * CHANNEL_CONFIGS.len() * FORMATS.len());
217 for sample_format in &FORMATS {
218 for (_channel_mask, channel_count) in &CHANNEL_CONFIGS {
219 for sample_rate in &SAMPLE_RATES {
220 output.push(SupportedStreamConfigRange {
221 channels: *channel_count,
222 min_sample_rate: *sample_rate as u32,
223 max_sample_rate: *sample_rate as u32,
224 buffer_size,
225 sample_format: *sample_format,
226 });
227 }
228 }
229 }
230
231 output.into_iter()
232}
233
234fn device_supported_configs(device: &AudioDeviceInfo) -> VecIntoIter<SupportedStreamConfigRange> {
235 let sample_rates = if !device.sample_rates.is_empty() {
236 device.sample_rates.as_slice()
237 } else {
238 &SAMPLE_RATES
239 };
240
241 const ALL_CHANNELS: [i32; 2] = [1, 2];
242 let channel_counts = if !device.channel_counts.is_empty() {
243 device.channel_counts.as_slice()
244 } else {
245 &ALL_CHANNELS
246 };
247
248 const ALL_FORMATS: [SampleFormat; 2] = [SampleFormat::I16, SampleFormat::F32];
249 let formats = if !device.formats.is_empty() {
250 device.formats.as_slice()
251 } else {
252 &ALL_FORMATS
253 };
254
255 let buffer_size = buffer_size_range();
256 let mut output = Vec::with_capacity(sample_rates.len() * channel_counts.len() * formats.len());
257 for sample_rate in sample_rates {
258 for channel_count in channel_counts {
259 assert!(*channel_count > 0);
260 if *channel_count > 2 {
261 // could be supported by the device
262 // TODO: more channels available in native AAudio
263 continue;
264 }
265 for format in formats {
266 output.push(SupportedStreamConfigRange {
267 channels: cmp::min(*channel_count as u16, 2u16),
268 min_sample_rate: *sample_rate as u32,
269 max_sample_rate: *sample_rate as u32,
270 buffer_size,
271 sample_format: *format,
272 });
273 }
274 }
275 }
276
277 output.into_iter()
278}
279
280fn configure_for_device(
281 builder: ndk::audio::AudioStreamBuilder,
282 device: &Device,
283 config: StreamConfig,
284) -> ndk::audio::AudioStreamBuilder {
285 let mut builder = if let Some(info) = &device.0 {
286 builder.device_id(info.id)
287 } else {
288 builder
289 };
290 builder = builder.sample_rate(config.sample_rate.try_into().unwrap());
291
292 // Following the pattern from Oboe and Google's AAudio, we let AAudio choose the optimal
293 // callback size dynamically by default. See
294 // - https://developer.android.com/ndk/reference/group/audio#aaudiostreambuilder_setframesperdatacallback
295 // - https://developer.android.com/ndk/guides/audio/audio-latency#buffer-size
296 if let BufferSize::Fixed(size) = config.buffer_size {
297 // For fixed sizes, the user explicitly wants control over the callback size.
298 builder = builder
299 .frames_per_data_callback(size as i32)
300 .buffer_capacity_in_frames(2 * size as i32);
301 }
302
303 builder
304}
305
306fn build_input_stream<D, E>(
307 device: &Device,
308 config: StreamConfig,
309 mut data_callback: D,
310 mut error_callback: E,
311 builder: ndk::audio::AudioStreamBuilder,
312 sample_format: SampleFormat,
313) -> Result<Stream, BuildStreamError>
314where
315 D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
316 E: FnMut(StreamError) + Send + 'static,
317{
318 let builder = configure_for_device(builder, device, config);
319 let created = Instant::now();
320 let channel_count = config.channels as i32;
321 let stream = builder
322 .data_callback(Box::new(move |stream, data, num_frames| {
323 let cb_info = InputCallbackInfo {
324 timestamp: InputStreamTimestamp {
325 callback: to_stream_instant(created.elapsed()),
326 capture: stream_instant(stream),
327 },
328 };
329 (data_callback)(
330 &unsafe {
331 Data::from_parts(
332 data as *mut _,
333 (num_frames * channel_count).try_into().unwrap(),
334 sample_format,
335 )
336 },
337 &cb_info,
338 );
339 ndk::audio::AudioCallbackResult::Continue
340 }))
341 .error_callback(Box::new(move |_stream, error| {
342 (error_callback)(StreamError::from(error))
343 }))
344 .open_stream()?;
345
346 // SAFETY: Stream implements Send + Sync (see unsafe impl below). Arc<Mutex<AudioStream>>
347 // is safe because the Mutex provides exclusive access and AudioStream's thread safety
348 // is documented in the AAudio C API.
349 #[allow(clippy::arc_with_non_send_sync)]
350 Ok(Stream {
351 inner: Arc::new(Mutex::new(stream)),
352 direction: DeviceDirection::Input,
353 })
354}
355
356fn build_output_stream<D, E>(
357 device: &Device,
358 config: StreamConfig,
359 mut data_callback: D,
360 mut error_callback: E,
361 builder: ndk::audio::AudioStreamBuilder,
362 sample_format: SampleFormat,
363) -> Result<Stream, BuildStreamError>
364where
365 D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
366 E: FnMut(StreamError) + Send + 'static,
367{
368 let builder = configure_for_device(builder, device, config);
369 let created = Instant::now();
370 let channel_count = config.channels as i32;
371 let tune_dynamically = config.buffer_size == BufferSize::Default;
372
373 let tuning = Arc::new(BufferTuningState::default());
374 let tuning_for_callback = tuning.clone();
375
376 let stream = builder
377 .data_callback(Box::new(move |stream, data, num_frames| {
378 // Deliver audio data to user callback
379 let cb_info = OutputCallbackInfo {
380 timestamp: OutputStreamTimestamp {
381 callback: to_stream_instant(created.elapsed()),
382 playback: stream_instant(stream),
383 },
384 };
385 (data_callback)(
386 &mut unsafe {
387 Data::from_parts(
388 data as *mut _,
389 (num_frames * channel_count).try_into().unwrap(),
390 sample_format,
391 )
392 },
393 &cb_info,
394 );
395
396 // Dynamic buffer tuning for output streams
397 // See: https://developer.android.com/ndk/guides/audio/aaudio/aaudio#tuning-buffers
398 if tune_dynamically {
399 let underrun_count = stream.x_run_count();
400 let previous = tuning_for_callback
401 .previous_underrun_count
402 .load(Ordering::Relaxed);
403
404 if underrun_count > previous {
405 // The number of frames per burst can vary dynamically
406 let mut burst_size = stream.frames_per_burst();
407 if burst_size <= 0 {
408 burst_size = 256; // fallback from AAudio documentation
409 } else if burst_size < 16 {
410 burst_size = 16; // floor from Oboe
411 }
412
413 let new_mixer_bursts = tuning_for_callback
414 .mixer_bursts
415 .load(Ordering::Relaxed)
416 .saturating_add(1);
417 let mut buffer_size = burst_size * new_mixer_bursts;
418
419 let buffer_capacity = tuning_for_callback.capacity.load(Ordering::Relaxed);
420 if buffer_size > buffer_capacity {
421 buffer_size = buffer_capacity;
422 }
423
424 if stream.set_buffer_size_in_frames(buffer_size).is_ok() {
425 tuning_for_callback
426 .mixer_bursts
427 .store(new_mixer_bursts, Ordering::Relaxed);
428 }
429
430 tuning_for_callback
431 .previous_underrun_count
432 .store(underrun_count, Ordering::Relaxed);
433 }
434 }
435
436 ndk::audio::AudioCallbackResult::Continue
437 }))
438 .error_callback(Box::new(move |_stream, error| {
439 (error_callback)(StreamError::from(error))
440 }))
441 .open_stream()?;
442
443 // After stream opens, query and cache the values
444 let capacity = stream.buffer_capacity_in_frames();
445 tuning.capacity.store(capacity, Ordering::Relaxed);
446
447 let mixer_bursts = match AudioManager::get_mixer_bursts() {
448 Ok(bursts) => bursts.max(0),
449 Err(_) => {
450 let burst_size = stream.frames_per_burst();
451 if burst_size > 0 {
452 stream.buffer_size_in_frames() / burst_size
453 } else {
454 0 // defer to dynamic tuning
455 }
456 }
457 };
458 tuning.mixer_bursts.store(mixer_bursts, Ordering::Relaxed);
459
460 // SAFETY: Stream implements Send + Sync (see unsafe impl below). Arc<Mutex<AudioStream>>
461 // is safe because the Mutex provides exclusive access and AudioStream's thread safety
462 // is documented in the AAudio C API.
463 #[allow(clippy::arc_with_non_send_sync)]
464 Ok(Stream {
465 inner: Arc::new(Mutex::new(stream)),
466 direction: DeviceDirection::Output,
467 })
468}
469
470impl DeviceTrait for Device {
471 type SupportedInputConfigs = SupportedInputConfigs;
472 type SupportedOutputConfigs = SupportedOutputConfigs;
473 type Stream = Stream;
474
475 fn name(&self) -> Result<String, DeviceNameError> {
476 match &self.0 {
477 None => Ok("default".to_string()),
478 Some(info) => {
479 let name = if info.address.is_empty() {
480 format!("{}:{:?}", info.product_name, info.device_type)
481 } else {
482 format!(
483 "{}:{:?}:{}",
484 info.product_name, info.device_type, info.address
485 )
486 };
487 Ok(name)
488 }
489 }
490 }
491
492 fn description(&self) -> Result<DeviceDescription, DeviceNameError> {
493 match &self.0 {
494 None => Ok(DeviceDescriptionBuilder::new("Default Device".to_string()).build()),
495 Some(info) => {
496 let device_type: DeviceType = info.device_type.into();
497 let name = match device_type {
498 DeviceType::Unknown => info.product_name.clone(),
499 _ => format!("{} ({})", info.product_name, device_type),
500 };
501 let mut builder = DeviceDescriptionBuilder::new(name)
502 .device_type(device_type)
503 .interface_type(info.device_type.into())
504 .direction(info.direction);
505
506 // Add address if not empty
507 if !info.address.is_empty() {
508 builder = builder.address(info.address.clone());
509 }
510
511 Ok(builder.build())
512 }
513 }
514 }
515
516 fn id(&self) -> Result<DeviceId, DeviceIdError> {
517 let device_str = match &self.0 {
518 None => "-1".to_string(), // Default device
519 Some(info) => info.id.to_string(),
520 };
521 Ok(DeviceId(crate::platform::HostId::AAudio, device_str))
522 }
523
524 fn supported_input_configs(
525 &self,
526 ) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
527 if let Some(info) = &self.0 {
528 // Output-only devices do not support input
529 if matches!(info.direction, DeviceDirection::Output) {
530 return Err(SupportedStreamConfigsError::BackendSpecific {
531 err: BackendSpecificError {
532 description: "output-only device does not support input".to_string(),
533 },
534 });
535 }
536 Ok(device_supported_configs(info))
537 } else {
538 Ok(default_supported_configs())
539 }
540 }
541
542 fn supported_output_configs(
543 &self,
544 ) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError> {
545 if let Some(info) = &self.0 {
546 // Input-only devices do not support output
547 if matches!(info.direction, DeviceDirection::Input) {
548 return Err(SupportedStreamConfigsError::BackendSpecific {
549 err: BackendSpecificError {
550 description: "input-only device does not support output".to_string(),
551 },
552 });
553 }
554 Ok(device_supported_configs(info))
555 } else {
556 Ok(default_supported_configs())
557 }
558 }
559
560 fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
561 let mut configs: Vec<_> = self.supported_input_configs().unwrap().collect();
562 configs.sort_by(|a, b| b.cmp_default_heuristics(a));
563 let config = configs
564 .into_iter()
565 .next()
566 .ok_or(DefaultStreamConfigError::StreamTypeNotSupported)?
567 .with_max_sample_rate();
568 Ok(config)
569 }
570
571 fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
572 let mut configs: Vec<_> = self.supported_output_configs().unwrap().collect();
573 configs.sort_by(|a, b| b.cmp_default_heuristics(a));
574 let config = configs
575 .into_iter()
576 .next()
577 .ok_or(DefaultStreamConfigError::StreamTypeNotSupported)?
578 .with_max_sample_rate();
579 Ok(config)
580 }
581
582 fn build_input_stream_raw<D, E>(
583 &self,
584 config: StreamConfig,
585 sample_format: SampleFormat,
586 data_callback: D,
587 error_callback: E,
588 _timeout: Option<Duration>,
589 ) -> Result<Self::Stream, BuildStreamError>
590 where
591 D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
592 E: FnMut(StreamError) + Send + 'static,
593 {
594 let format = match sample_format {
595 SampleFormat::I16 => ndk::audio::AudioFormat::PCM_I16,
596 SampleFormat::F32 => ndk::audio::AudioFormat::PCM_Float,
597 sample_format => {
598 return Err(BackendSpecificError {
599 description: format!("{} format is not supported on Android.", sample_format),
600 }
601 .into())
602 }
603 };
604 let channel_count = match config.channels {
605 1 => 1,
606 2 => 2,
607 channels => {
608 // TODO: more channels available in native AAudio
609 return Err(BackendSpecificError {
610 description: format!(
611 "{} channels are not supported yet (only 1 or 2).",
612 channels
613 ),
614 }
615 .into());
616 }
617 };
618
619 let builder = ndk::audio::AudioStreamBuilder::new()?
620 .direction(ndk::audio::AudioDirection::Input)
621 .channel_count(channel_count)
622 .format(format);
623
624 build_input_stream(
625 self,
626 config,
627 data_callback,
628 error_callback,
629 builder,
630 sample_format,
631 )
632 }
633
634 fn build_output_stream_raw<D, E>(
635 &self,
636 config: StreamConfig,
637 sample_format: SampleFormat,
638 data_callback: D,
639 error_callback: E,
640 _timeout: Option<Duration>,
641 ) -> Result<Self::Stream, BuildStreamError>
642 where
643 D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
644 E: FnMut(StreamError) + Send + 'static,
645 {
646 let format = match sample_format {
647 SampleFormat::I16 => ndk::audio::AudioFormat::PCM_I16,
648 SampleFormat::F32 => ndk::audio::AudioFormat::PCM_Float,
649 sample_format => {
650 return Err(BackendSpecificError {
651 description: format!("{} format is not supported on Android.", sample_format),
652 }
653 .into())
654 }
655 };
656 let channel_count = match config.channels {
657 1 => 1,
658 2 => 2,
659 channels => {
660 // TODO: more channels available in native AAudio
661 return Err(BackendSpecificError {
662 description: format!(
663 "{} channels are not supported yet (only 1 or 2).",
664 channels
665 ),
666 }
667 .into());
668 }
669 };
670
671 let builder = ndk::audio::AudioStreamBuilder::new()?
672 .direction(ndk::audio::AudioDirection::Output)
673 .channel_count(channel_count)
674 .format(format);
675
676 build_output_stream(
677 self,
678 config,
679 data_callback,
680 error_callback,
681 builder,
682 sample_format,
683 )
684 }
685}
686
687impl StreamTrait for Stream {
688 fn play(&self) -> Result<(), PlayStreamError> {
689 let stream = self.inner.lock().unwrap();
690
691 stream.request_start().map_err(PlayStreamError::from)?;
692 stream
693 .wait_for_state_change(
694 ndk::audio::AudioStreamState::Starting,
695 DEFAULT_TIMEOUT_NANOS,
696 )
697 .map(|_| ())
698 .map_err(PlayStreamError::from)
699 }
700
701 fn pause(&self) -> Result<(), PauseStreamError> {
702 match self.direction {
703 DeviceDirection::Output => {
704 let stream = self.inner.lock().unwrap();
705
706 stream.request_pause().map_err(PauseStreamError::from)?;
707 stream
708 .wait_for_state_change(
709 ndk::audio::AudioStreamState::Pausing,
710 DEFAULT_TIMEOUT_NANOS,
711 )
712 .map(|_| ())
713 .map_err(PauseStreamError::from)
714 }
715 _ => Err(BackendSpecificError {
716 description: "Pause only supported on output streams.".to_owned(),
717 }
718 .into()),
719 }
720 }
721
722 fn buffer_size(&self) -> Option<crate::FrameCount> {
723 let stream = self.inner.lock().ok()?;
724
725 // frames_per_data_callback is only set for BufferSize::Fixed; for Default AAudio
726 // schedules callbacks at the burst size, so that is the best available estimate.
727 let frames = match stream.frames_per_data_callback() {
728 Some(size) if size > 0 => size,
729 _ => stream.frames_per_burst(),
730 };
731 if frames > 0 {
732 Some(frames as crate::FrameCount)
733 } else {
734 None
735 }
736 }
737}