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