#include "../../SDL_internal.h"
#if SDL_AUDIO_DRIVER_JACK
#include "SDL_timer.h"
#include "SDL_audio.h"
#include "../SDL_audio_c.h"
#include "SDL_jackaudio.h"
#include "SDL_loadso.h"
#include "../../thread/SDL_systhread.h"
static jack_client_t * (*JACK_jack_client_open) (const char *, jack_options_t, jack_status_t *, ...);
static int (*JACK_jack_client_close) (jack_client_t *);
static void (*JACK_jack_on_shutdown) (jack_client_t *, JackShutdownCallback, void *);
static int (*JACK_jack_activate) (jack_client_t *);
static int (*JACK_jack_deactivate) (jack_client_t *);
static void * (*JACK_jack_port_get_buffer) (jack_port_t *, jack_nframes_t);
static int (*JACK_jack_port_unregister) (jack_client_t *, jack_port_t *);
static void (*JACK_jack_free) (void *);
static const char ** (*JACK_jack_get_ports) (jack_client_t *, const char *, const char *, unsigned long);
static jack_nframes_t (*JACK_jack_get_sample_rate) (jack_client_t *);
static jack_nframes_t (*JACK_jack_get_buffer_size) (jack_client_t *);
static jack_port_t * (*JACK_jack_port_register) (jack_client_t *, const char *, const char *, unsigned long, unsigned long);
static jack_port_t * (*JACK_jack_port_by_name) (jack_client_t *, const char *);
static const char * (*JACK_jack_port_name) (const jack_port_t *);
static const char * (*JACK_jack_port_type) (const jack_port_t *);
static int (*JACK_jack_connect) (jack_client_t *, const char *, const char *);
static int (*JACK_jack_set_process_callback) (jack_client_t *, JackProcessCallback, void *);
static int load_jack_syms(void);
#ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC
static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC;
static void *jack_handle = NULL;
static int
load_jack_sym(const char *fn, void **addr)
{
*addr = SDL_LoadFunction(jack_handle, fn);
if (*addr == NULL) {
return 0;
}
return 1;
}
#define SDL_JACK_SYM(x) \
if (!load_jack_sym(#x, (void **) (char *) &JACK_##x)) return -1
static void
UnloadJackLibrary(void)
{
if (jack_handle != NULL) {
SDL_UnloadObject(jack_handle);
jack_handle = NULL;
}
}
static int
LoadJackLibrary(void)
{
int retval = 0;
if (jack_handle == NULL) {
jack_handle = SDL_LoadObject(jack_library);
if (jack_handle == NULL) {
retval = -1;
} else {
retval = load_jack_syms();
if (retval < 0) {
UnloadJackLibrary();
}
}
}
return retval;
}
#else
#define SDL_JACK_SYM(x) JACK_##x = x
static void
UnloadJackLibrary(void)
{
}
static int
LoadJackLibrary(void)
{
load_jack_syms();
return 0;
}
#endif
static int
load_jack_syms(void)
{
SDL_JACK_SYM(jack_client_open);
SDL_JACK_SYM(jack_client_close);
SDL_JACK_SYM(jack_on_shutdown);
SDL_JACK_SYM(jack_activate);
SDL_JACK_SYM(jack_deactivate);
SDL_JACK_SYM(jack_port_get_buffer);
SDL_JACK_SYM(jack_port_unregister);
SDL_JACK_SYM(jack_free);
SDL_JACK_SYM(jack_get_ports);
SDL_JACK_SYM(jack_get_sample_rate);
SDL_JACK_SYM(jack_get_buffer_size);
SDL_JACK_SYM(jack_port_register);
SDL_JACK_SYM(jack_port_by_name);
SDL_JACK_SYM(jack_port_name);
SDL_JACK_SYM(jack_port_type);
SDL_JACK_SYM(jack_connect);
SDL_JACK_SYM(jack_set_process_callback);
return 0;
}
static void
jackShutdownCallback(void *arg)
{
SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
SDL_OpenedAudioDeviceDisconnected(this);
SDL_SemPost(this->hidden->iosem);
}
static int
jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg)
{
SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
jack_port_t **ports = this->hidden->sdlports;
const int total_channels = this->spec.channels;
const int total_frames = this->spec.samples;
int channelsi;
if (!SDL_AtomicGet(&this->enabled)) {
SDL_memset(this->hidden->iobuffer, '\0', this->spec.size);
}
for (channelsi = 0; channelsi < total_channels; channelsi++) {
float *dst = (float *) JACK_jack_port_get_buffer(ports[channelsi], nframes);
if (dst) {
const float *src = ((float *) this->hidden->iobuffer) + channelsi;
int framesi;
for (framesi = 0; framesi < total_frames; framesi++) {
*(dst++) = *src;
src += total_channels;
}
}
}
SDL_SemPost(this->hidden->iosem);
return 0;
}
static void
JACK_WaitDevice(_THIS)
{
if (SDL_AtomicGet(&this->enabled)) {
if (SDL_SemWait(this->hidden->iosem) == -1) {
SDL_OpenedAudioDeviceDisconnected(this);
}
}
}
static Uint8 *
JACK_GetDeviceBuf(_THIS)
{
return (Uint8 *) this->hidden->iobuffer;
}
static int
jackProcessCaptureCallback(jack_nframes_t nframes, void *arg)
{
SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
if (SDL_AtomicGet(&this->enabled)) {
jack_port_t **ports = this->hidden->sdlports;
const int total_channels = this->spec.channels;
const int total_frames = this->spec.samples;
int channelsi;
for (channelsi = 0; channelsi < total_channels; channelsi++) {
const float *src = (const float *) JACK_jack_port_get_buffer(ports[channelsi], nframes);
if (src) {
float *dst = ((float *) this->hidden->iobuffer) + channelsi;
int framesi;
for (framesi = 0; framesi < total_frames; framesi++) {
*dst = *(src++);
dst += total_channels;
}
}
}
}
SDL_SemPost(this->hidden->iosem);
return 0;
}
static int
JACK_CaptureFromDevice(_THIS, void *buffer, int buflen)
{
SDL_assert(buflen == this->spec.size);
if (SDL_SemWait(this->hidden->iosem) == -1) {
return -1;
}
SDL_memcpy(buffer, this->hidden->iobuffer, buflen);
return buflen;
}
static void
JACK_FlushCapture(_THIS)
{
SDL_SemWait(this->hidden->iosem);
}
static void
JACK_CloseDevice(_THIS)
{
if (this->hidden->client) {
JACK_jack_deactivate(this->hidden->client);
if (this->hidden->sdlports) {
const int channels = this->spec.channels;
int i;
for (i = 0; i < channels; i++) {
JACK_jack_port_unregister(this->hidden->client, this->hidden->sdlports[i]);
}
SDL_free(this->hidden->sdlports);
}
JACK_jack_client_close(this->hidden->client);
}
if (this->hidden->iosem) {
SDL_DestroySemaphore(this->hidden->iosem);
}
SDL_free(this->hidden->iobuffer);
SDL_free(this->hidden);
}
static int
JACK_OpenDevice(_THIS, const char *devname)
{
SDL_bool iscapture = this->iscapture;
const unsigned long sysportflags = iscapture ? JackPortIsOutput : JackPortIsInput;
const unsigned long sdlportflags = iscapture ? JackPortIsInput : JackPortIsOutput;
const JackProcessCallback callback = iscapture ? jackProcessCaptureCallback : jackProcessPlaybackCallback;
const char *sdlportstr = iscapture ? "input" : "output";
const char **devports = NULL;
int *audio_ports;
jack_client_t *client = NULL;
jack_status_t status;
int channels = 0;
int ports = 0;
int i;
this->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof (*this->hidden));
if (this->hidden == NULL) {
return SDL_OutOfMemory();
}
client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL);
this->hidden->client = client;
if (client == NULL) {
return SDL_SetError("Can't open JACK client");
}
devports = JACK_jack_get_ports(client, NULL, NULL, JackPortIsPhysical | sysportflags);
if (!devports || !devports[0]) {
return SDL_SetError("No physical JACK ports available");
}
while (devports[++ports]) {
}
audio_ports = SDL_calloc(ports, sizeof *audio_ports);
for (i = 0; i < ports; i++) {
const jack_port_t *dport = JACK_jack_port_by_name(client, devports[i]);
const char *type = JACK_jack_port_type(dport);
const int len = SDL_strlen(type);
if (len >= 5 && !SDL_memcmp(type+len-5, "audio", 5)) {
audio_ports[channels++] = i;
}
}
if (channels == 0) {
return SDL_SetError("No physical JACK ports available");
}
this->spec.format = AUDIO_F32SYS;
this->spec.freq = JACK_jack_get_sample_rate(client);
this->spec.channels = channels;
this->spec.samples = JACK_jack_get_buffer_size(client);
SDL_CalculateAudioSpec(&this->spec);
this->hidden->iosem = SDL_CreateSemaphore(0);
if (!this->hidden->iosem) {
return -1;
}
this->hidden->iobuffer = (float *) SDL_calloc(1, this->spec.size);
if (!this->hidden->iobuffer) {
return SDL_OutOfMemory();
}
this->hidden->sdlports = (jack_port_t **) SDL_calloc(channels, sizeof (jack_port_t *));
if (this->hidden->sdlports == NULL) {
return SDL_OutOfMemory();
}
for (i = 0; i < channels; i++) {
char portname[32];
SDL_snprintf(portname, sizeof (portname), "sdl_jack_%s_%d", sdlportstr, i);
this->hidden->sdlports[i] = JACK_jack_port_register(client, portname, JACK_DEFAULT_AUDIO_TYPE, sdlportflags, 0);
if (this->hidden->sdlports[i] == NULL) {
return SDL_SetError("jack_port_register failed");
}
}
if (JACK_jack_set_process_callback(client, callback, this) != 0) {
return SDL_SetError("JACK: Couldn't set process callback");
}
JACK_jack_on_shutdown(client, jackShutdownCallback, this);
if (JACK_jack_activate(client) != 0) {
return SDL_SetError("Failed to activate JACK client");
}
for (i = 0; i < channels; i++) {
const char *sdlport = JACK_jack_port_name(this->hidden->sdlports[i]);
const char *srcport = iscapture ? devports[audio_ports[i]] : sdlport;
const char *dstport = iscapture ? sdlport : devports[audio_ports[i]];
if (JACK_jack_connect(client, srcport, dstport) != 0) {
return SDL_SetError("Couldn't connect JACK ports: %s => %s", srcport, dstport);
}
}
JACK_jack_free(devports);
SDL_free(audio_ports);
return 0;
}
static void
JACK_Deinitialize(void)
{
UnloadJackLibrary();
}
static SDL_bool
JACK_Init(SDL_AudioDriverImpl * impl)
{
if (LoadJackLibrary() < 0) {
return SDL_FALSE;
} else {
jack_status_t status;
jack_client_t *client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL);
if (client == NULL) {
UnloadJackLibrary();
return SDL_FALSE;
}
JACK_jack_client_close(client);
}
impl->OpenDevice = JACK_OpenDevice;
impl->WaitDevice = JACK_WaitDevice;
impl->GetDeviceBuf = JACK_GetDeviceBuf;
impl->CloseDevice = JACK_CloseDevice;
impl->Deinitialize = JACK_Deinitialize;
impl->CaptureFromDevice = JACK_CaptureFromDevice;
impl->FlushCapture = JACK_FlushCapture;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap JACK_bootstrap = {
"jack", "JACK Audio Connection Kit", JACK_Init, SDL_FALSE
};
#endif