aboutsummaryrefslogtreecommitdiffstats
path: root/Alc/backends/jack.c
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2014-12-22 13:30:35 -0800
committerChris Robinson <[email protected]>2014-12-22 13:30:35 -0800
commit7e6d0d6b4fdfbf3f3f4f08a70de7563ed02a4948 (patch)
treeb0041ff385ba9b23eae9d5f3e0cd57c7fd04c9f9 /Alc/backends/jack.c
parent23197ddbc35db09301b22636ea83e770a7900458 (diff)
Fill out the JACK playback backend
A few notes about it: The OpenAL device's requested buffer metrics are ignored, and instead the device will keep one JACK-sized buffer's worth of audio prepared for JACK's next process request. Output is restricted to 32-bit float stereo. Part of this is because JACK requires a buffer size that's a power of 2 (measured in samples), and the ringbuffer requires a buffer size that's a power of 2 (measured in bytes). A channel count of 6 (5.1) or 7 (6.1) will not work without causing a sample to split over the edge of the ringbuffer. Additioanlly, JACK doesn't provide information about what channel configuration a device or set of ports has, so there's no way to know what ports 3 and up map to (even the first two ports are unknown, but assuming stereo seems to work well enough). There is no device latency measurement (for AL_SOFT_source_latency) due to the non-atomicity between the ringbuffer's read size and port latency. A method is needed to ensure the ringbuffer's read size and port latency can both be measured between the end of a JACK update cycle (when the port latency has been updated with the newly-retrieved audio) and the following ringbuffer read update.
Diffstat (limited to 'Alc/backends/jack.c')
-rw-r--r--Alc/backends/jack.c341
1 files changed, 326 insertions, 15 deletions
diff --git a/Alc/backends/jack.c b/Alc/backends/jack.c
index bb7054fe..a3f44740 100644
--- a/Alc/backends/jack.c
+++ b/Alc/backends/jack.c
@@ -39,11 +39,32 @@ static const ALCchar jackDevice[] = "JACK Default";
#ifdef HAVE_DYNLOAD
-#define JACK_FUNCS(MAGIC) \
- MAGIC(jack_client_open); \
- MAGIC(jack_client_close); \
- MAGIC(jack_client_name_size); \
- MAGIC(jack_get_client_name);
+#define JACK_FUNCS(MAGIC) \
+ MAGIC(jack_client_open); \
+ MAGIC(jack_client_close); \
+ MAGIC(jack_client_name_size); \
+ MAGIC(jack_get_client_name); \
+ MAGIC(jack_connect); \
+ MAGIC(jack_activate); \
+ MAGIC(jack_deactivate); \
+ MAGIC(jack_port_register); \
+ MAGIC(jack_port_unregister); \
+ MAGIC(jack_port_get_buffer); \
+ MAGIC(jack_port_name); \
+ MAGIC(jack_get_ports); \
+ MAGIC(jack_free); \
+ MAGIC(jack_get_sample_rate); \
+ MAGIC(jack_ringbuffer_create); \
+ MAGIC(jack_ringbuffer_free); \
+ MAGIC(jack_ringbuffer_get_read_vector); \
+ MAGIC(jack_ringbuffer_get_write_vector); \
+ MAGIC(jack_ringbuffer_read_advance); \
+ MAGIC(jack_ringbuffer_write_advance); \
+ MAGIC(jack_ringbuffer_write_space); \
+ MAGIC(jack_set_process_callback); \
+ MAGIC(jack_set_buffer_size_callback); \
+ MAGIC(jack_set_buffer_size); \
+ MAGIC(jack_get_buffer_size);
static void *jack_handle;
#define MAKE_FUNC(f) static __typeof(f) * p##f
@@ -54,6 +75,27 @@ JACK_FUNCS(MAKE_FUNC);
#define jack_client_close pjack_client_close
#define jack_client_name_size pjack_client_name_size
#define jack_get_client_name pjack_get_client_name
+#define jack_connect pjack_connect
+#define jack_activate pjack_activate
+#define jack_deactivate pjack_deactivate
+#define jack_port_register pjack_port_register
+#define jack_port_unregister pjack_port_unregister
+#define jack_port_get_buffer pjack_port_get_buffer
+#define jack_port_name pjack_port_name
+#define jack_get_ports pjack_get_ports
+#define jack_free pjack_free
+#define jack_get_sample_rate pjack_get_sample_rate
+#define jack_ringbuffer_create pjack_ringbuffer_create
+#define jack_ringbuffer_free pjack_ringbuffer_free
+#define jack_ringbuffer_get_read_vector pjack_ringbuffer_get_read_vector
+#define jack_ringbuffer_get_write_vector pjack_ringbuffer_get_write_vector
+#define jack_ringbuffer_read_advance pjack_ringbuffer_read_advance
+#define jack_ringbuffer_write_advance pjack_ringbuffer_write_advance
+#define jack_ringbuffer_write_space pjack_ringbuffer_write_space
+#define jack_set_process_callback pjack_set_process_callback
+#define jack_set_buffer_size_callback pjack_set_buffer_size_callback
+#define jack_set_buffer_size pjack_set_buffer_size
+#define jack_get_buffer_size pjack_get_buffer_size
#endif
@@ -95,11 +137,20 @@ typedef struct ALCjackPlayback {
DERIVE_FROM_TYPE(ALCbackend);
jack_client_t *Client;
+ jack_port_t *Port[MAX_OUTPUT_CHANNELS];
+
+ jack_ringbuffer_t *Ring;
+ alcnd_t Cond;
volatile int killNow;
althrd_t thread;
} ALCjackPlayback;
+static int ALCjackPlayback_bufferSizeNotify(jack_nframes_t numframes, void *arg);
+
+static int ALCjackPlayback_process(jack_nframes_t numframes, void *arg);
+static int ALCjackPlayback_mixerProc(void *arg);
+
static void ALCjackPlayback_Construct(ALCjackPlayback *self, ALCdevice *device);
static void ALCjackPlayback_Destruct(ALCjackPlayback *self);
static ALCenum ALCjackPlayback_open(ALCjackPlayback *self, const ALCchar *name);
@@ -110,8 +161,8 @@ static void ALCjackPlayback_stop(ALCjackPlayback *self);
static DECLARE_FORWARD2(ALCjackPlayback, ALCbackend, ALCenum, captureSamples, void*, ALCuint)
static DECLARE_FORWARD(ALCjackPlayback, ALCbackend, ALCuint, availableSamples)
static DECLARE_FORWARD(ALCjackPlayback, ALCbackend, ALint64, getLatency)
-static DECLARE_FORWARD(ALCjackPlayback, ALCbackend, void, lock)
-static DECLARE_FORWARD(ALCjackPlayback, ALCbackend, void, unlock)
+static void ALCjackPlayback_lock(ALCjackPlayback *self);
+static void ALCjackPlayback_unlock(ALCjackPlayback *self);
DECLARE_DEFAULT_ALLOCATORS(ALCjackPlayback)
DEFINE_ALCBACKEND_VTABLE(ALCjackPlayback);
@@ -119,51 +170,295 @@ DEFINE_ALCBACKEND_VTABLE(ALCjackPlayback);
static void ALCjackPlayback_Construct(ALCjackPlayback *self, ALCdevice *device)
{
+ ALuint i;
+
ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
SET_VTABLE2(ALCjackPlayback, ALCbackend, self);
+ alcnd_init(&self->Cond);
+
self->Client = NULL;
+ for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
+ self->Port[i] = NULL;
+ self->Ring = NULL;
+
self->killNow = 1;
}
static void ALCjackPlayback_Destruct(ALCjackPlayback *self)
{
+ ALuint i;
+
if(self->Client)
+ {
+ for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
+ {
+ if(self->Port[i])
+ jack_port_unregister(self->Client, self->Port[i]);
+ self->Port[i] = NULL;
+ }
jack_client_close(self->Client);
- self->Client = NULL;
+ self->Client = NULL;
+ }
+
+ alcnd_destroy(&self->Cond);
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
}
+static int ALCjackPlayback_bufferSizeNotify(jack_nframes_t numframes, void *arg)
+{
+ ALCjackPlayback *self = arg;
+ ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
+
+ ALCjackPlayback_lock(self);
+ device->UpdateSize = numframes;
+ TRACE("%u update size x%u\n", device->UpdateSize, device->NumUpdates);
+
+ jack_ringbuffer_free(self->Ring);
+ self->Ring = jack_ringbuffer_create(device->UpdateSize * device->NumUpdates *
+ FrameSizeFromDevFmt(device->FmtChans, device->FmtType));
+ if(!self->Ring)
+ {
+ ERR("Failed to reallocate ringbuffer\n");
+ aluHandleDisconnect(device);
+ }
+ ALCjackPlayback_unlock(self);
+ return 0;
+}
+
+
+static int ALCjackPlayback_process(jack_nframes_t numframes, void *arg)
+{
+ ALCjackPlayback *self = arg;
+ ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
+ ALuint frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType);
+ jack_default_audio_sample_t *out[MAX_OUTPUT_CHANNELS];
+ jack_ringbuffer_data_t data[2];
+ jack_nframes_t total = 0;
+ jack_nframes_t todo;
+ ALuint i, c, numchans;
+
+ jack_ringbuffer_get_read_vector(self->Ring, data);
+
+ for(c = 0;c < MAX_OUTPUT_CHANNELS && self->Port[c];c++)
+ out[c] = jack_port_get_buffer(self->Port[c], numframes);
+ numchans = c;
+ for(;c < MAX_OUTPUT_CHANNELS;c++)
+ out[c] = NULL;
+
+ todo = minu(numframes, data[0].len/frame_size);
+ for(c = 0;c < numchans;c++)
+ {
+ for(i = 0;i < todo;i++)
+ out[c][i] = ((ALfloat*)data[0].buf)[i*numchans + c];
+ out[c] += todo;
+ }
+ total += todo;
+
+ todo = minu(numframes-total, data[1].len/frame_size);
+ for(c = 0;c < numchans;c++)
+ {
+ for(i = 0;i < todo;i++)
+ out[c][i] = ((ALfloat*)data[1].buf)[i*numchans + c];
+ out[c] += todo;
+ }
+ total += todo;
+
+ jack_ringbuffer_read_advance(self->Ring, total*frame_size);
+ alcnd_signal(&self->Cond);
+
+ if(numframes > total)
+ {
+ todo = numframes-total;
+ for(c = 0;c < numchans;c++)
+ {
+ for(i = 0;i < todo;i++)
+ out[c][i] = 0.0f;
+ }
+ }
+
+ return 0;
+}
+
+static int ALCjackPlayback_mixerProc(void *arg)
+{
+ ALCjackPlayback *self = arg;
+ ALCdevice *device = STATIC_CAST(ALCbackend,self)->mDevice;
+ ALuint frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType);
+ jack_ringbuffer_data_t data[2];
+
+ SetRTPriority();
+ althrd_setname(althrd_current(), MIXER_THREAD_NAME);
+
+ ALCjackPlayback_lock(self);
+ while(!self->killNow && device->Connected)
+ {
+ ALuint todo, len1, len2;
+
+ if(jack_ringbuffer_write_space(self->Ring)/frame_size < device->UpdateSize)
+ {
+ alcnd_wait(&self->Cond, &STATIC_CAST(ALCbackend,self)->mMutex);
+ continue;
+ }
+
+ jack_ringbuffer_get_write_vector(self->Ring, data);
+ todo = (data[0].len+data[1].len)/frame_size;
+ todo -= todo%device->UpdateSize;
+
+ len1 = minu(data[0].len/frame_size, todo);
+ len2 = minu(data[1].len/frame_size, todo-len1);
+
+ aluMixData(device, data[0].buf, len1);
+ if(len2 > 0)
+ aluMixData(device, data[1].buf, len2);
+ jack_ringbuffer_write_advance(self->Ring, todo*frame_size);
+ }
+ ALCjackPlayback_unlock(self);
+
+ return 0;
+}
+
+
static ALCenum ALCjackPlayback_open(ALCjackPlayback *self, const ALCchar *name)
{
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
+ const char *client_name = "alsoft";
+ jack_status_t status;
if(!name)
name = jackDevice;
else if(strcmp(name, jackDevice) != 0)
return ALC_INVALID_VALUE;
+ self->Client = jack_client_open(client_name, 0, &status, NULL);
+ if(self->Client == NULL)
+ {
+ ERR("jack_client_open() failed, status = 0x%02x\n", status);
+ return ALC_INVALID_VALUE;
+ }
+ if((status&JackServerStarted))
+ TRACE("JACK server started\n");
+ if((status&JackNameNotUnique))
+ {
+ client_name = jack_get_client_name(self->Client);
+ TRACE("Client name not unique, got `%s' instead\n", client_name);
+ }
+
+ jack_set_process_callback(self->Client, ALCjackPlayback_process, self);
+ jack_set_buffer_size_callback(self->Client, ALCjackPlayback_bufferSizeNotify, self);
al_string_copy_cstr(&device->DeviceName, name);
- return ALC_INVALID_VALUE;
+ return ALC_NO_ERROR;
}
-static void ALCjackPlayback_close(ALCjackPlayback *UNUSED(self))
+static void ALCjackPlayback_close(ALCjackPlayback *self)
{
+ ALuint i;
+
+ for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
+ {
+ if(self->Port[i])
+ jack_port_unregister(self->Client, self->Port[i]);
+ self->Port[i] = NULL;
+ }
+ jack_client_close(self->Client);
+ self->Client = NULL;
}
-static ALCboolean ALCjackPlayback_reset(ALCjackPlayback *UNUSED(self))
+static ALCboolean ALCjackPlayback_reset(ALCjackPlayback *self)
{
- //jack_set_process_callback (client, process, &data);
- return ALC_FALSE;
+ ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
+ ALuint numchans, i;
+
+ for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
+ {
+ if(self->Port[i])
+ jack_port_unregister(self->Client, self->Port[i]);
+ self->Port[i] = NULL;
+ }
+
+ /* Ignore the requested buffer metrics and just keep one JACK-sized buffer
+ * ready for when requested. Note that even though the ringbuffer will have
+ * 2 periods worth of space, only half of it will be filled at a given time
+ * because there's one byte less of it that's writeable, and we only write
+ * in update-sized chunks. */
+ device->Frequency = jack_get_sample_rate(self->Client);
+ device->UpdateSize = jack_get_buffer_size(self->Client);
+ device->NumUpdates = 2;
+
+ /* FIXME: Force stereo, 32-bit float output. */
+ device->FmtChans = DevFmtStereo;
+ device->FmtType = DevFmtFloat;
+
+ numchans = ChannelsFromDevFmt(device->FmtChans);
+ for(i = 0;i < numchans;i++)
+ {
+ char name[64];
+ snprintf(name, sizeof(name), "channel_%d", i+1);
+ self->Port[i] = jack_port_register(self->Client, name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ if(self->Port[i] == NULL)
+ {
+ ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(device->FmtChans));
+ return ALC_FALSE;
+ }
+ }
+
+ if(self->Ring)
+ jack_ringbuffer_free(self->Ring);
+ self->Ring = jack_ringbuffer_create(device->UpdateSize * device->NumUpdates *
+ FrameSizeFromDevFmt(device->FmtChans, device->FmtType));
+ if(!self->Ring)
+ {
+ ERR("Failed to allocate ringbuffer\n");
+ return ALC_FALSE;
+ }
+
+ SetDefaultChannelOrder(device);
+
+ return ALC_TRUE;
}
-static ALCboolean ALCjackPlayback_start(ALCjackPlayback *UNUSED(self))
+static ALCboolean ALCjackPlayback_start(ALCjackPlayback *self)
{
- return ALC_FALSE;
+ const char **ports;
+ ALuint i;
+
+ if(jack_activate(self->Client))
+ {
+ ERR("Failed to activate client\n");
+ return ALC_FALSE;
+ }
+
+ ports = jack_get_ports(self->Client, NULL, NULL, JackPortIsPhysical|JackPortIsInput);
+ if(ports == NULL)
+ {
+ ERR("No physical playback ports found\n");
+ jack_deactivate(self->Client);
+ return ALC_FALSE;
+ }
+ for(i = 0;i < MAX_OUTPUT_CHANNELS && self->Port[i];i++)
+ {
+ if(!ports[i])
+ {
+ ERR("No physical playback port for \"%s\"\n", jack_port_name(self->Port[i]));
+ break;
+ }
+ if(jack_connect(self->Client, jack_port_name(self->Port[i]), ports[i]))
+ ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(self->Port[i]), ports[i]);
+ }
+ jack_free(ports);
+
+ self->killNow = 0;
+ if(althrd_create(&self->thread, ALCjackPlayback_mixerProc, self) != althrd_success)
+ {
+ jack_deactivate(self->Client);
+ return ALC_FALSE;
+ }
+
+ return ALC_TRUE;
}
static void ALCjackPlayback_stop(ALCjackPlayback *self)
@@ -174,7 +469,23 @@ static void ALCjackPlayback_stop(ALCjackPlayback *self)
return;
self->killNow = 1;
+ ALCjackPlayback_lock(self);
+ alcnd_signal(&self->Cond);
+ ALCjackPlayback_unlock(self);
althrd_join(self->thread, &res);
+
+ jack_deactivate(self->Client);
+}
+
+
+static void ALCjackPlayback_lock(ALCjackPlayback *self)
+{
+ almtx_lock(&STATIC_CAST(ALCbackend,self)->mMutex);
+}
+
+static void ALCjackPlayback_unlock(ALCjackPlayback *self)
+{
+ almtx_unlock(&STATIC_CAST(ALCbackend,self)->mMutex);
}