2015-06-30 20:49:36 +00:00
|
|
|
# libsoundio
|
|
|
|
|
2015-07-29 01:47:28 +00:00
|
|
|
C99 library providing cross-platform audio input and output. The API is
|
2015-06-30 20:49:36 +00:00
|
|
|
suitable for real-time software such as digital audio workstations as well
|
|
|
|
as consumer software such as music players.
|
|
|
|
|
2015-07-29 01:47:28 +00:00
|
|
|
This library is an abstraction; however in the delicate balance between
|
|
|
|
performance and power, and API convenience, the scale is tipped closer to
|
|
|
|
the former. Features that only exist in some sound backends are exposed.
|
2015-07-01 08:02:44 +00:00
|
|
|
|
2015-07-29 01:47:28 +00:00
|
|
|
The goal of this library is to be the only resource needed to implement top
|
|
|
|
quality audio playback and capture on desktop and laptop systems. This
|
|
|
|
includes detailed documentation explaining how audio works on each supported
|
|
|
|
backend, how they are abstracted to provide the libsoundio API, and what
|
|
|
|
assumptions you can and cannot make in order to guarantee consistent, reliable
|
|
|
|
behavior on every platform.
|
|
|
|
|
|
|
|
**This project is a work-in-progress.**
|
2015-07-06 06:42:56 +00:00
|
|
|
|
2015-07-25 02:16:48 +00:00
|
|
|
## Features and Limitations
|
2015-07-20 05:36:50 +00:00
|
|
|
|
2015-07-23 22:04:41 +00:00
|
|
|
* Supported backends:
|
2015-07-30 03:55:28 +00:00
|
|
|
- [JACK](http://jackaudio.org/)
|
2015-07-20 05:36:50 +00:00
|
|
|
- [PulseAudio](http://www.freedesktop.org/wiki/Software/PulseAudio/)
|
|
|
|
- [ALSA](http://www.alsa-project.org/)
|
|
|
|
- (planned) [CoreAudio](https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/CoreAudioOverview/Introduction/Introduction.html)
|
2015-08-01 01:52:51 +00:00
|
|
|
- Dummy (silence)
|
2015-07-20 05:36:50 +00:00
|
|
|
- (planned) [WASAPI](https://msdn.microsoft.com/en-us/library/windows/desktop/dd371455%28v=vs.85%29.aspx)
|
|
|
|
- (planned) [ASIO](http://www.asio4all.com/)
|
|
|
|
* C library. Depends only on the respective backend API libraries and libc.
|
|
|
|
Does *not* depend on libstdc++, and does *not* have exceptions, run-time type
|
|
|
|
information, or [setjmp](http://latentcontent.net/2007/12/05/libpng-worst-api-ever/).
|
2015-07-29 01:47:28 +00:00
|
|
|
* Errors are communicated via return codes, not logging to stdio. This is one
|
|
|
|
of my many complaints against [PortAudio](http://www.portaudio.com/).
|
2015-07-20 05:36:50 +00:00
|
|
|
* Supports channel layouts (also known as channel maps), important for
|
|
|
|
surround sound applications.
|
|
|
|
* Ability to monitor devices and get an event when available devices change.
|
2015-07-29 01:47:28 +00:00
|
|
|
* Ability to get an event when the backend is disconnected, for example when
|
|
|
|
the JACK server or PulseAudio server shuts down.
|
2015-07-24 03:55:36 +00:00
|
|
|
* Detects which input device is default and which output device is default.
|
2015-07-20 05:36:50 +00:00
|
|
|
* Ability to connect to multiple backends at once. For example you could have
|
|
|
|
an ALSA device open and a JACK device open at the same time.
|
|
|
|
* Meticulously checks all return codes and memory allocations and uses
|
|
|
|
meaningful error codes.
|
|
|
|
|
|
|
|
## Synopsis
|
|
|
|
|
|
|
|
Complete program to emit a sine wave over the default device using the best
|
|
|
|
backend:
|
|
|
|
|
|
|
|
```c
|
|
|
|
#include <soundio/soundio.h>
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
__attribute__ ((cold))
|
|
|
|
__attribute__ ((noreturn))
|
|
|
|
__attribute__ ((format (printf, 1, 2)))
|
|
|
|
static void panic(const char *format, ...) {
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
|
|
vfprintf(stderr, format, ap);
|
|
|
|
fprintf(stderr, "\n");
|
|
|
|
va_end(ap);
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
static const float PI = 3.1415926535f;
|
|
|
|
static float seconds_offset = 0.0f;
|
|
|
|
static void write_callback(struct SoundIoOutStream *outstream, int requested_frame_count) {
|
|
|
|
float float_sample_rate = outstream->sample_rate;
|
|
|
|
float seconds_per_frame = 1.0f / float_sample_rate;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
for (;;) {
|
2015-07-21 05:55:30 +00:00
|
|
|
int frame_count = requested_frame_count;
|
|
|
|
|
2015-07-20 05:36:50 +00:00
|
|
|
struct SoundIoChannelArea *areas;
|
|
|
|
if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count)))
|
|
|
|
panic("%s", soundio_strerror(err));
|
|
|
|
|
|
|
|
if (!frame_count)
|
|
|
|
break;
|
|
|
|
|
|
|
|
const struct SoundIoChannelLayout *layout = &outstream->layout;
|
|
|
|
|
|
|
|
float pitch = 440.0f;
|
|
|
|
float radians_per_second = pitch * 2.0f * PI;
|
|
|
|
for (int frame = 0; frame < frame_count; frame += 1) {
|
|
|
|
float sample = sinf((seconds_offset + frame * seconds_per_frame) * radians_per_second);
|
|
|
|
for (int channel = 0; channel < layout->channel_count; channel += 1) {
|
|
|
|
float *ptr = (float*)(areas[channel].ptr + areas[channel].step * frame);
|
|
|
|
*ptr = sample;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
seconds_offset += seconds_per_frame * frame_count;
|
|
|
|
|
2015-07-23 22:04:41 +00:00
|
|
|
if ((err = soundio_outstream_end_write(outstream, frame_count)))
|
2015-07-20 05:36:50 +00:00
|
|
|
panic("%s", soundio_strerror(err));
|
2015-07-21 05:55:30 +00:00
|
|
|
|
|
|
|
requested_frame_count -= frame_count;
|
|
|
|
if (requested_frame_count <= 0)
|
|
|
|
break;
|
2015-07-20 05:36:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
|
|
struct SoundIo *soundio = soundio_create();
|
|
|
|
if (!soundio)
|
|
|
|
panic("out of memory");
|
|
|
|
|
|
|
|
if ((err = soundio_connect(soundio)))
|
|
|
|
panic("error connecting: %s", soundio_strerror(err));
|
|
|
|
|
|
|
|
int default_out_device_index = soundio_get_default_output_device_index(soundio);
|
|
|
|
if (default_out_device_index < 0)
|
|
|
|
panic("no output device found");
|
|
|
|
|
|
|
|
struct SoundIoDevice *device = soundio_get_output_device(soundio, default_out_device_index);
|
|
|
|
if (!device)
|
|
|
|
panic("out of memory");
|
|
|
|
|
|
|
|
fprintf(stderr, "Output device: %s: %s\n", device->name, device->description);
|
|
|
|
|
|
|
|
struct SoundIoOutStream *outstream = soundio_outstream_create(device);
|
|
|
|
outstream->format = SoundIoFormatFloat32NE;
|
|
|
|
outstream->write_callback = write_callback;
|
|
|
|
|
|
|
|
if ((err = soundio_outstream_open(outstream)))
|
|
|
|
panic("unable to open device: %s", soundio_strerror(err));
|
|
|
|
|
2015-07-23 22:04:41 +00:00
|
|
|
if (outstream->layout_error)
|
|
|
|
fprintf(stderr, "unable to set channel layout: %s\n", soundio_strerror(outstream->layout_error));
|
|
|
|
|
2015-07-20 05:36:50 +00:00
|
|
|
if ((err = soundio_outstream_start(outstream)))
|
|
|
|
panic("unable to start device: %s", soundio_strerror(err));
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
soundio_wait_events(soundio);
|
|
|
|
|
|
|
|
soundio_outstream_destroy(outstream);
|
|
|
|
soundio_device_unref(device);
|
|
|
|
soundio_destroy(soundio);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2015-07-22 19:07:20 +00:00
|
|
|
### Backend Priority
|
2015-06-30 20:49:36 +00:00
|
|
|
|
2015-07-20 06:06:17 +00:00
|
|
|
When you use `soundio_connect`, libsoundio tries these backends in order.
|
|
|
|
If unable to connect to that backend, due to the backend not being installed,
|
|
|
|
or the server not running, or the platform is wrong, the next backend is tried.
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
0. JACK
|
|
|
|
0. PulseAudio
|
|
|
|
0. ALSA (Linux)
|
|
|
|
0. CoreAudio (OSX)
|
2015-07-20 05:36:50 +00:00
|
|
|
0. WASAPI (Windows)
|
2015-07-21 00:33:21 +00:00
|
|
|
0. ASIO (Windows)
|
2015-07-01 08:02:44 +00:00
|
|
|
0. Dummy
|
|
|
|
|
2015-07-20 05:36:50 +00:00
|
|
|
If you don't like this order, you can use `soundio_connect_backend` to
|
|
|
|
explicitly choose a backend to connect to. You can use `soundio_backend_count`
|
|
|
|
and `soundio_get_backend` to get the list of available backends.
|
|
|
|
|
|
|
|
For complete API documentation, see `src/soundio.h`.
|
|
|
|
|
2015-07-01 08:02:44 +00:00
|
|
|
## Contributing
|
|
|
|
|
2015-07-02 22:54:36 +00:00
|
|
|
libsoundio is programmed in a tiny subset of C++11:
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
* No STL.
|
|
|
|
* No `new` or `delete`.
|
|
|
|
* No `class`. All fields in structs are `public`.
|
|
|
|
* No exceptions or run-time type information.
|
|
|
|
* No references.
|
|
|
|
* No linking against libstdc++.
|
2015-06-30 20:49:36 +00:00
|
|
|
|
2015-07-20 05:36:50 +00:00
|
|
|
Do not be fooled - this is a *C library*, not a C++ library. We just take
|
2015-07-02 22:54:36 +00:00
|
|
|
advantage of a select few C++11 compiler features such as templates, and then
|
|
|
|
link against libc.
|
|
|
|
|
2015-07-02 10:48:27 +00:00
|
|
|
### Building
|
|
|
|
|
2015-07-02 22:54:36 +00:00
|
|
|
Install the dependencies:
|
|
|
|
|
|
|
|
* cmake
|
|
|
|
* ALSA library (optional)
|
|
|
|
* libjack2 (optional)
|
|
|
|
* libpulseaudio (optional)
|
|
|
|
|
2015-07-02 10:48:27 +00:00
|
|
|
```
|
2015-07-02 22:54:36 +00:00
|
|
|
mkdir build
|
|
|
|
cd build
|
|
|
|
cmake ..
|
|
|
|
make
|
|
|
|
sudo make install
|
2015-07-02 10:48:27 +00:00
|
|
|
```
|
|
|
|
|
2015-07-08 06:29:09 +00:00
|
|
|
### Building for Windows
|
2015-07-02 10:48:27 +00:00
|
|
|
|
|
|
|
You can build libsoundio with [mxe](http://mxe.cc/). Follow the
|
|
|
|
[requirements](http://mxe.cc/#requirements) section to install the
|
|
|
|
packages necessary on your system. Then somewhere on your file system:
|
|
|
|
|
|
|
|
```
|
2015-07-02 22:54:36 +00:00
|
|
|
git clone https://github.com/mxe/mxe
|
|
|
|
cd mxe
|
|
|
|
make gcc
|
2015-07-02 10:48:27 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
Then in the libsoundio source directory (replace "/path/to/mxe" with the
|
|
|
|
appropriate path):
|
|
|
|
|
|
|
|
```
|
2015-07-02 22:54:36 +00:00
|
|
|
mkdir build-win
|
|
|
|
cd build-win
|
|
|
|
cmake .. -DCMAKE_TOOLCHAIN_FILE=/path/to/mxe/usr/i686-w64-mingw32.static/share/cmake/mxe-conf.cmake
|
|
|
|
make
|
2015-07-02 10:48:27 +00:00
|
|
|
```
|
|
|
|
|
2015-07-07 00:16:03 +00:00
|
|
|
#### Running the Tests
|
|
|
|
|
|
|
|
```
|
|
|
|
make test
|
|
|
|
```
|
|
|
|
|
|
|
|
For more detailed output:
|
|
|
|
|
|
|
|
```
|
|
|
|
make
|
|
|
|
./unit_tests
|
|
|
|
```
|
|
|
|
|
|
|
|
To see test coverage, install lcov, run `make coverage` and then
|
|
|
|
view `coverage/index.html` in a browser.
|
|
|
|
|
2015-06-30 20:49:36 +00:00
|
|
|
## Roadmap
|
|
|
|
|
2015-07-04 10:08:15 +00:00
|
|
|
0. implement CoreAudio (OSX) backend, get examples working
|
2015-08-02 05:10:43 +00:00
|
|
|
0. Add some builtin channel layouts from
|
|
|
|
https://developer.apple.com/library/mac/documentation/MusicAudio/Reference/CoreAudioDataTypesRef/#//apple_ref/doc/constant_group/Audio_Channel_Layout_Tags
|
|
|
|
0. Make sure PulseAudio can handle refresh devices crashing before
|
|
|
|
block_until_have_devices
|
2015-07-20 05:36:50 +00:00
|
|
|
0. implement WASAPI (Windows) backend, get examples working
|
2015-07-25 01:43:14 +00:00
|
|
|
0. implement ASIO (Windows) backend, get examples working
|
2015-07-30 05:39:27 +00:00
|
|
|
0. Integrate into libgroove and test with Groove Basin
|
2015-07-30 03:55:28 +00:00
|
|
|
0. clear buffer maybe could take an argument to say how many frames to not clear
|
2015-07-28 20:36:31 +00:00
|
|
|
0. Verify that JACK xrun callback context is the same as process callback.
|
|
|
|
If not, might need to hav xrun callback set a flag and have process callback
|
|
|
|
call the underflow callback.
|
2015-07-24 21:03:49 +00:00
|
|
|
0. Create a test for pausing and resuming input and output streams.
|
2015-07-25 01:43:14 +00:00
|
|
|
0. Create a test for the latency / synchronization API.
|
|
|
|
- Input is an audio file and some events indexed at particular frame - when
|
|
|
|
listening the events should line up exactly with a beat or visual
|
|
|
|
indicator, even when the latency is large.
|
|
|
|
- Play the audio file, have the user press an input right at the beat. Find
|
|
|
|
out what the frame index it thinks the user pressed it at and make sure
|
|
|
|
that is correct.
|
2015-07-29 01:47:28 +00:00
|
|
|
0. Create a test for input stream overflow handling.
|
2015-07-25 01:43:14 +00:00
|
|
|
0. Allow calling functions from outside the callbacks as long as they first
|
|
|
|
call lock and then unlock when done.
|
|
|
|
0. Should pause/resume be callable from outside the callbacks?
|
2015-07-02 22:54:36 +00:00
|
|
|
0. clean up API and improve documentation
|
2015-07-07 09:55:32 +00:00
|
|
|
- make sure every function which can return an error documents which errors
|
|
|
|
it can return
|
2015-07-02 22:54:36 +00:00
|
|
|
0. use a documentation generator and host the docs somewhere
|
2015-07-25 01:43:14 +00:00
|
|
|
0. -fvisibility=hidden and then explicitly export stuff, or
|
|
|
|
explicitly make the unexported stuff private
|
2015-07-10 06:35:58 +00:00
|
|
|
0. add len arguments to APIs that have char *
|
2015-08-02 05:10:43 +00:00
|
|
|
- replace strdup with soundio_str_dupe
|
2015-07-21 05:55:30 +00:00
|
|
|
0. Support PulseAudio proplist properties for main context and streams
|
2015-08-01 01:53:39 +00:00
|
|
|
0. Expose JACK options in `jack_client_open`
|
2015-07-22 04:21:00 +00:00
|
|
|
0. custom allocator support
|
|
|
|
0. mlock memory which is accessed in the real time path
|
2015-07-22 19:07:20 +00:00
|
|
|
0. make rtprio warning a callback and have existing behavior be the default callback
|
2015-07-23 22:04:41 +00:00
|
|
|
0. write detailed docs on buffer underflows explaining when they occur, what state
|
|
|
|
changes are related to them, and how to recover from them.
|
2015-07-28 18:28:07 +00:00
|
|
|
0. Consider testing on FreeBSD
|
2015-07-30 17:39:10 +00:00
|
|
|
0. PulseAudio idea: when prebuf gets set to 0 need to pass `PA_STREAM_START_CORKED`.
|
|
|
|
In PulseAudio, to get buffer duration and period duration, fill the buffer
|
|
|
|
with silence before starting, start the stream corked, and have the
|
|
|
|
callback be a callback that just provides silence. Once
|
|
|
|
`soundio_outstream_start` is called, switch to the real callback, then call
|
|
|
|
`pa_stream_flush`, then uncork the stream.
|
|
|
|
0. In ALSA do we need to wake up the poll when destroying the in or out stream?
|
2015-07-30 19:07:58 +00:00
|
|
|
0. Detect PulseAudio server going offline and emit `on_backend_disconnect`.
|
2015-07-01 08:02:44 +00:00
|
|
|
|
|
|
|
## Planned Uses for libsoundio
|
|
|
|
|
|
|
|
* [Genesis](https://github.com/andrewrk/genesis)
|
|
|
|
* [libgroove](https://github.com/andrewrk/libgroove) ([Groove Basin](https://github.com/andrewrk/groovebasin))
|