Skip to content
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

User Callback API StreamEvent Overhaul #288

Merged
merged 10 commits into from
Jun 24, 2019

Conversation

mitchmindtree
Copy link
Member

@mitchmindtree mitchmindtree commented Jun 21, 2019

Add new StreamEvent type - enables more flexible user callback API.

This adds the following types:

  • StreamEvent
  • CloseStreamCause
  • StreamError

These allow for notifying the user of the following events:

  • A stream has been played.
  • A stream has been paused.
  • A stream has been closed due to the user destroying stream.
  • A stream has been closed due to an error.

TODO

  • Add new StreamEvent API to top level.
  • Update examples for new StreamEvent API.
  • Update ALSA backend.
  • Update WASAPI backend.
  • Update CoreAudio backend.
  • Update emscripten backend.
  • Update null backend.

Closes #268.

This adds the following types:

- `StreamEvent`
- `CloseStreamCause`
- `StreamError`

These allow for notifying the user of the following events:

- A stream has been played.
- A stream has been paused.
- A stream has been closed due to user destroying stream.
- A stream has been closed due to an error.
This commit significantly refactors the alsa backend's `EventLoop::run`
implementation in order to allow for better error handling throughout
the loop. This removes many cases that would previously `panic!` in
favour of calling the user callback with the necessary error and
removing the corrupt stream. Seeing as the method cannot return, a
catch-all `panic!` still exists at the end of the method, however this
refactor should make it much easier to remove this restriction in the
future.
@mitchmindtree mitchmindtree force-pushed the callback_event branch 5 times, most recently from 6db67df to 806455a Compare June 22, 2019 01:40
Refactors much of the `EventLoop::run` implementation in order to make
error handling a little easier.
@mitchmindtree
Copy link
Member Author

cc @ishitatsuyuki it would be great to get your thoughts on this too as it aims to address the other half of #268. My plan is to do the coreaudio backend tomorrow and then it should be ready to go.

@mitchmindtree mitchmindtree force-pushed the callback_event branch 5 times, most recently from b76cf12 to 577301d Compare June 22, 2019 15:06
@mitchmindtree mitchmindtree changed the title WIP User Callback API StreamEvent Overhaul User Callback API StreamEvent Overhaul Jun 22, 2019
@mitchmindtree
Copy link
Member Author

OK I think this should be ready to go now. I'll leave this open for a bit longer for feedback. In the meantime, I'm going to start a branch addressing #204 based on this branch.

@ishitatsuyuki
Copy link
Collaborator

I'm sorry I'm a bit late! Thanks for implementing this, but I'm not sure I agree with the need of play/pause events. First, the ability to inject events into the audio processing thread varies a lot between platforms, and this seems to introduce more problems. For example, the macOS implementation relies on the existence of Mutex to deliver such events, and this will not change even after the EventLoop refactor. And second, I'm not sure if exposing such events would be useful, as it's not something the low level API implements. Did you have some particular library in mind when making this?

Given that waking up the audio thread with arbitrary events is not portable, I think we are left with two options:

  • Pass any events if exist when the data callback is called. Which means, it takes (Id, Buffer, Option<ErrorsAndEvents>). (Or using Vec and other combinations of flags. This could also include xrun informations.)
  • Use another thread and callback solely for errors (basically Android's API).

Also a small note: device are often considered invalidated when it gets disconnected. It's a state where you cannot perform operations, but still need to destroy it as usual, therefore is not the same as close.

@mitchmindtree
Copy link
Member Author

but I'm not sure I agree with the need of play/pause events. First, the ability to inject events into the audio processing thread varies a lot between platforms, and this seems to introduce more problems

Yes I think you're right, originally I thought it might be nice for users to have access to play/pause events in case they wanted to reset some state (like oscillator phases e.g.) but this is not really essential and not worth the burden added to CPAL. I'll remove the Play and Pause events.

Given that waking up the audio thread with arbitrary events is not portable, I think we are left with two options:

  • Pass any events if exist when the data callback is called. Which means, it takes (Id, Buffer, Option). (Or using Vec and other combinations of flags. This could also include xrun informations.)
  • Use another thread and callback solely for errors (basically Android's API).

I think I tend to agree with your comment from #268:

Personally I think error callback forms a pretty hard-to-use API.

I think the only error we should expose on all platforms is device disconnect, and for that purpose we should not pass the burden of synchronization to users.

I'm leaning towards the first option, however I think the signature should probably be something like (Id, StreamDataResult, StreamFlags). StreamDataResult would be an alias for Result<StreamData, StreamError> as in many cases device invalidation indicates that the data is invalid, and even if the data is valid the data and error can be delivered via two separate calls.

I think it's still important that StreamError can indicate both DeviceInvalidated and BackendSpecific errors, as backends like wasapi and alsa can both error for other reasons like file descriptor errors, etc.

StreamFlags could be a bitflags type where events such as buffer underruns and other events can be indicated, but this could be addressed in a follow up PR.

What are your thoughts?

mitchmindtree added a commit to mitchmindtree/cpal that referenced this pull request Jun 24, 2019
This is an implementation of the API described at RustAudio#204. Please see that
issue for more details on the motivation.

-----

A **Host** provides access to the available audio devices on the system.
Some platforms have more than one host available, e.g.
wasapi/asio/dsound on windows, alsa/pulse/jack on linux and so on. As a
result, some audio devices are only available on certain hosts, while
others are only available on other hosts. Every platform supported by
CPAL has at least one **DefaultHost** that is guaranteed to be available
(alsa, wasapi and coreaudio). Currently, the default hosts are the only
hosts supported by CPAL, however this will change as of landing RustAudio#221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack RustAudio#250 (cc @derekdreery) and pulseaudio (cc @knappador) RustAudio#259.

This introduces a suite of traits allowing for both compile time and
runtime dispatch of different hosts and their uniquely associated device
and event loop types.

A new private **host** module has been added containing the individual
host implementations, each in their own submodule gated to the platforms
on which they are available.

A new **platform** module has been added containing platform-specific
items, including a dynamically dispatched host type that allows for
easily switching between hosts at runtime.

The **ALL_HOSTS** slice contains a **HostId** for each host supported on
the current platform. The **available_hosts** function produces a
**HostId** for each host that is currently *available* on the platform.
The **host_from_id** function allows for initialising a host from its
associated ID, failing with a **HostUnavailable** error. The
**default_host** function returns the default host and should never
fail.

Please see the examples for a demonstration of the change in usage. For
the most part, things look the same at the surface level, however the
role of device enumeration and creating the event loop have been moved
from global functions to host methods. The enumerate.rs example has been
updated to enumerate all devices for each host, not just the default.

**TODO**

- [x] Add the new **Host** API
- [x] Update examples for the new API.
- [x] ALSA host
- [ ] WASAPI host
- [ ] CoreAudio host
- [ ] Emscripten host **Follow-up PR**
- [ ] ASIO host RustAudio#221

cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after RustAudio#288 lands and this gets rebased.
mitchmindtree added a commit to mitchmindtree/cpal that referenced this pull request Jun 24, 2019
This is an implementation of the API described at RustAudio#204. Please see that
issue for more details on the motivation.

-----

A **Host** provides access to the available audio devices on the system.
Some platforms have more than one host available, e.g.
wasapi/asio/dsound on windows, alsa/pulse/jack on linux and so on. As a
result, some audio devices are only available on certain hosts, while
others are only available on other hosts. Every platform supported by
CPAL has at least one **DefaultHost** that is guaranteed to be available
(alsa, wasapi and coreaudio). Currently, the default hosts are the only
hosts supported by CPAL, however this will change as of landing RustAudio#221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack RustAudio#250 (cc @derekdreery) and pulseaudio (cc @knappador) RustAudio#259.

This introduces a suite of traits allowing for both compile time and
runtime dispatch of different hosts and their uniquely associated device
and event loop types.

A new private **host** module has been added containing the individual
host implementations, each in their own submodule gated to the platforms
on which they are available.

A new **platform** module has been added containing platform-specific
items, including a dynamically dispatched host type that allows for
easily switching between hosts at runtime.

The **ALL_HOSTS** slice contains a **HostId** for each host supported on
the current platform. The **available_hosts** function produces a
**HostId** for each host that is currently *available* on the platform.
The **host_from_id** function allows for initialising a host from its
associated ID, failing with a **HostUnavailable** error. The
**default_host** function returns the default host and should never
fail.

Please see the examples for a demonstration of the change in usage. For
the most part, things look the same at the surface level, however the
role of device enumeration and creating the event loop have been moved
from global functions to host methods. The enumerate.rs example has been
updated to enumerate all devices for each host, not just the default.

**TODO**

- [x] Add the new **Host** API
- [x] Update examples for the new API.
- [x] ALSA host
- [ ] WASAPI host
- [ ] CoreAudio host
- [ ] Emscripten host **Follow-up PR**
- [ ] ASIO host RustAudio#221

cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after RustAudio#288 lands and this gets rebased.
@mitchmindtree
Copy link
Member Author

OK, I've made the changes mentioned in my last comment - I'm going to land this as is for now, but if anyone has any feedback or input feel free to share here or open a follow-up PR!

@mitchmindtree mitchmindtree merged commit 2b9e2e0 into RustAudio:master Jun 24, 2019
mitchmindtree added a commit to mitchmindtree/cpal that referenced this pull request Jun 24, 2019
This is an implementation of the API described at RustAudio#204. Please see that
issue for more details on the motivation.

-----

A **Host** provides access to the available audio devices on the system.
Some platforms have more than one host available, e.g.
wasapi/asio/dsound on windows, alsa/pulse/jack on linux and so on. As a
result, some audio devices are only available on certain hosts, while
others are only available on other hosts. Every platform supported by
CPAL has at least one **DefaultHost** that is guaranteed to be available
(alsa, wasapi and coreaudio). Currently, the default hosts are the only
hosts supported by CPAL, however this will change as of landing RustAudio#221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack RustAudio#250 (cc @derekdreery) and pulseaudio (cc @knappador) RustAudio#259.

This introduces a suite of traits allowing for both compile time and
runtime dispatch of different hosts and their uniquely associated device
and event loop types.

A new private **host** module has been added containing the individual
host implementations, each in their own submodule gated to the platforms
on which they are available.

A new **platform** module has been added containing platform-specific
items, including a dynamically dispatched host type that allows for
easily switching between hosts at runtime.

The **ALL_HOSTS** slice contains a **HostId** for each host supported on
the current platform. The **available_hosts** function produces a
**HostId** for each host that is currently *available* on the platform.
The **host_from_id** function allows for initialising a host from its
associated ID, failing with a **HostUnavailable** error. The
**default_host** function returns the default host and should never
fail.

Please see the examples for a demonstration of the change in usage. For
the most part, things look the same at the surface level, however the
role of device enumeration and creating the event loop have been moved
from global functions to host methods. The enumerate.rs example has been
updated to enumerate all devices for each host, not just the default.

**TODO**

- [x] Add the new **Host** API
- [x] Update examples for the new API.
- [x] ALSA host
- [ ] WASAPI host
- [ ] CoreAudio host
- [ ] Emscripten host **Follow-up PR**
- [ ] ASIO host RustAudio#221

cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after RustAudio#288 lands and this gets rebased.
@mitchmindtree mitchmindtree mentioned this pull request Jun 24, 2019
8 tasks
@mitchmindtree mitchmindtree deleted the callback_event branch June 25, 2019 14:28
mitchmindtree added a commit to mitchmindtree/cpal that referenced this pull request Jul 5, 2019
This is quite a significant update for CPAL, including a number of
breaking changes. Here is a list of the breaking changes along with
links to where you can find more information:

- A `Host` API has been introduced in RustAudio#289 along with a follow-up
  refactor in RustAudio#295. Please see the examples for a demonstration of how
  to update your code. The necessary changes should hopefully be
  minimal. If this has caused you any major difficulty please let us
  know in an issue!
- An ASIO host has been introduced in RustAudio#292. This adds support for
  Steinberg's ASIO audio driver API on Windows. Please see the ASIO
  section of the README for more information on how to setup CPAL with
  ASIO support for your project.
- The user callback API has been overhauled to emit `StreamEvent`s
  rather than buffers in order to support handling stream errors. RustAudio#288.
- Error handling in general was overhauled in RustAudio#286. This meant taking
  advantage of the `failure` crate and adding support for
  backend-specific errors with custom messages. Many unnecessary
  `panic!`s have been removed, but a few remain that would indicate bugs
  in CPAL.

In general, checking out the updated examples will be the easiest way to
get a quick overview on how you can update your own code for these
changes.

The CHANGELOG.md has been updated to include these changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Better error handling interface
2 participants