@@ -12,9 +12,9 @@ use std::{
1212} ;
1313
1414use crate :: {
15- Data , DeviceDescription , DeviceId , Error , InputCallbackInfo , InputDevices , OutputCallbackInfo ,
16- OutputDevices , SampleFormat , SizedSample , StreamConfig , StreamInstant , SupportedStreamConfig ,
17- SupportedStreamConfigRange ,
15+ Data , DeviceDescription , DeviceId , DuplexCallbackInfo , DuplexStreamConfig , Error , ErrorKind ,
16+ InputCallbackInfo , InputDevices , OutputCallbackInfo , OutputDevices , SampleFormat , SizedSample ,
17+ StreamConfig , StreamInstant , SupportedStreamConfig , SupportedStreamConfigRange ,
1818} ;
1919
2020/// A [`Host`] provides access to the available audio devices on the system.
@@ -114,10 +114,12 @@ pub trait DeviceTrait: PartialEq + Eq + Hash + Debug + Display {
114114 type SupportedInputConfigs : Iterator < Item = SupportedStreamConfigRange > ;
115115 /// The iterator type yielding supported output stream formats.
116116 type SupportedOutputConfigs : Iterator < Item = SupportedStreamConfigRange > ;
117- /// The stream type created by [`build_input_stream_raw`] and [`build_output_stream_raw`].
117+ /// The stream type created by [`build_input_stream_raw`] and [`build_output_stream_raw`],
118+ /// and [`build_duplex_stream_raw`].
118119 ///
119120 /// [`build_input_stream_raw`]: Self::build_input_stream_raw
120121 /// [`build_output_stream_raw`]: Self::build_output_stream_raw
122+ /// [`build_duplex_stream_raw`]: Self::build_duplex_stream_raw
121123 type Stream : StreamTrait ;
122124
123125 /// Structured description of the device with metadata.
@@ -160,6 +162,18 @@ pub trait DeviceTrait: PartialEq + Eq + Hash + Debug + Display {
160162 . is_ok_and ( |mut iter| iter. next ( ) . is_some ( ) )
161163 }
162164
165+ /// True if the device can build a synchronized duplex stream where the captured input and
166+ /// rendered output share a single clock.
167+ ///
168+ /// Returning `true` is a contract that input and output sides will run from one device-level
169+ /// callback, or an OS driver aggregate (such as an Aggregate Device on MacOS).
170+ ///
171+ /// The default implementation returns `false`; hosts that can guarantee a shared clock should
172+ /// override.
173+ fn supports_duplex ( & self ) -> bool {
174+ false
175+ }
176+
163177 /// An iterator yielding input stream configurations that are supported by the device.
164178 ///
165179 /// # Errors
@@ -407,6 +421,102 @@ pub trait DeviceTrait: PartialEq + Eq + Hash + Debug + Display {
407421 where
408422 D : FnMut ( & mut Data , & OutputCallbackInfo ) + Send + ' static ,
409423 E : FnMut ( Error ) + Send + ' static ;
424+
425+ /// Create a synchronized duplex stream whose input and output share the same clock
426+ /// or OS provided bidirectional aggregate device (MacOS). MacOS Aggregate device drift
427+ /// compensation is not required.
428+ ///
429+ /// # Parameters
430+ ///
431+ /// * `config` - Channels, sample rate, and buffer size shared by both directions.
432+ /// * `data_callback` - Called periodically with captured input and a mutable output buffer.
433+ /// * `error_callback` - Called when a stream error occurs (e.g., device disconnected).
434+ /// * `timeout` - Time to wait for the backend to initialize the stream. `None` waits
435+ /// indefinitely. Note: not all backends honor this value.
436+ ///
437+ /// # Errors
438+ ///
439+ /// - [`ErrorKind::UnsupportedOperation`] if the host or device does not support duplex
440+ /// streams.
441+ /// - [`ErrorKind::UnsupportedConfig`] if the sample rate, channel counts, buffer size, or
442+ /// sample format is not supported by the device.
443+ /// - [`ErrorKind::DeviceNotAvailable`] if the device has been disconnected.
444+ /// - [`ErrorKind::DeviceBusy`] if the device is temporarily in use by another application.
445+ /// - [`ErrorKind::PermissionDenied`] if the process lacks permission to access the device
446+ /// (e.g. microphone access on macOS).
447+ /// - [`ErrorKind::InvalidInput`] if the configuration parameters are invalid.
448+ /// - [`ErrorKind::StreamInvalidated`] if the device's sample rate or buffer size changed
449+ /// during stream creation, or an internal lock was poisoned.
450+ /// - [`ErrorKind::ResourceExhausted`] if the host fails to spawn an internal monitoring
451+ /// thread.
452+ /// - [`ErrorKind::BackendError`] for unclassified backend failures.
453+ ///
454+ /// [`ErrorKind::UnsupportedOperation`]: crate::ErrorKind::UnsupportedOperation
455+ /// [`ErrorKind::UnsupportedConfig`]: crate::ErrorKind::UnsupportedConfig
456+ /// [`ErrorKind::DeviceNotAvailable`]: crate::ErrorKind::DeviceNotAvailable
457+ /// [`ErrorKind::DeviceBusy`]: crate::ErrorKind::DeviceBusy
458+ /// [`ErrorKind::PermissionDenied`]: crate::ErrorKind::PermissionDenied
459+ /// [`ErrorKind::InvalidInput`]: crate::ErrorKind::InvalidInput
460+ /// [`ErrorKind::StreamInvalidated`]: crate::ErrorKind::StreamInvalidated
461+ /// [`ErrorKind::ResourceExhausted`]: crate::ErrorKind::ResourceExhausted
462+ /// [`ErrorKind::BackendError`]: crate::ErrorKind::BackendError
463+ fn build_duplex_stream < T , D , E > (
464+ & self ,
465+ config : DuplexStreamConfig ,
466+ mut data_callback : D ,
467+ error_callback : E ,
468+ timeout : Option < Duration > ,
469+ ) -> Result < Self :: Stream , Error >
470+ where
471+ T : SizedSample ,
472+ D : FnMut ( & [ T ] , & mut [ T ] , & DuplexCallbackInfo ) + Send + ' static ,
473+ E : FnMut ( Error ) + Send + ' static ,
474+ {
475+ self . build_duplex_stream_raw (
476+ config,
477+ T :: FORMAT ,
478+ move |input, output, info| {
479+ data_callback (
480+ input
481+ . as_slice ( )
482+ . expect ( "host supplied incorrect sample type" ) ,
483+ output
484+ . as_slice_mut ( )
485+ . expect ( "host supplied incorrect sample type" ) ,
486+ info,
487+ )
488+ } ,
489+ error_callback,
490+ timeout,
491+ )
492+ }
493+
494+ /// Create a dynamically typed synchronized duplex stream.
495+ ///
496+ /// Hosts that support duplex streams must override this method;
497+ /// the default implementation returns [`ErrorKind::UnsupportedOperation`].
498+ ///
499+ /// See [`build_duplex_stream`](Self::build_duplex_stream) for parameter and error
500+ /// documentation.
501+ ///
502+ /// [`ErrorKind::UnsupportedOperation`]: crate::ErrorKind::UnsupportedOperation
503+ fn build_duplex_stream_raw < D , E > (
504+ & self ,
505+ _config : DuplexStreamConfig ,
506+ _sample_format : SampleFormat ,
507+ _data_callback : D ,
508+ _error_callback : E ,
509+ _timeout : Option < Duration > ,
510+ ) -> Result < Self :: Stream , Error >
511+ where
512+ D : FnMut ( & Data , & mut Data , & DuplexCallbackInfo ) + Send + ' static ,
513+ E : FnMut ( Error ) + Send + ' static ,
514+ {
515+ Err ( Error :: with_message (
516+ ErrorKind :: UnsupportedOperation ,
517+ "duplex streams are not supported by this host" ,
518+ ) )
519+ }
410520}
411521
412522/// A stream created from [`Device`](DeviceTrait), with methods to control it.
0 commit comments