summaryrefslogtreecommitdiffstats
path: root/media/libcubeb/src/cubeb_jack.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/libcubeb/src/cubeb_jack.cpp')
-rw-r--r--media/libcubeb/src/cubeb_jack.cpp1047
1 files changed, 1047 insertions, 0 deletions
diff --git a/media/libcubeb/src/cubeb_jack.cpp b/media/libcubeb/src/cubeb_jack.cpp
new file mode 100644
index 000000000..8f995da66
--- /dev/null
+++ b/media/libcubeb/src/cubeb_jack.cpp
@@ -0,0 +1,1047 @@
+/*
+ * Copyright © 2012 David Richards
+ * Copyright © 2013 Sebastien Alaiwan
+ * Copyright © 2016 Damien Zammit
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#define _DEFAULT_SOURCE
+#define _BSD_SOURCE
+#define _POSIX_SOURCE
+#include <algorithm>
+#include <dlfcn.h>
+#include <limits>
+#include <stdio.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <string.h>
+#include <limits.h>
+#include <poll.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <math.h>
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
+#include "cubeb_resampler.h"
+
+#include <jack/jack.h>
+#include <jack/statistics.h>
+
+#define JACK_API_VISIT(X) \
+ X(jack_activate) \
+ X(jack_client_close) \
+ X(jack_client_open) \
+ X(jack_connect) \
+ X(jack_free) \
+ X(jack_get_ports) \
+ X(jack_get_sample_rate) \
+ X(jack_get_xrun_delayed_usecs) \
+ X(jack_get_buffer_size) \
+ X(jack_port_get_buffer) \
+ X(jack_port_name) \
+ X(jack_port_register) \
+ X(jack_port_unregister) \
+ X(jack_port_get_latency_range) \
+ X(jack_set_process_callback) \
+ X(jack_set_xrun_callback) \
+ X(jack_set_graph_order_callback) \
+ X(jack_set_error_function) \
+ X(jack_set_info_function)
+
+#define IMPORT_FUNC(x) static decltype(x) * api_##x;
+JACK_API_VISIT(IMPORT_FUNC);
+
+static const int MAX_STREAMS = 16;
+static const int MAX_CHANNELS = 8;
+static const int FIFO_SIZE = 4096 * sizeof(float);
+
+enum devstream {
+ NONE = 0,
+ IN_ONLY,
+ OUT_ONLY,
+ DUPLEX,
+};
+
+static void
+s16ne_to_float(float * dst, const int16_t * src, size_t n)
+{
+ for (size_t i = 0; i < n; i++)
+ *(dst++) = (float)((float)*(src++) / 32767.0f);
+}
+
+static void
+float_to_s16ne(int16_t * dst, float * src, size_t n)
+{
+ for (size_t i = 0; i < n; i++) {
+ if (*src > 1.f) *src = 1.f;
+ if (*src < -1.f) *src = -1.f;
+ *(dst++) = (int16_t)((int16_t)(*(src++) * 32767));
+ }
+}
+
+extern "C"
+{
+/*static*/ int jack_init (cubeb ** context, char const * context_name);
+}
+static char const * cbjack_get_backend_id(cubeb * context);
+static int cbjack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels);
+static int cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames);
+static int cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_frames);
+static int cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate);
+static void cbjack_destroy(cubeb * context);
+static void cbjack_interleave_capture(cubeb_stream * stream, float **in, jack_nframes_t nframes, bool format_mismatch);
+static void cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short **bufs_in, float **bufs_out, jack_nframes_t nframes);
+static void cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float **bufs_in, float **bufs_out, jack_nframes_t nframes);
+static int cbjack_stream_device_destroy(cubeb_stream * stream,
+ cubeb_device * device);
+static int cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device);
+static int cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection ** collection);
+static int cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
+ cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback,
+ void * user_ptr);
+static void cbjack_stream_destroy(cubeb_stream * stream);
+static int cbjack_stream_start(cubeb_stream * stream);
+static int cbjack_stream_stop(cubeb_stream * stream);
+static int cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position);
+static int cbjack_stream_set_volume(cubeb_stream * stm, float volume);
+
+static struct cubeb_ops const cbjack_ops = {
+ .init = jack_init,
+ .get_backend_id = cbjack_get_backend_id,
+ .get_max_channel_count = cbjack_get_max_channel_count,
+ .get_min_latency = cbjack_get_min_latency,
+ .get_preferred_sample_rate = cbjack_get_preferred_sample_rate,
+ .enumerate_devices = cbjack_enumerate_devices,
+ .destroy = cbjack_destroy,
+ .stream_init = cbjack_stream_init,
+ .stream_destroy = cbjack_stream_destroy,
+ .stream_start = cbjack_stream_start,
+ .stream_stop = cbjack_stream_stop,
+ .stream_get_position = cbjack_stream_get_position,
+ .stream_get_latency = cbjack_get_latency,
+ .stream_set_volume = cbjack_stream_set_volume,
+ .stream_set_panning = NULL,
+ .stream_get_current_device = cbjack_stream_get_current_device,
+ .stream_device_destroy = cbjack_stream_device_destroy,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL
+};
+
+struct cubeb_stream {
+ cubeb * context;
+
+ /**< Mutex for each stream */
+ pthread_mutex_t mutex;
+
+ bool in_use; /**< Set to false iff the stream is free */
+ bool ports_ready; /**< Set to true iff the JACK ports are ready */
+
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+ void * user_ptr;
+ cubeb_stream_params in_params;
+ cubeb_stream_params out_params;
+
+ cubeb_resampler * resampler;
+
+ uint64_t position;
+ bool pause;
+ float ratio;
+ enum devstream devs;
+ char stream_name[256];
+ jack_port_t * output_ports[MAX_CHANNELS];
+ jack_port_t * input_ports[MAX_CHANNELS];
+ float volume;
+};
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+ void * libjack;
+
+ /**< Mutex for whole context */
+ pthread_mutex_t mutex;
+
+ /**< Audio buffers, converted to float */
+ float in_float_interleaved_buffer[FIFO_SIZE * MAX_CHANNELS];
+ float out_float_interleaved_buffer[FIFO_SIZE * MAX_CHANNELS];
+
+ /**< Audio buffer, at the sampling rate of the output */
+ float in_resampled_interleaved_buffer_float[FIFO_SIZE * MAX_CHANNELS * 3];
+ int16_t in_resampled_interleaved_buffer_s16ne[FIFO_SIZE * MAX_CHANNELS * 3];
+ float out_resampled_interleaved_buffer_float[FIFO_SIZE * MAX_CHANNELS * 3];
+ int16_t out_resampled_interleaved_buffer_s16ne[FIFO_SIZE * MAX_CHANNELS * 3];
+
+ cubeb_stream streams[MAX_STREAMS];
+ unsigned int active_streams;
+
+ cubeb_device_info * devinfo[2];
+ cubeb_device_collection_changed_callback collection_changed_callback;
+
+ bool active;
+ unsigned int jack_sample_rate;
+ unsigned int jack_latency;
+ unsigned int jack_xruns;
+ unsigned int jack_buffer_size;
+ unsigned int fragment_size;
+ unsigned int output_bytes_per_frame;
+ jack_client_t * jack_client;
+};
+
+static int
+load_jack_lib(cubeb * context)
+{
+#ifdef __APPLE__
+ context->libjack = dlopen("libjack.0.dylib", RTLD_LAZY);
+ context->libjack = dlopen("/usr/local/lib/libjack.0.dylib", RTLD_LAZY);
+#elif defined(__WIN32__)
+# ifdef _WIN64
+ context->libjack = LoadLibrary("libjack64.dll");
+# else
+ context->libjack = LoadLibrary("libjack.dll");
+# endif
+#else
+ context->libjack = dlopen("libjack.so.0", RTLD_LAZY);
+#endif
+ if (!context->libjack) {
+ return CUBEB_ERROR;
+ }
+
+#define LOAD(x) \
+ { \
+ api_##x = (decltype(x)*)dlsym(context->libjack, #x); \
+ if (!api_##x) { \
+ dlclose(context->libjack); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ JACK_API_VISIT(LOAD);
+#undef LOAD
+
+ return CUBEB_OK;
+}
+
+static void
+cbjack_connect_ports (cubeb_stream * stream)
+{
+ const char ** phys_in_ports = api_jack_get_ports (stream->context->jack_client,
+ NULL, NULL,
+ JackPortIsInput
+ | JackPortIsPhysical);
+ const char ** phys_out_ports = api_jack_get_ports (stream->context->jack_client,
+ NULL, NULL,
+ JackPortIsOutput
+ | JackPortIsPhysical);
+
+ if (*phys_in_ports == NULL) {
+ goto skipplayback;
+ }
+
+ // Connect outputs to playback
+ for (unsigned int c = 0; c < stream->out_params.channels && phys_in_ports[c] != NULL; c++) {
+ const char *src_port = api_jack_port_name (stream->output_ports[c]);
+
+ api_jack_connect (stream->context->jack_client, src_port, phys_in_ports[c]);
+ }
+
+skipplayback:
+ if (*phys_out_ports == NULL) {
+ goto end;
+ }
+ // Connect inputs to capture
+ for (unsigned int c = 0; c < stream->in_params.channels && phys_out_ports[c] != NULL; c++) {
+ const char *src_port = api_jack_port_name (stream->input_ports[c]);
+
+ api_jack_connect (stream->context->jack_client, phys_out_ports[c], src_port);
+ }
+end:
+ api_jack_free(phys_out_ports);
+ api_jack_free(phys_in_ports);
+}
+
+static int
+cbjack_xrun_callback(void * arg)
+{
+ cubeb * ctx = (cubeb *)arg;
+
+ float delay = api_jack_get_xrun_delayed_usecs(ctx->jack_client);
+ int fragments = (int)ceilf( ((delay / 1000000.0) * ctx->jack_sample_rate )
+ / (float)(ctx->jack_buffer_size) );
+ ctx->jack_xruns += fragments;
+ return 0;
+}
+
+static int
+cbjack_graph_order_callback(void * arg)
+{
+ cubeb * ctx = (cubeb *)arg;
+ int i;
+ jack_latency_range_t latency_range;
+ jack_nframes_t port_latency, max_latency = 0;
+
+ for (int j = 0; j < MAX_STREAMS; j++) {
+ cubeb_stream *stm = &ctx->streams[j];
+
+ if (!stm->in_use)
+ continue;
+ if (!stm->ports_ready)
+ continue;
+
+ for (i = 0; i < (int)stm->out_params.channels; ++i) {
+ api_jack_port_get_latency_range(stm->output_ports[i], JackPlaybackLatency, &latency_range);
+ port_latency = latency_range.max;
+ if (port_latency > max_latency)
+ max_latency = port_latency;
+ }
+ /* Cap minimum latency to 128 frames */
+ if (max_latency < 128)
+ max_latency = 128;
+ }
+
+ ctx->jack_latency = max_latency;
+
+ return 0;
+}
+
+static int
+cbjack_process(jack_nframes_t nframes, void * arg)
+{
+ cubeb * ctx = (cubeb *)arg;
+ int t_jack_xruns = ctx->jack_xruns;
+ int i;
+
+ for (int j = 0; j < MAX_STREAMS; j++) {
+ cubeb_stream *stm = &ctx->streams[j];
+ float *bufs_out[stm->out_params.channels];
+ float *bufs_in[stm->in_params.channels];
+
+ if (!stm->in_use)
+ continue;
+
+ // handle xruns by skipping audio that should have been played
+ for (i = 0; i < t_jack_xruns; i++) {
+ stm->position += ctx->fragment_size * stm->ratio;
+ }
+ ctx->jack_xruns -= t_jack_xruns;
+
+ if (!stm->ports_ready)
+ continue;
+
+ if (stm->devs & OUT_ONLY) {
+ // get jack output buffers
+ for (i = 0; i < (int)stm->out_params.channels; i++)
+ bufs_out[i] = (float*)api_jack_port_get_buffer(stm->output_ports[i], nframes);
+ }
+ if (stm->devs & IN_ONLY) {
+ // get jack input buffers
+ for (i = 0; i < (int)stm->in_params.channels; i++)
+ bufs_in[i] = (float*)api_jack_port_get_buffer(stm->input_ports[i], nframes);
+ }
+ if (stm->pause) {
+ // paused, play silence on output
+ if (stm->devs & OUT_ONLY) {
+ for (unsigned int c = 0; c < stm->out_params.channels; c++) {
+ float* buffer_out = bufs_out[c];
+ for (long f = 0; f < nframes; f++) {
+ buffer_out[f] = 0.f;
+ }
+ }
+ }
+ if (stm->devs & IN_ONLY) {
+ // paused, capture silence
+ for (unsigned int c = 0; c < stm->in_params.channels; c++) {
+ float* buffer_in = bufs_in[c];
+ for (long f = 0; f < nframes; f++) {
+ buffer_in[f] = 0.f;
+ }
+ }
+ }
+ } else {
+
+ // try to lock stream mutex
+ if (pthread_mutex_trylock(&stm->mutex) == 0) {
+
+ int16_t *in_s16ne = stm->context->in_resampled_interleaved_buffer_s16ne;
+ float *in_float = stm->context->in_resampled_interleaved_buffer_float;
+
+ // unpaused, play audio
+ if (stm->devs == DUPLEX) {
+ if (stm->out_params.format == CUBEB_SAMPLE_S16NE) {
+ cbjack_interleave_capture(stm, bufs_in, nframes, true);
+ cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, bufs_out, nframes);
+ } else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ cbjack_interleave_capture(stm, bufs_in, nframes, false);
+ cbjack_deinterleave_playback_refill_float(stm, &in_float, bufs_out, nframes);
+ }
+ } else if (stm->devs == IN_ONLY) {
+ if (stm->in_params.format == CUBEB_SAMPLE_S16NE) {
+ cbjack_interleave_capture(stm, bufs_in, nframes, true);
+ cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, nullptr, nframes);
+ } else if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ cbjack_interleave_capture(stm, bufs_in, nframes, false);
+ cbjack_deinterleave_playback_refill_float(stm, &in_float, nullptr, nframes);
+ }
+ } else if (stm->devs == OUT_ONLY) {
+ if (stm->out_params.format == CUBEB_SAMPLE_S16NE) {
+ cbjack_deinterleave_playback_refill_s16ne(stm, nullptr, bufs_out, nframes);
+ } else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ cbjack_deinterleave_playback_refill_float(stm, nullptr, bufs_out, nframes);
+ }
+ }
+ // unlock stream mutex
+ pthread_mutex_unlock(&stm->mutex);
+
+ } else {
+ // could not lock mutex
+ // output silence
+ if (stm->devs & OUT_ONLY) {
+ for (unsigned int c = 0; c < stm->out_params.channels; c++) {
+ float* buffer_out = bufs_out[c];
+ for (long f = 0; f < nframes; f++) {
+ buffer_out[f] = 0.f;
+ }
+ }
+ }
+ if (stm->devs & IN_ONLY) {
+ // capture silence
+ for (unsigned int c = 0; c < stm->in_params.channels; c++) {
+ float* buffer_in = bufs_in[c];
+ for (long f = 0; f < nframes; f++) {
+ buffer_in[f] = 0.f;
+ }
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+
+static void
+cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, float ** bufs_out, jack_nframes_t nframes)
+{
+ float * out_interleaved_buffer = nullptr;
+
+ float * inptr = (in != NULL) ? *in : nullptr;
+ float * outptr = (bufs_out != NULL) ? *bufs_out : nullptr;
+
+ long needed_frames = (bufs_out != NULL) ? nframes : 0;
+ long done_frames = 0;
+ long input_frames_count = (in != NULL) ? nframes : 0;
+
+
+ done_frames = cubeb_resampler_fill(stream->resampler,
+ inptr,
+ &input_frames_count,
+ (bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_float : NULL,
+ needed_frames);
+
+ out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float;
+
+ if (outptr) {
+ // convert interleaved output buffers to contiguous buffers
+ for (unsigned int c = 0; c < stream->out_params.channels; c++) {
+ float* buffer = bufs_out[c];
+ for (long f = 0; f < done_frames; f++) {
+ buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume;
+ }
+ if (done_frames < needed_frames) {
+ // draining
+ for (long f = done_frames; f < needed_frames; f++) {
+ buffer[f] = 0.f;
+ }
+ }
+ if (done_frames == 0) {
+ // stop, but first zero out the existing buffer
+ for (long f = 0; f < needed_frames; f++) {
+ buffer[f] = 0.f;
+ }
+ }
+ }
+ }
+
+ if (done_frames >= 0 && done_frames < needed_frames) {
+ // set drained
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
+ // stop stream
+ cbjack_stream_stop(stream);
+ }
+ if (done_frames > 0 && done_frames <= needed_frames) {
+ // advance stream position
+ stream->position += done_frames * stream->ratio;
+ }
+ if (done_frames < 0 || done_frames > needed_frames) {
+ // stream error
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_ERROR);
+ }
+}
+
+static void
+cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, float ** bufs_out, jack_nframes_t nframes)
+{
+ float * out_interleaved_buffer = nullptr;
+
+ short * inptr = (in != NULL) ? *in : nullptr;
+ float * outptr = (bufs_out != NULL) ? *bufs_out : nullptr;
+
+ long needed_frames = (bufs_out != NULL) ? nframes : 0;
+ long done_frames = 0;
+ long input_frames_count = (in != NULL) ? nframes : 0;
+
+ done_frames = cubeb_resampler_fill(stream->resampler,
+ inptr,
+ &input_frames_count,
+ (bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_s16ne : NULL,
+ needed_frames);
+
+ s16ne_to_float(stream->context->out_resampled_interleaved_buffer_float, stream->context->out_resampled_interleaved_buffer_s16ne, done_frames * stream->out_params.channels);
+
+ out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float;
+
+ if (outptr) {
+ // convert interleaved output buffers to contiguous buffers
+ for (unsigned int c = 0; c < stream->out_params.channels; c++) {
+ float* buffer = bufs_out[c];
+ for (long f = 0; f < done_frames; f++) {
+ buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume;
+ }
+ if (done_frames < needed_frames) {
+ // draining
+ for (long f = done_frames; f < needed_frames; f++) {
+ buffer[f] = 0.f;
+ }
+ }
+ if (done_frames == 0) {
+ // stop, but first zero out the existing buffer
+ for (long f = 0; f < needed_frames; f++) {
+ buffer[f] = 0.f;
+ }
+ }
+ }
+ }
+
+ if (done_frames >= 0 && done_frames < needed_frames) {
+ // set drained
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
+ // stop stream
+ cbjack_stream_stop(stream);
+ }
+ if (done_frames > 0 && done_frames <= needed_frames) {
+ // advance stream position
+ stream->position += done_frames * stream->ratio;
+ }
+ if (done_frames < 0 || done_frames > needed_frames) {
+ // stream error
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_ERROR);
+ }
+}
+
+static void
+cbjack_interleave_capture(cubeb_stream * stream, float **in, jack_nframes_t nframes, bool format_mismatch)
+{
+ float *in_buffer = stream->context->in_float_interleaved_buffer;
+
+ for (unsigned int c = 0; c < stream->in_params.channels; c++) {
+ for (long f = 0; f < nframes; f++) {
+ in_buffer[(f * stream->in_params.channels) + c] = in[c][f] * stream->volume;
+ }
+ }
+ if (format_mismatch) {
+ float_to_s16ne(stream->context->in_resampled_interleaved_buffer_s16ne, in_buffer, nframes * stream->in_params.channels);
+ } else {
+ memset(stream->context->in_resampled_interleaved_buffer_float, 0, (FIFO_SIZE * MAX_CHANNELS * 3) * sizeof(float));
+ memcpy(stream->context->in_resampled_interleaved_buffer_float, in_buffer, (FIFO_SIZE * MAX_CHANNELS * 2) * sizeof(float));
+ }
+}
+
+static void
+silent_jack_error_callback(char const * /*msg*/)
+{
+}
+
+/*static*/ int
+jack_init (cubeb ** context, char const * context_name)
+{
+ int r;
+
+ *context = NULL;
+
+ cubeb * ctx = (cubeb *)calloc(1, sizeof(*ctx));
+ if (ctx == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ r = load_jack_lib(ctx);
+ if (r != 0) {
+ cbjack_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ api_jack_set_error_function(silent_jack_error_callback);
+ api_jack_set_info_function(silent_jack_error_callback);
+
+ ctx->ops = &cbjack_ops;
+
+ ctx->mutex = PTHREAD_MUTEX_INITIALIZER;
+ for (r = 0; r < MAX_STREAMS; r++) {
+ ctx->streams[r].mutex = PTHREAD_MUTEX_INITIALIZER;
+ }
+
+ const char * jack_client_name = "cubeb";
+ if (context_name)
+ jack_client_name = context_name;
+
+ ctx->jack_client = api_jack_client_open(jack_client_name,
+ JackNoStartServer,
+ NULL);
+
+ if (ctx->jack_client == NULL) {
+ cbjack_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ ctx->jack_xruns = 0;
+
+ api_jack_set_process_callback (ctx->jack_client, cbjack_process, ctx);
+ api_jack_set_xrun_callback (ctx->jack_client, cbjack_xrun_callback, ctx);
+ api_jack_set_graph_order_callback (ctx->jack_client, cbjack_graph_order_callback, ctx);
+
+ if (api_jack_activate (ctx->jack_client)) {
+ cbjack_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ ctx->jack_sample_rate = api_jack_get_sample_rate(ctx->jack_client);
+ ctx->jack_latency = 128 * 1000 / ctx->jack_sample_rate;
+
+ ctx->active = true;
+ *context = ctx;
+
+ return CUBEB_OK;
+}
+
+static char const *
+cbjack_get_backend_id(cubeb * /*context*/)
+{
+ return "jack";
+}
+
+static int
+cbjack_get_max_channel_count(cubeb * /*ctx*/, uint32_t * max_channels)
+{
+ *max_channels = MAX_CHANNELS;
+ return CUBEB_OK;
+}
+
+static int
+cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_ms)
+{
+ *latency_ms = stm->context->jack_latency;
+ return CUBEB_OK;
+}
+
+static int
+cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params /*params*/, uint32_t * latency_ms)
+{
+ *latency_ms = ctx->jack_latency;
+ return CUBEB_OK;
+}
+
+static int
+cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ if (!ctx->jack_client) {
+ jack_client_t * testclient = api_jack_client_open("test-samplerate",
+ JackNoStartServer,
+ NULL);
+ if (!testclient) {
+ return CUBEB_ERROR;
+ }
+
+ *rate = api_jack_get_sample_rate(testclient);
+ api_jack_client_close(testclient);
+
+ } else {
+ *rate = api_jack_get_sample_rate(ctx->jack_client);
+ }
+ return CUBEB_OK;
+}
+
+static void
+cbjack_destroy(cubeb * context)
+{
+ context->active = false;
+
+ if (context->jack_client != NULL)
+ api_jack_client_close (context->jack_client);
+
+ if (context->libjack)
+ dlclose(context->libjack);
+
+ free(context);
+}
+
+static cubeb_stream *
+context_alloc_stream(cubeb * context, char const * stream_name)
+{
+ for (int i = 0; i < MAX_STREAMS; i++) {
+ if (!context->streams[i].in_use) {
+ cubeb_stream * stm = &context->streams[i];
+ stm->in_use = true;
+ snprintf(stm->stream_name, 255, "%s_%u", stream_name, i);
+ return stm;
+ }
+ }
+ return NULL;
+}
+
+static int
+cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
+ cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int /*latency_frames*/,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback,
+ void * user_ptr)
+{
+ int stream_actual_rate = 0;
+ int jack_rate = api_jack_get_sample_rate(context->jack_client);
+
+ if (output_stream_params
+ && (output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
+ output_stream_params->format != CUBEB_SAMPLE_S16NE)
+ ) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ if (input_stream_params
+ && (input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
+ input_stream_params->format != CUBEB_SAMPLE_S16NE)
+ ) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ if (input_device || output_device)
+ return CUBEB_ERROR_NOT_SUPPORTED;
+
+ *stream = NULL;
+
+ // Find a free stream.
+ pthread_mutex_lock(&context->mutex);
+ cubeb_stream * stm = context_alloc_stream(context, stream_name);
+
+ // No free stream?
+ if (stm == NULL) {
+ pthread_mutex_unlock(&context->mutex);
+ return CUBEB_ERROR;
+ }
+
+ // unlock context mutex
+ pthread_mutex_unlock(&context->mutex);
+
+ // Lock active stream
+ pthread_mutex_lock(&stm->mutex);
+
+ stm->ports_ready = false;
+ stm->user_ptr = user_ptr;
+ stm->context = context;
+ stm->devs = NONE;
+ if (output_stream_params && !input_stream_params) {
+ stm->out_params = *output_stream_params;
+ stream_actual_rate = stm->out_params.rate;
+ stm->out_params.rate = jack_rate;
+ stm->devs = OUT_ONLY;
+ if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ context->output_bytes_per_frame = sizeof(float);
+ } else {
+ context->output_bytes_per_frame = sizeof(short);
+ }
+ }
+ if (input_stream_params && output_stream_params) {
+ stm->in_params = *input_stream_params;
+ stm->out_params = *output_stream_params;
+ stream_actual_rate = stm->out_params.rate;
+ stm->in_params.rate = jack_rate;
+ stm->out_params.rate = jack_rate;
+ stm->devs = DUPLEX;
+ if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ context->output_bytes_per_frame = sizeof(float);
+ stm->in_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ } else {
+ context->output_bytes_per_frame = sizeof(short);
+ stm->in_params.format = CUBEB_SAMPLE_S16NE;
+ }
+ } else if (input_stream_params && !output_stream_params) {
+ stm->in_params = *input_stream_params;
+ stream_actual_rate = stm->in_params.rate;
+ stm->in_params.rate = jack_rate;
+ stm->devs = IN_ONLY;
+ if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ context->output_bytes_per_frame = sizeof(float);
+ } else {
+ context->output_bytes_per_frame = sizeof(short);
+ }
+ }
+
+ stm->ratio = (float)stream_actual_rate / (float)jack_rate;
+
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->position = 0;
+ stm->volume = 1.0f;
+ context->jack_buffer_size = api_jack_get_buffer_size(context->jack_client);
+ context->fragment_size = context->jack_buffer_size;
+
+ if (stm->devs == NONE) {
+ pthread_mutex_unlock(&stm->mutex);
+ return CUBEB_ERROR;
+ }
+
+ stm->resampler = NULL;
+
+ if (stm->devs == DUPLEX) {
+ stm->resampler = cubeb_resampler_create(stm,
+ &stm->in_params,
+ &stm->out_params,
+ stream_actual_rate,
+ stm->data_callback,
+ stm->user_ptr,
+ CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ } else if (stm->devs == IN_ONLY) {
+ stm->resampler = cubeb_resampler_create(stm,
+ &stm->in_params,
+ nullptr,
+ stream_actual_rate,
+ stm->data_callback,
+ stm->user_ptr,
+ CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ } else if (stm->devs == OUT_ONLY) {
+ stm->resampler = cubeb_resampler_create(stm,
+ nullptr,
+ &stm->out_params,
+ stream_actual_rate,
+ stm->data_callback,
+ stm->user_ptr,
+ CUBEB_RESAMPLER_QUALITY_DESKTOP);
+ }
+
+ if (!stm->resampler) {
+ stm->in_use = false;
+ pthread_mutex_unlock(&stm->mutex);
+ return CUBEB_ERROR;
+ }
+
+ if (stm->devs == DUPLEX || stm->devs == OUT_ONLY) {
+ for (unsigned int c = 0; c < stm->out_params.channels; c++) {
+ char portname[256];
+ snprintf(portname, 255, "%s_out_%d", stm->stream_name, c);
+ stm->output_ports[c] = api_jack_port_register(stm->context->jack_client,
+ portname,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput,
+ 0);
+ }
+ }
+
+ if (stm->devs == DUPLEX || stm->devs == IN_ONLY) {
+ for (unsigned int c = 0; c < stm->in_params.channels; c++) {
+ char portname[256];
+ snprintf(portname, 255, "%s_in_%d", stm->stream_name, c);
+ stm->input_ports[c] = api_jack_port_register(stm->context->jack_client,
+ portname,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsInput,
+ 0);
+ }
+ }
+
+ cbjack_connect_ports(stm);
+
+ *stream = stm;
+
+ stm->ports_ready = true;
+ stm->pause = true;
+ pthread_mutex_unlock(&stm->mutex);
+
+ return CUBEB_OK;
+}
+
+static void
+cbjack_stream_destroy(cubeb_stream * stream)
+{
+ pthread_mutex_lock(&stream->mutex);
+ stream->ports_ready = false;
+
+ if (stream->devs == DUPLEX || stream->devs == OUT_ONLY) {
+ for (unsigned int c = 0; c < stream->out_params.channels; c++) {
+ if (stream->output_ports[c]) {
+ api_jack_port_unregister (stream->context->jack_client, stream->output_ports[c]);
+ stream->output_ports[c] = NULL;
+ }
+ }
+ }
+
+ if (stream->devs == DUPLEX || stream->devs == IN_ONLY) {
+ for (unsigned int c = 0; c < stream->in_params.channels; c++) {
+ if (stream->input_ports[c]) {
+ api_jack_port_unregister (stream->context->jack_client, stream->input_ports[c]);
+ stream->input_ports[c] = NULL;
+ }
+ }
+ }
+
+ if (stream->resampler) {
+ cubeb_resampler_destroy(stream->resampler);
+ stream->resampler = NULL;
+ }
+ stream->in_use = false;
+ pthread_mutex_unlock(&stream->mutex);
+}
+
+static int
+cbjack_stream_start(cubeb_stream * stream)
+{
+ stream->pause = false;
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED);
+ return CUBEB_OK;
+}
+
+static int
+cbjack_stream_stop(cubeb_stream * stream)
+{
+ stream->pause = true;
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED);
+ return CUBEB_OK;
+}
+
+static int
+cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position)
+{
+ *position = stream->position;
+ return CUBEB_OK;
+}
+
+static int
+cbjack_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ stm->volume = volume;
+ return CUBEB_OK;
+}
+
+
+static int
+cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device)
+{
+ *device = (cubeb_device *)calloc(1, sizeof(cubeb_device));
+ if (*device == NULL)
+ return CUBEB_ERROR;
+
+ const char * j_in = "JACK capture";
+ const char * j_out = "JACK playback";
+ const char * empty = "";
+
+ if (stm->devs == DUPLEX) {
+ (*device)->input_name = strdup(j_in);
+ (*device)->output_name = strdup(j_out);
+ } else if (stm->devs == IN_ONLY) {
+ (*device)->input_name = strdup(j_in);
+ (*device)->output_name = strdup(empty);
+ } else if (stm->devs == OUT_ONLY) {
+ (*device)->input_name = strdup(empty);
+ (*device)->output_name = strdup(j_out);
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+cbjack_stream_device_destroy(cubeb_stream * /*stream*/,
+ cubeb_device * device)
+{
+ if (device->input_name)
+ free(device->input_name);
+ if (device->output_name)
+ free(device->output_name);
+ free(device);
+ return CUBEB_OK;
+}
+
+static int
+cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection ** collection)
+{
+ if (!context)
+ return CUBEB_ERROR;
+
+ uint32_t rate;
+ uint8_t i = 0;
+ uint8_t j;
+ cbjack_get_preferred_sample_rate(context, &rate);
+ const char * j_in = "JACK capture";
+ const char * j_out = "JACK playback";
+
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
+ context->devinfo[i] = (cubeb_device_info *)malloc(sizeof(cubeb_device_info));
+ context->devinfo[i]->device_id = strdup(j_out);
+ context->devinfo[i]->devid = context->devinfo[i]->device_id;
+ context->devinfo[i]->friendly_name = strdup(j_out);
+ context->devinfo[i]->group_id = strdup(j_out);
+ context->devinfo[i]->vendor_name = strdup(j_out);
+ context->devinfo[i]->type = CUBEB_DEVICE_TYPE_OUTPUT;
+ context->devinfo[i]->state = CUBEB_DEVICE_STATE_ENABLED;
+ context->devinfo[i]->preferred = CUBEB_DEVICE_PREF_ALL;
+ context->devinfo[i]->format = CUBEB_DEVICE_FMT_F32NE;
+ context->devinfo[i]->default_format = CUBEB_DEVICE_FMT_F32NE;
+ context->devinfo[i]->max_channels = MAX_CHANNELS;
+ context->devinfo[i]->min_rate = rate;
+ context->devinfo[i]->max_rate = rate;
+ context->devinfo[i]->default_rate = rate;
+ context->devinfo[i]->latency_lo = 0;
+ context->devinfo[i]->latency_hi = 0;
+ i++;
+ }
+
+ if (type & CUBEB_DEVICE_TYPE_INPUT) {
+ context->devinfo[i] = (cubeb_device_info *)malloc(sizeof(cubeb_device_info));
+ context->devinfo[i]->device_id = strdup(j_in);
+ context->devinfo[i]->devid = context->devinfo[i]->device_id;
+ context->devinfo[i]->friendly_name = strdup(j_in);
+ context->devinfo[i]->group_id = strdup(j_in);
+ context->devinfo[i]->vendor_name = strdup(j_in);
+ context->devinfo[i]->type = CUBEB_DEVICE_TYPE_INPUT;
+ context->devinfo[i]->state = CUBEB_DEVICE_STATE_ENABLED;
+ context->devinfo[i]->preferred = CUBEB_DEVICE_PREF_ALL;
+ context->devinfo[i]->format = CUBEB_DEVICE_FMT_F32NE;
+ context->devinfo[i]->default_format = CUBEB_DEVICE_FMT_F32NE;
+ context->devinfo[i]->max_channels = MAX_CHANNELS;
+ context->devinfo[i]->min_rate = rate;
+ context->devinfo[i]->max_rate = rate;
+ context->devinfo[i]->default_rate = rate;
+ context->devinfo[i]->latency_lo = 0;
+ context->devinfo[i]->latency_hi = 0;
+ i++;
+ }
+
+ *collection = (cubeb_device_collection *)
+ malloc(sizeof(cubeb_device_collection) +
+ i * sizeof(cubeb_device_info *));
+
+ (*collection)->count = i;
+
+ for (j = 0; j < i; j++) {
+ (*collection)->device[j] = context->devinfo[j];
+ }
+ return CUBEB_OK;
+}