summaryrefslogtreecommitdiff
path: root/modules/jack
diff options
context:
space:
mode:
authorAlfred E. Heggestad <aeh@db.org>2015-11-30 08:35:21 +0100
committerAlfred E. Heggestad <aeh@db.org>2015-11-30 08:35:21 +0100
commit029ecbaa928aea9b31d805e0e33bf3ed05dfafc9 (patch)
tree240579ea191a2849cd3c26017f77d6253ccdb9c7 /modules/jack
parentd94d4acbc9c5c5cb77700a873a0208540ea8e8e8 (diff)
add JACK audio driver (WIP)
Diffstat (limited to 'modules/jack')
-rw-r--r--modules/jack/jack.c41
-rw-r--r--modules/jack/jack_play.c231
-rw-r--r--modules/jack/jack_src.c224
-rw-r--r--modules/jack/mod_jack.h14
-rw-r--r--modules/jack/module.mk12
5 files changed, 522 insertions, 0 deletions
diff --git a/modules/jack/jack.c b/modules/jack/jack.c
new file mode 100644
index 0000000..6a3fecf
--- /dev/null
+++ b/modules/jack/jack.c
@@ -0,0 +1,41 @@
+/**
+ * @file jack.c JACK audio driver
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "mod_jack.h"
+
+
+static struct auplay *auplay;
+static struct ausrc *ausrc;
+
+
+static int module_init(void)
+{
+ int err = 0;
+
+ err |= auplay_register(&auplay, "jack", jack_play_alloc);
+ err |= ausrc_register(&ausrc, "jack", jack_src_alloc);
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ auplay = mem_deref(auplay);
+ ausrc = mem_deref(ausrc);
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(jack) = {
+ "jack",
+ "sound",
+ module_init,
+ module_close
+};
diff --git a/modules/jack/jack_play.c b/modules/jack/jack_play.c
new file mode 100644
index 0000000..380197f
--- /dev/null
+++ b/modules/jack/jack_play.c
@@ -0,0 +1,231 @@
+/**
+ * @file jack.c JACK audio driver -- player
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <jack/jack.h>
+#include "mod_jack.h"
+
+
+struct auplay_st {
+ const struct auplay *ap; /* pointer to base-class (inheritance) */
+
+ struct auplay_prm prm;
+ int16_t *sampv;
+ size_t sampc; /* includes number of channels */
+ auplay_write_h *wh;
+ void *arg;
+
+ jack_client_t *client;
+ jack_port_t *portv[2];
+ jack_nframes_t nframes; /* num frames per port (channel) */
+};
+
+
+static inline float ausamp_short2float(int16_t in)
+{
+ float out;
+
+ out = (float) (in / (1.0 * 0x8000));
+
+ return out;
+}
+
+
+/**
+ * The process callback for this JACK application is called in a
+ * special realtime thread once for each audio cycle.
+ *
+ * This client does nothing more than copy data from its input
+ * port to its output port. It will exit when stopped by
+ * the user (e.g. using Ctrl-C on a unix-ish operating system)
+ *
+ * XXX avoid memory allocations in this function
+ */
+static int process_handler(jack_nframes_t nframes, void *arg)
+{
+ struct auplay_st *st = arg;
+ size_t sampc = nframes * st->prm.ch;
+ size_t ch, j;
+
+ /* 1. read data from app (signed 16-bit) interleaved */
+ st->wh(st->sampv, sampc, st->arg);
+
+ /* 2. convert from 16-bit to float and copy to Jack */
+
+ /* 3. de-interleave [LRLRLRLR] -> [LLLLL]+[RRRRR] */
+ for (ch = 0; ch < st->prm.ch; ch++) {
+
+ jack_default_audio_sample_t *buffer;
+
+ buffer = jack_port_get_buffer(st->portv[ch], st->nframes);
+
+ for (j = 0; j < nframes; j++) {
+ int16_t samp = st->sampv[j*st->prm.ch + ch];
+ buffer[j] = ausamp_short2float(samp);
+ }
+ }
+
+ return 0;
+}
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+
+ info("jack: destroy\n");
+
+ if (st->client)
+ jack_client_close(st->client);
+
+ mem_deref(st->sampv);
+}
+
+
+static int start_jack(struct auplay_st *st)
+{
+ const char **ports;
+ const char *client_name = "baresip";
+ const char *server_name = NULL;
+ jack_options_t options = JackNullOption;
+ jack_status_t status;
+ unsigned ch;
+ jack_nframes_t engine_srate;
+
+ /* open a client connection to the JACK server */
+
+ st->client = jack_client_open(client_name, options,
+ &status, server_name);
+ if (st->client == NULL) {
+ warning("jack: jack_client_open() failed, "
+ "status = 0x%2.0x\n", status);
+
+ if (status & JackServerFailed) {
+ warning("jack: Unable to connect to JACK server\n");
+ }
+ return ENODEV;
+ }
+ if (status & JackServerStarted) {
+ info("jack: JACK server started\n");
+ }
+ if (status & JackNameNotUnique) {
+ client_name = jack_get_client_name(st->client);
+ info("jack: unique name `%s' assigned\n", client_name);
+ }
+
+ jack_set_process_callback(st->client, process_handler, st);
+
+ engine_srate = jack_get_sample_rate(st->client);
+ st->nframes = jack_get_buffer_size(st->client);
+
+ info("jack: engine sample rate: %" PRIu32 " max_frames=%u\n",
+ engine_srate, st->nframes);
+
+ /* currently the application must use the same sample-rate
+ as the jack server backend */
+ if (engine_srate != st->prm.srate) {
+ warning("jack: samplerate %uHz expected\n", engine_srate);
+ return EINVAL;
+ }
+
+ /* create one port per channel */
+ for (ch=0; ch<st->prm.ch; ch++) {
+
+ char buf[32];
+ re_snprintf(buf, sizeof(buf), "output_%u", ch+1);
+
+ st->portv[ch] = jack_port_register (st->client, buf,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput, 0);
+ if ( st->portv[ch] == NULL) {
+ warning("jack: no more JACK ports available\n");
+ return ENODEV;
+ }
+ }
+
+ /* Tell the JACK server that we are ready to roll. Our
+ * process() callback will start running now. */
+
+ if (jack_activate (st->client)) {
+ warning("jack: cannot activate client");
+ return ENODEV;
+ }
+
+ /* Connect the ports. You can't do this before the client is
+ * activated, because we can't make connections to clients
+ * that aren't running. Note the confusing (but necessary)
+ * orientation of the driver backend ports: playback ports are
+ * "input" to the backend, and capture ports are "output" from
+ * it.
+ */
+
+ ports = jack_get_ports (st->client, NULL, NULL,
+ JackPortIsInput);
+ if (ports == NULL) {
+ warning("jack: no physical playback ports\n");
+ return ENODEV;
+ }
+
+ for (ch=0; ch<st->prm.ch; ch++) {
+
+ if (jack_connect (st->client, jack_port_name (st->portv[ch]),
+ ports[ch])) {
+ warning("jack: cannot connect output ports\n");
+ }
+ }
+
+ jack_free(ports);
+
+ return 0;
+}
+
+
+int jack_play_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay_st *st;
+ int err = 0;
+
+ if (!stp || !ap || !prm || !wh)
+ return EINVAL;
+
+ info("jack: play %uHz,%uch\n", prm->srate, prm->ch);
+
+ if (prm->ch > ARRAY_SIZE(st->portv))
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), auplay_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->prm = *prm;
+ st->ap = ap;
+ st->wh = wh;
+ st->arg = arg;
+
+ err = start_jack(st);
+ if (err)
+ goto out;
+
+ st->sampc = st->nframes * prm->ch;
+ st->sampv = mem_alloc(st->sampc * sizeof(int16_t), NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ info("jack: sampc=%zu\n", st->sampc);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/jack/jack_src.c b/modules/jack/jack_src.c
new file mode 100644
index 0000000..2139e35
--- /dev/null
+++ b/modules/jack/jack_src.c
@@ -0,0 +1,224 @@
+/**
+ * @file jack_src.c JACK audio driver -- source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <math.h>
+#include <jack/jack.h>
+#include "mod_jack.h"
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* pointer to base-class (inheritance) */
+
+ struct ausrc_prm prm;
+ int16_t *sampv;
+ size_t sampc; /* includes number of channels */
+ ausrc_read_h *rh;
+ void *arg;
+
+ jack_client_t *client;
+ jack_port_t *portv[2];
+ jack_nframes_t nframes; /* num frames per port (channel) */
+};
+
+
+static inline int16_t ausamp_float2short(float in)
+{
+ double scaled_value;
+ int16_t out;
+
+ scaled_value = in * (8.0 * 0x10000000);
+
+ if (scaled_value >= (1.0 * 0x7fffffff)) {
+ out = 32767;
+ }
+ else if (scaled_value <= (-8.0 * 0x10000000)) {
+ out = -32768;
+ }
+ else
+ out = (short) (lrint (scaled_value) >> 16);
+
+ return out;
+}
+
+
+static int process_handler(jack_nframes_t nframes, void *arg)
+{
+ struct ausrc_st *st = arg;
+ size_t sampc = nframes * st->prm.ch;
+ size_t ch, j;
+
+ /* 2. convert from 16-bit to float and copy to Jack */
+
+ /* 3. de-interleave [LRLRLRLR] -> [LLLLL]+[RRRRR] */
+ for (ch = 0; ch < st->prm.ch; ch++) {
+
+ const jack_default_audio_sample_t *buffer;
+
+ buffer = jack_port_get_buffer(st->portv[ch], st->nframes);
+
+ for (j = 0; j < nframes; j++) {
+ int16_t samp;
+ samp = ausamp_float2short(buffer[j]);
+ st->sampv[j*st->prm.ch + ch] = samp;
+ }
+ }
+
+ /* 1. read data from app (signed 16-bit) interleaved */
+ st->rh(st->sampv, sampc, st->arg);
+
+ return 0;
+}
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ info("jack: source destroy\n");
+
+ if (st->client)
+ jack_client_close(st->client);
+
+ mem_deref(st->sampv);
+}
+
+
+static int start_jack(struct ausrc_st *st)
+{
+ const char **ports;
+ const char *client_name = "baresip";
+ const char *server_name = NULL;
+ jack_options_t options = JackNullOption;
+ jack_status_t status;
+ unsigned ch;
+ jack_nframes_t engine_srate;
+
+ /* open a client connection to the JACK server */
+
+ st->client = jack_client_open(client_name, options,
+ &status, server_name);
+ if (st->client == NULL) {
+ warning("jack: jack_client_open() failed, "
+ "status = 0x%2.0x\n", status);
+
+ if (status & JackServerFailed) {
+ warning("jack: Unable to connect to JACK server\n");
+ }
+ return ENODEV;
+ }
+ if (status & JackServerStarted) {
+ info("jack: JACK server started\n");
+ }
+ if (status & JackNameNotUnique) {
+ client_name = jack_get_client_name(st->client);
+ info("jack: unique name `%s' assigned\n", client_name);
+ }
+
+ jack_set_process_callback(st->client, process_handler, st);
+
+ engine_srate = jack_get_sample_rate(st->client);
+ st->nframes = jack_get_buffer_size(st->client);
+
+ info("jack: engine sample rate: %" PRIu32 " max_frames=%u\n",
+ engine_srate, st->nframes);
+
+ /* currently the application must use the same sample-rate
+ as the jack server backend */
+ if (engine_srate != st->prm.srate) {
+ warning("jack: samplerate %uHz expected\n", engine_srate);
+ return EINVAL;
+ }
+
+ /* create one port per channel */
+ for (ch=0; ch<st->prm.ch; ch++) {
+
+ char buf[32];
+ re_snprintf(buf, sizeof(buf), "input_%u", ch+1);
+
+ st->portv[ch] = jack_port_register(st->client, buf,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsInput, 0);
+ if ( st->portv[ch] == NULL) {
+ warning("jack: no more JACK ports available\n");
+ return ENODEV;
+ }
+ }
+
+ /* Tell the JACK server that we are ready to roll. Our
+ * process() callback will start running now. */
+
+ if (jack_activate (st->client)) {
+ warning("jack: cannot activate client");
+ return ENODEV;
+ }
+
+ ports = jack_get_ports (st->client, NULL, NULL,
+ JackPortIsOutput);
+ if (ports == NULL) {
+ warning("jack: no physical playback ports\n");
+ return ENODEV;
+ }
+
+ for (ch=0; ch<st->prm.ch; ch++) {
+
+ if (jack_connect(st->client, ports[ch],
+ jack_port_name(st->portv[ch]))) {
+ warning("jack: cannot connect output ports\n");
+ }
+ }
+
+ jack_free(ports);
+
+ return 0;
+}
+
+
+int jack_src_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ int err = 0;
+
+ if (!stp || !as || !prm || !rh)
+ return EINVAL;
+
+ if (prm->ch > ARRAY_SIZE(st->portv))
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), ausrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->prm = *prm;
+ st->as = as;
+ st->rh = rh;
+ st->arg = arg;
+
+ err = start_jack(st);
+ if (err)
+ goto out;
+
+ st->sampc = st->nframes * prm->ch;
+ st->sampv = mem_alloc(st->sampc * sizeof(int16_t), NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ info("jack: source sampc=%zu\n", st->sampc);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/jack/mod_jack.h b/modules/jack/mod_jack.h
new file mode 100644
index 0000000..b154ebf
--- /dev/null
+++ b/modules/jack/mod_jack.h
@@ -0,0 +1,14 @@
+/**
+ * @file mod_jack.h JACK audio driver -- internal api
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+int jack_play_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
+int jack_src_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
diff --git a/modules/jack/module.mk b/modules/jack/module.mk
new file mode 100644
index 0000000..486ffd6
--- /dev/null
+++ b/modules/jack/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := jack
+$(MOD)_SRCS += jack.c jack_play.c jack_src.c
+$(MOD)_CFLAGS += $(shell pkg-config --cflags jack)
+$(MOD)_LFLAGS += $(shell pkg-config --libs jack)
+
+include mk/mod.mk