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: */