Atlas - SDL_jackaudio.c
Home / ext / SDL / src / audio / jack Lines: 1 | Size: 15238 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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#ifdef SDL_AUDIO_DRIVER_JACK 24 25#include "../SDL_sysaudio.h" 26#include "SDL_jackaudio.h" 27#include "../../thread/SDL_systhread.h" 28 29static jack_client_t *(*JACK_jack_client_open)(const char *, jack_options_t, jack_status_t *, ...); 30static int (*JACK_jack_client_close)(jack_client_t *); 31static void (*JACK_jack_on_shutdown)(jack_client_t *, JackShutdownCallback, void *); 32static int (*JACK_jack_activate)(jack_client_t *); 33static int (*JACK_jack_deactivate)(jack_client_t *); 34static void *(*JACK_jack_port_get_buffer)(jack_port_t *, jack_nframes_t); 35static int (*JACK_jack_port_unregister)(jack_client_t *, jack_port_t *); 36static void (*JACK_jack_free)(void *); 37static const char **(*JACK_jack_get_ports)(jack_client_t *, const char *, const char *, unsigned long); 38static jack_nframes_t (*JACK_jack_get_sample_rate)(jack_client_t *); 39static jack_nframes_t (*JACK_jack_get_buffer_size)(jack_client_t *); 40static jack_port_t *(*JACK_jack_port_register)(jack_client_t *, const char *, const char *, unsigned long, unsigned long); 41static jack_port_t *(*JACK_jack_port_by_name)(jack_client_t *, const char *); 42static const char *(*JACK_jack_port_name)(const jack_port_t *); 43static const char *(*JACK_jack_port_type)(const jack_port_t *); 44static int (*JACK_jack_connect)(jack_client_t *, const char *, const char *); 45static int (*JACK_jack_set_process_callback)(jack_client_t *, JackProcessCallback, void *); 46static int (*JACK_jack_set_sample_rate_callback)(jack_client_t *, JackSampleRateCallback, void *); 47static int (*JACK_jack_set_buffer_size_callback)(jack_client_t *, JackBufferSizeCallback, void *); 48 49static bool load_jack_syms(void); 50 51#ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC 52 53SDL_ELF_NOTE_DLOPEN( 54 "audio-libjack", 55 "Support for audio through libjack", 56 SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, 57 SDL_AUDIO_DRIVER_JACK_DYNAMIC 58) 59 60static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC; 61static SDL_SharedObject *jack_handle = NULL; 62 63// !!! FIXME: this is copy/pasted in several places now 64static bool load_jack_sym(const char *fn, void **addr) 65{ 66 *addr = SDL_LoadFunction(jack_handle, fn); 67 if (!*addr) { 68 // Don't call SDL_SetError(): SDL_LoadFunction already did. 69 return false; 70 } 71 72 return true; 73} 74 75// cast funcs to char* first, to please GCC's strict aliasing rules. 76#define SDL_JACK_SYM(x) \ 77 if (!load_jack_sym(#x, (void **)(char *)&JACK_##x)) \ 78 return false 79 80static void UnloadJackLibrary(void) 81{ 82 if (jack_handle) { 83 SDL_UnloadObject(jack_handle); 84 jack_handle = NULL; 85 } 86} 87 88static bool LoadJackLibrary(void) 89{ 90 bool result = true; 91 if (!jack_handle) { 92 jack_handle = SDL_LoadObject(jack_library); 93 if (!jack_handle) { 94 result = false; 95 // Don't call SDL_SetError(): SDL_LoadObject already did. 96 } else { 97 result = load_jack_syms(); 98 if (!result) { 99 UnloadJackLibrary(); 100 } 101 } 102 } 103 return result; 104} 105 106#else 107 108#define SDL_JACK_SYM(x) JACK_##x = x 109 110static void UnloadJackLibrary(void) 111{ 112} 113 114static bool LoadJackLibrary(void) 115{ 116 load_jack_syms(); 117 return true; 118} 119 120#endif // SDL_AUDIO_DRIVER_JACK_DYNAMIC 121 122static bool load_jack_syms(void) 123{ 124 SDL_JACK_SYM(jack_client_open); 125 SDL_JACK_SYM(jack_client_close); 126 SDL_JACK_SYM(jack_on_shutdown); 127 SDL_JACK_SYM(jack_activate); 128 SDL_JACK_SYM(jack_deactivate); 129 SDL_JACK_SYM(jack_port_get_buffer); 130 SDL_JACK_SYM(jack_port_unregister); 131 SDL_JACK_SYM(jack_free); 132 SDL_JACK_SYM(jack_get_ports); 133 SDL_JACK_SYM(jack_get_sample_rate); 134 SDL_JACK_SYM(jack_get_buffer_size); 135 SDL_JACK_SYM(jack_port_register); 136 SDL_JACK_SYM(jack_port_by_name); 137 SDL_JACK_SYM(jack_port_name); 138 SDL_JACK_SYM(jack_port_type); 139 SDL_JACK_SYM(jack_connect); 140 SDL_JACK_SYM(jack_set_process_callback); 141 SDL_JACK_SYM(jack_set_sample_rate_callback); 142 SDL_JACK_SYM(jack_set_buffer_size_callback); 143 144 return true; 145} 146 147static void jackShutdownCallback(void *arg) // JACK went away; device is lost. 148{ 149 SDL_AudioDeviceDisconnected((SDL_AudioDevice *)arg); 150} 151 152static int jackSampleRateCallback(jack_nframes_t nframes, void *arg) 153{ 154 //SDL_Log("JACK Sample Rate Callback! %d", (int) nframes); 155 SDL_AudioDevice *device = (SDL_AudioDevice *) arg; 156 SDL_AudioSpec newspec; 157 SDL_copyp(&newspec, &device->spec); 158 newspec.freq = (int) nframes; 159 if (!SDL_AudioDeviceFormatChanged(device, &newspec, device->sample_frames)) { 160 SDL_AudioDeviceDisconnected(device); 161 } 162 return 0; 163} 164 165static int jackBufferSizeCallback(jack_nframes_t nframes, void *arg) 166{ 167 //SDL_Log("JACK Buffer Size Callback! %d", (int) nframes); 168 SDL_AudioDevice *device = (SDL_AudioDevice *) arg; 169 SDL_AudioSpec newspec; 170 SDL_copyp(&newspec, &device->spec); 171 if (!SDL_AudioDeviceFormatChanged(device, &newspec, (int) nframes)) { 172 SDL_AudioDeviceDisconnected(device); 173 } 174 return 0; 175} 176 177static int jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg) 178{ 179 SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames); 180 SDL_PlaybackAudioThreadIterate((SDL_AudioDevice *)arg); 181 return 0; 182} 183 184static bool JACK_PlayDevice(SDL_AudioDevice *device, const Uint8 *ui8buffer, int buflen) 185{ 186 const float *buffer = (float *) ui8buffer; 187 jack_port_t **ports = device->hidden->sdlports; 188 const int total_channels = device->spec.channels; 189 const int total_frames = device->sample_frames; 190 const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames; 191 192 for (int channelsi = 0; channelsi < total_channels; channelsi++) { 193 float *dst = (float *)JACK_jack_port_get_buffer(ports[channelsi], nframes); 194 if (dst) { 195 const float *src = buffer + channelsi; 196 for (int framesi = 0; framesi < total_frames; framesi++) { 197 *(dst++) = *src; 198 src += total_channels; 199 } 200 } 201 } 202 203 return true; 204} 205 206static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) 207{ 208 return (Uint8 *)device->hidden->iobuffer; 209} 210 211static int jackProcessRecordingCallback(jack_nframes_t nframes, void *arg) 212{ 213 SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames); 214 SDL_RecordingAudioThreadIterate((SDL_AudioDevice *)arg); 215 return 0; 216} 217 218static int JACK_RecordDevice(SDL_AudioDevice *device, void *vbuffer, int buflen) 219{ 220 float *buffer = (float *) vbuffer; 221 jack_port_t **ports = device->hidden->sdlports; 222 const int total_channels = device->spec.channels; 223 const int total_frames = device->sample_frames; 224 const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames; 225 226 for (int channelsi = 0; channelsi < total_channels; channelsi++) { 227 const float *src = (const float *)JACK_jack_port_get_buffer(ports[channelsi], nframes); 228 if (src) { 229 float *dst = buffer + channelsi; 230 for (int framesi = 0; framesi < total_frames; framesi++) { 231 *dst = *(src++); 232 dst += total_channels; 233 } 234 } 235 } 236 237 return buflen; 238} 239 240static void JACK_FlushRecording(SDL_AudioDevice *device) 241{ 242 // do nothing, the data will just be replaced next callback. 243} 244 245static void JACK_CloseDevice(SDL_AudioDevice *device) 246{ 247 if (device->hidden) { 248 if (device->hidden->client) { 249 JACK_jack_deactivate(device->hidden->client); 250 251 if (device->hidden->sdlports) { 252 const int channels = device->spec.channels; 253 int i; 254 for (i = 0; i < channels; i++) { 255 JACK_jack_port_unregister(device->hidden->client, device->hidden->sdlports[i]); 256 } 257 SDL_free(device->hidden->sdlports); 258 } 259 260 JACK_jack_client_close(device->hidden->client); 261 } 262 263 SDL_free(device->hidden->iobuffer); 264 SDL_free(device->hidden); 265 device->hidden = NULL; 266 267 SDL_AudioThreadFinalize(device); 268 } 269} 270 271// !!! FIXME: unify this (PulseAudio has a getAppName, Pipewire has a thing, etc) 272static const char *GetJackAppName(void) 273{ 274 return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING); 275} 276 277static bool JACK_OpenDevice(SDL_AudioDevice *device) 278{ 279 /* Note that JACK uses "output" for recording devices (they output audio 280 data to us) and "input" for playback (we input audio data to them). 281 Likewise, SDL's playback port will be "output" (we write data out) 282 and recording will be "input" (we read data in). */ 283 const bool recording = device->recording; 284 const unsigned long sysportflags = recording ? JackPortIsOutput : JackPortIsInput; 285 const unsigned long sdlportflags = recording ? JackPortIsInput : JackPortIsOutput; 286 const JackProcessCallback callback = recording ? jackProcessRecordingCallback : jackProcessPlaybackCallback; 287 const char *sdlportstr = recording ? "input" : "output"; 288 const char **devports = NULL; 289 int *audio_ports; 290 jack_client_t *client = NULL; 291 jack_status_t status; 292 int channels = 0; 293 int ports = 0; 294 int i; 295 296 // Initialize all variables that we clean on shutdown 297 device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); 298 if (!device->hidden) { 299 return false; 300 } 301 302 client = JACK_jack_client_open(GetJackAppName(), JackNoStartServer, &status, NULL); 303 device->hidden->client = client; 304 if (!client) { 305 return SDL_SetError("Can't open JACK client"); 306 } 307 308 devports = JACK_jack_get_ports(client, NULL, NULL, JackPortIsPhysical | sysportflags); 309 if (!devports || !devports[0]) { 310 return SDL_SetError("No physical JACK ports available"); 311 } 312 313 while (devports[++ports]) { 314 // spin to count devports 315 } 316 317 // Filter out non-audio ports 318 audio_ports = SDL_calloc(ports, sizeof(*audio_ports)); 319 for (i = 0; i < ports; i++) { 320 const jack_port_t *dport = JACK_jack_port_by_name(client, devports[i]); 321 const char *type = JACK_jack_port_type(dport); 322 const int len = SDL_strlen(type); 323 // See if type ends with "audio" 324 if (len >= 5 && !SDL_memcmp(type + len - 5, "audio", 5)) { 325 audio_ports[channels++] = i; 326 } 327 } 328 if (channels == 0) { 329 SDL_free(audio_ports); 330 return SDL_SetError("No physical JACK ports available"); 331 } 332 333 // Jack pretty much demands what it wants. 334 device->spec.format = SDL_AUDIO_F32; 335 device->spec.freq = JACK_jack_get_sample_rate(client); 336 device->spec.channels = channels; 337 device->sample_frames = JACK_jack_get_buffer_size(client); 338 339 SDL_UpdatedAudioDeviceFormat(device); 340 341 if (!recording) { 342 device->hidden->iobuffer = (float *)SDL_calloc(1, device->buffer_size); 343 if (!device->hidden->iobuffer) { 344 SDL_free(audio_ports); 345 return false; 346 } 347 } 348 349 // Build SDL's ports, which we will connect to the device ports. 350 device->hidden->sdlports = (jack_port_t **)SDL_calloc(channels, sizeof(jack_port_t *)); 351 if (!device->hidden->sdlports) { 352 SDL_free(audio_ports); 353 return false; 354 } 355 356 for (i = 0; i < channels; i++) { 357 char portname[32]; 358 (void)SDL_snprintf(portname, sizeof(portname), "sdl_jack_%s_%d", sdlportstr, i); 359 device->hidden->sdlports[i] = JACK_jack_port_register(client, portname, JACK_DEFAULT_AUDIO_TYPE, sdlportflags, 0); 360 if (device->hidden->sdlports[i] == NULL) { 361 SDL_free(audio_ports); 362 return SDL_SetError("jack_port_register failed"); 363 } 364 } 365 366 if (JACK_jack_set_buffer_size_callback(client, jackBufferSizeCallback, device) != 0) { 367 SDL_free(audio_ports); 368 return SDL_SetError("JACK: Couldn't set buffer size callback"); 369 } else if (JACK_jack_set_sample_rate_callback(client, jackSampleRateCallback, device) != 0) { 370 SDL_free(audio_ports); 371 return SDL_SetError("JACK: Couldn't set sample rate callback"); 372 } else if (JACK_jack_set_process_callback(client, callback, device) != 0) { 373 SDL_free(audio_ports); 374 return SDL_SetError("JACK: Couldn't set process callback"); 375 } 376 377 JACK_jack_on_shutdown(client, jackShutdownCallback, device); 378 379 if (JACK_jack_activate(client) != 0) { 380 SDL_free(audio_ports); 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(device->hidden->sdlports[i]); 387 const char *srcport = recording ? devports[audio_ports[i]] : sdlport; 388 const char *dstport = recording ? sdlport : devports[audio_ports[i]]; 389 if (JACK_jack_connect(client, srcport, dstport) != 0) { 390 SDL_free(audio_ports); 391 return SDL_SetError("Couldn't connect JACK ports: %s => %s", srcport, dstport); 392 } 393 } 394 395 // don't need these anymore. 396 JACK_jack_free(devports); 397 SDL_free(audio_ports); 398 399 // We're ready to rock and roll. :-) 400 return true; 401} 402 403static void JACK_Deinitialize(void) 404{ 405 UnloadJackLibrary(); 406} 407 408static bool JACK_Init(SDL_AudioDriverImpl *impl) 409{ 410 if (!LoadJackLibrary()) { 411 return false; 412 } else { 413 // Make sure a JACK server is running and available. 414 jack_status_t status; 415 jack_client_t *client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL); 416 if (!client) { 417 UnloadJackLibrary(); 418 return SDL_SetError("Can't open JACK client"); 419 } 420 JACK_jack_client_close(client); 421 } 422 423 impl->OpenDevice = JACK_OpenDevice; 424 impl->GetDeviceBuf = JACK_GetDeviceBuf; 425 impl->PlayDevice = JACK_PlayDevice; 426 impl->CloseDevice = JACK_CloseDevice; 427 impl->Deinitialize = JACK_Deinitialize; 428 impl->RecordDevice = JACK_RecordDevice; 429 impl->FlushRecording = JACK_FlushRecording; 430 impl->OnlyHasDefaultPlaybackDevice = true; 431 impl->OnlyHasDefaultRecordingDevice = true; 432 impl->HasRecordingSupport = true; 433 impl->ProvidesOwnCallbackThread = true; 434 435 return true; 436} 437 438AudioBootStrap JACK_bootstrap = { 439 "jack", "JACK Audio Connection Kit", JACK_Init, false, false 440}; 441 442#endif // SDL_AUDIO_DRIVER_JACK 443[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.