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
Removing the EventLoop
- rebased
#354
Removing the EventLoop
- rebased
#354
Conversation
some quick facts:
|
Oh hey, and I tested the (feedback) examples and apparently it works. I'm running PulseAudio with Arch if that matters. |
Oh strange, it's working for me too now! Even switching through all my different available audio devices seems to work. Ahh, I can recreate the error by switching back to the non-rebased branch. It looks like one of the ALSA patches introduced in the rebase must have addressed whatever was going on here. |
Feels like #327. |
2df33a6
to
ef39ea9
Compare
What does this mean (compared to the currently existing event loop APIs)? And why not make them Send + Sync? |
@MOZGIII Because it’s not portable. Android is a clear obstacle in this case, and there are also some platforms where thread safety is unknown. If you need the Send/Sync version, then you need to use the concrete (platform specific) types. |
Oh, ok. I'll still be able to use generics, right? Are the structs this is about just to abstract away things to dynamic dispatch while hiding it as an implementation detail? |
Actually, it'd be perfect if some lower-level tweaking abilities were exposed if I were to use concrete types - like buffer sizes etc. Any plans on enabling that? |
I was thinking of exposing those platform specific options, but I'm not in charge of development at the moment. It's completely possible and a matter of being implemented or not. |
Sounds good. I'd look into exposing the things I need, but there's way too much turbulence going on with the code base, so I'll just wait for this to land first. |
@msiglreith I just had a read through your ASIO update and it looks good to me! From my understanding reading through the thread at ishitatsuyuki#2 it looks like the remaining steps are:
Does this sound about right? Would you be interested in finishing this off? If not, let me know and I'll rebase and make these changes myself next time I get access to a Windows machine. |
Hi 👋, 1st and 2nd point are rather design decision imo. It was not clear to me how the lifetime of the objects are coupled in the API. The suggested approach looks viable. For the 3rd one, there are two aspects:
The earliest date for me to start working on it again would most likely be at end of the year. |
Thanks for the quick response!
No worries! I'll try to get onto this in the next day or two. |
771d53d
to
20b914e
Compare
|
||
// Run for 3 seconds before closing. | ||
println!("Playing for 3 seconds... "); | ||
std::thread::sleep(std::time::Duration::from_secs(3)); | ||
drop(input_stream); |
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.
why an explicit drop here?
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.
The stream terminates when it's dropped, and this makes it explicit.
// TODO: use AtomicU64 instead | ||
next_stream_id: AtomicUsize, | ||
pub struct Stream { | ||
/// The high-priority audio processing thread calling callbacks. |
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.
These comments feel kind of misleading as priority for these threads are never set
//! > **Note**: Creating and running a stream will *not* block the thread. On modern platforms, the | ||
//! > given callback is called by a dedicated, high-priority thread responsible for delivering | ||
//! > audio data to the system's audio device in a timely manner. On older platforms that only | ||
//! > provide a blocking API (e.g. ALSA), CPAL will create a thread in order to consistently |
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.
should be noted that it's currently one thread per stream
- Shares the `Device`'s `driver` and `asio_streams` `Arc`s with the `Stream`s to ensure they remain valid if the `Host` or `Device` are dropped early. - Ensures that a stream's callback is removed upon `Drop`.
20b914e
to
39ade49
Compare
Thanks for the review @msiglreith! I think this is just about ready to go now other than:
@ishitatsuyuki off the top of your head, do you remember if this is necessary for all of those types? Or is it e.g. just the |
My original intention was AAudio, which is not only not thread safe but also prohibits calling certain functions within a callback. Therefore I went for setting the AAudio on the other hand is stateless for stream creation, so the Though, I think ASIO is not thread safe at all? Therefore we might also need |
let buffer_len = frames_available as usize | ||
* stream.bytes_per_frame as usize / sample_size; | ||
let thread = | ||
thread::spawn(move || run_inner(run_context, &mut data_callback, &mut error_callback)); |
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.
Should this thread initialize the COM library to COINIT_MULTITHREADED? And, if so, does this mean that the calling thread can have it's requirement on that threading model relaxed since the device setup is less timing-sensitive?
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.
COM is initialised lazily using thread_local.
Threading model will not be relaxed AFAIK because what matters is the thread that created the handle. As long as the handle is created before the thread spawns, we'll need to use MTA when doing it.
This is in order to ensure consistent restrictions across platforms in a manner that ensures thread safety across each of the supported platforms. Please see added comments in the diff for details on which platforms impose these restrictions.
This makes some tweaks to the ASIO backend in order to fix some cases where races may have occured. This should allow us to remove the `Sync` bound on the `Device` and `Host` types.
Originally this restriction was placed due to uncertainty around the thread safety of the ASIO API. While the ASIO API itself makes no thread-safety guarantees that we are aware of, the `asio-sys` high-level bindings enforce synchronised access to the API and state transitions via a mutex.
Explicitly make dynamically dispatched API !Send + !Sync
OK this should be good to go! |
Any plans on publishing a new version to crates.io in the near future? |
Thank you for your hard work in bringing this to completion! |
This PR is a rebase of @ishitatsuyuki's work in #301. Originally opened at ishitatsuyuki#3 but closed in favour opening a new PR here.
Backends
Other TODO
ALSA multiple streams bug
Edit: This bug seems to have been addressed in the rebase.
After testing each of the examples with the ALSA backend on my Arch Linux system, I noticed that the
feedback.rs
example no longer works. No audio output was occurring and after some investigation it seems the output stream callback only seems to get called once.beep.rs
andrecord_wav.rs
both work fine, which leads me to wonder if there's an issue with the way multiple streams are being handled.feedback.rs
also works fine onmaster
, so this has likely been introduced in this PR.@ishitatsuyuki does anything come to mind as a potential cause for this? I did notice that we're now spawning a whole OS thread per stream. I wonder if there's some kind of race going on in the stream loop? I might see if I can find anything about thread safety in the ALSA docs. I also wonder if we can reduce the number of threads necessary by just using a single thread on which all of the streams run, similar to the old
EventLoop::run
impl. If there is some race going on, maybe using a single thread for all streams would help to avoid this.Also, I was reading through PortAudio's non-blocking ALSA code and noticed that, when spinning up a thread for a new stream, they also attempt to set the thread's priority to the highest possible. It might be worth seeing if there is some way we can do this too, though this work can probably wait for a follow-up PR.