sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_jackaudio.c (14332B)


      1 /*
      2   Simple DirectMedia Layer
      3   Copyright (C) 1997-2020 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 #include "../../SDL_internal.h"
     22 
     23 #if SDL_AUDIO_DRIVER_JACK
     24 
     25 #include "SDL_timer.h"
     26 #include "SDL_audio.h"
     27 #include "../SDL_audio_c.h"
     28 #include "SDL_jackaudio.h"
     29 #include "SDL_loadso.h"
     30 #include "../../thread/SDL_systhread.h"
     31 
     32 
     33 static jack_client_t * (*JACK_jack_client_open) (const char *, jack_options_t, jack_status_t *, ...);
     34 static int (*JACK_jack_client_close) (jack_client_t *);
     35 static void (*JACK_jack_on_shutdown) (jack_client_t *, JackShutdownCallback, void *);
     36 static int (*JACK_jack_activate) (jack_client_t *);
     37 static int (*JACK_jack_deactivate) (jack_client_t *);
     38 static void * (*JACK_jack_port_get_buffer) (jack_port_t *, jack_nframes_t);
     39 static int (*JACK_jack_port_unregister) (jack_client_t *, jack_port_t *);
     40 static void (*JACK_jack_free) (void *);
     41 static const char ** (*JACK_jack_get_ports) (jack_client_t *, const char *, const char *, unsigned long);
     42 static jack_nframes_t (*JACK_jack_get_sample_rate) (jack_client_t *);
     43 static jack_nframes_t (*JACK_jack_get_buffer_size) (jack_client_t *);
     44 static jack_port_t * (*JACK_jack_port_register) (jack_client_t *, const char *, const char *, unsigned long, unsigned long);
     45 static jack_port_t * (*JACK_jack_port_by_name) (jack_client_t *, const char *);
     46 static const char * (*JACK_jack_port_name) (const jack_port_t *);
     47 static const char * (*JACK_jack_port_type) (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_by_name);
    139     SDL_JACK_SYM(jack_port_name);
    140     SDL_JACK_SYM(jack_port_type);
    141     SDL_JACK_SYM(jack_connect);
    142     SDL_JACK_SYM(jack_set_process_callback);
    143     return 0;
    144 }
    145 
    146 
    147 static void
    148 jackShutdownCallback(void *arg)  /* JACK went away; device is lost. */
    149 {
    150     SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
    151     SDL_OpenedAudioDeviceDisconnected(this);
    152     SDL_SemPost(this->hidden->iosem);  /* unblock the SDL thread. */
    153 }
    154 
    155 // !!! FIXME: implement and register these!
    156 //typedef int(* JackSampleRateCallback)(jack_nframes_t nframes, void *arg)
    157 //typedef int(* JackBufferSizeCallback)(jack_nframes_t nframes, void *arg)
    158 
    159 static int
    160 jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg)
    161 {
    162     SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
    163     jack_port_t **ports = this->hidden->sdlports;
    164     const int total_channels = this->spec.channels;
    165     const int total_frames = this->spec.samples;
    166     int channelsi;
    167 
    168     if (!SDL_AtomicGet(&this->enabled)) {
    169         /* silence the buffer to avoid repeats and corruption. */
    170         SDL_memset(this->hidden->iobuffer, '\0', this->spec.size);
    171     }
    172 
    173     for (channelsi = 0; channelsi < total_channels; channelsi++) {
    174         float *dst = (float *) JACK_jack_port_get_buffer(ports[channelsi], nframes);
    175         if (dst) {
    176             const float *src = ((float *) this->hidden->iobuffer) + channelsi;
    177             int framesi;
    178             for (framesi = 0; framesi < total_frames; framesi++) {
    179                 *(dst++) = *src;
    180                 src += total_channels;
    181             }
    182         }
    183     }
    184 
    185     SDL_SemPost(this->hidden->iosem);  /* tell SDL thread we're done; refill the buffer. */
    186     return 0;  /* success */
    187 }
    188 
    189 
    190 /* This function waits until it is possible to write a full sound buffer */
    191 static void
    192 JACK_WaitDevice(_THIS)
    193 {
    194     if (SDL_AtomicGet(&this->enabled)) {
    195         if (SDL_SemWait(this->hidden->iosem) == -1) {
    196             SDL_OpenedAudioDeviceDisconnected(this);
    197         }
    198     }
    199 }
    200 
    201 static Uint8 *
    202 JACK_GetDeviceBuf(_THIS)
    203 {
    204     return (Uint8 *) this->hidden->iobuffer;
    205 }
    206 
    207 
    208 static int
    209 jackProcessCaptureCallback(jack_nframes_t nframes, void *arg)
    210 {
    211     SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
    212     if (SDL_AtomicGet(&this->enabled)) {
    213         jack_port_t **ports = this->hidden->sdlports;
    214         const int total_channels = this->spec.channels;
    215         const int total_frames = this->spec.samples;
    216         int channelsi;
    217     
    218         for (channelsi = 0; channelsi < total_channels; channelsi++) {
    219             const float *src = (const float *) JACK_jack_port_get_buffer(ports[channelsi], nframes);
    220             if (src) {
    221                 float *dst = ((float *) this->hidden->iobuffer) + channelsi;
    222                 int framesi;
    223                 for (framesi = 0; framesi < total_frames; framesi++) {
    224                     *dst = *(src++);
    225                     dst += total_channels;
    226                 }
    227             }
    228         }
    229     }
    230 
    231     SDL_SemPost(this->hidden->iosem);  /* tell SDL thread we're done; new buffer is ready! */
    232     return 0;  /* success */
    233 }
    234 
    235 static int
    236 JACK_CaptureFromDevice(_THIS, void *buffer, int buflen)
    237 {
    238     SDL_assert(buflen == this->spec.size);  /* we always fill a full buffer. */
    239 
    240     /* Wait for JACK to fill the iobuffer */
    241     if (SDL_SemWait(this->hidden->iosem) == -1) {
    242         return -1;
    243     }
    244 
    245     SDL_memcpy(buffer, this->hidden->iobuffer, buflen);
    246     return buflen;
    247 }
    248 
    249 static void
    250 JACK_FlushCapture(_THIS)
    251 {
    252     SDL_SemWait(this->hidden->iosem);
    253 }
    254 
    255 
    256 static void
    257 JACK_CloseDevice(_THIS)
    258 {
    259     if (this->hidden->client) {
    260         JACK_jack_deactivate(this->hidden->client);
    261 
    262         if (this->hidden->sdlports) {
    263             const int channels = this->spec.channels;
    264             int i;
    265             for (i = 0; i < channels; i++) {
    266                 JACK_jack_port_unregister(this->hidden->client, this->hidden->sdlports[i]);
    267             }
    268             SDL_free(this->hidden->sdlports);
    269         }
    270 
    271         JACK_jack_client_close(this->hidden->client);
    272     }
    273 
    274     if (this->hidden->iosem) {
    275         SDL_DestroySemaphore(this->hidden->iosem);
    276     }
    277 
    278     SDL_free(this->hidden->iobuffer);
    279     SDL_free(this->hidden);
    280 }
    281 
    282 static int
    283 JACK_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
    284 {
    285     /* Note that JACK uses "output" for capture devices (they output audio
    286         data to us) and "input" for playback (we input audio data to them).
    287         Likewise, SDL's playback port will be "output" (we write data out)
    288         and capture will be "input" (we read data in). */
    289     const unsigned long sysportflags = iscapture ? JackPortIsOutput : JackPortIsInput;
    290     const unsigned long sdlportflags = iscapture ? JackPortIsInput : JackPortIsOutput;
    291     const JackProcessCallback callback = iscapture ? jackProcessCaptureCallback : jackProcessPlaybackCallback;
    292     const char *sdlportstr = iscapture ? "input" : "output";
    293     const char **devports = NULL;
    294     int *audio_ports;
    295     jack_client_t *client = NULL;
    296     jack_status_t status;
    297     int channels = 0;
    298     int ports = 0;
    299     int i;
    300 
    301     /* Initialize all variables that we clean on shutdown */
    302     this->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof (*this->hidden));
    303     if (this->hidden == NULL) {
    304         return SDL_OutOfMemory();
    305     }
    306 
    307     /* !!! FIXME: we _still_ need an API to specify an app name */
    308     client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL);
    309     this->hidden->client = client;
    310     if (client == NULL) {
    311         return SDL_SetError("Can't open JACK client");
    312     }
    313 
    314     devports = JACK_jack_get_ports(client, NULL, NULL, JackPortIsPhysical | sysportflags);
    315     if (!devports || !devports[0]) {
    316         return SDL_SetError("No physical JACK ports available");
    317     }
    318 
    319     while (devports[++ports]) {
    320         /* spin to count devports */
    321     }
    322 
    323     /* Filter out non-audio ports */
    324     audio_ports = SDL_calloc(ports, sizeof *audio_ports);
    325     for (i = 0; i < ports; i++) {
    326         const jack_port_t *dport = JACK_jack_port_by_name(client, devports[i]);
    327         const char *type = JACK_jack_port_type(dport);
    328         const int len = SDL_strlen(type);
    329         /* See if type ends with "audio" */
    330         if (len >= 5 && !SDL_memcmp(type+len-5, "audio", 5)) {
    331             audio_ports[channels++] = i;
    332         }
    333     }
    334     if (channels == 0) {
    335         return SDL_SetError("No physical JACK ports available");
    336     }
    337 
    338 
    339     /* !!! 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." */
    340 
    341     /* Jack pretty much demands what it wants. */
    342     this->spec.format = AUDIO_F32SYS;
    343     this->spec.freq = JACK_jack_get_sample_rate(client);
    344     this->spec.channels = channels;
    345     this->spec.samples = JACK_jack_get_buffer_size(client);
    346 
    347     SDL_CalculateAudioSpec(&this->spec);
    348 
    349     this->hidden->iosem = SDL_CreateSemaphore(0);
    350     if (!this->hidden->iosem) {
    351         return -1;  /* error was set by SDL_CreateSemaphore */
    352     }
    353 
    354     this->hidden->iobuffer = (float *) SDL_calloc(1, this->spec.size);
    355     if (!this->hidden->iobuffer) {
    356         return SDL_OutOfMemory();
    357     }
    358 
    359     /* Build SDL's ports, which we will connect to the device ports. */
    360     this->hidden->sdlports = (jack_port_t **) SDL_calloc(channels, sizeof (jack_port_t *));
    361     if (this->hidden->sdlports == NULL) {
    362         return SDL_OutOfMemory();
    363     }
    364 
    365     for (i = 0; i < channels; i++) {
    366         char portname[32];
    367         SDL_snprintf(portname, sizeof (portname), "sdl_jack_%s_%d", sdlportstr, i);
    368         this->hidden->sdlports[i] = JACK_jack_port_register(client, portname, JACK_DEFAULT_AUDIO_TYPE, sdlportflags, 0);
    369         if (this->hidden->sdlports[i] == NULL) {
    370             return SDL_SetError("jack_port_register failed");
    371         }
    372     }
    373 
    374     if (JACK_jack_set_process_callback(client, callback, this) != 0) {
    375         return SDL_SetError("JACK: Couldn't set process callback");
    376     }
    377 
    378     JACK_jack_on_shutdown(client, jackShutdownCallback, this);
    379 
    380     if (JACK_jack_activate(client) != 0) {
    381         return SDL_SetError("Failed to activate JACK client");
    382     }
    383 
    384     /* once activated, we can connect all the ports. */
    385     for (i = 0; i < channels; i++) {
    386         const char *sdlport = JACK_jack_port_name(this->hidden->sdlports[i]);
    387         const char *srcport = iscapture ? devports[audio_ports[i]] : sdlport;
    388         const char *dstport = iscapture ? sdlport : devports[audio_ports[i]];
    389         if (JACK_jack_connect(client, srcport, dstport) != 0) {
    390             return SDL_SetError("Couldn't connect JACK ports: %s => %s", srcport, dstport);
    391         }
    392     }
    393 
    394     /* don't need these anymore. */
    395     JACK_jack_free(devports);
    396     SDL_free(audio_ports);
    397 
    398     /* We're ready to rock and roll. :-) */
    399     return 0;
    400 }
    401 
    402 static void
    403 JACK_Deinitialize(void)
    404 {
    405     UnloadJackLibrary();
    406 }
    407 
    408 static int
    409 JACK_Init(SDL_AudioDriverImpl * impl)
    410 {
    411     if (LoadJackLibrary() < 0) {
    412         return 0;
    413     } else {
    414         /* Make sure a JACK server is running and available. */
    415         jack_status_t status;
    416         jack_client_t *client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL);
    417         if (client == NULL) {
    418             UnloadJackLibrary();
    419             return 0;
    420         }
    421         JACK_jack_client_close(client);
    422     }
    423 
    424     /* Set the function pointers */
    425     impl->OpenDevice = JACK_OpenDevice;
    426     impl->WaitDevice = JACK_WaitDevice;
    427     impl->GetDeviceBuf = JACK_GetDeviceBuf;
    428     impl->CloseDevice = JACK_CloseDevice;
    429     impl->Deinitialize = JACK_Deinitialize;
    430     impl->CaptureFromDevice = JACK_CaptureFromDevice;
    431     impl->FlushCapture = JACK_FlushCapture;
    432     impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
    433     impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
    434     impl->HasCaptureSupport = SDL_TRUE;
    435 
    436     return 1;   /* this audio target is available. */
    437 }
    438 
    439 AudioBootStrap JACK_bootstrap = {
    440     "jack", "JACK Audio Connection Kit", JACK_Init, 0
    441 };
    442 
    443 #endif /* SDL_AUDIO_DRIVER_JACK */
    444 
    445 /* vi: set ts=4 sw=4 expandtab: */