src/audio/jack/SDL_jackaudio.c
author Sam Lantinga <slouken@libsdl.org>
Wed, 03 Jan 2018 10:03:25 -0800
changeset 11811 5d94cb6b24d3
parent 11085 d03a7de85bd5
child 12005 94f3f018d3eb
permissions -rw-r--r--
Updated copyright for 2018
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 
    22 #include "../../SDL_internal.h"
    23 
    24 #if SDL_AUDIO_DRIVER_JACK
    25 
    26 #include "SDL_assert.h"
    27 #include "SDL_timer.h"
    28 #include "SDL_audio.h"
    29 #include "../SDL_audio_c.h"
    30 #include "SDL_jackaudio.h"
    31 #include "SDL_loadso.h"
    32 #include "../../thread/SDL_systhread.h"
    33 
    34 
    35 static jack_client_t * (*JACK_jack_client_open) (const char *, jack_options_t, jack_status_t *, ...);
    36 static int (*JACK_jack_client_close) (jack_client_t *);
    37 static void (*JACK_jack_on_shutdown) (jack_client_t *, JackShutdownCallback, void *);
    38 static int (*JACK_jack_activate) (jack_client_t *);
    39 static int (*JACK_jack_deactivate) (jack_client_t *);
    40 static void * (*JACK_jack_port_get_buffer) (jack_port_t *, jack_nframes_t);
    41 static int (*JACK_jack_port_unregister) (jack_client_t *, jack_port_t *);
    42 static void (*JACK_jack_free) (void *);
    43 static const char ** (*JACK_jack_get_ports) (jack_client_t *, const char *, const char *, unsigned long);
    44 static jack_nframes_t (*JACK_jack_get_sample_rate) (jack_client_t *);
    45 static jack_nframes_t (*JACK_jack_get_buffer_size) (jack_client_t *);
    46 static jack_port_t * (*JACK_jack_port_register) (jack_client_t *, const char *, const char *, unsigned long, unsigned long);
    47 static const char * (*JACK_jack_port_name) (const jack_port_t *);
    48 static int (*JACK_jack_connect) (jack_client_t *, const char *, const char *);
    49 static int (*JACK_jack_set_process_callback) (jack_client_t *, JackProcessCallback, void *);
    50 
    51 static int load_jack_syms(void);
    52 
    53 
    54 #ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC
    55 
    56 static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC;
    57 static void *jack_handle = NULL;
    58 
    59 /* !!! FIXME: this is copy/pasted in several places now */
    60 static int
    61 load_jack_sym(const char *fn, void **addr)
    62 {
    63     *addr = SDL_LoadFunction(jack_handle, fn);
    64     if (*addr == NULL) {
    65         /* Don't call SDL_SetError(): SDL_LoadFunction already did. */
    66         return 0;
    67     }
    68 
    69     return 1;
    70 }
    71 
    72 /* cast funcs to char* first, to please GCC's strict aliasing rules. */
    73 #define SDL_JACK_SYM(x) \
    74     if (!load_jack_sym(#x, (void **) (char *) &JACK_##x)) return -1
    75 
    76 static void
    77 UnloadJackLibrary(void)
    78 {
    79     if (jack_handle != NULL) {
    80         SDL_UnloadObject(jack_handle);
    81         jack_handle = NULL;
    82     }
    83 }
    84 
    85 static int
    86 LoadJackLibrary(void)
    87 {
    88     int retval = 0;
    89     if (jack_handle == NULL) {
    90         jack_handle = SDL_LoadObject(jack_library);
    91         if (jack_handle == NULL) {
    92             retval = -1;
    93             /* Don't call SDL_SetError(): SDL_LoadObject already did. */
    94         } else {
    95             retval = load_jack_syms();
    96             if (retval < 0) {
    97                 UnloadJackLibrary();
    98             }
    99         }
   100     }
   101     return retval;
   102 }
   103 
   104 #else
   105 
   106 #define SDL_JACK_SYM(x) JACK_##x = x
   107 
   108 static void
   109 UnloadJackLibrary(void)
   110 {
   111 }
   112 
   113 static int
   114 LoadJackLibrary(void)
   115 {
   116     load_jack_syms();
   117     return 0;
   118 }
   119 
   120 #endif /* SDL_AUDIO_DRIVER_JACK_DYNAMIC */
   121 
   122 
   123 static int
   124 load_jack_syms(void)
   125 {
   126     SDL_JACK_SYM(jack_client_open);
   127     SDL_JACK_SYM(jack_client_close);
   128     SDL_JACK_SYM(jack_on_shutdown);
   129     SDL_JACK_SYM(jack_activate);
   130     SDL_JACK_SYM(jack_deactivate);
   131     SDL_JACK_SYM(jack_port_get_buffer);
   132     SDL_JACK_SYM(jack_port_unregister);
   133     SDL_JACK_SYM(jack_free);
   134     SDL_JACK_SYM(jack_get_ports);
   135     SDL_JACK_SYM(jack_get_sample_rate);
   136     SDL_JACK_SYM(jack_get_buffer_size);
   137     SDL_JACK_SYM(jack_port_register);
   138     SDL_JACK_SYM(jack_port_name);
   139     SDL_JACK_SYM(jack_connect);
   140     SDL_JACK_SYM(jack_set_process_callback);
   141     return 0;
   142 }
   143 
   144 
   145 static void
   146 jackShutdownCallback(void *arg)  /* JACK went away; device is lost. */
   147 {
   148     SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
   149     SDL_OpenedAudioDeviceDisconnected(this);
   150     SDL_SemPost(this->hidden->iosem);  /* unblock the SDL thread. */
   151 }
   152 
   153 // !!! FIXME: implement and register these!
   154 //typedef int(* JackSampleRateCallback)(jack_nframes_t nframes, void *arg)
   155 //typedef int(* JackBufferSizeCallback)(jack_nframes_t nframes, void *arg)
   156 
   157 static int
   158 jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg)
   159 {
   160     SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
   161     jack_port_t **ports = this->hidden->sdlports;
   162     const int total_channels = this->spec.channels;
   163     const int total_frames = this->spec.samples;
   164     int channelsi;
   165 
   166     if (!SDL_AtomicGet(&this->enabled)) {
   167         /* silence the buffer to avoid repeats and corruption. */
   168         SDL_memset(this->hidden->iobuffer, '\0', this->spec.size);
   169     }
   170 
   171     for (channelsi = 0; channelsi < total_channels; channelsi++) {
   172         float *dst = (float *) JACK_jack_port_get_buffer(ports[channelsi], nframes);
   173         if (dst) {
   174             const float *src = ((float *) this->hidden->iobuffer) + channelsi;
   175             int framesi;
   176             for (framesi = 0; framesi < total_frames; framesi++) {
   177                 *(dst++) = *src;
   178                 src += total_channels;
   179             }
   180         }
   181     }
   182 
   183     SDL_SemPost(this->hidden->iosem);  /* tell SDL thread we're done; refill the buffer. */
   184     return 0;  /* success */
   185 }
   186 
   187 
   188 /* This function waits until it is possible to write a full sound buffer */
   189 static void
   190 JACK_WaitDevice(_THIS)
   191 {
   192     if (SDL_AtomicGet(&this->enabled)) {
   193         if (SDL_SemWait(this->hidden->iosem) == -1) {
   194             SDL_OpenedAudioDeviceDisconnected(this);
   195         }
   196     }
   197 }
   198 
   199 static Uint8 *
   200 JACK_GetDeviceBuf(_THIS)
   201 {
   202     return (Uint8 *) this->hidden->iobuffer;
   203 }
   204 
   205 
   206 static int
   207 jackProcessCaptureCallback(jack_nframes_t nframes, void *arg)
   208 {
   209     SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
   210     if (SDL_AtomicGet(&this->enabled)) {
   211         jack_port_t **ports = this->hidden->sdlports;
   212         const int total_channels = this->spec.channels;
   213         const int total_frames = this->spec.samples;
   214         int channelsi;
   215     
   216         for (channelsi = 0; channelsi < total_channels; channelsi++) {
   217             const float *src = (const float *) JACK_jack_port_get_buffer(ports[channelsi], nframes);
   218             if (src) {
   219                 float *dst = ((float *) this->hidden->iobuffer) + channelsi;
   220                 int framesi;
   221                 for (framesi = 0; framesi < total_frames; framesi++) {
   222                     *dst = *(src++);
   223                     dst += total_channels;
   224                 }
   225             }
   226         }
   227     }
   228 
   229     SDL_SemPost(this->hidden->iosem);  /* tell SDL thread we're done; new buffer is ready! */
   230     return 0;  /* success */
   231 }
   232 
   233 static int
   234 JACK_CaptureFromDevice(_THIS, void *buffer, int buflen)
   235 {
   236     SDL_assert(buflen == this->spec.size);  /* we always fill a full buffer. */
   237 
   238     /* Wait for JACK to fill the iobuffer */
   239     if (SDL_SemWait(this->hidden->iosem) == -1) {
   240         return -1;
   241     }
   242 
   243     SDL_memcpy(buffer, this->hidden->iobuffer, buflen);
   244     return buflen;
   245 }
   246 
   247 static void
   248 JACK_FlushCapture(_THIS)
   249 {
   250     SDL_SemWait(this->hidden->iosem);
   251 }
   252 
   253 
   254 static void
   255 JACK_CloseDevice(_THIS)
   256 {
   257     if (this->hidden->client) {
   258         JACK_jack_deactivate(this->hidden->client);
   259 
   260         if (this->hidden->sdlports) {
   261             const int channels = this->spec.channels;
   262             int i;
   263             for (i = 0; i < channels; i++) {
   264                 JACK_jack_port_unregister(this->hidden->client, this->hidden->sdlports[i]);
   265             }
   266             SDL_free(this->hidden->sdlports);
   267         }
   268 
   269         JACK_jack_client_close(this->hidden->client);
   270     }
   271 
   272     if (this->hidden->iosem) {
   273         SDL_DestroySemaphore(this->hidden->iosem);
   274     }
   275 
   276     if (this->hidden->devports) {
   277         JACK_jack_free(this->hidden->devports);
   278     }
   279 
   280     SDL_free(this->hidden->iobuffer);
   281 }
   282 
   283 static int
   284 JACK_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
   285 {
   286     /* Note that JACK uses "output" for capture devices (they output audio
   287         data to us) and "input" for playback (we input audio data to them).
   288         Likewise, SDL's playback port will be "output" (we write data out)
   289         and capture will be "input" (we read data in). */
   290     const unsigned long sysportflags = iscapture ? JackPortIsOutput : JackPortIsInput;
   291     const unsigned long sdlportflags = iscapture ? JackPortIsInput : JackPortIsOutput;
   292     const JackProcessCallback callback = iscapture ? jackProcessCaptureCallback : jackProcessPlaybackCallback;
   293     const char *sdlportstr = iscapture ? "input" : "output";
   294     const char **devports = NULL;
   295     jack_client_t *client = NULL;
   296     jack_status_t status;
   297     int channels = 0;
   298     int i;
   299 
   300     /* Initialize all variables that we clean on shutdown */
   301     this->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof (*this->hidden));
   302     if (this->hidden == NULL) {
   303         return SDL_OutOfMemory();
   304     }
   305 
   306     /* !!! FIXME: we _still_ need an API to specify an app name */
   307     client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL);
   308     this->hidden->client = client;
   309     if (client == NULL) {
   310         return SDL_SetError("Can't open JACK client");
   311     }
   312 
   313     devports = JACK_jack_get_ports(client, NULL, NULL, JackPortIsPhysical | sysportflags);
   314     this->hidden->devports = devports;
   315     if (!devports || !devports[0]) {
   316         return SDL_SetError("No physical JACK ports available");
   317     }
   318 
   319     while (devports[++channels]) {
   320         /* spin to count devports */
   321     }
   322 
   323     /* !!! 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." */
   324 
   325     /* Jack pretty much demands what it wants. */
   326     this->spec.format = AUDIO_F32SYS;
   327     this->spec.freq = JACK_jack_get_sample_rate(client);
   328     this->spec.channels = channels;
   329     this->spec.samples = JACK_jack_get_buffer_size(client);
   330 
   331     SDL_CalculateAudioSpec(&this->spec);
   332 
   333     this->hidden->iosem = SDL_CreateSemaphore(0);
   334     if (!this->hidden->iosem) {
   335         return -1;  /* error was set by SDL_CreateSemaphore */
   336     }
   337 
   338     this->hidden->iobuffer = (float *) SDL_calloc(1, this->spec.size);
   339     if (!this->hidden->iobuffer) {
   340         return SDL_OutOfMemory();
   341     }
   342 
   343     /* Build SDL's ports, which we will connect to the device ports. */
   344     this->hidden->sdlports = (jack_port_t **) SDL_calloc(channels, sizeof (jack_port_t *));
   345     if (this->hidden->sdlports == NULL) {
   346         return SDL_OutOfMemory();
   347     }
   348 
   349     for (i = 0; i < channels; i++) {
   350         char portname[32];
   351         SDL_snprintf(portname, sizeof (portname), "sdl_jack_%s_%d", sdlportstr, i);
   352         this->hidden->sdlports[i] = JACK_jack_port_register(client, portname, JACK_DEFAULT_AUDIO_TYPE, sdlportflags, 0);
   353         if (this->hidden->sdlports[i] == NULL) {
   354             return SDL_SetError("jack_port_register failed");
   355         }
   356     }
   357 
   358     if (JACK_jack_set_process_callback(client, callback, this) != 0) {
   359         return SDL_SetError("JACK: Couldn't set process callback");
   360     }
   361 
   362     JACK_jack_on_shutdown(client, jackShutdownCallback, this);
   363 
   364     if (JACK_jack_activate(client) != 0) {
   365         return SDL_SetError("Failed to activate JACK client");
   366     }
   367 
   368     /* once activated, we can connect all the ports. */
   369     for (i = 0; i < channels; i++) {
   370         const char *sdlport = JACK_jack_port_name(this->hidden->sdlports[i]);
   371         const char *srcport = iscapture ? devports[i] : sdlport;
   372         const char *dstport = iscapture ? sdlport : devports[i];
   373         if (JACK_jack_connect(client, srcport, dstport) != 0) {
   374             return SDL_SetError("Couldn't connect JACK ports: %s => %s", srcport, dstport);
   375         }
   376     }
   377 
   378     /* don't need these anymore. */
   379     this->hidden->devports = NULL;
   380     JACK_jack_free(devports);
   381 
   382     /* We're ready to rock and roll. :-) */
   383     return 0;
   384 }
   385 
   386 static void
   387 JACK_Deinitialize(void)
   388 {
   389     UnloadJackLibrary();
   390 }
   391 
   392 static int
   393 JACK_Init(SDL_AudioDriverImpl * impl)
   394 {
   395     if (LoadJackLibrary() < 0) {
   396         return 0;
   397     } else {
   398         /* Make sure a JACK server is running and available. */
   399         jack_status_t status;
   400         jack_client_t *client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL);
   401         if (client == NULL) {
   402             UnloadJackLibrary();
   403             return 0;
   404         }
   405         JACK_jack_client_close(client);
   406     }
   407 
   408     /* Set the function pointers */
   409     impl->OpenDevice = JACK_OpenDevice;
   410     impl->WaitDevice = JACK_WaitDevice;
   411     impl->GetDeviceBuf = JACK_GetDeviceBuf;
   412     impl->CloseDevice = JACK_CloseDevice;
   413     impl->Deinitialize = JACK_Deinitialize;
   414     impl->CaptureFromDevice = JACK_CaptureFromDevice;
   415     impl->FlushCapture = JACK_FlushCapture;
   416     impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
   417     impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
   418     impl->HasCaptureSupport = SDL_TRUE;
   419 
   420     return 1;   /* this audio target is available. */
   421 }
   422 
   423 AudioBootStrap JACK_bootstrap = {
   424     "jack", "JACK Audio Connection Kit", JACK_Init, 0
   425 };
   426 
   427 #endif /* SDL_AUDIO_DRIVER_JACK */
   428 
   429 /* vi: set ts=4 sw=4 expandtab: */