comparison src/jack/jack.c @ 12:3da1b8942b8b trunk

[svn] - remove src/Input src/Output src/Effect src/General src/Visualization src/Container
author nenolod
date Mon, 18 Sep 2006 03:14:20 -0700
parents src/Output/jack/jack.c@6303e3a8a6b8
children f6887767487c
comparison
equal deleted inserted replaced
11:cff1d04026ae 12:3da1b8942b8b
1 /* xmms - jack output plugin
2 * Copyright 2002 Chris Morgan<cmorgan@alum.wpi.edu>
3 *
4 * audacious port (2005) by Giacomo Lozito from develia.org
5 *
6 * This code maps xmms calls into the jack translation library
7 */
8
9 #include "audacious/configdb.h"
10 #include "audacious/util.h"
11 #include <dlfcn.h>
12 #include <gtk/gtk.h>
13 #include <glib/gi18n.h>
14 #include <stdio.h>
15 #include "config.h"
16 #include "bio2jack.h" /* includes for the bio2jack library */
17 #include "jack.h"
18 #include "xconvert.h" /* xmms rate conversion header file */
19 #include <string.h>
20
21
22
23 /* set to 1 for verbose output */
24 #define VERBOSE_OUTPUT 0
25
26 jackconfig jack_cfg;
27
28 #define OUTFILE stderr
29
30 #define TRACE(...) \
31 if(jack_cfg.isTraceEnabled) { \
32 fprintf(OUTFILE, "%s:", __FUNCTION__), \
33 fprintf(OUTFILE, __VA_ARGS__), \
34 fflush(OUTFILE); \
35 }
36
37 #define ERR(...) \
38 if(jack_cfg.isTraceEnabled) { \
39 fprintf(OUTFILE, "ERR: %s:", __FUNCTION__), \
40 fprintf(OUTFILE, __VA_ARGS__), \
41 fflush(OUTFILE); \
42 }
43
44
45 static int driver = 0; /* handle to the jack output device */
46
47 typedef struct format_info {
48 AFormat format;
49 long frequency;
50 int channels;
51 long bps;
52 } format_info_t;
53
54 static format_info_t input;
55 static format_info_t effect;
56 static format_info_t output;
57
58 static convert_freq_func_t freq_convert; /* rate convert function */
59 static struct xmms_convert_buffers *convertb; /* convert buffer */
60
61 #define MAKE_FUNCPTR(f) static typeof(f) * fp_##f = NULL;
62 MAKE_FUNCPTR(xmms_convert_buffers_new);
63 MAKE_FUNCPTR(xmms_convert_buffers_destroy);
64 MAKE_FUNCPTR(xmms_convert_get_frequency_func);
65 void *xmmslibhandle; /* handle to the dlopen'ed libxmms.so */
66
67 static int isXmmsFrequencyAvailable = 0;
68
69 static gboolean output_opened; /* true if we have a connection to jack */
70
71 static GtkWidget *dialog, *button, *label;
72
73 void jack_set_volume(int l, int r);
74
75 /* Giacomo's note: removed the destructor from the original xmms-jack, cause
76 destructors + thread join + NPTL currently leads to problems; solved this
77 by adding a cleanup function callback for output plugins in Audacious, this
78 is used to close the JACK connection and to perform a correct shutdown */
79 void jack_cleanup(void)
80 {
81 int errval;
82 TRACE("cleanup\n");
83
84 if((errval = JACK_Close(driver)))
85 ERR("error closing device, errval of %d\n", errval);
86
87 /* only clean this up if we have the function to call */
88 if(isXmmsFrequencyAvailable)
89 {
90 fp_xmms_convert_buffers_destroy(convertb); /* clean up the rate conversion buffers */
91 dlclose(xmmslibhandle);
92 }
93
94 return;
95 }
96
97
98 void jack_sample_rate_error(void)
99 {
100 dialog = gtk_dialog_new();
101 gtk_window_set_title(GTK_WINDOW(dialog), ("Sample rate mismatch"));
102 gtk_container_border_width(GTK_CONTAINER(dialog), 5);
103 label = gtk_label_new((
104 "Xmms is asking for a sample rate that differs from\n "
105 "that of the jack server. Xmms 1.2.8 or later\n"
106 "contains resampling routines that xmms-jack will\n"
107 "dynamically load and use to perform resampling.\n"
108 "Or you can restart the jack server\n"
109 "with a sample rate that matches the one that\n"
110 "xmms desires. -r is the option for the jack\n"
111 "alsa driver so -r 44100 or -r 48000 should do\n\n"
112 "Chris Morgan <cmorgan@alum.wpi.edu>\n"));
113
114 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label, TRUE, TRUE, 0);
115 gtk_widget_show(label);
116
117 button = gtk_button_new_with_label((" Close "));
118 gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(dialog));
119 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), button, FALSE, FALSE, 0);
120 gtk_widget_show(button);
121
122 gtk_widget_show(dialog);
123 gtk_widget_grab_focus(button);
124 }
125
126
127 /* Return the number of milliseconds of audio data that has been */
128 /* written out to the device */
129 gint jack_get_written_time(void)
130 {
131 long return_val;
132 return_val = JACK_GetPosition(driver, MILLISECONDS, WRITTEN);
133
134 TRACE("returning %ld milliseconds\n", return_val);
135 return return_val;
136 }
137
138
139 /* Return the current number of milliseconds of audio data that has */
140 /* been played out of the audio device, not including the buffer */
141 gint jack_get_output_time(void)
142 {
143 gint return_val;
144
145 /* don't try to get any values if the device is still closed */
146 if(JACK_GetState(driver) == CLOSED)
147 return_val = 0;
148 else
149 return_val = JACK_GetPosition(driver, MILLISECONDS, PLAYED); /* get played position in terms of milliseconds */
150
151 TRACE("returning %d milliseconds\n", return_val);
152 return return_val;
153 }
154
155
156 /* returns TRUE if we are currently playing */
157 /* NOTE: this was confusing at first BUT, if the device is open and there */
158 /* is no more audio to be played, then the device is NOT PLAYING */
159 gint jack_playing(void)
160 {
161 gint return_val;
162
163 /* If we are playing see if we ACTUALLY have something to play */
164 if(JACK_GetState(driver) == PLAYING)
165 {
166 /* If we have zero bytes stored, we are done playing */
167 if(JACK_GetBytesStored(driver) == 0)
168 return_val = FALSE;
169 else
170 return_val = TRUE;
171 }
172 else
173 return_val = FALSE;
174
175 TRACE("returning %d\n", return_val);
176 return return_val;
177 }
178
179
180 void jack_set_port_connection_mode()
181 {
182 /* setup the port connection mode that determines how bio2jack will connect ports */
183 enum JACK_PORT_CONNECTION_MODE mode;
184
185 if(strcmp(jack_cfg.port_connection_mode, "CONNECT_ALL") == 0)
186 mode = CONNECT_ALL;
187 else if(strcmp(jack_cfg.port_connection_mode, "CONNECT_OUTPUT") == 0)
188 mode = CONNECT_OUTPUT;
189 else if(strcmp(jack_cfg.port_connection_mode, "CONNECT_NONE") == 0)
190 mode = CONNECT_NONE;
191 else
192 {
193 TRACE("Defaulting to CONNECT_ALL");
194 mode = CONNECT_ALL;
195 }
196 JACK_SetPortConnectionMode(mode);
197 }
198
199 /* Initialize necessary things */
200 void jack_init(void)
201 {
202 /* read the isTraceEnabled setting from the config file */
203 ConfigDb *cfgfile;
204
205 cfgfile = bmp_cfg_db_open();
206 if (!cfgfile)
207 {
208 jack_cfg.isTraceEnabled = FALSE;
209 jack_cfg.port_connection_mode = "CONNECT_ALL"; /* default to connect all */
210 jack_cfg.volume_left = 25; /* set default volume to 25 % */
211 jack_cfg.volume_right = 25;
212 } else
213 {
214 bmp_cfg_db_get_bool(cfgfile, "jack", "isTraceEnabled", &jack_cfg.isTraceEnabled);
215 if(!bmp_cfg_db_get_string(cfgfile, "jack", "port_connection_mode", &jack_cfg.port_connection_mode))
216 jack_cfg.port_connection_mode = "CONNECT_ALL";
217 if(!bmp_cfg_db_get_int(cfgfile, "jack", "volume_left", &jack_cfg.volume_left))
218 jack_cfg.volume_left = 25;
219 if(!bmp_cfg_db_get_int(cfgfile, "jack", "volume_right", &jack_cfg.volume_right))
220 jack_cfg.volume_right = 25;
221 }
222
223 bmp_cfg_db_close(cfgfile);
224
225 TRACE("initializing\n");
226 JACK_Init(); /* initialize the driver */
227
228 /* set the bio2jack name so users will see xmms-jack in their */
229 /* jack client list */
230 JACK_SetClientName("audacious-jack");
231
232 /* set the port connection mode */
233 jack_set_port_connection_mode();
234
235 /* XXX unportable to 2.x */
236 xmmslibhandle = dlopen("libaudacious.so", RTLD_NOW);
237 if(xmmslibhandle)
238 {
239 fp_xmms_convert_buffers_new = dlsym(xmmslibhandle, "xmms_convert_buffers_new");
240 fp_xmms_convert_buffers_destroy = dlsym(xmmslibhandle, "xmms_convert_buffers_destroy");
241 fp_xmms_convert_get_frequency_func = dlsym(xmmslibhandle, "xmms_convert_get_frequency_func");
242
243 if(!fp_xmms_convert_buffers_new)
244 {
245 TRACE("fp_xmms_convert_buffers_new couldn't be dlsym'ed\n");
246 TRACE("dlerror: %s\n", dlerror());
247 }
248
249 if(!fp_xmms_convert_buffers_destroy)
250 {
251 TRACE("fp_xmms_convert_buffers_destroy couldn't be dlsym'ed\n");
252 TRACE("dlerror: %s\n", dlerror());
253 }
254
255 if(!fp_xmms_convert_get_frequency_func)
256 {
257 TRACE("fp_xmms_get_frequency_func couldn't be dlsym'ed\n");
258 TRACE("dlerror: %s\n", dlerror());
259 }
260
261 if(!fp_xmms_convert_buffers_new || !fp_xmms_convert_buffers_destroy ||
262 !fp_xmms_convert_get_frequency_func)
263 {
264 dlclose(xmmslibhandle); /* close the library, no need to keep it open */
265 TRACE("One or more frequency convertion functions are missing, upgrade to xmms >=1.2.8\n");
266 } else
267 {
268 TRACE("Found frequency convertion functions, setting isXmmsFrequencyAvailable to 1\n");
269 isXmmsFrequencyAvailable = 1;
270 }
271 } else
272 {
273 TRACE("unable to dlopen '%s'\n", "libaudacious.so");
274 }
275
276 /* only initialize this stuff if we have the functions available */
277 if(isXmmsFrequencyAvailable)
278 {
279 convertb = fp_xmms_convert_buffers_new ();
280 freq_convert = fp_xmms_convert_get_frequency_func(FMT_S16_LE, 2);
281 }
282
283 output_opened = FALSE;
284 }
285
286
287 /* Return the amount of data that can be written to the device */
288 gint jack_free(void)
289 {
290 unsigned long return_val = JACK_GetBytesFreeSpace(driver);
291 unsigned long tmp;
292
293 /* adjust for frequency differences, otherwise xmms could send us */
294 /* as much data as we have free, then we go to convert this to */
295 /* the output frequency and won't have enough space, so adjust */
296 /* by the ratio of the two */
297 if(effect.frequency != output.frequency)
298 {
299 tmp = return_val;
300 return_val = (return_val * effect.frequency) / output.frequency;
301 TRACE("adjusting from %ld to %ld free bytes to compensate for frequency differences\n", tmp, return_val);
302 }
303
304 if(return_val > G_MAXINT)
305 {
306 TRACE("Warning: return_val > G_MAXINT\n");
307 return_val = G_MAXINT;
308 }
309
310 TRACE("free space of %ld bytes\n", return_val);
311
312 return return_val;
313 }
314
315
316 /* Close the device */
317 void jack_close(void)
318 {
319 ConfigDb *cfgfile;
320
321 cfgfile = bmp_cfg_db_open();
322 bmp_cfg_db_set_int(cfgfile, "jack", "volume_left", jack_cfg.volume_left); /* stores the volume setting */
323 bmp_cfg_db_set_int(cfgfile, "jack", "volume_right", jack_cfg.volume_right);
324 bmp_cfg_db_close(cfgfile);
325
326 TRACE("\n");
327
328 JACK_Reset(driver); /* flush buffers, reset position and set state to STOPPED */
329 TRACE("resetting driver, not closing now, destructor will close for us\n");
330 }
331
332
333 /* Open the device up */
334 gint jack_open(AFormat fmt, gint sample_rate, gint num_channels)
335 {
336 int bits_per_sample;
337 int retval;
338 unsigned long rate;
339
340 TRACE("fmt == %d, sample_rate == %d, num_channels == %d\n",
341 fmt, sample_rate, num_channels);
342
343 if((fmt == FMT_U8) || (fmt == FMT_S8))
344 {
345 bits_per_sample = 8;
346 } else
347 {
348 bits_per_sample = 16;
349 }
350
351 /* record some useful information */
352 input.format = fmt;
353 input.frequency = sample_rate;
354 input.bps = bits_per_sample * sample_rate * num_channels;
355 input.channels = num_channels;
356
357 /* setup the effect as matching the input format */
358 effect.format = input.format;
359 effect.frequency = input.frequency;
360 effect.channels = input.channels;
361 effect.bps = input.bps;
362
363 /* if we are already opened then don't open again */
364 if(output_opened)
365 {
366 /* if something has changed we should close and re-open the connect to jack */
367 if((output.channels != input.channels) ||
368 (output.frequency != input.frequency) ||
369 (output.format != input.format))
370 {
371 TRACE("output.channels is %d, jack_open called with %d channels\n", output.channels, input.channels);
372 TRACE("output.frequency is %ld, jack_open called with %ld\n", output.frequency, input.frequency);
373 TRACE("output.format is %d, jack_open called with %d\n", output.format, input.format);
374 jack_close();
375 } else
376 {
377 TRACE("output_opened is TRUE and no options changed, not reopening\n");
378 return 1;
379 }
380 }
381
382 /* try to open the jack device with the requested rate at first */
383 output.frequency = input.frequency;
384 output.bps = input.bps;
385 output.channels = input.channels;
386 output.format = input.format;
387
388 rate = output.frequency;
389 retval = JACK_Open(&driver, bits_per_sample, &rate, output.channels);
390 output.frequency = rate; /* avoid compile warning as output.frequency differs in type
391 from what JACK_Open() wants for the type of the rate parameter */
392 if((retval == ERR_RATE_MISMATCH) && isXmmsFrequencyAvailable)
393 {
394 TRACE("xmms(input) wants rate of '%ld', jacks rate(output) is '%ld', opening at jack rate\n", input.frequency, output.frequency);
395
396 /* open the jack device with true jack's rate, return 0 upon failure */
397 retval = JACK_Open(&driver, bits_per_sample, &rate, output.channels);
398 output.frequency = rate; /* avoid compile warning as output.frequency differs in type
399 from what JACK_Open() wants for the type of the rate parameter */
400 if(retval)
401 {
402 TRACE("failed to open jack with JACK_Open(), error %d\n", retval);
403 return 0;
404 }
405 TRACE("success!!\n");
406 } else if((retval == ERR_RATE_MISMATCH) && !isXmmsFrequencyAvailable)
407 {
408 TRACE("JACK_Open(), sample rate mismatch with no resampling routines available\n");
409
410 jack_sample_rate_error(); /* notify the user that we can't resample */
411
412 return 0;
413 } else if(retval != ERR_SUCCESS)
414 {
415 TRACE("failed to open jack with JACK_Open(), error %d\n", retval);
416 return 0;
417 }
418
419 jack_set_volume(jack_cfg.volume_left, jack_cfg.volume_right); /* sets the volume to stored value */
420 output_opened = TRUE;
421
422 return 1;
423 }
424
425
426 /* write some audio out to the device */
427 void jack_write(gpointer ptr, gint length)
428 {
429 long written;
430 EffectPlugin *ep;
431 AFormat new_format;
432 int new_frequency, new_channels;
433 long positionMS;
434
435 TRACE("starting length of %d\n", length);
436
437 /* copy the current values into temporary values */
438 new_format = input.format;
439 new_frequency = input.frequency;
440 new_channels = input.channels;
441
442 /* query xmms for the current plugin */
443 ep = get_current_effect_plugin();
444 if(effects_enabled() && ep && ep->query_format)
445 {
446 ep->query_format(&new_format, &new_frequency, &new_channels);
447 }
448
449 /* if the format has changed take this into account by modifying */
450 /* the time offset and reopening the device with the new format settings */
451 if (new_format != effect.format ||
452 new_frequency != effect.frequency ||
453 new_channels != effect.channels)
454 {
455 TRACE("format changed, storing new values and opening/closing jack\n");
456 TRACE("effect.format == %d, new_format == %d, effect.frequency == %ld, new_frequency == %d, effect.channels == %d, new_channels = %d\n",
457 effect.format, new_format, effect.frequency, new_frequency, effect.channels, new_channels);
458
459 positionMS = JACK_GetPosition(driver, MILLISECONDS, PLAYED);
460
461 jack_close();
462 jack_open(new_format, new_frequency, new_channels);
463
464 /* restore the position after the open and close */
465 JACK_SetState(driver, PAUSED);
466 JACK_SetPosition(driver, MILLISECONDS, positionMS);
467 JACK_SetState(driver, PLAYING);
468 }
469
470 /* if effects are enabled and we have a plugin, run the current */
471 /* samples through the plugin */
472 if (effects_enabled() && ep && ep->mod_samples)
473 {
474 length = ep->mod_samples(&ptr, length,
475 input.format,
476 input.frequency,
477 input.channels);
478 TRACE("effects_enabled(), length is now %d\n", length);
479 }
480
481 TRACE("effect.frequency == %ld, input.frequency == %ld, output.frequency == %ld\n",
482 effect.frequency, input.frequency, output.frequency);
483
484 /* if we need rate conversion, perform it here */
485 if((effect.frequency != output.frequency) && isXmmsFrequencyAvailable)
486 {
487 TRACE("performing rate conversion from '%ld'(effect) to '%ld'(output)\n", effect.frequency, output.frequency);
488 length = freq_convert (convertb, &ptr, length, effect.frequency, output.frequency);
489 }
490
491 TRACE("length = %d\n", length);
492 /* loop until we have written all the data out to the jack device */
493 /* this is due to xmms' audio driver api */
494 char *buf = (char*)ptr;
495 while(length > 0)
496 {
497 TRACE("writing %d bytes\n", length);
498 written = JACK_Write(driver, (unsigned char*)buf, length);
499 length-=written;
500 buf+=written;
501 }
502 TRACE("finished\n");
503 }
504
505
506 /* Flush any output currently buffered */
507 /* and set the number of bytes written based on ms_offset_time, */
508 /* the number of milliseconds of offset passed in */
509 /* This is done so the driver itself keeps track of */
510 /* current playing position of the mp3 */
511 void jack_flush(gint ms_offset_time)
512 {
513 TRACE("setting values for ms_offset_time of %d\n", ms_offset_time);
514
515 JACK_Reset(driver); /* flush buffers and set state to STOPPED */
516
517 /* update the internal driver values to correspond to the input time given */
518 JACK_SetPosition(driver, MILLISECONDS, ms_offset_time);
519
520 JACK_SetState(driver, PLAYING);
521 }
522
523
524 /* Pause the jack device */
525 void jack_pause(short p)
526 {
527 TRACE("p == %d\n", p);
528
529 /* pause the device if p is non-zero, unpause the device if p is zero and */
530 /* we are currently paused */
531 if(p)
532 JACK_SetState(driver, PAUSED);
533 else if(JACK_GetState(driver) == PAUSED)
534 JACK_SetState(driver, PLAYING);
535 }
536
537
538 /* Set the volume */
539 void jack_set_volume(int l, int r)
540 {
541 if(output.channels == 1)
542 {
543 TRACE("l(%d)\n", l);
544 } else if(output.channels > 1)
545 {
546 TRACE("l(%d), r(%d)\n", l, r);
547 }
548
549 if(output.channels > 0) {
550 JACK_SetVolumeForChannel(driver, 0, l);
551 jack_cfg.volume_left = l;
552 }
553 if(output.channels > 1) {
554 JACK_SetVolumeForChannel(driver, 1, r);
555 jack_cfg.volume_right = r;
556 }
557 }
558
559
560 /* Get the current volume setting */
561 void jack_get_volume(int *l, int *r)
562 {
563 unsigned int _l, _r;
564
565 if(output.channels > 0)
566 {
567 JACK_GetVolumeForChannel(driver, 0, &_l);
568 (*l) = _l;
569 }
570 if(output.channels > 1)
571 {
572 JACK_GetVolumeForChannel(driver, 1, &_r);
573 (*r) = _r;
574 }
575
576 #if VERBOSE_OUTPUT
577 if(output.channels == 1)
578 TRACE("l(%d)\n", *l);
579 else if(output.channels > 1)
580 TRACE("l(%d), r(%d)\n", *l, *r);
581 #endif
582 }
583
584
585 void jack_about(void)
586 {
587 static GtkWidget *aboutbox;
588
589 if (!aboutbox)
590 {
591 aboutbox = xmms_show_message(
592 _("About JACK Output Plugin 0.15"),
593 _("XMMS jack Driver 0.15\n\n"
594 "xmms-jack.sf.net\nChris Morgan<cmorgan@alum.wpi.edu>\n\n"
595 "Audacious port by\nGiacomo Lozito from develia.org"),
596 _("Ok"), FALSE, NULL, NULL);
597 g_signal_connect(GTK_OBJECT(aboutbox), "destroy",
598 (GCallback)gtk_widget_destroyed, &aboutbox);
599 }
600 }
601
602 static void
603 jack_tell_audio(AFormat * fmt, gint * srate, gint * nch)
604 {
605 (*fmt) = input.format;
606 (*srate) = input.frequency;
607 (*nch) = input.channels;
608 }
609
610 OutputPlugin jack_op =
611 {
612 NULL,
613 NULL,
614 "JACK Output Plugin 0.15",
615 jack_init,
616 jack_cleanup,
617 jack_about,
618 jack_configure,
619 jack_get_volume,
620 jack_set_volume,
621 jack_open,
622 jack_write,
623 jack_close,
624 jack_flush,
625 jack_pause,
626 jack_free,
627 jack_playing,
628 jack_get_output_time,
629 jack_get_written_time,
630 jack_tell_audio
631 };
632
633
634 OutputPlugin *get_oplugin_info(void)
635 {
636 return &jack_op;
637 }