Mercurial > pidgin.yaz
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, |