| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| |
| #include "../../SDL_internal.h" |
| |
| #if SDL_AUDIO_DRIVER_JACK |
| |
| #include "SDL_assert.h" |
| #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; |
| |
| /* !!! FIXME: this is copy/pasted in several places now */ |
| static int |
| load_jack_sym(const char *fn, void **addr) |
| { |
| *addr = SDL_LoadFunction(jack_handle, fn); |
| if (*addr == NULL) { |
| /* Don't call SDL_SetError(): SDL_LoadFunction already did. */ |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* cast funcs to char* first, to please GCC's strict aliasing rules. */ |
| #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; |
| /* Don't call SDL_SetError(): SDL_LoadObject already did. */ |
| } 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 /* SDL_AUDIO_DRIVER_JACK_DYNAMIC */ |
| |
| |
| 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) /* JACK went away; device is lost. */ |
| { |
| SDL_AudioDevice *this = (SDL_AudioDevice *) arg; |
| SDL_OpenedAudioDeviceDisconnected(this); |
| SDL_SemPost(this->hidden->iosem); /* unblock the SDL thread. */ |
| } |
| |
| // !!! FIXME: implement and register these! |
| //typedef int(* JackSampleRateCallback)(jack_nframes_t nframes, void *arg) |
| //typedef int(* JackBufferSizeCallback)(jack_nframes_t nframes, void *arg) |
| |
| 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)) { |
| /* silence the buffer to avoid repeats and corruption. */ |
| 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); /* tell SDL thread we're done; refill the buffer. */ |
| return 0; /* success */ |
| } |
| |
| |
| /* This function waits until it is possible to write a full sound buffer */ |
| 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); /* tell SDL thread we're done; new buffer is ready! */ |
| return 0; /* success */ |
| } |
| |
| static int |
| JACK_CaptureFromDevice(_THIS, void *buffer, int buflen) |
| { |
| SDL_assert(buflen == this->spec.size); /* we always fill a full buffer. */ |
| |
| /* Wait for JACK to fill the iobuffer */ |
| 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); |
| } |
| |
| static int |
| JACK_OpenDevice(_THIS, void *handle, const char *devname, int iscapture) |
| { |
| /* Note that JACK uses "output" for capture devices (they output audio |
| data to us) and "input" for playback (we input audio data to them). |
| Likewise, SDL's playback port will be "output" (we write data out) |
| and capture will be "input" (we read data in). */ |
| 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; |
| |
| /* Initialize all variables that we clean on shutdown */ |
| this->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof (*this->hidden)); |
| if (this->hidden == NULL) { |
| return SDL_OutOfMemory(); |
| } |
| |
| /* !!! FIXME: we _still_ need an API to specify an app name */ |
| 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]) { |
| /* spin to count devports */ |
| } |
| |
| /* Filter out non-audio 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); |
| /* See if type ends with "audio" */ |
| 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"); |
| } |
| |
| |
| /* !!! FIXME: docs say about buffer size: "This size may change, clients that depend on it must register a bufsize_callback so they will be notified if it does." */ |
| |
| /* Jack pretty much demands what it wants. */ |
| 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; /* error was set by SDL_CreateSemaphore */ |
| } |
| |
| this->hidden->iobuffer = (float *) SDL_calloc(1, this->spec.size); |
| if (!this->hidden->iobuffer) { |
| return SDL_OutOfMemory(); |
| } |
| |
| /* Build SDL's ports, which we will connect to the device ports. */ |
| 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"); |
| } |
| |
| /* once activated, we can connect all the ports. */ |
| 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); |
| } |
| } |
| |
| /* don't need these anymore. */ |
| JACK_jack_free(devports); |
| SDL_free(audio_ports); |
| |
| /* We're ready to rock and roll. :-) */ |
| return 0; |
| } |
| |
| static void |
| JACK_Deinitialize(void) |
| { |
| UnloadJackLibrary(); |
| } |
| |
| static int |
| JACK_Init(SDL_AudioDriverImpl * impl) |
| { |
| if (LoadJackLibrary() < 0) { |
| return 0; |
| } else { |
| /* Make sure a JACK server is running and available. */ |
| jack_status_t status; |
| jack_client_t *client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL); |
| if (client == NULL) { |
| UnloadJackLibrary(); |
| return 0; |
| } |
| JACK_jack_client_close(client); |
| } |
| |
| /* Set the function pointers */ |
| 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 1; /* this audio target is available. */ |
| } |
| |
| AudioBootStrap JACK_bootstrap = { |
| "jack", "JACK Audio Connection Kit", JACK_Init, 0 |
| }; |
| |
| #endif /* SDL_AUDIO_DRIVER_JACK */ |
| |
| /* vi: set ts=4 sw=4 expandtab: */ |