comparison src/gtksound.c @ 13718:988186eb1688

[gaim-migrate @ 16127] Use Gstreamer for IM sounds, kill ao/audiofile committer: Tailor Script <tailor@pidgin.im>
author Sean Egan <seanegan@gmail.com>
date Tue, 02 May 2006 22:24:46 +0000
parents 47c12348cfd1
children 164932c4d050
comparison
equal deleted inserted replaced
13717:3b3430042849 13718:988186eb1688
29 #ifdef _WIN32 29 #ifdef _WIN32
30 #include <windows.h> 30 #include <windows.h>
31 #include <mmsystem.h> 31 #include <mmsystem.h>
32 #endif 32 #endif
33 33
34 #ifdef USE_AO 34 #ifdef USE_GSTREAMER
35 # include <ao/ao.h> 35 # include <gst/gst.h>
36 # include <audiofile.h> 36 #endif /* USE_GSTREAMER */
37 #endif /* USE_AO */
38 37
39 #include "debug.h" 38 #include "debug.h"
40 #include "notify.h" 39 #include "notify.h"
41 #include "prefs.h" 40 #include "prefs.h"
42 #include "sound.h" 41 #include "sound.h"
53 52
54 #define PLAY_SOUND_TIMEOUT 15000 53 #define PLAY_SOUND_TIMEOUT 15000
55 54
56 static guint mute_login_sounds_timeout = 0; 55 static guint mute_login_sounds_timeout = 0;
57 static gboolean mute_login_sounds = FALSE; 56 static gboolean mute_login_sounds = FALSE;
58 static gboolean sound_initialized = FALSE;
59 57
60 static struct gaim_sound_event sounds[GAIM_NUM_SOUNDS] = { 58 static struct gaim_sound_event sounds[GAIM_NUM_SOUNDS] = {
61 {N_("Buddy logs in"), "login", "login.wav"}, 59 {N_("Buddy logs in"), "login", "login.wav"},
62 {N_("Buddy logs out"), "logout", "logout.wav"}, 60 {N_("Buddy logs out"), "logout", "logout.wav"},
63 {N_("Message received"), "im_recv", "receive.wav"}, 61 {N_("Message received"), "im_recv", "receive.wav"},
70 /* this isn't a terminator, it's the buddy pounce default sound event ;-) */ 68 /* this isn't a terminator, it's the buddy pounce default sound event ;-) */
71 {NULL, "pounce_default", "alert.wav"}, 69 {NULL, "pounce_default", "alert.wav"},
72 {N_("Someone says your screen name in chat"), "nick_said", "alert.wav"} 70 {N_("Someone says your screen name in chat"), "nick_said", "alert.wav"}
73 }; 71 };
74 72
75 #ifdef USE_AO
76 static int ao_driver = -1;
77 #endif /* USE_AO */
78
79 static gboolean 73 static gboolean
80 unmute_login_sounds_cb(gpointer data) 74 unmute_login_sounds_cb(gpointer data)
81 { 75 {
82 mute_login_sounds = FALSE; 76 mute_login_sounds = FALSE;
83 mute_login_sounds_timeout = 0; 77 mute_login_sounds_timeout = 0;
231 { 225 {
232 if (mute_login_sounds_timeout != 0) 226 if (mute_login_sounds_timeout != 0)
233 g_source_remove(mute_login_sounds_timeout); 227 g_source_remove(mute_login_sounds_timeout);
234 mute_login_sounds = TRUE; 228 mute_login_sounds = TRUE;
235 mute_login_sounds_timeout = gaim_timeout_add(10000, unmute_login_sounds_cb, NULL); 229 mute_login_sounds_timeout = gaim_timeout_add(10000, unmute_login_sounds_cb, NULL);
236 }
237
238 static void
239 _pref_sound_method_changed(const char *name, GaimPrefType type,
240 gconstpointer val, gpointer data)
241 {
242 if(type != GAIM_PREF_STRING || strcmp(name, "/gaim/gtk/sound/method"))
243 return;
244
245 sound_initialized = TRUE;
246
247 #ifdef USE_AO
248 ao_driver = -1;
249
250 if(!strcmp(val, "esd"))
251 ao_driver = ao_driver_id("esd");
252 else if(!strcmp(val, "arts"))
253 ao_driver = ao_driver_id("arts");
254 else if(!strcmp(val, "nas"))
255 ao_driver = ao_driver_id("nas");
256 else if(!strcmp(val, "automatic"))
257 ao_driver = ao_default_driver_id();
258
259 if(ao_driver != -1) {
260 ao_info *info = ao_driver_info(ao_driver);
261 gaim_debug_info("sound",
262 "Sound output driver loaded: %s\n", info->name);
263 }
264 #endif /* USE_AO */
265 } 230 }
266 231
267 const char * 232 const char *
268 gaim_gtk_sound_get_event_option(GaimSoundEventID event) 233 gaim_gtk_sound_get_event_option(GaimSoundEventID event)
269 { 234 {
330 gaim_prefs_add_bool("/gaim/gtk/sound/mute", FALSE); 295 gaim_prefs_add_bool("/gaim/gtk/sound/mute", FALSE);
331 gaim_prefs_add_string("/gaim/gtk/sound/command", ""); 296 gaim_prefs_add_string("/gaim/gtk/sound/command", "");
332 gaim_prefs_add_string("/gaim/gtk/sound/method", "automatic"); 297 gaim_prefs_add_string("/gaim/gtk/sound/method", "automatic");
333 gaim_prefs_add_int("/gaim/gtk/sound/volume", 50); 298 gaim_prefs_add_int("/gaim/gtk/sound/volume", 50);
334 299
335 #ifdef USE_AO 300 #ifdef USE_GSTREAMER
336 gaim_debug_info("sound", "Initializing sound output drivers.\n"); 301 gaim_debug_info("sound", "Initializing sound output drivers.\n");
337 ao_initialize(); 302 gst_init(NULL, NULL);
338 #endif /* USE_AO */ 303 #endif /* USE_GSTREAMER */
339
340 gaim_prefs_connect_callback(gaim_gtk_sound_get_handle(), "/gaim/gtk/sound/method",
341 _pref_sound_method_changed, NULL);
342 304
343 gaim_signal_connect(blist_handle, "buddy-signed-on", 305 gaim_signal_connect(blist_handle, "buddy-signed-on",
344 gtk_sound_handle, GAIM_CALLBACK(buddy_state_cb), 306 gtk_sound_handle, GAIM_CALLBACK(buddy_state_cb),
345 GINT_TO_POINTER(GAIM_SOUND_BUDDY_ARRIVE)); 307 GINT_TO_POINTER(GAIM_SOUND_BUDDY_ARRIVE));
346 gaim_signal_connect(blist_handle, "buddy-signed-off", 308 gaim_signal_connect(blist_handle, "buddy-signed-off",
367 } 329 }
368 330
369 static void 331 static void
370 gaim_gtk_sound_uninit(void) 332 gaim_gtk_sound_uninit(void)
371 { 333 {
372 #ifdef USE_AO 334 #ifdef USE_GSTREAMER
373 ao_shutdown(); 335 gst_deinit();
374 #endif 336 #endif
375 sound_initialized = FALSE;
376 337
377 gaim_signals_disconnect_by_handle(gaim_gtk_sound_get_handle()); 338 gaim_signals_disconnect_by_handle(gaim_gtk_sound_get_handle());
378 } 339 }
379 340
380 #ifdef USE_AO 341 #ifdef USE_GSTREAMER
381 static gboolean 342 static gboolean
382 expire_old_child(gpointer data) 343 expire_old_child(gpointer data)
383 { 344 {
384 int ret; 345 int ret;
385 pid_t pid = GPOINTER_TO_INT(data); 346 pid_t pid = GPOINTER_TO_INT(data);
393 } 354 }
394 355
395 return FALSE; /* do not run again */ 356 return FALSE; /* do not run again */
396 } 357 }
397 358
398 /* Uncomment the following line to enable debugging of clipping in the scaling. */ 359 static gboolean
399 /* #define DEBUG_CLIPPING */ 360 bus_call (GstBus *bus,
400 361 GstMessage *msg,
401 static void 362 gpointer data)
402 scale_pcm_data(char *data, int nframes, int bits, int channels, 363 {
403 double intercept, double minclip, double maxclip, 364 GstElement *play = data;
404 float scale) 365 GError *err;
405 { 366
406 int i; 367 switch (GST_MESSAGE_TYPE (msg)) {
407 float v; 368 case GST_MESSAGE_EOS:
408 gint16 *data16 = (gint16*)data; 369 gst_element_set_state(play, GST_STATE_NULL);
409 gint32 *data32 = (gint32*)data; 370 gst_object_unref(GST_OBJECT(play));
410 gint64 *data64 = (gint64*)data; 371 break;
411 372 case GST_MESSAGE_ERROR:
412 switch(bits) { 373 gst_message_parse_error(msg, &err, NULL);
413 case 16: 374 gaim_debug_error("gstreamer", err->message);
414 for(i = 0; i < nframes * channels; i++) { 375 g_error_free(err);
415 v = ((data16[i] - intercept) * scale) + intercept; 376 break;
416 #ifdef DEBUG_CLIPPING 377 case GST_MESSAGE_WARNING:
417 if (v > maxclip) 378 gst_message_parse_warning(msg, &err, NULL);
418 printf("Clipping detected!\n"); 379 gaim_debug_warning("gstreamer", err->message);
419 else if (v < minclip) 380 g_error_free(err);
420 printf("Clipping detected!\n"); 381 break;
382 default:
383 break;
384 }
385 return TRUE;
386 }
421 #endif 387 #endif
422 v = CLAMP(v, minclip, maxclip); 388
423 data16[i]=(gint16)v; 389 static void
424 } 390 gaim_gtk_sound_play_file(const char *filename)
425 break; 391 {
426 case 32: 392 const char *method;
427 for(i = 0; i < nframes * channels; i++) { 393 #ifdef USE_GSTREAMER
428 v = ((data32[i] - intercept) * scale) + intercept; 394 float volume;
429 #ifdef DEBUG_CLIPPING 395 char *uri;
430 if (v > maxclip) 396 GstElement *sink = NULL;
431 printf("Clipping detected!\n"); 397 GstElement *play = NULL;
432 else if (v < minclip)
433 printf("Clipping detected!\n");
434 #endif 398 #endif
435 v = CLAMP(v, minclip, maxclip);
436 data32[i]=(gint32)v;
437 }
438 break;
439 case 64:
440 for(i = 0; i < nframes * channels; i++) {
441 v = ((data64[i] - intercept) * scale) + intercept;
442 #ifdef DEBUG_CLIPPING
443 if (v > maxclip)
444 printf("Clipping detected!\n");
445 else if (v < minclip)
446 printf("Clipping detected!\n");
447 #endif
448 v = CLAMP(v, minclip, maxclip);
449 data64[i]=(gint64)v;
450 }
451 break;
452 default:
453 gaim_debug_warning("gtksound", "Scaling of %d bit pcm data not supported.\n", bits);
454 break;
455 }
456 }
457 #endif /* USE_AO */
458
459 static void
460 gaim_gtk_sound_play_file(const char *filename)
461 {
462 const char *method;
463 #ifdef USE_AO
464 pid_t pid;
465 AFfilehandle file;
466 int volume = 50;
467 #endif
468
469 if (!sound_initialized)
470 gaim_prefs_trigger_callback("/gaim/gtk/sound/method");
471 399
472 if (gaim_prefs_get_bool("/gaim/gtk/sound/mute")) 400 if (gaim_prefs_get_bool("/gaim/gtk/sound/mute"))
473 return; 401 return;
474 402
475 method = gaim_prefs_get_string("/gaim/gtk/sound/method"); 403 method = gaim_prefs_get_string("/gaim/gtk/sound/method");
517 } 445 }
518 446
519 g_free(command); 447 g_free(command);
520 return; 448 return;
521 } 449 }
522 #ifdef USE_AO 450 #ifdef USE_GSTREAMER
523 volume = gaim_prefs_get_int("/gaim/gtk/sound/volume"); 451 volume = (float)(CLAMP(gaim_prefs_get_int("/gaim/gtk/sound/volume"),0,100)) / 50;
524 volume = CLAMP(volume, 0, 100); 452 if (!strcmp(method, "automatic")) {
525 453 if (gaim_running_gnome()) {
526 pid = fork(); 454 sink = gst_element_factory_make("gconfaudiosink", "sink");
527 if (pid < 0)
528 return;
529 else if (pid == 0) {
530 /* Child process */
531
532 /* calculating the scaling factor:
533 * scale(x) = (x+30)^2 / 6400
534 * scale(0) = 0.1406 (quiet)
535 * scale(50) = 1.0 (no scaling, normal volume)
536 * scale(100) = 2.6406 (roughly maximized without clipping)
537 */
538 float scale = ( ((float)volume + 30) * ((float)volume + 30) ) / 6400;
539 file = afOpenFile(filename, "rb", NULL);
540 if(file) {
541 ao_device *device;
542 ao_sample_format format;
543 int in_fmt;
544 int bytes_per_frame;
545 double slope, intercept, minclip, maxclip;
546
547 format.rate = afGetRate(file, AF_DEFAULT_TRACK);
548 format.channels = afGetChannels(file, AF_DEFAULT_TRACK);
549 afGetSampleFormat(file, AF_DEFAULT_TRACK, &in_fmt,
550 &format.bits);
551
552 afGetPCMMapping(file, AF_DEFAULT_TRACK, &slope,
553 &intercept, &minclip, &maxclip);
554
555 /* XXX: libao doesn't seem to like 8-bit sounds, so we'll
556 * let libaudiofile make them a bit better for us */
557 if(format.bits == 8)
558 format.bits = 16;
559
560 afSetVirtualSampleFormat(file, AF_DEFAULT_TRACK,
561 AF_SAMPFMT_TWOSCOMP, format.bits);
562
563 #if G_BYTE_ORDER == G_BIG_ENDIAN
564 format.byte_format = AO_FMT_BIG;
565 afSetVirtualByteOrder(file, AF_DEFAULT_TRACK,
566 AF_BYTEORDER_BIGENDIAN);
567 #elif G_BYTE_ORDER == G_LITTLE_ENDIAN
568 format.byte_format = AO_FMT_LITTLE;
569 afSetVirtualByteOrder(file, AF_DEFAULT_TRACK,
570 AF_BYTEORDER_LITTLEENDIAN);
571 #else
572 #warning Unknown endianness
573 #endif
574
575 bytes_per_frame = format.bits * format.channels / 8;
576
577 device = ao_open_live(ao_driver, &format, NULL);
578
579 if(device) {
580 int frames_read;
581 char buf[4096];
582 int buf_frames = sizeof(buf) / bytes_per_frame;
583
584 while((frames_read = afReadFrames(file, AF_DEFAULT_TRACK,
585 buf, buf_frames))) {
586 /* no need to scale at volume == 50 */
587 if(volume != 50)
588 scale_pcm_data(buf, frames_read, format.bits, format.channels,
589 intercept, minclip, maxclip, scale);
590 if(!ao_play(device, buf, frames_read * bytes_per_frame))
591 break;
592 }
593 ao_close(device);
594 }
595 afCloseFile(file);
596 } 455 }
597 ao_shutdown(); 456 } else if (!strcmp(method, "esd")) {
598 _exit(0); 457 sink = gst_element_factory_make("esdsink", "sink");
599 } else { 458 } else if (!strcmp(method, "arts")) {
600 /* Parent process */ 459 sink = gst_element_factory_make("artssink", "sink");
601 gaim_timeout_add(PLAY_SOUND_TIMEOUT, expire_old_child, GINT_TO_POINTER(pid)); 460 } else if (!strcmp(method, "nas")) {
602 } 461 sink = gst_element_factory_make("nassink", "sink");
603 #else /* USE_AO */ 462 }
463
464 uri = g_strdup_printf("file://%s", filename);
465 play = gst_element_factory_make("playbin", "play");
466
467 g_object_set(G_OBJECT(play), "uri", uri,
468 "volume", volume,
469 "audio-sink", sink, NULL);
470
471 gst_bus_add_watch(gst_pipeline_get_bus(GST_PIPELINE(play)),
472 bus_call, play);
473 gst_element_set_state(play, GST_STATE_PLAYING);
474
475 g_free(uri);
476
477 #else /* USE_GSTREAMER */
604 gdk_beep(); 478 gdk_beep();
605 return; 479 return;
606 #endif /* USE_AO */ 480 #endif /* USE_GSTREAMER */
607 #else /* _WIN32 */ 481 #else /* _WIN32 */
608 gaim_debug_info("sound", "Playing %s\n", filename); 482 gaim_debug_info("sound", "Playing %s\n", filename);
609 483
610 if (G_WIN32_HAVE_WIDECHAR_API ()) { 484 if (G_WIN32_HAVE_WIDECHAR_API ()) {
611 wchar_t *wc_filename = g_utf8_to_utf16(filename, 485 wchar_t *wc_filename = g_utf8_to_utf16(filename,