]>
Commit | Line | Data |
---|---|---|
2e445703 GM |
1 | /* |
2 | * QEMU JACK Audio Connection Kit Client | |
3 | * | |
4 | * Copyright (c) 2020 Geoffrey McRae (gnif) | |
5 | * | |
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
7 | * of this software and associated documentation files (the "Software"), to deal | |
8 | * in the Software without restriction, including without limitation the rights | |
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
10 | * copies of the Software, and to permit persons to whom the Software is | |
11 | * furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in | |
14 | * all copies or substantial portions of the Software. | |
15 | * | |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
22 | * THE SOFTWARE. | |
23 | */ | |
24 | ||
25 | #include "qemu/osdep.h" | |
26 | #include "qemu/module.h" | |
27 | #include "qemu/atomic.h" | |
a6e03739 | 28 | #include "qemu/main-loop.h" |
2e445703 GM |
29 | #include "audio.h" |
30 | ||
31 | #define AUDIO_CAP "jack" | |
32 | #include "audio_int.h" | |
33 | ||
34 | #include <jack/jack.h> | |
35 | #include <jack/thread.h> | |
36 | ||
37 | struct QJack; | |
38 | ||
39 | typedef enum QJackState { | |
40 | QJACK_STATE_DISCONNECTED, | |
2e445703 GM |
41 | QJACK_STATE_RUNNING, |
42 | QJACK_STATE_SHUTDOWN | |
43 | } | |
44 | QJackState; | |
45 | ||
46 | typedef struct QJackBuffer { | |
47 | int channels; | |
48 | int frames; | |
49 | uint32_t used; | |
50 | int rptr, wptr; | |
51 | float **data; | |
52 | } | |
53 | QJackBuffer; | |
54 | ||
55 | typedef struct QJackClient { | |
56 | AudiodevJackPerDirectionOptions *opt; | |
57 | ||
58 | bool out; | |
81e0efb2 | 59 | bool enabled; |
2e445703 GM |
60 | bool connect_ports; |
61 | int packets; | |
62 | ||
63 | QJackState state; | |
64 | jack_client_t *client; | |
65 | jack_nframes_t freq; | |
a6e03739 | 66 | QEMUBH *shutdown_bh; |
2e445703 GM |
67 | |
68 | struct QJack *j; | |
69 | int nchannels; | |
70 | int buffersize; | |
71 | jack_port_t **port; | |
72 | QJackBuffer fifo; | |
73 | } | |
74 | QJackClient; | |
75 | ||
76 | typedef struct QJackOut { | |
77 | HWVoiceOut hw; | |
78 | QJackClient c; | |
79 | } | |
80 | QJackOut; | |
81 | ||
82 | typedef struct QJackIn { | |
83 | HWVoiceIn hw; | |
84 | QJackClient c; | |
85 | } | |
86 | QJackIn; | |
87 | ||
88 | static int qjack_client_init(QJackClient *c); | |
89 | static void qjack_client_connect_ports(QJackClient *c); | |
90 | static void qjack_client_fini(QJackClient *c); | |
a6e03739 | 91 | static QemuMutex qjack_shutdown_lock; |
2e445703 GM |
92 | |
93 | static void qjack_buffer_create(QJackBuffer *buffer, int channels, int frames) | |
94 | { | |
95 | buffer->channels = channels; | |
96 | buffer->frames = frames; | |
97 | buffer->used = 0; | |
98 | buffer->rptr = 0; | |
99 | buffer->wptr = 0; | |
b21e2380 | 100 | buffer->data = g_new(float *, channels); |
2e445703 | 101 | for (int i = 0; i < channels; ++i) { |
b21e2380 | 102 | buffer->data[i] = g_new(float, frames); |
2e445703 GM |
103 | } |
104 | } | |
105 | ||
106 | static void qjack_buffer_clear(QJackBuffer *buffer) | |
107 | { | |
108 | assert(buffer->data); | |
d73415a3 | 109 | qatomic_store_release(&buffer->used, 0); |
2e445703 GM |
110 | buffer->rptr = 0; |
111 | buffer->wptr = 0; | |
112 | } | |
113 | ||
114 | static void qjack_buffer_free(QJackBuffer *buffer) | |
115 | { | |
116 | if (!buffer->data) { | |
117 | return; | |
118 | } | |
119 | ||
120 | for (int i = 0; i < buffer->channels; ++i) { | |
121 | g_free(buffer->data[i]); | |
122 | } | |
123 | ||
124 | g_free(buffer->data); | |
125 | buffer->data = NULL; | |
126 | } | |
127 | ||
128 | /* write PCM interleaved */ | |
129 | static int qjack_buffer_write(QJackBuffer *buffer, float *data, int size) | |
130 | { | |
131 | assert(buffer->data); | |
132 | const int samples = size / sizeof(float); | |
133 | int frames = samples / buffer->channels; | |
d73415a3 | 134 | const int avail = buffer->frames - qatomic_load_acquire(&buffer->used); |
2e445703 GM |
135 | |
136 | if (frames > avail) { | |
137 | frames = avail; | |
138 | } | |
139 | ||
140 | int copy = frames; | |
141 | int wptr = buffer->wptr; | |
142 | ||
143 | while (copy) { | |
144 | ||
145 | for (int c = 0; c < buffer->channels; ++c) { | |
146 | buffer->data[c][wptr] = *data++; | |
147 | } | |
148 | ||
149 | if (++wptr == buffer->frames) { | |
150 | wptr = 0; | |
151 | } | |
152 | ||
153 | --copy; | |
154 | } | |
155 | ||
156 | buffer->wptr = wptr; | |
157 | ||
d73415a3 | 158 | qatomic_add(&buffer->used, frames); |
2e445703 GM |
159 | return frames * buffer->channels * sizeof(float); |
160 | }; | |
161 | ||
162 | /* write PCM linear */ | |
163 | static int qjack_buffer_write_l(QJackBuffer *buffer, float **dest, int frames) | |
164 | { | |
165 | assert(buffer->data); | |
d73415a3 | 166 | const int avail = buffer->frames - qatomic_load_acquire(&buffer->used); |
2e445703 GM |
167 | int wptr = buffer->wptr; |
168 | ||
169 | if (frames > avail) { | |
170 | frames = avail; | |
171 | } | |
172 | ||
173 | int right = buffer->frames - wptr; | |
174 | if (right > frames) { | |
175 | right = frames; | |
176 | } | |
177 | ||
178 | const int left = frames - right; | |
179 | for (int c = 0; c < buffer->channels; ++c) { | |
180 | memcpy(buffer->data[c] + wptr, dest[c] , right * sizeof(float)); | |
181 | memcpy(buffer->data[c] , dest[c] + right, left * sizeof(float)); | |
182 | } | |
183 | ||
184 | wptr += frames; | |
185 | if (wptr >= buffer->frames) { | |
186 | wptr -= buffer->frames; | |
187 | } | |
188 | buffer->wptr = wptr; | |
189 | ||
d73415a3 | 190 | qatomic_add(&buffer->used, frames); |
2e445703 GM |
191 | return frames; |
192 | } | |
193 | ||
194 | /* read PCM interleaved */ | |
195 | static int qjack_buffer_read(QJackBuffer *buffer, float *dest, int size) | |
196 | { | |
197 | assert(buffer->data); | |
198 | const int samples = size / sizeof(float); | |
199 | int frames = samples / buffer->channels; | |
d73415a3 | 200 | const int avail = qatomic_load_acquire(&buffer->used); |
2e445703 GM |
201 | |
202 | if (frames > avail) { | |
203 | frames = avail; | |
204 | } | |
205 | ||
206 | int copy = frames; | |
207 | int rptr = buffer->rptr; | |
208 | ||
209 | while (copy) { | |
210 | ||
211 | for (int c = 0; c < buffer->channels; ++c) { | |
212 | *dest++ = buffer->data[c][rptr]; | |
213 | } | |
214 | ||
215 | if (++rptr == buffer->frames) { | |
216 | rptr = 0; | |
217 | } | |
218 | ||
219 | --copy; | |
220 | } | |
221 | ||
222 | buffer->rptr = rptr; | |
223 | ||
d73415a3 | 224 | qatomic_sub(&buffer->used, frames); |
2e445703 GM |
225 | return frames * buffer->channels * sizeof(float); |
226 | } | |
227 | ||
228 | /* read PCM linear */ | |
229 | static int qjack_buffer_read_l(QJackBuffer *buffer, float **dest, int frames) | |
230 | { | |
231 | assert(buffer->data); | |
232 | int copy = frames; | |
d73415a3 | 233 | const int used = qatomic_load_acquire(&buffer->used); |
2e445703 GM |
234 | int rptr = buffer->rptr; |
235 | ||
236 | if (copy > used) { | |
237 | copy = used; | |
238 | } | |
239 | ||
240 | int right = buffer->frames - rptr; | |
241 | if (right > copy) { | |
242 | right = copy; | |
243 | } | |
244 | ||
245 | const int left = copy - right; | |
246 | for (int c = 0; c < buffer->channels; ++c) { | |
247 | memcpy(dest[c] , buffer->data[c] + rptr, right * sizeof(float)); | |
248 | memcpy(dest[c] + right, buffer->data[c] , left * sizeof(float)); | |
249 | } | |
250 | ||
251 | rptr += copy; | |
252 | if (rptr >= buffer->frames) { | |
253 | rptr -= buffer->frames; | |
254 | } | |
255 | buffer->rptr = rptr; | |
256 | ||
d73415a3 | 257 | qatomic_sub(&buffer->used, copy); |
2e445703 GM |
258 | return copy; |
259 | } | |
260 | ||
261 | static int qjack_process(jack_nframes_t nframes, void *arg) | |
262 | { | |
263 | QJackClient *c = (QJackClient *)arg; | |
264 | ||
265 | if (c->state != QJACK_STATE_RUNNING) { | |
266 | return 0; | |
267 | } | |
268 | ||
269 | /* get the buffers for the ports */ | |
270 | float *buffers[c->nchannels]; | |
271 | for (int i = 0; i < c->nchannels; ++i) { | |
272 | buffers[i] = jack_port_get_buffer(c->port[i], nframes); | |
273 | } | |
274 | ||
275 | if (c->out) { | |
81e0efb2 GM |
276 | if (likely(c->enabled)) { |
277 | qjack_buffer_read_l(&c->fifo, buffers, nframes); | |
278 | } else { | |
3c8de96c | 279 | for (int i = 0; i < c->nchannels; ++i) { |
81e0efb2 GM |
280 | memset(buffers[i], 0, nframes * sizeof(float)); |
281 | } | |
282 | } | |
2e445703 | 283 | } else { |
81e0efb2 GM |
284 | if (likely(c->enabled)) { |
285 | qjack_buffer_write_l(&c->fifo, buffers, nframes); | |
286 | } | |
2e445703 GM |
287 | } |
288 | ||
289 | return 0; | |
290 | } | |
291 | ||
292 | static void qjack_port_registration(jack_port_id_t port, int reg, void *arg) | |
293 | { | |
294 | if (reg) { | |
295 | QJackClient *c = (QJackClient *)arg; | |
296 | c->connect_ports = true; | |
297 | } | |
298 | } | |
299 | ||
300 | static int qjack_xrun(void *arg) | |
301 | { | |
302 | QJackClient *c = (QJackClient *)arg; | |
303 | if (c->state != QJACK_STATE_RUNNING) { | |
304 | return 0; | |
305 | } | |
306 | ||
307 | qjack_buffer_clear(&c->fifo); | |
308 | return 0; | |
309 | } | |
310 | ||
a6e03739 GM |
311 | static void qjack_shutdown_bh(void *opaque) |
312 | { | |
313 | QJackClient *c = (QJackClient *)opaque; | |
314 | qjack_client_fini(c); | |
315 | } | |
316 | ||
2e445703 GM |
317 | static void qjack_shutdown(void *arg) |
318 | { | |
319 | QJackClient *c = (QJackClient *)arg; | |
320 | c->state = QJACK_STATE_SHUTDOWN; | |
a6e03739 | 321 | qemu_bh_schedule(c->shutdown_bh); |
2e445703 GM |
322 | } |
323 | ||
324 | static void qjack_client_recover(QJackClient *c) | |
325 | { | |
a6e03739 GM |
326 | if (c->state != QJACK_STATE_DISCONNECTED) { |
327 | return; | |
2e445703 GM |
328 | } |
329 | ||
330 | /* packets is used simply to throttle this */ | |
a6e03739 | 331 | if (c->packets % 100 == 0) { |
2e445703 | 332 | |
81e0efb2 GM |
333 | /* if enabled then attempt to recover */ |
334 | if (c->enabled) { | |
2e445703 GM |
335 | dolog("attempting to reconnect to server\n"); |
336 | qjack_client_init(c); | |
337 | } | |
338 | } | |
339 | } | |
340 | ||
341 | static size_t qjack_write(HWVoiceOut *hw, void *buf, size_t len) | |
342 | { | |
343 | QJackOut *jo = (QJackOut *)hw; | |
344 | ++jo->c.packets; | |
345 | ||
346 | if (jo->c.state != QJACK_STATE_RUNNING) { | |
347 | qjack_client_recover(&jo->c); | |
348 | return len; | |
349 | } | |
350 | ||
351 | qjack_client_connect_ports(&jo->c); | |
352 | return qjack_buffer_write(&jo->c.fifo, buf, len); | |
353 | } | |
354 | ||
355 | static size_t qjack_read(HWVoiceIn *hw, void *buf, size_t len) | |
356 | { | |
357 | QJackIn *ji = (QJackIn *)hw; | |
358 | ++ji->c.packets; | |
359 | ||
360 | if (ji->c.state != QJACK_STATE_RUNNING) { | |
361 | qjack_client_recover(&ji->c); | |
362 | return len; | |
363 | } | |
364 | ||
365 | qjack_client_connect_ports(&ji->c); | |
366 | return qjack_buffer_read(&ji->c.fifo, buf, len); | |
367 | } | |
368 | ||
369 | static void qjack_client_connect_ports(QJackClient *c) | |
370 | { | |
371 | if (!c->connect_ports || !c->opt->connect_ports) { | |
372 | return; | |
373 | } | |
374 | ||
375 | c->connect_ports = false; | |
376 | const char **ports; | |
377 | ports = jack_get_ports(c->client, c->opt->connect_ports, NULL, | |
378 | c->out ? JackPortIsInput : JackPortIsOutput); | |
379 | ||
380 | if (!ports) { | |
381 | return; | |
382 | } | |
383 | ||
384 | for (int i = 0; i < c->nchannels && ports[i]; ++i) { | |
385 | const char *p = jack_port_name(c->port[i]); | |
386 | if (jack_port_connected_to(c->port[i], ports[i])) { | |
387 | continue; | |
388 | } | |
389 | ||
390 | if (c->out) { | |
391 | dolog("connect %s -> %s\n", p, ports[i]); | |
392 | jack_connect(c->client, p, ports[i]); | |
393 | } else { | |
394 | dolog("connect %s -> %s\n", ports[i], p); | |
395 | jack_connect(c->client, ports[i], p); | |
396 | } | |
397 | } | |
398 | } | |
399 | ||
400 | static int qjack_client_init(QJackClient *c) | |
401 | { | |
402 | jack_status_t status; | |
403 | char client_name[jack_client_name_size()]; | |
404 | jack_options_t options = JackNullOption; | |
405 | ||
bc81e6e5 GM |
406 | if (c->state == QJACK_STATE_RUNNING) { |
407 | return 0; | |
408 | } | |
409 | ||
2e445703 GM |
410 | c->connect_ports = true; |
411 | ||
412 | snprintf(client_name, sizeof(client_name), "%s-%s", | |
413 | c->out ? "out" : "in", | |
2833d697 | 414 | c->opt->client_name ? c->opt->client_name : audio_application_name()); |
2e445703 GM |
415 | |
416 | if (c->opt->exact_name) { | |
417 | options |= JackUseExactName; | |
418 | } | |
419 | ||
420 | if (!c->opt->start_server) { | |
421 | options |= JackNoStartServer; | |
422 | } | |
423 | ||
424 | if (c->opt->server_name) { | |
425 | options |= JackServerName; | |
426 | } | |
427 | ||
428 | c->client = jack_client_open(client_name, options, &status, | |
429 | c->opt->server_name); | |
430 | ||
431 | if (c->client == NULL) { | |
432 | dolog("jack_client_open failed: status = 0x%2.0x\n", status); | |
433 | if (status & JackServerFailed) { | |
434 | dolog("unable to connect to JACK server\n"); | |
435 | } | |
436 | return -1; | |
437 | } | |
438 | ||
439 | c->freq = jack_get_sample_rate(c->client); | |
440 | ||
441 | if (status & JackServerStarted) { | |
442 | dolog("JACK server started\n"); | |
443 | } | |
444 | ||
445 | if (status & JackNameNotUnique) { | |
446 | dolog("JACK unique name assigned %s\n", | |
447 | jack_get_client_name(c->client)); | |
448 | } | |
449 | ||
450 | jack_set_process_callback(c->client, qjack_process , c); | |
451 | jack_set_port_registration_callback(c->client, qjack_port_registration, c); | |
452 | jack_set_xrun_callback(c->client, qjack_xrun, c); | |
453 | jack_on_shutdown(c->client, qjack_shutdown, c); | |
454 | ||
2e445703 | 455 | /* allocate and register the ports */ |
b21e2380 | 456 | c->port = g_new(jack_port_t *, c->nchannels); |
2e445703 GM |
457 | for (int i = 0; i < c->nchannels; ++i) { |
458 | ||
459 | char port_name[16]; | |
460 | snprintf( | |
461 | port_name, | |
462 | sizeof(port_name), | |
463 | c->out ? "output %d" : "input %d", | |
464 | i); | |
465 | ||
466 | c->port[i] = jack_port_register( | |
467 | c->client, | |
468 | port_name, | |
469 | JACK_DEFAULT_AUDIO_TYPE, | |
470 | c->out ? JackPortIsOutput : JackPortIsInput, | |
471 | 0); | |
472 | } | |
473 | ||
474 | /* activate the session */ | |
475 | jack_activate(c->client); | |
476 | c->buffersize = jack_get_buffer_size(c->client); | |
477 | ||
36963ed1 GM |
478 | /* |
479 | * ensure the buffersize is no smaller then 512 samples, some (all?) qemu | |
480 | * virtual devices do not work correctly otherwise | |
481 | */ | |
482 | if (c->buffersize < 512) { | |
483 | c->buffersize = 512; | |
484 | } | |
485 | ||
369829a4 VR |
486 | /* create a 3 period buffer */ |
487 | qjack_buffer_create(&c->fifo, c->nchannels, c->buffersize * 3); | |
36963ed1 | 488 | |
2e445703 GM |
489 | qjack_client_connect_ports(c); |
490 | c->state = QJACK_STATE_RUNNING; | |
491 | return 0; | |
492 | } | |
493 | ||
494 | static int qjack_init_out(HWVoiceOut *hw, struct audsettings *as, | |
495 | void *drv_opaque) | |
496 | { | |
497 | QJackOut *jo = (QJackOut *)hw; | |
498 | Audiodev *dev = (Audiodev *)drv_opaque; | |
499 | ||
2e445703 | 500 | jo->c.out = true; |
81e0efb2 | 501 | jo->c.enabled = false; |
2e445703 GM |
502 | jo->c.nchannels = as->nchannels; |
503 | jo->c.opt = dev->u.jack.out; | |
81e0efb2 | 504 | |
a6e03739 GM |
505 | jo->c.shutdown_bh = qemu_bh_new(qjack_shutdown_bh, &jo->c); |
506 | ||
2e445703 GM |
507 | int ret = qjack_client_init(&jo->c); |
508 | if (ret != 0) { | |
a6e03739 | 509 | qemu_bh_delete(jo->c.shutdown_bh); |
2e445703 GM |
510 | return ret; |
511 | } | |
512 | ||
513 | /* report the buffer size to qemu */ | |
514 | hw->samples = jo->c.buffersize; | |
515 | ||
516 | /* report the audio format we support */ | |
517 | struct audsettings os = { | |
518 | .freq = jo->c.freq, | |
519 | .nchannels = jo->c.nchannels, | |
520 | .fmt = AUDIO_FORMAT_F32, | |
521 | .endianness = 0 | |
522 | }; | |
523 | audio_pcm_init_info(&hw->info, &os); | |
524 | ||
525 | dolog("JACK output configured for %dHz (%d samples)\n", | |
526 | jo->c.freq, jo->c.buffersize); | |
527 | ||
528 | return 0; | |
529 | } | |
530 | ||
531 | static int qjack_init_in(HWVoiceIn *hw, struct audsettings *as, | |
532 | void *drv_opaque) | |
533 | { | |
534 | QJackIn *ji = (QJackIn *)hw; | |
535 | Audiodev *dev = (Audiodev *)drv_opaque; | |
536 | ||
2e445703 | 537 | ji->c.out = false; |
81e0efb2 | 538 | ji->c.enabled = false; |
2e445703 GM |
539 | ji->c.nchannels = as->nchannels; |
540 | ji->c.opt = dev->u.jack.in; | |
81e0efb2 | 541 | |
a6e03739 GM |
542 | ji->c.shutdown_bh = qemu_bh_new(qjack_shutdown_bh, &ji->c); |
543 | ||
2e445703 GM |
544 | int ret = qjack_client_init(&ji->c); |
545 | if (ret != 0) { | |
a6e03739 | 546 | qemu_bh_delete(ji->c.shutdown_bh); |
2e445703 GM |
547 | return ret; |
548 | } | |
549 | ||
550 | /* report the buffer size to qemu */ | |
551 | hw->samples = ji->c.buffersize; | |
552 | ||
553 | /* report the audio format we support */ | |
554 | struct audsettings is = { | |
555 | .freq = ji->c.freq, | |
556 | .nchannels = ji->c.nchannels, | |
557 | .fmt = AUDIO_FORMAT_F32, | |
558 | .endianness = 0 | |
559 | }; | |
560 | audio_pcm_init_info(&hw->info, &is); | |
561 | ||
562 | dolog("JACK input configured for %dHz (%d samples)\n", | |
563 | ji->c.freq, ji->c.buffersize); | |
564 | ||
565 | return 0; | |
566 | } | |
567 | ||
a6e03739 | 568 | static void qjack_client_fini_locked(QJackClient *c) |
2e445703 GM |
569 | { |
570 | switch (c->state) { | |
571 | case QJACK_STATE_RUNNING: | |
2e445703 GM |
572 | jack_deactivate(c->client); |
573 | /* fallthrough */ | |
574 | ||
575 | case QJACK_STATE_SHUTDOWN: | |
576 | jack_client_close(c->client); | |
a6e03739 GM |
577 | c->client = NULL; |
578 | ||
579 | qjack_buffer_free(&c->fifo); | |
580 | g_free(c->port); | |
581 | ||
582 | c->state = QJACK_STATE_DISCONNECTED; | |
2e445703 GM |
583 | /* fallthrough */ |
584 | ||
585 | case QJACK_STATE_DISCONNECTED: | |
586 | break; | |
587 | } | |
a6e03739 | 588 | } |
2e445703 | 589 | |
a6e03739 GM |
590 | static void qjack_client_fini(QJackClient *c) |
591 | { | |
592 | qemu_mutex_lock(&qjack_shutdown_lock); | |
593 | qjack_client_fini_locked(c); | |
594 | qemu_mutex_unlock(&qjack_shutdown_lock); | |
2e445703 GM |
595 | } |
596 | ||
597 | static void qjack_fini_out(HWVoiceOut *hw) | |
598 | { | |
599 | QJackOut *jo = (QJackOut *)hw; | |
2e445703 | 600 | qjack_client_fini(&jo->c); |
a6e03739 GM |
601 | |
602 | qemu_bh_delete(jo->c.shutdown_bh); | |
2e445703 GM |
603 | } |
604 | ||
605 | static void qjack_fini_in(HWVoiceIn *hw) | |
606 | { | |
607 | QJackIn *ji = (QJackIn *)hw; | |
2e445703 | 608 | qjack_client_fini(&ji->c); |
a6e03739 GM |
609 | |
610 | qemu_bh_delete(ji->c.shutdown_bh); | |
2e445703 GM |
611 | } |
612 | ||
613 | static void qjack_enable_out(HWVoiceOut *hw, bool enable) | |
614 | { | |
81e0efb2 GM |
615 | QJackOut *jo = (QJackOut *)hw; |
616 | jo->c.enabled = enable; | |
2e445703 GM |
617 | } |
618 | ||
619 | static void qjack_enable_in(HWVoiceIn *hw, bool enable) | |
620 | { | |
81e0efb2 GM |
621 | QJackIn *ji = (QJackIn *)hw; |
622 | ji->c.enabled = enable; | |
2e445703 GM |
623 | } |
624 | ||
ead789eb | 625 | #if !defined(WIN32) && defined(CONFIG_PTHREAD_SETNAME_NP_W_TID) |
2e445703 GM |
626 | static int qjack_thread_creator(jack_native_thread_t *thread, |
627 | const pthread_attr_t *attr, void *(*function)(void *), void *arg) | |
628 | { | |
629 | int ret = pthread_create(thread, attr, function, arg); | |
630 | if (ret != 0) { | |
631 | return ret; | |
632 | } | |
633 | ||
634 | /* set the name of the thread */ | |
635 | pthread_setname_np(*thread, "jack-client"); | |
636 | ||
637 | return ret; | |
638 | } | |
ead789eb | 639 | #endif |
2e445703 GM |
640 | |
641 | static void *qjack_init(Audiodev *dev) | |
642 | { | |
643 | assert(dev->driver == AUDIODEV_DRIVER_JACK); | |
2e445703 GM |
644 | return dev; |
645 | } | |
646 | ||
647 | static void qjack_fini(void *opaque) | |
648 | { | |
649 | } | |
650 | ||
651 | static struct audio_pcm_ops jack_pcm_ops = { | |
652 | .init_out = qjack_init_out, | |
653 | .fini_out = qjack_fini_out, | |
654 | .write = qjack_write, | |
9833438e | 655 | .buffer_get_free = audio_generic_buffer_get_free, |
2e445703 GM |
656 | .run_buffer_out = audio_generic_run_buffer_out, |
657 | .enable_out = qjack_enable_out, | |
658 | ||
659 | .init_in = qjack_init_in, | |
660 | .fini_in = qjack_fini_in, | |
661 | .read = qjack_read, | |
a2893c83 | 662 | .run_buffer_in = audio_generic_run_buffer_in, |
2e445703 GM |
663 | .enable_in = qjack_enable_in |
664 | }; | |
665 | ||
666 | static struct audio_driver jack_driver = { | |
667 | .name = "jack", | |
668 | .descr = "JACK Audio Connection Kit Client", | |
669 | .init = qjack_init, | |
670 | .fini = qjack_fini, | |
671 | .pcm_ops = &jack_pcm_ops, | |
672 | .can_be_default = 1, | |
673 | .max_voices_out = INT_MAX, | |
674 | .max_voices_in = INT_MAX, | |
675 | .voice_size_out = sizeof(QJackOut), | |
676 | .voice_size_in = sizeof(QJackIn) | |
677 | }; | |
678 | ||
679 | static void qjack_error(const char *msg) | |
680 | { | |
681 | dolog("E: %s\n", msg); | |
682 | } | |
683 | ||
684 | static void qjack_info(const char *msg) | |
685 | { | |
686 | dolog("I: %s\n", msg); | |
687 | } | |
688 | ||
689 | static void register_audio_jack(void) | |
690 | { | |
a6e03739 | 691 | qemu_mutex_init(&qjack_shutdown_lock); |
2e445703 | 692 | audio_driver_register(&jack_driver); |
ead789eb | 693 | #if !defined(WIN32) && defined(CONFIG_PTHREAD_SETNAME_NP_W_TID) |
2e445703 | 694 | jack_set_thread_creator(qjack_thread_creator); |
ead789eb | 695 | #endif |
2e445703 GM |
696 | jack_set_error_function(qjack_error); |
697 | jack_set_info_function(qjack_info); | |
698 | } | |
699 | type_init(register_audio_jack); |