Tizen 2.0 Release
[framework/multimedia/gst-plugins-good0.10.git] / ext / pulse / pulsemixerctrl.c
1 /*-*- Mode: C; c-basic-offset: 2 -*-*/
2
3 /*
4  *  GStreamer pulseaudio plugin
5  *
6  *  Copyright (c) 2004-2008 Lennart Poettering
7  *
8  *  gst-pulse is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU Lesser General Public License as
10  *  published by the Free Software Foundation; either version 2.1 of the
11  *  License, or (at your option) any later version.
12  *
13  *  gst-pulse is distributed in the hope that it will be useful, but
14  *  WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  *  Lesser General Public License for more details.
17  *
18  *  You should have received a copy of the GNU Lesser General Public
19  *  License along with gst-pulse; if not, write to the Free Software
20  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21  *  USA.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <gst/gst.h>
29
30 #include "pulsemixerctrl.h"
31 #include "pulsemixertrack.h"
32 #include "pulseutil.h"
33
34 GST_DEBUG_CATEGORY_EXTERN (pulse_debug);
35 #define GST_CAT_DEFAULT pulse_debug
36
37 static void
38 gst_pulsemixer_ctrl_context_state_cb (pa_context * context, void *userdata)
39 {
40   GstPulseMixerCtrl *c = GST_PULSEMIXER_CTRL (userdata);
41
42   /* Called from the background thread! */
43
44   switch (pa_context_get_state (context)) {
45     case PA_CONTEXT_READY:
46     case PA_CONTEXT_TERMINATED:
47     case PA_CONTEXT_FAILED:
48       pa_threaded_mainloop_signal (c->mainloop, 0);
49       break;
50
51     case PA_CONTEXT_UNCONNECTED:
52     case PA_CONTEXT_CONNECTING:
53     case PA_CONTEXT_AUTHORIZING:
54     case PA_CONTEXT_SETTING_NAME:
55       break;
56   }
57 }
58
59 static void
60 gst_pulsemixer_ctrl_sink_info_cb (pa_context * context, const pa_sink_info * i,
61     int eol, void *userdata)
62 {
63   GstPulseMixerCtrl *c = userdata;
64   gboolean vol_chg = FALSE;
65   gboolean old_mute;
66
67   /* Called from the background thread! */
68
69   if (c->outstandig_queries > 0)
70     c->outstandig_queries--;
71
72   if (c->ignore_queries > 0 || c->time_event) {
73
74     if (c->ignore_queries > 0)
75       c->ignore_queries--;
76
77     return;
78   }
79
80   if (!i && eol < 0) {
81     c->operation_success = FALSE;
82     pa_threaded_mainloop_signal (c->mainloop, 0);
83     return;
84   }
85
86   if (eol)
87     return;
88
89   g_free (c->name);
90   g_free (c->description);
91   c->name = g_strdup (i->name);
92   c->description = g_strdup (i->description);
93   c->index = i->index;
94   c->channel_map = i->channel_map;
95   vol_chg = !pa_cvolume_equal (&c->volume, &i->volume);
96   c->volume = i->volume;
97   old_mute = c->muted;
98   c->muted = !!i->mute;
99   c->type = GST_PULSEMIXER_SINK;
100
101   if (c->track) {
102     GstMixerTrackFlags flags = c->track->flags;
103
104     flags =
105         (flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0);
106     c->track->flags = flags;
107   }
108
109   c->operation_success = TRUE;
110   pa_threaded_mainloop_signal (c->mainloop, 0);
111
112   if (vol_chg && c->track) {
113     gint volumes[PA_CHANNELS_MAX];
114     gint i;
115     for (i = 0; i < c->volume.channels; i++)
116       volumes[i] = (gint) (c->volume.values[i]);
117     GST_LOG_OBJECT (c->object, "Sending volume change notification");
118     gst_mixer_volume_changed (GST_MIXER (c->object), c->track, volumes);
119   }
120   if ((c->muted != old_mute) && c->track) {
121     GST_LOG_OBJECT (c->object, "Sending mute toggled notification");
122     gst_mixer_mute_toggled (GST_MIXER (c->object), c->track, c->muted);
123   }
124 }
125
126 static void
127 gst_pulsemixer_ctrl_source_info_cb (pa_context * context,
128     const pa_source_info * i, int eol, void *userdata)
129 {
130   GstPulseMixerCtrl *c = userdata;
131   gboolean vol_chg = FALSE;
132   gboolean old_mute;
133
134   /* Called from the background thread! */
135
136   if (c->outstandig_queries > 0)
137     c->outstandig_queries--;
138
139   if (c->ignore_queries > 0 || c->time_event) {
140
141     if (c->ignore_queries > 0)
142       c->ignore_queries--;
143
144     return;
145   }
146
147   if (!i && eol < 0) {
148     c->operation_success = FALSE;
149     pa_threaded_mainloop_signal (c->mainloop, 0);
150     return;
151   }
152
153   if (eol)
154     return;
155
156   g_free (c->name);
157   g_free (c->description);
158   c->name = g_strdup (i->name);
159   c->description = g_strdup (i->description);
160   c->index = i->index;
161   c->channel_map = i->channel_map;
162   vol_chg = !pa_cvolume_equal (&c->volume, &i->volume);
163   c->volume = i->volume;
164   old_mute = c->muted;
165   c->muted = !!i->mute;
166   c->type = GST_PULSEMIXER_SOURCE;
167
168   if (c->track) {
169     GstMixerTrackFlags flags = c->track->flags;
170
171     flags =
172         (flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0);
173     c->track->flags = flags;
174   }
175
176   c->operation_success = TRUE;
177   pa_threaded_mainloop_signal (c->mainloop, 0);
178
179   if (vol_chg && c->track) {
180     gint volumes[PA_CHANNELS_MAX];
181     gint i;
182     for (i = 0; i < c->volume.channels; i++)
183       volumes[i] = (gint) (c->volume.values[i]);
184     GST_LOG_OBJECT (c->object, "Sending volume change notification");
185     gst_mixer_volume_changed (GST_MIXER (c->object), c->track, volumes);
186   }
187   if ((c->muted != old_mute) && c->track) {
188     GST_LOG_OBJECT (c->object, "Sending mute toggled notification");
189     gst_mixer_mute_toggled (GST_MIXER (c->object), c->track, c->muted);
190   }
191 }
192
193 static void
194 gst_pulsemixer_ctrl_subscribe_cb (pa_context * context,
195     pa_subscription_event_type_t t, uint32_t idx, void *userdata)
196 {
197   GstPulseMixerCtrl *c = GST_PULSEMIXER_CTRL (userdata);
198   pa_operation *o = NULL;
199
200   /* Called from the background thread! */
201
202   if (c->index != idx)
203     return;
204
205   if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
206     return;
207
208   if (c->type == GST_PULSEMIXER_SINK)
209     o = pa_context_get_sink_info_by_index (c->context, c->index,
210         gst_pulsemixer_ctrl_sink_info_cb, c);
211   else
212     o = pa_context_get_source_info_by_index (c->context, c->index,
213         gst_pulsemixer_ctrl_source_info_cb, c);
214
215   if (!o) {
216     GST_WARNING_OBJECT (c->object, "Failed to get sink info: %s",
217         pa_strerror (pa_context_errno (c->context)));
218     return;
219   }
220
221   pa_operation_unref (o);
222
223   c->outstandig_queries++;
224 }
225
226 static void
227 gst_pulsemixer_ctrl_success_cb (pa_context * context, int success,
228     void *userdata)
229 {
230   GstPulseMixerCtrl *c = (GstPulseMixerCtrl *) userdata;
231
232   c->operation_success = !!success;
233   pa_threaded_mainloop_signal (c->mainloop, 0);
234 }
235
236 #define CHECK_DEAD_GOTO(c, label)                                       \
237   G_STMT_START {                                                        \
238     if (!(c)->context ||                                                \
239         !PA_CONTEXT_IS_GOOD(pa_context_get_state((c)->context))) {      \
240       GST_WARNING_OBJECT ((c)->object, "Not connected: %s",             \
241                           (c)->context ? pa_strerror(pa_context_errno((c)->context)) : "NULL"); \
242       goto label;                                                       \
243     }                                                                   \
244   } G_STMT_END
245
246 static gboolean
247 gst_pulsemixer_ctrl_open (GstPulseMixerCtrl * c)
248 {
249   int e;
250   gchar *name;
251   pa_operation *o = NULL;
252
253   g_assert (c);
254
255   GST_DEBUG_OBJECT (c->object, "ctrl open");
256
257   c->mainloop = pa_threaded_mainloop_new ();
258   if (!c->mainloop)
259     return FALSE;
260
261   e = pa_threaded_mainloop_start (c->mainloop);
262   if (e < 0)
263     return FALSE;
264
265   name = gst_pulse_client_name ();
266
267   pa_threaded_mainloop_lock (c->mainloop);
268
269   if (!(c->context =
270           pa_context_new (pa_threaded_mainloop_get_api (c->mainloop), name))) {
271     GST_WARNING_OBJECT (c->object, "Failed to create context");
272     goto unlock_and_fail;
273   }
274
275   pa_context_set_state_callback (c->context,
276       gst_pulsemixer_ctrl_context_state_cb, c);
277   pa_context_set_subscribe_callback (c->context,
278       gst_pulsemixer_ctrl_subscribe_cb, c);
279
280   if (pa_context_connect (c->context, c->server, 0, NULL) < 0) {
281     GST_WARNING_OBJECT (c->object, "Failed to connect context: %s",
282         pa_strerror (pa_context_errno (c->context)));
283     goto unlock_and_fail;
284   }
285
286   /* Wait until the context is ready */
287   while (pa_context_get_state (c->context) != PA_CONTEXT_READY) {
288     CHECK_DEAD_GOTO (c, unlock_and_fail);
289     pa_threaded_mainloop_wait (c->mainloop);
290   }
291
292   /* Subscribe to events */
293   if (!(o =
294           pa_context_subscribe (c->context,
295               PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE,
296               gst_pulsemixer_ctrl_success_cb, c))) {
297     GST_WARNING_OBJECT (c->object, "Failed to subscribe to events: %s",
298         pa_strerror (pa_context_errno (c->context)));
299     goto unlock_and_fail;
300   }
301
302   c->operation_success = FALSE;
303   while (pa_operation_get_state (o) != PA_OPERATION_DONE) {
304     CHECK_DEAD_GOTO (c, unlock_and_fail);
305     pa_threaded_mainloop_wait (c->mainloop);
306   }
307
308   if (!c->operation_success) {
309     GST_WARNING_OBJECT (c->object, "Failed to subscribe to events: %s",
310         pa_strerror (pa_context_errno (c->context)));
311     goto unlock_and_fail;
312   }
313   pa_operation_unref (o);
314   o = NULL;
315
316   /* Get sink info */
317
318   if (c->type == GST_PULSEMIXER_UNKNOWN || c->type == GST_PULSEMIXER_SINK) {
319     GST_WARNING_OBJECT (c->object, "Get info for '%s'", c->device);
320     if (!(o =
321             pa_context_get_sink_info_by_name (c->context, c->device,
322                 gst_pulsemixer_ctrl_sink_info_cb, c))) {
323       GST_WARNING_OBJECT (c->object, "Failed to get sink info: %s",
324           pa_strerror (pa_context_errno (c->context)));
325       goto unlock_and_fail;
326     }
327
328     c->operation_success = FALSE;
329     while (pa_operation_get_state (o) != PA_OPERATION_DONE) {
330       CHECK_DEAD_GOTO (c, unlock_and_fail);
331       pa_threaded_mainloop_wait (c->mainloop);
332     }
333
334     pa_operation_unref (o);
335     o = NULL;
336
337     if (!c->operation_success && (c->type == GST_PULSEMIXER_SINK
338             || pa_context_errno (c->context) != PA_ERR_NOENTITY)) {
339       GST_WARNING_OBJECT (c->object, "Failed to get sink info: %s",
340           pa_strerror (pa_context_errno (c->context)));
341       goto unlock_and_fail;
342     }
343   }
344
345   if (c->type == GST_PULSEMIXER_UNKNOWN || c->type == GST_PULSEMIXER_SOURCE) {
346     if (!(o =
347             pa_context_get_source_info_by_name (c->context, c->device,
348                 gst_pulsemixer_ctrl_source_info_cb, c))) {
349       GST_WARNING_OBJECT (c->object, "Failed to get source info: %s",
350           pa_strerror (pa_context_errno (c->context)));
351       goto unlock_and_fail;
352     }
353
354     c->operation_success = FALSE;
355     while (pa_operation_get_state (o) != PA_OPERATION_DONE) {
356       CHECK_DEAD_GOTO (c, unlock_and_fail);
357       pa_threaded_mainloop_wait (c->mainloop);
358     }
359
360     pa_operation_unref (o);
361     o = NULL;
362
363     if (!c->operation_success) {
364       GST_WARNING_OBJECT (c->object, "Failed to get source info: %s",
365           pa_strerror (pa_context_errno (c->context)));
366       goto unlock_and_fail;
367     }
368   }
369
370   g_assert (c->type != GST_PULSEMIXER_UNKNOWN);
371
372   c->track = gst_pulsemixer_track_new (c);
373   c->tracklist = g_list_append (c->tracklist, c->track);
374
375   pa_threaded_mainloop_unlock (c->mainloop);
376   g_free (name);
377
378   return TRUE;
379
380 unlock_and_fail:
381
382   if (o)
383     pa_operation_unref (o);
384
385   if (c->mainloop)
386     pa_threaded_mainloop_unlock (c->mainloop);
387
388   g_free (name);
389
390   return FALSE;
391 }
392
393 static void
394 gst_pulsemixer_ctrl_close (GstPulseMixerCtrl * c)
395 {
396   g_assert (c);
397
398   GST_DEBUG_OBJECT (c->object, "ctrl close");
399
400   if (c->mainloop)
401     pa_threaded_mainloop_stop (c->mainloop);
402
403   if (c->context) {
404     pa_context_disconnect (c->context);
405     pa_context_unref (c->context);
406     c->context = NULL;
407   }
408
409   if (c->mainloop) {
410     pa_threaded_mainloop_free (c->mainloop);
411     c->mainloop = NULL;
412     c->time_event = NULL;
413   }
414
415   if (c->tracklist) {
416     g_list_free (c->tracklist);
417     c->tracklist = NULL;
418   }
419
420   if (c->track) {
421     GST_PULSEMIXER_TRACK (c->track)->control = NULL;
422     g_object_unref (c->track);
423     c->track = NULL;
424   }
425 }
426
427 GstPulseMixerCtrl *
428 gst_pulsemixer_ctrl_new (GObject * object, const gchar * server,
429     const gchar * device, GstPulseMixerType type)
430 {
431   GstPulseMixerCtrl *c = NULL;
432   g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE ((object),
433           GST_TYPE_MIXER), c);
434
435   GST_DEBUG_OBJECT (object, "new mixer ctrl for %s", device);
436   c = g_new (GstPulseMixerCtrl, 1);
437   c->object = g_object_ref (object);
438   c->tracklist = NULL;
439   c->server = g_strdup (server);
440   c->device = g_strdup (device);
441   c->mainloop = NULL;
442   c->context = NULL;
443   c->track = NULL;
444   c->ignore_queries = c->outstandig_queries = 0;
445
446   pa_cvolume_mute (&c->volume, PA_CHANNELS_MAX);
447   pa_channel_map_init (&c->channel_map);
448   c->muted = FALSE;
449   c->index = PA_INVALID_INDEX;
450   c->type = type;
451   c->name = NULL;
452   c->description = NULL;
453
454   c->time_event = NULL;
455   c->update_volume = c->update_mute = FALSE;
456
457   if (!(gst_pulsemixer_ctrl_open (c))) {
458     gst_pulsemixer_ctrl_free (c);
459     return NULL;
460   }
461
462   return c;
463 }
464
465 void
466 gst_pulsemixer_ctrl_free (GstPulseMixerCtrl * c)
467 {
468   g_assert (c);
469
470   gst_pulsemixer_ctrl_close (c);
471
472   g_free (c->server);
473   g_free (c->device);
474   g_free (c->name);
475   g_free (c->description);
476   g_object_unref (c->object);
477   g_free (c);
478 }
479
480 const GList *
481 gst_pulsemixer_ctrl_list_tracks (GstPulseMixerCtrl * c)
482 {
483   g_assert (c);
484
485   return c->tracklist;
486 }
487
488 static void
489 gst_pulsemixer_ctrl_timeout_event (pa_mainloop_api * a, pa_time_event * e,
490     const struct timeval *tv, void *userdata)
491 {
492   pa_operation *o;
493   GstPulseMixerCtrl *c = GST_PULSEMIXER_CTRL (userdata);
494
495   if (c->update_volume) {
496     if (c->type == GST_PULSEMIXER_SINK)
497       o = pa_context_set_sink_volume_by_index (c->context, c->index, &c->volume,
498           NULL, NULL);
499     else
500       o = pa_context_set_source_volume_by_index (c->context, c->index,
501           &c->volume, NULL, NULL);
502
503     if (!o)
504       GST_WARNING_OBJECT (c->object, "Failed to set device volume: %s",
505           pa_strerror (pa_context_errno (c->context)));
506     else
507       pa_operation_unref (o);
508
509     c->update_volume = FALSE;
510   }
511
512   if (c->update_mute) {
513     if (c->type == GST_PULSEMIXER_SINK)
514       o = pa_context_set_sink_mute_by_index (c->context, c->index, c->muted,
515           NULL, NULL);
516     else
517       o = pa_context_set_source_mute_by_index (c->context, c->index, c->muted,
518           NULL, NULL);
519
520     if (!o)
521       GST_WARNING_OBJECT (c->object, "Failed to set device mute: %s",
522           pa_strerror (pa_context_errno (c->context)));
523     else
524       pa_operation_unref (o);
525
526     c->update_mute = FALSE;
527   }
528
529   /* Make sure that all outstanding queries are being ignored */
530   c->ignore_queries = c->outstandig_queries;
531
532   g_assert (e == c->time_event);
533   a->time_free (e);
534   c->time_event = NULL;
535 }
536
537 #define UPDATE_DELAY 50000
538
539 static void
540 restart_time_event (GstPulseMixerCtrl * c)
541 {
542   struct timeval tv;
543   pa_mainloop_api *api;
544
545   g_assert (c);
546
547   if (c->time_event)
548     return;
549
550   /* Updating the volume too often will cause a lot of traffic
551    * when accessing a networked server. Therefore we make sure
552    * to update the volume only once every 50ms */
553
554   api = pa_threaded_mainloop_get_api (c->mainloop);
555
556   c->time_event =
557       api->time_new (api, pa_timeval_add (pa_gettimeofday (&tv), UPDATE_DELAY),
558       gst_pulsemixer_ctrl_timeout_event, c);
559 }
560
561 void
562 gst_pulsemixer_ctrl_set_volume (GstPulseMixerCtrl * c, GstMixerTrack * track,
563     gint * volumes)
564 {
565   pa_cvolume v;
566   int i;
567
568   g_assert (c);
569   g_assert (track == c->track);
570
571   pa_threaded_mainloop_lock (c->mainloop);
572
573   for (i = 0; i < c->channel_map.channels; i++)
574     v.values[i] = (pa_volume_t) volumes[i];
575
576   v.channels = c->channel_map.channels;
577
578   c->volume = v;
579   c->update_volume = TRUE;
580
581   restart_time_event (c);
582
583   pa_threaded_mainloop_unlock (c->mainloop);
584 }
585
586 void
587 gst_pulsemixer_ctrl_get_volume (GstPulseMixerCtrl * c, GstMixerTrack * track,
588     gint * volumes)
589 {
590   int i;
591
592   g_assert (c);
593   g_assert (track == c->track);
594
595   pa_threaded_mainloop_lock (c->mainloop);
596
597   for (i = 0; i < c->channel_map.channels; i++)
598     volumes[i] = c->volume.values[i];
599
600   pa_threaded_mainloop_unlock (c->mainloop);
601 }
602
603 void
604 gst_pulsemixer_ctrl_set_record (GstPulseMixerCtrl * c, GstMixerTrack * track,
605     gboolean record)
606 {
607   g_assert (c);
608   g_assert (track == c->track);
609 }
610
611 void
612 gst_pulsemixer_ctrl_set_mute (GstPulseMixerCtrl * c, GstMixerTrack * track,
613     gboolean mute)
614 {
615   g_assert (c);
616   g_assert (track == c->track);
617
618   pa_threaded_mainloop_lock (c->mainloop);
619
620   c->muted = mute;
621   c->update_mute = TRUE;
622
623   if (c->track) {
624     GstMixerTrackFlags flags = c->track->flags;
625
626     flags =
627         (flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0);
628     c->track->flags = flags;
629   }
630
631   restart_time_event (c);
632
633   pa_threaded_mainloop_unlock (c->mainloop);
634 }
635
636 GstMixerFlags
637 gst_pulsemixer_ctrl_get_mixer_flags (GstPulseMixerCtrl * mixer)
638 {
639   return GST_MIXER_FLAG_AUTO_NOTIFICATIONS;
640 }