1 /*-*- Mode: C; c-basic-offset: 2 -*-*/
4 * GStreamer pulseaudio plugin
6 * Copyright (c) 2004-2008 Lennart Poettering
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.
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.
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
30 #include "pulsemixerctrl.h"
31 #include "pulsemixertrack.h"
32 #include "pulseutil.h"
34 GST_DEBUG_CATEGORY_EXTERN (pulse_debug);
35 #define GST_CAT_DEFAULT pulse_debug
38 gst_pulsemixer_ctrl_context_state_cb (pa_context * context, void *userdata)
40 GstPulseMixerCtrl *c = GST_PULSEMIXER_CTRL (userdata);
42 /* Called from the background thread! */
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);
51 case PA_CONTEXT_UNCONNECTED:
52 case PA_CONTEXT_CONNECTING:
53 case PA_CONTEXT_AUTHORIZING:
54 case PA_CONTEXT_SETTING_NAME:
60 gst_pulsemixer_ctrl_sink_info_cb (pa_context * context, const pa_sink_info * i,
61 int eol, void *userdata)
63 GstPulseMixerCtrl *c = userdata;
64 gboolean vol_chg = FALSE;
67 /* Called from the background thread! */
69 if (c->outstandig_queries > 0)
70 c->outstandig_queries--;
72 if (c->ignore_queries > 0 || c->time_event) {
74 if (c->ignore_queries > 0)
81 c->operation_success = FALSE;
82 pa_threaded_mainloop_signal (c->mainloop, 0);
90 g_free (c->description);
91 c->name = g_strdup (i->name);
92 c->description = g_strdup (i->description);
94 c->channel_map = i->channel_map;
95 vol_chg = !pa_cvolume_equal (&c->volume, &i->volume);
96 c->volume = i->volume;
99 c->type = GST_PULSEMIXER_SINK;
102 GstMixerTrackFlags flags = c->track->flags;
105 (flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0);
106 c->track->flags = flags;
109 c->operation_success = TRUE;
110 pa_threaded_mainloop_signal (c->mainloop, 0);
112 if (vol_chg && c->track) {
113 gint volumes[PA_CHANNELS_MAX];
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);
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);
127 gst_pulsemixer_ctrl_source_info_cb (pa_context * context,
128 const pa_source_info * i, int eol, void *userdata)
130 GstPulseMixerCtrl *c = userdata;
131 gboolean vol_chg = FALSE;
134 /* Called from the background thread! */
136 if (c->outstandig_queries > 0)
137 c->outstandig_queries--;
139 if (c->ignore_queries > 0 || c->time_event) {
141 if (c->ignore_queries > 0)
148 c->operation_success = FALSE;
149 pa_threaded_mainloop_signal (c->mainloop, 0);
157 g_free (c->description);
158 c->name = g_strdup (i->name);
159 c->description = g_strdup (i->description);
161 c->channel_map = i->channel_map;
162 vol_chg = !pa_cvolume_equal (&c->volume, &i->volume);
163 c->volume = i->volume;
165 c->muted = !!i->mute;
166 c->type = GST_PULSEMIXER_SOURCE;
169 GstMixerTrackFlags flags = c->track->flags;
172 (flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0);
173 c->track->flags = flags;
176 c->operation_success = TRUE;
177 pa_threaded_mainloop_signal (c->mainloop, 0);
179 if (vol_chg && c->track) {
180 gint volumes[PA_CHANNELS_MAX];
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);
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);
194 gst_pulsemixer_ctrl_subscribe_cb (pa_context * context,
195 pa_subscription_event_type_t t, uint32_t idx, void *userdata)
197 GstPulseMixerCtrl *c = GST_PULSEMIXER_CTRL (userdata);
198 pa_operation *o = NULL;
200 /* Called from the background thread! */
205 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
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);
212 o = pa_context_get_source_info_by_index (c->context, c->index,
213 gst_pulsemixer_ctrl_source_info_cb, c);
216 GST_WARNING_OBJECT (c->object, "Failed to get sink info: %s",
217 pa_strerror (pa_context_errno (c->context)));
221 pa_operation_unref (o);
223 c->outstandig_queries++;
227 gst_pulsemixer_ctrl_success_cb (pa_context * context, int success,
230 GstPulseMixerCtrl *c = (GstPulseMixerCtrl *) userdata;
232 c->operation_success = !!success;
233 pa_threaded_mainloop_signal (c->mainloop, 0);
236 #define CHECK_DEAD_GOTO(c, label) \
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"); \
247 gst_pulsemixer_ctrl_open (GstPulseMixerCtrl * c)
251 pa_operation *o = NULL;
255 GST_DEBUG_OBJECT (c->object, "ctrl open");
257 c->mainloop = pa_threaded_mainloop_new ();
261 e = pa_threaded_mainloop_start (c->mainloop);
265 name = gst_pulse_client_name ();
267 pa_threaded_mainloop_lock (c->mainloop);
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;
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);
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;
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);
292 /* Subscribe to events */
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;
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);
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;
313 pa_operation_unref (o);
318 if (c->type == GST_PULSEMIXER_UNKNOWN || c->type == GST_PULSEMIXER_SINK) {
319 GST_WARNING_OBJECT (c->object, "Get info for '%s'", c->device);
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;
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);
334 pa_operation_unref (o);
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;
345 if (c->type == GST_PULSEMIXER_UNKNOWN || c->type == GST_PULSEMIXER_SOURCE) {
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;
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);
360 pa_operation_unref (o);
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;
370 g_assert (c->type != GST_PULSEMIXER_UNKNOWN);
372 c->track = gst_pulsemixer_track_new (c);
373 c->tracklist = g_list_append (c->tracklist, c->track);
375 pa_threaded_mainloop_unlock (c->mainloop);
383 pa_operation_unref (o);
386 pa_threaded_mainloop_unlock (c->mainloop);
394 gst_pulsemixer_ctrl_close (GstPulseMixerCtrl * c)
398 GST_DEBUG_OBJECT (c->object, "ctrl close");
401 pa_threaded_mainloop_stop (c->mainloop);
404 pa_context_disconnect (c->context);
405 pa_context_unref (c->context);
410 pa_threaded_mainloop_free (c->mainloop);
412 c->time_event = NULL;
416 g_list_free (c->tracklist);
421 GST_PULSEMIXER_TRACK (c->track)->control = NULL;
422 g_object_unref (c->track);
428 gst_pulsemixer_ctrl_new (GObject * object, const gchar * server,
429 const gchar * device, GstPulseMixerType type)
431 GstPulseMixerCtrl *c = NULL;
432 g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE ((object),
435 GST_DEBUG_OBJECT (object, "new mixer ctrl for %s", device);
436 c = g_new (GstPulseMixerCtrl, 1);
437 c->object = g_object_ref (object);
439 c->server = g_strdup (server);
440 c->device = g_strdup (device);
444 c->ignore_queries = c->outstandig_queries = 0;
446 pa_cvolume_mute (&c->volume, PA_CHANNELS_MAX);
447 pa_channel_map_init (&c->channel_map);
449 c->index = PA_INVALID_INDEX;
452 c->description = NULL;
454 c->time_event = NULL;
455 c->update_volume = c->update_mute = FALSE;
457 if (!(gst_pulsemixer_ctrl_open (c))) {
458 gst_pulsemixer_ctrl_free (c);
466 gst_pulsemixer_ctrl_free (GstPulseMixerCtrl * c)
470 gst_pulsemixer_ctrl_close (c);
475 g_free (c->description);
476 g_object_unref (c->object);
481 gst_pulsemixer_ctrl_list_tracks (GstPulseMixerCtrl * c)
489 gst_pulsemixer_ctrl_timeout_event (pa_mainloop_api * a, pa_time_event * e,
490 const struct timeval *tv, void *userdata)
493 GstPulseMixerCtrl *c = GST_PULSEMIXER_CTRL (userdata);
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,
500 o = pa_context_set_source_volume_by_index (c->context, c->index,
501 &c->volume, NULL, NULL);
504 GST_WARNING_OBJECT (c->object, "Failed to set device volume: %s",
505 pa_strerror (pa_context_errno (c->context)));
507 pa_operation_unref (o);
509 c->update_volume = FALSE;
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,
517 o = pa_context_set_source_mute_by_index (c->context, c->index, c->muted,
521 GST_WARNING_OBJECT (c->object, "Failed to set device mute: %s",
522 pa_strerror (pa_context_errno (c->context)));
524 pa_operation_unref (o);
526 c->update_mute = FALSE;
529 /* Make sure that all outstanding queries are being ignored */
530 c->ignore_queries = c->outstandig_queries;
532 g_assert (e == c->time_event);
534 c->time_event = NULL;
537 #define UPDATE_DELAY 50000
540 restart_time_event (GstPulseMixerCtrl * c)
543 pa_mainloop_api *api;
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 */
554 api = pa_threaded_mainloop_get_api (c->mainloop);
557 api->time_new (api, pa_timeval_add (pa_gettimeofday (&tv), UPDATE_DELAY),
558 gst_pulsemixer_ctrl_timeout_event, c);
562 gst_pulsemixer_ctrl_set_volume (GstPulseMixerCtrl * c, GstMixerTrack * track,
569 g_assert (track == c->track);
571 pa_threaded_mainloop_lock (c->mainloop);
573 for (i = 0; i < c->channel_map.channels; i++)
574 v.values[i] = (pa_volume_t) volumes[i];
576 v.channels = c->channel_map.channels;
579 c->update_volume = TRUE;
581 restart_time_event (c);
583 pa_threaded_mainloop_unlock (c->mainloop);
587 gst_pulsemixer_ctrl_get_volume (GstPulseMixerCtrl * c, GstMixerTrack * track,
593 g_assert (track == c->track);
595 pa_threaded_mainloop_lock (c->mainloop);
597 for (i = 0; i < c->channel_map.channels; i++)
598 volumes[i] = c->volume.values[i];
600 pa_threaded_mainloop_unlock (c->mainloop);
604 gst_pulsemixer_ctrl_set_record (GstPulseMixerCtrl * c, GstMixerTrack * track,
608 g_assert (track == c->track);
612 gst_pulsemixer_ctrl_set_mute (GstPulseMixerCtrl * c, GstMixerTrack * track,
616 g_assert (track == c->track);
618 pa_threaded_mainloop_lock (c->mainloop);
621 c->update_mute = TRUE;
624 GstMixerTrackFlags flags = c->track->flags;
627 (flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0);
628 c->track->flags = flags;
631 restart_time_event (c);
633 pa_threaded_mainloop_unlock (c->mainloop);
637 gst_pulsemixer_ctrl_get_mixer_flags (GstPulseMixerCtrl * mixer)
639 return GST_MIXER_FLAG_AUTO_NOTIFICATIONS;