]>
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" | |
28 | #include "qemu-common.h" | |
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; | |
66 | ||
67 | struct QJack *j; | |
68 | int nchannels; | |
69 | int buffersize; | |
70 | jack_port_t **port; | |
71 | QJackBuffer fifo; | |
72 | } | |
73 | QJackClient; | |
74 | ||
75 | typedef struct QJackOut { | |
76 | HWVoiceOut hw; | |
77 | QJackClient c; | |
78 | } | |
79 | QJackOut; | |
80 | ||
81 | typedef struct QJackIn { | |
82 | HWVoiceIn hw; | |
83 | QJackClient c; | |
84 | } | |
85 | QJackIn; | |
86 | ||
87 | static int qjack_client_init(QJackClient *c); | |
88 | static void qjack_client_connect_ports(QJackClient *c); | |
89 | static void qjack_client_fini(QJackClient *c); | |
90 | ||
91 | static void qjack_buffer_create(QJackBuffer *buffer, int channels, int frames) | |
92 | { | |
93 | buffer->channels = channels; | |
94 | buffer->frames = frames; | |
95 | buffer->used = 0; | |
96 | buffer->rptr = 0; | |
97 | buffer->wptr = 0; | |
98 | buffer->data = g_malloc(channels * sizeof(float *)); | |
99 | for (int i = 0; i < channels; ++i) { | |
100 | buffer->data[i] = g_malloc(frames * sizeof(float)); | |
101 | } | |
102 | } | |
103 | ||
104 | static void qjack_buffer_clear(QJackBuffer *buffer) | |
105 | { | |
106 | assert(buffer->data); | |
d73415a3 | 107 | qatomic_store_release(&buffer->used, 0); |
2e445703 GM |
108 | buffer->rptr = 0; |
109 | buffer->wptr = 0; | |
110 | } | |
111 | ||
112 | static void qjack_buffer_free(QJackBuffer *buffer) | |
113 | { | |
114 | if (!buffer->data) { | |
115 | return; | |
116 | } | |
117 | ||
118 | for (int i = 0; i < buffer->channels; ++i) { | |
119 | g_free(buffer->data[i]); | |
120 | } | |
121 | ||
122 | g_free(buffer->data); | |
123 | buffer->data = NULL; | |
124 | } | |
125 | ||
126 | /* write PCM interleaved */ | |
127 | static int qjack_buffer_write(QJackBuffer *buffer, float *data, int size) | |
128 | { | |
129 | assert(buffer->data); | |
130 | const int samples = size / sizeof(float); | |
131 | int frames = samples / buffer->channels; | |
d73415a3 | 132 | const int avail = buffer->frames - qatomic_load_acquire(&buffer->used); |
2e445703 GM |
133 | |
134 | if (frames > avail) { | |
135 | frames = avail; | |
136 | } | |
137 | ||
138 | int copy = frames; | |
139 | int wptr = buffer->wptr; | |
140 | ||
141 | while (copy) { | |
142 | ||
143 | for (int c = 0; c < buffer->channels; ++c) { | |
144 | buffer->data[c][wptr] = *data++; | |
145 | } | |
146 | ||
147 | if (++wptr == buffer->frames) { | |
148 | wptr = 0; | |
149 | } | |
150 | ||
151 | --copy; | |
152 | } | |
153 | ||
154 | buffer->wptr = wptr; | |
155 | ||
d73415a3 | 156 | qatomic_add(&buffer->used, frames); |
2e445703 GM |
157 | return frames * buffer->channels * sizeof(float); |
158 | }; | |
159 | ||
160 | /* write PCM linear */ | |
161 | static int qjack_buffer_write_l(QJackBuffer *buffer, float **dest, int frames) | |
162 | { | |
163 | assert(buffer->data); | |
d73415a3 | 164 | const int avail = buffer->frames - qatomic_load_acquire(&buffer->used); |
2e445703 GM |
165 | int wptr = buffer->wptr; |
166 | ||
167 | if (frames > avail) { | |
168 | frames = avail; | |
169 | } | |
170 | ||
171 | int right = buffer->frames - wptr; | |
172 | if (right > frames) { | |
173 | right = frames; | |
174 | } | |
175 | ||
176 | const int left = frames - right; | |
177 | for (int c = 0; c < buffer->channels; ++c) { | |
178 | memcpy(buffer->data[c] + wptr, dest[c] , right * sizeof(float)); | |
179 | memcpy(buffer->data[c] , dest[c] + right, left * sizeof(float)); | |
180 | } | |
181 | ||
182 | wptr += frames; | |
183 | if (wptr >= buffer->frames) { | |
184 | wptr -= buffer->frames; | |
185 | } | |
186 | buffer->wptr = wptr; | |
187 | ||
d73415a3 | 188 | qatomic_add(&buffer->used, frames); |
2e445703 GM |
189 | return frames; |
190 | } | |
191 | ||
192 | /* read PCM interleaved */ | |
193 | static int qjack_buffer_read(QJackBuffer *buffer, float *dest, int size) | |
194 | { | |
195 | assert(buffer->data); | |
196 | const int samples = size / sizeof(float); | |
197 | int frames = samples / buffer->channels; | |
d73415a3 | 198 | const int avail = qatomic_load_acquire(&buffer->used); |
2e445703 GM |
199 | |
200 | if (frames > avail) { | |
201 | frames = avail; | |
202 | } | |
203 | ||
204 | int copy = frames; | |
205 | int rptr = buffer->rptr; | |
206 | ||
207 | while (copy) { | |
208 | ||
209 | for (int c = 0; c < buffer->channels; ++c) { | |
210 | *dest++ = buffer->data[c][rptr]; | |
211 | } | |
212 | ||
213 | if (++rptr == buffer->frames) { | |
214 | rptr = 0; | |
215 | } | |
216 | ||
217 | --copy; | |
218 | } | |
219 | ||
220 | buffer->rptr = rptr; | |
221 | ||
d73415a3 | 222 | qatomic_sub(&buffer->used, frames); |
2e445703 GM |
223 | return frames * buffer->channels * sizeof(float); |
224 | } | |
225 | ||
226 | /* read PCM linear */ | |
227 | static int qjack_buffer_read_l(QJackBuffer *buffer, float **dest, int frames) | |
228 | { | |
229 | assert(buffer->data); | |
230 | int copy = frames; | |
d73415a3 | 231 | const int used = qatomic_load_acquire(&buffer->used); |
2e445703 GM |
232 | int rptr = buffer->rptr; |
233 | ||
234 | if (copy > used) { | |
235 | copy = used; | |
236 | } | |
237 | ||
238 | int right = buffer->frames - rptr; | |
239 | if (right > copy) { | |
240 | right = copy; | |
241 | } | |
242 | ||
243 | const int left = copy - right; | |
244 | for (int c = 0; c < buffer->channels; ++c) { | |
245 | memcpy(dest[c] , buffer->data[c] + rptr, right * sizeof(float)); | |
246 | memcpy(dest[c] + right, buffer->data[c] , left * sizeof(float)); | |
247 | } | |
248 | ||
249 | rptr += copy; | |
250 | if (rptr >= buffer->frames) { | |
251 | rptr -= buffer->frames; | |
252 | } | |
253 | buffer->rptr = rptr; | |
254 | ||
d73415a3 | 255 | qatomic_sub(&buffer->used, copy); |
2e445703 GM |
256 | return copy; |
257 | } | |
258 | ||
259 | static int qjack_process(jack_nframes_t nframes, void *arg) | |
260 | { | |
261 | QJackClient *c = (QJackClient *)arg; | |
262 | ||
263 | if (c->state != QJACK_STATE_RUNNING) { | |
264 | return 0; | |
265 | } | |
266 | ||
267 | /* get the buffers for the ports */ | |
268 | float *buffers[c->nchannels]; | |
269 | for (int i = 0; i < c->nchannels; ++i) { | |
270 | buffers[i] = jack_port_get_buffer(c->port[i], nframes); | |
271 | } | |
272 | ||
273 | if (c->out) { | |
81e0efb2 GM |
274 | if (likely(c->enabled)) { |
275 | qjack_buffer_read_l(&c->fifo, buffers, nframes); | |
276 | } else { | |
277 | for(int i = 0; i < c->nchannels; ++i) { | |
278 | memset(buffers[i], 0, nframes * sizeof(float)); | |
279 | } | |
280 | } | |
2e445703 | 281 | } else { |
81e0efb2 GM |
282 | if (likely(c->enabled)) { |
283 | qjack_buffer_write_l(&c->fifo, buffers, nframes); | |
284 | } | |
2e445703 GM |
285 | } |
286 | ||
287 | return 0; | |
288 | } | |
289 | ||
290 | static void qjack_port_registration(jack_port_id_t port, int reg, void *arg) | |
291 | { | |
292 | if (reg) { | |
293 | QJackClient *c = (QJackClient *)arg; | |
294 | c->connect_ports = true; | |
295 | } | |
296 | } | |
297 | ||
298 | static int qjack_xrun(void *arg) | |
299 | { | |
300 | QJackClient *c = (QJackClient *)arg; | |
301 | if (c->state != QJACK_STATE_RUNNING) { | |
302 | return 0; | |
303 | } | |
304 | ||
305 | qjack_buffer_clear(&c->fifo); | |
306 | return 0; | |
307 | } | |
308 | ||
309 | static void qjack_shutdown(void *arg) | |
310 | { | |
311 | QJackClient *c = (QJackClient *)arg; | |
312 | c->state = QJACK_STATE_SHUTDOWN; | |
313 | } | |
314 | ||
315 | static void qjack_client_recover(QJackClient *c) | |
316 | { | |
317 | if (c->state == QJACK_STATE_SHUTDOWN) { | |
318 | qjack_client_fini(c); | |
319 | } | |
320 | ||
321 | /* packets is used simply to throttle this */ | |
322 | if (c->state == QJACK_STATE_DISCONNECTED && | |
323 | c->packets % 100 == 0) { | |
324 | ||
81e0efb2 GM |
325 | /* if enabled then attempt to recover */ |
326 | if (c->enabled) { | |
2e445703 GM |
327 | dolog("attempting to reconnect to server\n"); |
328 | qjack_client_init(c); | |
329 | } | |
330 | } | |
331 | } | |
332 | ||
333 | static size_t qjack_write(HWVoiceOut *hw, void *buf, size_t len) | |
334 | { | |
335 | QJackOut *jo = (QJackOut *)hw; | |
336 | ++jo->c.packets; | |
337 | ||
338 | if (jo->c.state != QJACK_STATE_RUNNING) { | |
339 | qjack_client_recover(&jo->c); | |
340 | return len; | |
341 | } | |
342 | ||
343 | qjack_client_connect_ports(&jo->c); | |
344 | return qjack_buffer_write(&jo->c.fifo, buf, len); | |
345 | } | |
346 | ||
347 | static size_t qjack_read(HWVoiceIn *hw, void *buf, size_t len) | |
348 | { | |
349 | QJackIn *ji = (QJackIn *)hw; | |
350 | ++ji->c.packets; | |
351 | ||
352 | if (ji->c.state != QJACK_STATE_RUNNING) { | |
353 | qjack_client_recover(&ji->c); | |
354 | return len; | |
355 | } | |
356 | ||
357 | qjack_client_connect_ports(&ji->c); | |
358 | return qjack_buffer_read(&ji->c.fifo, buf, len); | |
359 | } | |
360 | ||
361 | static void qjack_client_connect_ports(QJackClient *c) | |
362 | { | |
363 | if (!c->connect_ports || !c->opt->connect_ports) { | |
364 | return; | |
365 | } | |
366 | ||
367 | c->connect_ports = false; | |
368 | const char **ports; | |
369 | ports = jack_get_ports(c->client, c->opt->connect_ports, NULL, | |
370 | c->out ? JackPortIsInput : JackPortIsOutput); | |
371 | ||
372 | if (!ports) { | |
373 | return; | |
374 | } | |
375 | ||
376 | for (int i = 0; i < c->nchannels && ports[i]; ++i) { | |
377 | const char *p = jack_port_name(c->port[i]); | |
378 | if (jack_port_connected_to(c->port[i], ports[i])) { | |
379 | continue; | |
380 | } | |
381 | ||
382 | if (c->out) { | |
383 | dolog("connect %s -> %s\n", p, ports[i]); | |
384 | jack_connect(c->client, p, ports[i]); | |
385 | } else { | |
386 | dolog("connect %s -> %s\n", ports[i], p); | |
387 | jack_connect(c->client, ports[i], p); | |
388 | } | |
389 | } | |
390 | } | |
391 | ||
392 | static int qjack_client_init(QJackClient *c) | |
393 | { | |
394 | jack_status_t status; | |
395 | char client_name[jack_client_name_size()]; | |
396 | jack_options_t options = JackNullOption; | |
397 | ||
bc81e6e5 GM |
398 | if (c->state == QJACK_STATE_RUNNING) { |
399 | return 0; | |
400 | } | |
401 | ||
2e445703 GM |
402 | c->connect_ports = true; |
403 | ||
404 | snprintf(client_name, sizeof(client_name), "%s-%s", | |
405 | c->out ? "out" : "in", | |
406 | c->opt->client_name ? c->opt->client_name : qemu_get_vm_name()); | |
407 | ||
408 | if (c->opt->exact_name) { | |
409 | options |= JackUseExactName; | |
410 | } | |
411 | ||
412 | if (!c->opt->start_server) { | |
413 | options |= JackNoStartServer; | |
414 | } | |
415 | ||
416 | if (c->opt->server_name) { | |
417 | options |= JackServerName; | |
418 | } | |
419 | ||
420 | c->client = jack_client_open(client_name, options, &status, | |
421 | c->opt->server_name); | |
422 | ||
423 | if (c->client == NULL) { | |
424 | dolog("jack_client_open failed: status = 0x%2.0x\n", status); | |
425 | if (status & JackServerFailed) { | |
426 | dolog("unable to connect to JACK server\n"); | |
427 | } | |
428 | return -1; | |
429 | } | |
430 | ||
431 | c->freq = jack_get_sample_rate(c->client); | |
432 | ||
433 | if (status & JackServerStarted) { | |
434 | dolog("JACK server started\n"); | |
435 | } | |
436 | ||
437 | if (status & JackNameNotUnique) { | |
438 | dolog("JACK unique name assigned %s\n", | |
439 | jack_get_client_name(c->client)); | |
440 | } | |
441 | ||
442 | jack_set_process_callback(c->client, qjack_process , c); | |
443 | jack_set_port_registration_callback(c->client, qjack_port_registration, c); | |
444 | jack_set_xrun_callback(c->client, qjack_xrun, c); | |
445 | jack_on_shutdown(c->client, qjack_shutdown, c); | |
446 | ||
2e445703 GM |
447 | /* allocate and register the ports */ |
448 | c->port = g_malloc(sizeof(jack_port_t *) * c->nchannels); | |
449 | for (int i = 0; i < c->nchannels; ++i) { | |
450 | ||
451 | char port_name[16]; | |
452 | snprintf( | |
453 | port_name, | |
454 | sizeof(port_name), | |
455 | c->out ? "output %d" : "input %d", | |
456 | i); | |
457 | ||
458 | c->port[i] = jack_port_register( | |
459 | c->client, | |
460 | port_name, | |
461 | JACK_DEFAULT_AUDIO_TYPE, | |
462 | c->out ? JackPortIsOutput : JackPortIsInput, | |
463 | 0); | |
464 | } | |
465 | ||
466 | /* activate the session */ | |
467 | jack_activate(c->client); | |
468 | c->buffersize = jack_get_buffer_size(c->client); | |
469 | ||
36963ed1 GM |
470 | /* |
471 | * ensure the buffersize is no smaller then 512 samples, some (all?) qemu | |
472 | * virtual devices do not work correctly otherwise | |
473 | */ | |
474 | if (c->buffersize < 512) { | |
475 | c->buffersize = 512; | |
476 | } | |
477 | ||
478 | /* create a 2 period buffer */ | |
479 | qjack_buffer_create(&c->fifo, c->nchannels, c->buffersize * 2); | |
480 | ||
2e445703 GM |
481 | qjack_client_connect_ports(c); |
482 | c->state = QJACK_STATE_RUNNING; | |
483 | return 0; | |
484 | } | |
485 | ||
486 | static int qjack_init_out(HWVoiceOut *hw, struct audsettings *as, | |
487 | void *drv_opaque) | |
488 | { | |
489 | QJackOut *jo = (QJackOut *)hw; | |
490 | Audiodev *dev = (Audiodev *)drv_opaque; | |
491 | ||
bc81e6e5 | 492 | qjack_client_fini(&jo->c); |
2e445703 GM |
493 | |
494 | jo->c.out = true; | |
81e0efb2 | 495 | jo->c.enabled = false; |
2e445703 GM |
496 | jo->c.nchannels = as->nchannels; |
497 | jo->c.opt = dev->u.jack.out; | |
81e0efb2 | 498 | |
2e445703 GM |
499 | int ret = qjack_client_init(&jo->c); |
500 | if (ret != 0) { | |
501 | return ret; | |
502 | } | |
503 | ||
504 | /* report the buffer size to qemu */ | |
505 | hw->samples = jo->c.buffersize; | |
506 | ||
507 | /* report the audio format we support */ | |
508 | struct audsettings os = { | |
509 | .freq = jo->c.freq, | |
510 | .nchannels = jo->c.nchannels, | |
511 | .fmt = AUDIO_FORMAT_F32, | |
512 | .endianness = 0 | |
513 | }; | |
514 | audio_pcm_init_info(&hw->info, &os); | |
515 | ||
516 | dolog("JACK output configured for %dHz (%d samples)\n", | |
517 | jo->c.freq, jo->c.buffersize); | |
518 | ||
519 | return 0; | |
520 | } | |
521 | ||
522 | static int qjack_init_in(HWVoiceIn *hw, struct audsettings *as, | |
523 | void *drv_opaque) | |
524 | { | |
525 | QJackIn *ji = (QJackIn *)hw; | |
526 | Audiodev *dev = (Audiodev *)drv_opaque; | |
527 | ||
bc81e6e5 | 528 | qjack_client_fini(&ji->c); |
2e445703 GM |
529 | |
530 | ji->c.out = false; | |
81e0efb2 | 531 | ji->c.enabled = false; |
2e445703 GM |
532 | ji->c.nchannels = as->nchannels; |
533 | ji->c.opt = dev->u.jack.in; | |
81e0efb2 | 534 | |
2e445703 GM |
535 | int ret = qjack_client_init(&ji->c); |
536 | if (ret != 0) { | |
537 | return ret; | |
538 | } | |
539 | ||
540 | /* report the buffer size to qemu */ | |
541 | hw->samples = ji->c.buffersize; | |
542 | ||
543 | /* report the audio format we support */ | |
544 | struct audsettings is = { | |
545 | .freq = ji->c.freq, | |
546 | .nchannels = ji->c.nchannels, | |
547 | .fmt = AUDIO_FORMAT_F32, | |
548 | .endianness = 0 | |
549 | }; | |
550 | audio_pcm_init_info(&hw->info, &is); | |
551 | ||
552 | dolog("JACK input configured for %dHz (%d samples)\n", | |
553 | ji->c.freq, ji->c.buffersize); | |
554 | ||
555 | return 0; | |
556 | } | |
557 | ||
558 | static void qjack_client_fini(QJackClient *c) | |
559 | { | |
560 | switch (c->state) { | |
561 | case QJACK_STATE_RUNNING: | |
2e445703 GM |
562 | jack_deactivate(c->client); |
563 | /* fallthrough */ | |
564 | ||
565 | case QJACK_STATE_SHUTDOWN: | |
566 | jack_client_close(c->client); | |
567 | /* fallthrough */ | |
568 | ||
569 | case QJACK_STATE_DISCONNECTED: | |
570 | break; | |
571 | } | |
572 | ||
573 | qjack_buffer_free(&c->fifo); | |
574 | g_free(c->port); | |
575 | ||
576 | c->state = QJACK_STATE_DISCONNECTED; | |
577 | } | |
578 | ||
579 | static void qjack_fini_out(HWVoiceOut *hw) | |
580 | { | |
581 | QJackOut *jo = (QJackOut *)hw; | |
2e445703 GM |
582 | qjack_client_fini(&jo->c); |
583 | } | |
584 | ||
585 | static void qjack_fini_in(HWVoiceIn *hw) | |
586 | { | |
587 | QJackIn *ji = (QJackIn *)hw; | |
2e445703 GM |
588 | qjack_client_fini(&ji->c); |
589 | } | |
590 | ||
591 | static void qjack_enable_out(HWVoiceOut *hw, bool enable) | |
592 | { | |
81e0efb2 GM |
593 | QJackOut *jo = (QJackOut *)hw; |
594 | jo->c.enabled = enable; | |
2e445703 GM |
595 | } |
596 | ||
597 | static void qjack_enable_in(HWVoiceIn *hw, bool enable) | |
598 | { | |
81e0efb2 GM |
599 | QJackIn *ji = (QJackIn *)hw; |
600 | ji->c.enabled = enable; | |
2e445703 GM |
601 | } |
602 | ||
603 | static int qjack_thread_creator(jack_native_thread_t *thread, | |
604 | const pthread_attr_t *attr, void *(*function)(void *), void *arg) | |
605 | { | |
606 | int ret = pthread_create(thread, attr, function, arg); | |
607 | if (ret != 0) { | |
608 | return ret; | |
609 | } | |
610 | ||
611 | /* set the name of the thread */ | |
612 | pthread_setname_np(*thread, "jack-client"); | |
613 | ||
614 | return ret; | |
615 | } | |
616 | ||
617 | static void *qjack_init(Audiodev *dev) | |
618 | { | |
619 | assert(dev->driver == AUDIODEV_DRIVER_JACK); | |
2e445703 GM |
620 | return dev; |
621 | } | |
622 | ||
623 | static void qjack_fini(void *opaque) | |
624 | { | |
625 | } | |
626 | ||
627 | static struct audio_pcm_ops jack_pcm_ops = { | |
628 | .init_out = qjack_init_out, | |
629 | .fini_out = qjack_fini_out, | |
630 | .write = qjack_write, | |
631 | .run_buffer_out = audio_generic_run_buffer_out, | |
632 | .enable_out = qjack_enable_out, | |
633 | ||
634 | .init_in = qjack_init_in, | |
635 | .fini_in = qjack_fini_in, | |
636 | .read = qjack_read, | |
637 | .enable_in = qjack_enable_in | |
638 | }; | |
639 | ||
640 | static struct audio_driver jack_driver = { | |
641 | .name = "jack", | |
642 | .descr = "JACK Audio Connection Kit Client", | |
643 | .init = qjack_init, | |
644 | .fini = qjack_fini, | |
645 | .pcm_ops = &jack_pcm_ops, | |
646 | .can_be_default = 1, | |
647 | .max_voices_out = INT_MAX, | |
648 | .max_voices_in = INT_MAX, | |
649 | .voice_size_out = sizeof(QJackOut), | |
650 | .voice_size_in = sizeof(QJackIn) | |
651 | }; | |
652 | ||
653 | static void qjack_error(const char *msg) | |
654 | { | |
655 | dolog("E: %s\n", msg); | |
656 | } | |
657 | ||
658 | static void qjack_info(const char *msg) | |
659 | { | |
660 | dolog("I: %s\n", msg); | |
661 | } | |
662 | ||
663 | static void register_audio_jack(void) | |
664 | { | |
665 | audio_driver_register(&jack_driver); | |
666 | jack_set_thread_creator(qjack_thread_creator); | |
667 | jack_set_error_function(qjack_error); | |
668 | jack_set_info_function(qjack_info); | |
669 | } | |
670 | type_init(register_audio_jack); |