-
Notifications
You must be signed in to change notification settings - Fork 487
Duplex support #1096
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
gulbrand
wants to merge
2
commits into
RustAudio:master
Choose a base branch
from
gulbrand:duplex-support
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+884
−17
Open
Duplex support #1096
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| //! Feeds back the input stream directly into the output stream using a duplex stream. | ||
| //! | ||
| //! Unlike the `feedback.rs` example which uses separate input/output streams with a ring buffer, | ||
| //! duplex streams provide hardware-synchronized input/output without additional buffering. | ||
| //! | ||
| //! Note: Currently only supported on macOS (CoreAudio). Windows (WASAPI) and Linux (ALSA) | ||
| //! implementations are planned. | ||
|
|
||
| #[cfg(target_os = "macos")] | ||
| mod imp { | ||
| use clap::Parser; | ||
| use cpal::duplex::DuplexStreamConfig; | ||
| use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; | ||
| use cpal::{BufferSize, ChannelCount, FrameCount, Sample, SampleRate}; | ||
|
|
||
| #[derive(Parser, Debug)] | ||
| #[command(version, about = "CPAL duplex feedback example", long_about = None)] | ||
| struct Opt { | ||
| /// The audio device to use (must support duplex operation) | ||
| #[arg(short, long, value_name = "DEVICE")] | ||
| device: Option<String>, | ||
|
|
||
| /// Number of input channels | ||
| #[arg(long, value_name = "CHANNELS", default_value_t = 2)] | ||
| input_channels: ChannelCount, | ||
|
|
||
| /// Number of output channels | ||
| #[arg(long, value_name = "CHANNELS", default_value_t = 2)] | ||
| output_channels: ChannelCount, | ||
|
|
||
| /// Sample rate in Hz | ||
| #[arg(short, long, value_name = "RATE", default_value_t = 48000)] | ||
| sample_rate: SampleRate, | ||
|
|
||
| /// Buffer size in frames (omit for device default) | ||
| #[arg(short, long, value_name = "FRAMES")] | ||
| buffer_size: Option<FrameCount>, | ||
| } | ||
|
|
||
| pub fn run() -> anyhow::Result<()> { | ||
| let opt = Opt::parse(); | ||
| let host = cpal::default_host(); | ||
|
|
||
| // Find the device by device ID or use default | ||
| let device = match opt.device { | ||
| Some(device_id_str) => { | ||
| let device_id = device_id_str.parse().expect("failed to parse device id"); | ||
| host.device_by_id(&device_id) | ||
| .expect(&format!("failed to find device with id: {}", device_id_str)) | ||
| } | ||
| None => host | ||
| .default_output_device() | ||
| .expect("no default output device"), | ||
| }; | ||
|
|
||
| println!("Using device: \"{}\"", device.description()?.name()); | ||
|
|
||
| // Create duplex stream configuration. | ||
| let config = DuplexStreamConfig { | ||
| input_channels: opt.input_channels, | ||
| output_channels: opt.output_channels, | ||
| sample_rate: opt.sample_rate, | ||
| buffer_size: opt | ||
| .buffer_size | ||
| .map(|s| BufferSize::Fixed(s)) | ||
| .unwrap_or(BufferSize::Default), | ||
| }; | ||
|
|
||
| println!("Building duplex stream with config: {config:?}"); | ||
|
|
||
| let stream = device.build_duplex_stream::<f32, _, _>( | ||
| &config, | ||
| move |input, output, _info| { | ||
| output.fill(Sample::EQUILIBRIUM); | ||
| let copy_len = input.len().min(output.len()); | ||
| output[..copy_len].copy_from_slice(&input[..copy_len]); | ||
| }, | ||
| |err| eprintln!("Stream error: {err}"), | ||
| None, | ||
| )?; | ||
|
|
||
| println!("Successfully built duplex stream."); | ||
| println!( | ||
| "Input: {} channels, Output: {} channels, Sample rate: {} Hz, Buffer size: {:?} frames", | ||
| opt.input_channels, opt.output_channels, opt.sample_rate, opt.buffer_size | ||
| ); | ||
|
|
||
| println!("Starting duplex stream..."); | ||
| stream.play()?; | ||
|
|
||
| println!("Playing for 10 seconds... (speak into your microphone)"); | ||
| std::thread::sleep(std::time::Duration::from_secs(10)); | ||
|
|
||
| println!("Done!"); | ||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| fn main() { | ||
| #[cfg(target_os = "macos")] | ||
| imp::run().unwrap(); | ||
|
|
||
| #[cfg(not(target_os = "macos"))] | ||
| { | ||
| eprintln!("Duplex streams are not supported on this platform."); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| //! Duplex audio stream support with synchronized input/output. | ||
| //! | ||
| //! This module provides types for building duplex (simultaneous input/output) audio streams | ||
| //! with hardware clock synchronization. | ||
| //! | ||
| //! See `examples/duplex_feedback.rs` for a working example. | ||
|
|
||
| use crate::{ChannelCount, InputStreamTimestamp, OutputStreamTimestamp, SampleRate}; | ||
|
|
||
| /// Information passed to duplex callbacks. | ||
| /// | ||
| /// This contains timing information for the current audio buffer, combining | ||
| /// both input and output timing. A duplex stream has a single callback invocation | ||
| /// that provides synchronized input and output data. | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
| pub struct DuplexCallbackInfo { | ||
| input_timestamp: InputStreamTimestamp, | ||
| output_timestamp: OutputStreamTimestamp, | ||
| } | ||
|
|
||
| impl DuplexCallbackInfo { | ||
| /// Create a new DuplexCallbackInfo. | ||
| /// | ||
| /// Note: Both timestamps will share the same `callback` instant since there is | ||
| /// only one callback invocation for a duplex stream. | ||
| pub fn new( | ||
| input_timestamp: InputStreamTimestamp, | ||
| output_timestamp: OutputStreamTimestamp, | ||
| ) -> Self { | ||
| Self { | ||
| input_timestamp, | ||
| output_timestamp, | ||
| } | ||
| } | ||
|
|
||
| /// The timestamp for the input portion of the duplex stream. | ||
| /// | ||
| /// Contains the callback instant and when the input data was captured. | ||
| pub fn input_timestamp(&self) -> InputStreamTimestamp { | ||
| self.input_timestamp | ||
| } | ||
|
|
||
| /// The timestamp for the output portion of the duplex stream. | ||
| /// | ||
| /// Contains the callback instant and when the output data will be played. | ||
| pub fn output_timestamp(&self) -> OutputStreamTimestamp { | ||
| self.output_timestamp | ||
| } | ||
| } | ||
|
|
||
| /// Configuration for a duplex audio stream. | ||
| /// | ||
| /// Unlike separate input/output streams, duplex streams require matching | ||
| /// configuration for both directions since they share a single device context. | ||
| #[derive(Clone, Debug, Eq, PartialEq)] | ||
| pub struct DuplexStreamConfig { | ||
| /// Number of input channels. | ||
| pub input_channels: ChannelCount, | ||
|
|
||
| /// Number of output channels. | ||
| pub output_channels: ChannelCount, | ||
|
|
||
| /// Sample rate in Hz. | ||
| pub sample_rate: SampleRate, | ||
|
|
||
| /// Requested buffer size in frames. | ||
| pub buffer_size: crate::BufferSize, | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it is copy able. So we do not need & I think