add JACK backend skeleton

This commit is contained in:
Andrew Kelley 2015-07-24 18:43:14 -07:00
parent 5906bc93d9
commit 3dd9e513bc
10 changed files with 299 additions and 72 deletions

View file

@ -23,6 +23,16 @@ else(Threads_FOUND)
set(STATUS_THREADS "not found")
endif(Threads_FOUND)
find_package(JACK)
if(JACK_FOUND)
set(STATUS_JACK "OK")
set(SOUNDIO_HAVE_JACK true)
include_directories(${JACK_INCLUDE_DIR})
else()
set(STATUS_JACK "not found")
set(SOUNDIO_HAVE_JACK false)
set(JACK_LIBRARY "")
endif()
find_package(PulseAudio)
if(PULSEAUDIO_FOUND)
@ -71,6 +81,14 @@ set(TEST_SOURCES
"${CMAKE_SOURCE_DIR}/src/ring_buffer.cpp"
)
if(SOUNDIO_HAVE_JACK)
set(LIBSOUNDIO_SOURCES ${LIBSOUNDIO_SOURCES}
"${CMAKE_SOURCE_DIR}/src/jack.cpp"
)
set(TEST_SOURCES ${TEST_SOURCES}
"${CMAKE_SOURCE_DIR}/src/jack.cpp"
)
endif()
if(SOUNDIO_HAVE_PULSEAUDIO)
set(LIBSOUNDIO_SOURCES ${LIBSOUNDIO_SOURCES}
"${CMAKE_SOURCE_DIR}/src/pulseaudio.cpp"
@ -132,6 +150,7 @@ include_directories(
${ALSA_INCLUDE_DIRS}
)
target_link_libraries(libsoundio_shared LINK_PUBLIC
${JACK_LIBRARY}
${PULSEAUDIO_LIBRARY}
${ALSA_LIBRARIES}
m
@ -182,6 +201,7 @@ enable_testing()
add_executable(unit_tests ${TEST_SOURCES})
target_link_libraries(unit_tests LINK_PUBLIC
${CMAKE_THREAD_LIBS_INIT}
${JACK_LIBRARY}
${PULSEAUDIO_LIBRARY}
${ALSA_LIBRARIES}
m
@ -218,6 +238,7 @@ message(
"System Dependencies\n"
"-------------------\n"
"* threads : ${STATUS_THREADS}\n"
"* pulseaudio : ${STATUS_PULSEAUDIO}\n"
"* ALSA : ${STATUS_ALSA}\n"
"* JACK (optional) : ${STATUS_JACK}\n"
"* PulseAudio (optional) : ${STATUS_PULSEAUDIO}\n"
"* ALSA (optional) : ${STATUS_ALSA}\n"
)

View file

@ -232,25 +232,37 @@ view `coverage/index.html` in a browser.
## Roadmap
0. pipe record to playback example working with ALSA linux
0. implement JACK backend, get examples working
0. why does pulseaudio microphone use up all the CPU?
0. merge in/out stream structures and functions?
0. implement JACK backend, get examples working
0. implement CoreAudio (OSX) backend, get examples working
0. implement WASAPI (Windows) backend, get examples working
0. implement ASIO (Windows) backend, get examples working
0. Avoid calling `panic` in PulseAudio.
0. Figure out a way to test prebuf. I suspect prebuf not working for ALSA
which is why we have to pre-fill the ring buffer with silence for
the microphone example.
0. PulseAudio: when prebuf gets set to 0 need to pass `PA_STREAM_START_CORKED`.
0. In ALSA do we need to wake up the poll when destroying the in or out stream?
0. Create a test for clearing the playback buffer.
0. Create a test for pausing and resuming input and output streams.
0. implement ASIO (Windows) backend, get examples working
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.
0. Expose JACK options in `jack_client_open`
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?
0. clean up API and improve documentation
- make sure every function which can return an error documents which errors
it can return
0. use a documentation generator and host the docs somewhere
0. -fvisibility=hidden and then explicitly export stuff
0. -fvisibility=hidden and then explicitly export stuff, or
explicitly make the unexported stuff private
0. Integrate into libgroove and test with Groove Basin
0. look at microphone example and determine if fewer memcpys can be done
with the audio data

16
cmake/FindJACK.cmake Normal file
View file

@ -0,0 +1,16 @@
# Copyright (c) 2015 Andrew Kelley
# This file is MIT licensed.
# See http://opensource.org/licenses/MIT
# JACK_FOUND
# JACK_INCLUDE_DIR
# JACK_LIBRARY
find_path(JACK_INCLUDE_DIR NAMES jack/jack.h)
find_library(JACK_LIBRARY NAMES jack)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(JACK DEFAULT_MSG JACK_LIBRARY JACK_INCLUDE_DIR)
mark_as_advanced(JACK_INCLUDE_DIR JACK_LIBRARY)

View file

@ -49,15 +49,22 @@ static void panic(const char *format, ...) {
struct SoundIoRingBuffer *ring_buffer = NULL;
static int min_int(int a, int b) {
return (a < b) ? a : b;
}
static void read_callback(struct SoundIoInStream *instream, int available_frame_count) {
int err;
struct SoundIoChannelArea *areas;
char *write_ptr = soundio_ring_buffer_write_ptr(ring_buffer);
int frames_left = available_frame_count;
int free_bytes = soundio_ring_buffer_free_count(ring_buffer);
int free_count = free_bytes / instream->bytes_per_frame;
int write_count = min_int(available_frame_count, free_count);
int frames_left = write_count;
for (;;) {
int frame_count = frames_left;
struct SoundIoChannelArea *areas;
if ((err = soundio_instream_begin_read(instream, &areas, &frame_count)))
panic("begin read error: %s", soundio_strerror(err));
@ -68,6 +75,7 @@ static void read_callback(struct SoundIoInStream *instream, int available_frame_
// Due to an overflow there is a hole. Fill the ring buffer with
// silence for the size of the hole.
memset(write_ptr, 0, frame_count * instream->bytes_per_frame);
fprintf(stderr, "Dropped %d frames due to internal overflow\n", frame_count);
} else {
for (int frame = 0; frame < frame_count; frame += 1) {
for (int ch = 0; ch < instream->layout.channel_count; ch += 1) {
@ -87,12 +95,12 @@ static void read_callback(struct SoundIoInStream *instream, int available_frame_
break;
}
int advance_bytes = available_frame_count * instream->bytes_per_frame;
int advance_bytes = write_count * instream->bytes_per_frame;
soundio_ring_buffer_advance_write_ptr(ring_buffer, advance_bytes);
}
static int min_int(int a, int b) {
return (a < b) ? a : b;
int dropped_frames = available_frame_count - write_count;
if (dropped_frames > 0)
fprintf(stderr, "Dropped %d frames due to overflow\n", dropped_frames);
}
static void write_callback(struct SoundIoOutStream *outstream, int requested_frame_count) {
@ -134,10 +142,8 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra
}
static void underflow_callback(struct SoundIoOutStream *outstream) {
char *write_ptr = soundio_ring_buffer_write_ptr(ring_buffer);
int bytes_to_fill = outstream->buffer_duration * outstream->sample_rate * outstream->bytes_per_frame;
memset(write_ptr, 0, bytes_to_fill);
soundio_ring_buffer_advance_write_ptr(ring_buffer, bytes_to_fill);
static int count = 0;
fprintf(stderr, "underflow %d\n", ++count);
}
static int usage(char *exe) {
@ -279,8 +285,8 @@ int main(int argc, char **argv) {
outstream->format = *fmt;
outstream->sample_rate = sample_rate;
outstream->layout = *layout;
outstream->buffer_duration = 0.1;
outstream->period_duration = 0.25;
outstream->buffer_duration = 0.2;
outstream->period_duration = 0.1;
outstream->write_callback = write_callback;
outstream->underflow_callback = underflow_callback;

View file

@ -1013,11 +1013,13 @@ void outstream_thread_run(void *arg) {
}
continue;
case SND_PCM_STATE_PREPARED:
{
if ((err = snd_pcm_start(osa->handle)) < 0) {
outstream->error_callback(outstream, SoundIoErrorStreaming);
return;
}
continue;
}
case SND_PCM_STATE_RUNNING:
{
if ((err = wait_for_poll(osa)) < 0) {

View file

@ -16,7 +16,8 @@
#cmakedefine SOUNDIO_OS_BIG_ENDIAN
#cmakedefine SOUNDIO_OS_LITTLE_ENDIAN
#cmakedefine SOUNDIO_HAVE_ALSA
#cmakedefine SOUNDIO_HAVE_JACK
#cmakedefine SOUNDIO_HAVE_PULSEAUDIO
#cmakedefine SOUNDIO_HAVE_ALSA
#endif

157
src/jack.cpp Normal file
View file

@ -0,0 +1,157 @@
/*
* Copyright (c) 2015 Andrew Kelley
*
* This file is part of libsoundio, which is MIT licensed.
* See http://opensource.org/licenses/MIT
*/
#include "jack.hpp"
#include "soundio.hpp"
#include <jack/jack.h>
#include <stdio.h>
struct SoundIoJack {
jack_client_t *client;
};
static void flush_events_jack(struct SoundIoPrivate *) {
soundio_panic("TODO");
}
static void wait_events_jack(struct SoundIoPrivate *) {
soundio_panic("TODO");
}
static void wakeup_jack(struct SoundIoPrivate *) {
soundio_panic("TODO");
}
static int outstream_open_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) {
soundio_panic("TODO");
}
static void outstream_destroy_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) {
soundio_panic("TODO");
}
static int outstream_start_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) {
soundio_panic("TODO");
}
static int outstream_begin_write_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *,
SoundIoChannelArea **out_areas, int *frame_count)
{
soundio_panic("TODO");
}
static int outstream_end_write_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, int frame_count) {
soundio_panic("TODO");
}
static int outstream_clear_buffer_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) {
soundio_panic("TODO");
}
static int outstream_pause_jack(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, bool pause) {
soundio_panic("TODO");
}
static int instream_open_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) {
soundio_panic("TODO");
}
static void instream_destroy_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) {
soundio_panic("TODO");
}
static int instream_start_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) {
soundio_panic("TODO");
}
static int instream_begin_read_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *,
SoundIoChannelArea **out_areas, int *frame_count)
{
soundio_panic("TODO");
}
static int instream_end_read_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) {
soundio_panic("TODO");
}
static int instream_pause_jack(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, bool pause) {
soundio_panic("TODO");
}
static void destroy_jack(SoundIoPrivate *si) {
SoundIoJack *sij = (SoundIoJack *)si->backend_data;
if (!sij)
return;
destroy(sij);
si->backend_data = nullptr;
}
static void error_callback(const char *msg) {
//fprintf(stderr, "JACK error: %s\n", msg);
}
static void info_callback(const char *msg) {
//fprintf(stderr, "JACK info: %s\n", msg);
}
int soundio_jack_init(struct SoundIoPrivate *si) {
SoundIo *soundio = &si->pub;
assert(!si->backend_data);
SoundIoJack *sij = create<SoundIoJack>();
if (!sij) {
destroy_jack(si);
return SoundIoErrorNoMem;
}
si->backend_data = sij;
jack_set_error_function(error_callback);
jack_set_info_function(info_callback);
jack_status_t status;
sij->client = jack_client_open(soundio->app_name, JackNoStartServer, &status);
if (!sij->client) {
destroy_jack(si);
assert(!(status & JackInvalidOption));
if (status & JackNameNotUnique)
return SoundIoErrorNameNotUnique;
if (status & JackShmFailure)
return SoundIoErrorSystemResources;
if (status & JackNoSuchClient)
return SoundIoErrorNoSuchClient;
return SoundIoErrorInitAudioBackend;
}
si->destroy = destroy_jack;
si->flush_events = flush_events_jack;
si->wait_events = wait_events_jack;
si->wakeup = wakeup_jack;
si->outstream_open = outstream_open_jack;
si->outstream_destroy = outstream_destroy_jack;
si->outstream_start = outstream_start_jack;
si->outstream_begin_write = outstream_begin_write_jack;
si->outstream_end_write = outstream_end_write_jack;
si->outstream_clear_buffer = outstream_clear_buffer_jack;
si->outstream_pause = outstream_pause_jack;
si->instream_open = instream_open_jack;
si->instream_destroy = instream_destroy_jack;
si->instream_start = instream_start_jack;
si->instream_begin_read = instream_begin_read_jack;
si->instream_end_read = instream_end_read_jack;
si->instream_pause = instream_pause_jack;
return 0;
}

14
src/jack.hpp Normal file
View file

@ -0,0 +1,14 @@
/*
* Copyright (c) 2015 Andrew Kelley
*
* This file is part of libsoundio, which is MIT licensed.
* See http://opensource.org/licenses/MIT
*/
#ifndef SOUNDIO_JACK_HPP
#define SOUNDIO_JACK_HPP
int soundio_jack_init(struct SoundIoPrivate *si);
#endif

View file

@ -11,6 +11,10 @@
#include "os.hpp"
#include "config.h"
#ifdef SOUNDIO_HAVE_JACK
#include "jack.hpp"
#endif
#ifdef SOUNDIO_HAVE_PULSEAUDIO
#include "pulseaudio.hpp"
#endif
@ -23,6 +27,9 @@
#include <assert.h>
static const SoundIoBackend available_backends[] = {
#ifdef SOUNDIO_HAVE_JACK
SoundIoBackendJack,
#endif
#ifdef SOUNDIO_HAVE_PULSEAUDIO
SoundIoBackendPulseAudio,
#endif
@ -32,6 +39,26 @@ static const SoundIoBackend available_backends[] = {
SoundIoBackendDummy,
};
static int (*backend_init_fns[])(SoundIoPrivate *) = {
nullptr, // SoundIoBackendNone
#ifdef SOUNDIO_HAVE_JACK
soundio_jack_init,
#else
nullptr,
#endif
#ifdef SOUNDIO_HAVE_PULSEAUDIO
soundio_pulseaudio_init,
#else
nullptr,
#endif
#ifdef SOUNDIO_HAVE_ALSA
soundio_alsa_init,
#else
nullptr,
#endif
soundio_dummy_init,
};
const char *soundio_strerror(int error) {
switch ((enum SoundIoError)error) {
case SoundIoErrorNone: return "(no error)";
@ -43,6 +70,8 @@ const char *soundio_strerror(int error) {
case SoundIoErrorBackendUnavailable: return "backend unavailable";
case SoundIoErrorStreaming: return "unrecoverable streaming failure";
case SoundIoErrorIncompatibleDevice: return "incompatible device";
case SoundIoErrorNameNotUnique: return "name not unique";
case SoundIoErrorNoSuchClient: return "no such client";
}
soundio_panic("invalid error enum value: %d", error);
}
@ -105,6 +134,7 @@ const char * soundio_format_string(enum SoundIoFormat format) {
const char *soundio_backend_name(enum SoundIoBackend backend) {
switch (backend) {
case SoundIoBackendNone: return "(none)";
case SoundIoBackendJack: return "JACK";
case SoundIoBackendPulseAudio: return "PulseAudio";
case SoundIoBackendAlsa: return "ALSA";
case SoundIoBackendDummy: return "Dummy";
@ -158,41 +188,20 @@ int soundio_connect_backend(SoundIo *soundio, SoundIoBackend backend) {
if (si->current_backend)
return SoundIoErrorInvalid;
int err;
switch (backend) {
case SoundIoBackendPulseAudio:
#ifdef SOUNDIO_HAVE_PULSEAUDIO
si->current_backend = SoundIoBackendPulseAudio;
if ((err = soundio_pulseaudio_init(si))) {
soundio_disconnect(soundio);
return err;
}
return 0;
#else
return SoundIoErrorBackendUnavailable;
#endif
case SoundIoBackendAlsa:
#ifdef SOUNDIO_HAVE_ALSA
si->current_backend = SoundIoBackendAlsa;
if ((err = soundio_alsa_init(si))) {
soundio_disconnect(soundio);
return err;
}
return 0;
#else
return SoundIoErrorBackendUnavailable;
#endif
case SoundIoBackendDummy:
si->current_backend = SoundIoBackendDummy;
if ((err = soundio_dummy_init(si))) {
soundio_disconnect(soundio);
return err;
}
return 0;
case SoundIoBackendNone:
if (backend <= 0 || backend > SoundIoBackendDummy)
return SoundIoErrorInvalid;
int (*fn)(SoundIoPrivate *) = backend_init_fns[backend];
if (!fn)
return SoundIoErrorBackendUnavailable;
int err;
if ((err = backend_init_fns[backend](si))) {
soundio_disconnect(soundio);
return err;
}
return SoundIoErrorInvalid;
return 0;
}
void soundio_disconnect(struct SoundIo *soundio) {
@ -528,25 +537,9 @@ void soundio_destroy_devices_info(SoundIoDevicesInfo *devices_info) {
}
bool soundio_have_backend(SoundIoBackend backend) {
switch (backend) {
case SoundIoBackendPulseAudio:
#ifdef SOUNDIO_HAVE_PULSEAUDIO
return true;
#else
return false;
#endif
case SoundIoBackendAlsa:
#ifdef SOUNDIO_HAVE_ALSA
return true;
#else
return false;
#endif
case SoundIoBackendDummy:
return true;
case SoundIoBackendNone:
return false;
}
return false;
assert(backend > 0);
assert(backend <= SoundIoBackendDummy);
return backend_init_fns[backend];
}
int soundio_backend_count(struct SoundIo *soundio) {

View file

@ -26,6 +26,8 @@ enum SoundIoError {
SoundIoErrorBackendUnavailable,
SoundIoErrorStreaming,
SoundIoErrorIncompatibleDevice,
SoundIoErrorNameNotUnique,
SoundIoErrorNoSuchClient,
};
enum SoundIoChannelId {
@ -98,6 +100,7 @@ enum SoundIoChannelLayoutId {
enum SoundIoBackend {
SoundIoBackendNone,
SoundIoBackendJack,
SoundIoBackendPulseAudio,
SoundIoBackendAlsa,
SoundIoBackendDummy,
@ -205,7 +208,9 @@ struct SoundIo {
// variable to wake up. Called when soundio_wait_events would be woken up.
void (*on_events_signal)(struct SoundIo *);
// Optional: Application name. Used by PulseAudio. Defaults to "SoundIo".
// Optional: Application name.
// PulseAudio uses this for "application name".
// JACK uses this for `client_name`.
const char *app_name;
};