2 * This file is a part of MAFW
4 * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved.
6 * Contact: Visa Smolander <visa.smolander@nokia.com>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation; version 2.1 of
11 * the License, or (at your option) any later version.
13 * This library 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 this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
28 #ifndef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME
30 #include <pulse/pulseaudio.h>
31 #include <pulse/glib-mainloop.h>
32 #include <pulse/ext-stream-restore.h>
35 #include "mafw-gst-renderer-worker-volume.h"
39 #define G_LOG_DOMAIN "mafw-gst-renderer-worker-volume"
41 #define MAFW_GST_RENDERER_WORKER_VOLUME_SERVER NULL
43 #define MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PROPERTY "PULSE_PROP_media.role"
44 #define MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PREFIX "sink-input-by-media-role:"
45 #define MAFW_GST_RENDERER_WORKER_VOLUME_ROLE "x-maemo"
47 #define MAFW_GST_RENDERER_WORKER_SET_TIMEOUT 200
50 struct _MafwGstRendererWorkerVolume {
51 pa_glib_mainloop *mainloop;
55 MafwGstRendererWorkerVolumeChangedCb cb;
57 MafwGstRendererWorkerVolumeMuteCb mute_cb;
58 gpointer mute_user_data;
59 gdouble current_volume;
60 gboolean current_mute;
61 gboolean pending_operation;
62 gdouble pending_operation_volume;
63 gboolean pending_operation_mute;
64 guint change_request_id;
65 pa_operation *pa_operation;
69 MafwGstRendererWorkerVolume *wvolume;
70 MafwGstRendererWorkerVolumeInitCb cb;
74 #define _pa_volume_to_per_one(volume) \
75 ((guint) ((((gdouble)(volume) / (gdouble) PA_VOLUME_NORM) + \
76 (gdouble) 0.005) * (gdouble) 100.0) / (gdouble) 100.0)
77 #define _pa_volume_from_per_one(volume) \
78 ((pa_volume_t)((gdouble)(volume) * (gdouble) PA_VOLUME_NORM))
80 #define _pa_operation_running(wvolume) \
81 (wvolume->pa_operation != NULL && \
82 pa_operation_get_state(wvolume->pa_operation) == PA_OPERATION_RUNNING)
84 static void _state_cb_init(pa_context *c, void *data);
87 static gchar *_get_client_name(void) {
91 if (pa_get_binary_name(buf, sizeof(buf)))
92 name = g_strdup_printf("mafw-gst-renderer[%s]", buf);
94 name = g_strdup("mafw-gst-renderer");
99 static void _ext_stream_restore_read_cb(pa_context *c,
100 const pa_ext_stream_restore2_info *i,
104 MafwGstRendererWorkerVolume *wvolume = userdata;
109 g_critical("eol parameter should not be < 1. "
110 "Discarding volume event");
115 strcmp(i->name, MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PREFIX
116 MAFW_GST_RENDERER_WORKER_VOLUME_ROLE) != 0) {
120 volume = _pa_volume_to_per_one(pa_cvolume_max(&i->volume));
121 mute = i->mute != 0 ? TRUE : FALSE;
123 if (_pa_operation_running(wvolume) ||
124 (wvolume->pending_operation &&
125 (wvolume->pending_operation_volume != volume ||
126 wvolume->pending_operation_mute != mute))) {
127 g_debug("volume notification, but operation running, ignoring");
131 wvolume->pulse_volume = volume;
132 wvolume->pulse_mute = mute;
135 g_debug("ext stream volume is %lf (mute: %d) for role %s in device %s",
136 wvolume->pulse_volume, wvolume->pulse_mute, i->name, i->device);
137 if (!wvolume->pending_operation &&
138 wvolume->pulse_volume != wvolume->current_volume) {
139 wvolume->current_volume = wvolume->pulse_volume;
140 if (wvolume->cb != NULL) {
141 g_debug("signalling volume");
142 wvolume->cb(wvolume, wvolume->current_volume,
146 if (!wvolume->pending_operation &&
147 wvolume->pulse_mute != wvolume->current_mute) {
148 wvolume->current_mute = wvolume->pulse_mute;
149 if (wvolume->mute_cb != NULL) {
150 g_debug("signalling mute");
151 wvolume->mute_cb(wvolume, wvolume->current_mute,
152 wvolume->mute_user_data);
156 wvolume->pending_operation = FALSE;
159 static void _destroy_context(MafwGstRendererWorkerVolume *wvolume)
161 if (wvolume->pa_operation != NULL) {
162 if (pa_operation_get_state(wvolume->pa_operation) ==
163 PA_OPERATION_RUNNING) {
164 pa_operation_cancel(wvolume->pa_operation);
166 pa_operation_unref(wvolume->pa_operation);
167 wvolume->pa_operation = NULL;
169 pa_context_unref(wvolume->context);
172 static InitCbClosure *_init_cb_closure_new(MafwGstRendererWorkerVolume *wvolume,
173 MafwGstRendererWorkerVolumeInitCb cb,
176 InitCbClosure *closure;
178 closure = g_new(InitCbClosure, 1);
179 closure->wvolume = wvolume;
181 closure->user_data = user_data;
186 static void _connect(gpointer user_data)
189 pa_mainloop_api *api = NULL;
190 InitCbClosure *closure = user_data;
191 MafwGstRendererWorkerVolume *wvolume = closure->wvolume;
193 name = _get_client_name();
195 /* get the mainloop api and create a context */
196 api = pa_glib_mainloop_get_api(wvolume->mainloop);
197 wvolume->context = pa_context_new(api, name);
198 g_assert(wvolume->context != NULL);
200 /* register some essential callbacks */
201 pa_context_set_state_callback(wvolume->context, _state_cb_init,
204 g_debug("connecting to pulse");
206 g_assert(pa_context_connect(wvolume->context,
207 MAFW_GST_RENDERER_WORKER_VOLUME_SERVER,
208 PA_CONTEXT_NOAUTOSPAWN | PA_CONTEXT_NOFAIL,
213 static gboolean _reconnect(gpointer user_data)
215 InitCbClosure *closure = user_data;
216 MafwGstRendererWorkerVolume *wvolume = closure->wvolume;
218 g_warning("got disconnected from pulse, reconnecting");
219 _destroy_context(wvolume);
226 _state_cb(pa_context *c, void *data)
228 MafwGstRendererWorkerVolume *wvolume = data;
229 pa_context_state_t state;
231 state = pa_context_get_state(c);
234 case PA_CONTEXT_TERMINATED:
235 case PA_CONTEXT_FAILED:
237 InitCbClosure *closure;
239 closure = _init_cb_closure_new(wvolume, NULL, NULL);
240 g_idle_add(_reconnect, closure);
243 case PA_CONTEXT_READY: {
246 o = pa_ext_stream_restore2_read(c, _ext_stream_restore_read_cb,
249 pa_operation_unref(o);
258 static void _ext_stream_restore_read_cb_init(pa_context *c,
259 const pa_ext_stream_restore2_info *i,
263 InitCbClosure *closure = userdata;
266 g_critical("eol parameter should not be < 1");
270 strcmp(i->name, MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PREFIX
271 MAFW_GST_RENDERER_WORKER_VOLUME_ROLE) != 0)
274 closure->wvolume->pulse_volume =
275 _pa_volume_to_per_one(pa_cvolume_max(&i->volume));
276 closure->wvolume->pulse_mute = i->mute != 0 ? TRUE : FALSE;
277 closure->wvolume->current_volume = closure->wvolume->pulse_volume;
278 closure->wvolume->current_mute = closure->wvolume->pulse_mute;
280 /* NOT EMIT VOLUME, BUT DEBUG */
281 g_debug("ext stream volume is %lf (mute: %d) for role %s in device %s",
282 closure->wvolume->pulse_volume, i->mute, i->name, i->device);
284 if (closure->cb != NULL) {
285 g_debug("initialized: returning volume manager");
286 closure->cb(closure->wvolume, closure->user_data);
288 if (closure->wvolume->cb != NULL) {
289 g_debug("signalling volume after reconnection");
290 closure->wvolume->cb(closure->wvolume,
291 closure->wvolume->current_volume,
292 closure->wvolume->user_data);
294 if (closure->wvolume->mute_cb != NULL) {
295 g_debug("signalling mute after reconnection");
296 closure->wvolume->mute_cb(closure->wvolume,
304 pa_context_set_state_callback(closure->wvolume->context, _state_cb,
310 static void _ext_stream_restore_subscribe_cb(pa_context *c, void *userdata)
314 o = pa_ext_stream_restore2_read(c, _ext_stream_restore_read_cb, userdata);
316 pa_operation_unref(o);
320 _state_cb_init(pa_context *c, void *data)
322 InitCbClosure *closure = data;
323 MafwGstRendererWorkerVolume *wvolume = closure->wvolume;
324 pa_context_state_t state;
326 state = pa_context_get_state(c);
328 g_debug("state: %d", state);
331 case PA_CONTEXT_TERMINATED:
332 case PA_CONTEXT_FAILED:
333 g_critical("Connection to pulse failed, reconnection in 1 "
335 g_timeout_add_seconds(1, _reconnect, closure);
337 case PA_CONTEXT_READY: {
340 g_debug("PA_CONTEXT_READY");
342 o = pa_ext_stream_restore2_read(c,
343 _ext_stream_restore_read_cb_init,
346 pa_operation_unref(o);
348 pa_ext_stream_restore_set_subscribe_cb(
349 c, _ext_stream_restore_subscribe_cb, wvolume);
351 o = pa_ext_stream_restore_subscribe(c, 1, NULL, NULL);
353 pa_operation_unref(o);
362 static gboolean _destroy_idle(gpointer data)
364 MafwGstRendererWorkerVolume *wvolume = data;
366 g_debug("destroying");
368 _destroy_context(wvolume);
369 pa_glib_mainloop_free(wvolume->mainloop);
376 _state_cb_destroy(pa_context *c, void *data)
378 pa_context_state_t state;
380 state = pa_context_get_state(c);
383 case PA_CONTEXT_TERMINATED:
384 g_idle_add(_destroy_idle, data);
386 case PA_CONTEXT_FAILED:
387 g_error("Unexpected problem in volume management");
394 static void _success_cb(pa_context *c, int success, void *userdata)
397 g_critical("Setting volume to pulse operation failed");
401 static void _remove_set_timeout(MafwGstRendererWorkerVolume *wvolume)
403 if (wvolume->change_request_id != 0) {
404 g_source_remove(wvolume->change_request_id);
406 wvolume->change_request_id = 0;
409 static gboolean _set_timeout(gpointer data)
411 pa_ext_stream_restore2_info info;
412 pa_ext_stream_restore2_info *infos[1];
413 MafwGstRendererWorkerVolume *wvolume = data;
415 if (wvolume->pending_operation) {
416 g_debug("setting volume ignored as there is still a pending "
417 "operation. Waiting till next iteration");
418 } else if (wvolume->pulse_mute != wvolume->current_mute ||
419 wvolume->pulse_volume != wvolume->current_volume) {
421 info.name = MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PREFIX
422 MAFW_GST_RENDERER_WORKER_VOLUME_ROLE;
423 info.channel_map.channels = 1;
424 info.channel_map.map[0] = PA_CHANNEL_POSITION_MONO;
426 info.volume_is_absolute = TRUE;
429 info.mute = wvolume->current_mute;
430 pa_cvolume_init(&info.volume);
431 pa_cvolume_set(&info.volume, info.channel_map.channels,
432 _pa_volume_from_per_one(wvolume->
435 g_debug("setting volume to %lf and mute to %d",
436 wvolume->current_volume, wvolume->current_mute);
438 if (wvolume->pa_operation != NULL) {
439 pa_operation_unref(wvolume->pa_operation);
442 wvolume->pending_operation = TRUE;
443 wvolume->pending_operation_volume = wvolume->current_volume;
444 wvolume->pending_operation_mute = wvolume->current_mute;
446 wvolume->pa_operation = pa_ext_stream_restore2_write(
449 (const pa_ext_stream_restore2_info*
451 1, TRUE, _success_cb, wvolume);
453 if (wvolume->pa_operation == NULL) {
454 g_critical("NULL operation when writing volume to "
456 _remove_set_timeout(wvolume);
459 g_debug("removing volume timeout");
460 _remove_set_timeout(wvolume);
463 return wvolume->change_request_id != 0;
466 void mafw_gst_renderer_worker_volume_init(GMainContext *main_context,
467 MafwGstRendererWorkerVolumeInitCb cb,
469 MafwGstRendererWorkerVolumeChangedCb
471 gpointer changed_user_data,
472 MafwGstRendererWorkerVolumeMuteCb
473 mute_cb, gpointer mute_user_data)
475 MafwGstRendererWorkerVolume *wvolume = NULL;
476 InitCbClosure *closure;
478 g_return_if_fail(cb != NULL);
480 g_assert(g_setenv(MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PROPERTY,
481 MAFW_GST_RENDERER_WORKER_VOLUME_ROLE, FALSE));
483 g_debug("initializing volume manager");
485 wvolume = g_new0(MafwGstRendererWorkerVolume, 1);
487 wvolume->pulse_volume = 1.0;
488 wvolume->pulse_mute = FALSE;
489 wvolume->cb = changed_cb;
490 wvolume->user_data = changed_user_data;
491 wvolume->mute_cb = mute_cb;
492 wvolume->mute_user_data = mute_user_data;
494 wvolume->mainloop = pa_glib_mainloop_new(main_context);
495 g_assert(wvolume->mainloop != NULL);
497 closure = _init_cb_closure_new(wvolume, cb, user_data);
501 void mafw_gst_renderer_worker_volume_set(MafwGstRendererWorkerVolume *wvolume,
502 gdouble volume, gboolean mute)
504 gboolean signal_volume, signal_mute;
506 g_return_if_fail(wvolume != NULL);
507 g_return_if_fail(pa_context_get_state(wvolume->context) ==
510 #ifndef MAFW_GST_RENDERER_ENABLE_MUTE
514 signal_volume = wvolume->current_volume != volume &&
516 signal_mute = wvolume->current_mute != mute && wvolume->mute_cb != NULL;
518 wvolume->current_volume = volume;
519 wvolume->current_mute = mute;
521 g_debug("volume set: %lf (mute %d)", volume, mute);
524 g_debug("signalling volume");
525 wvolume->cb(wvolume, volume, wvolume->user_data);
529 g_debug("signalling mute");
530 wvolume->mute_cb(wvolume, mute, wvolume->mute_user_data);
533 if ((signal_mute || signal_volume) && wvolume->change_request_id == 0) {
534 wvolume->change_request_id =
535 g_timeout_add(MAFW_GST_RENDERER_WORKER_SET_TIMEOUT,
536 _set_timeout, wvolume);
538 _set_timeout(wvolume);
542 gdouble mafw_gst_renderer_worker_volume_get(
543 MafwGstRendererWorkerVolume *wvolume)
545 g_return_val_if_fail(wvolume != NULL, 0.0);
547 g_debug("getting volume; %lf", wvolume->current_volume);
549 return wvolume->current_volume;
552 gboolean mafw_gst_renderer_worker_volume_is_muted(
553 MafwGstRendererWorkerVolume *wvolume)
555 g_return_val_if_fail(wvolume != NULL, FALSE);
557 g_debug("getting mute; %d", wvolume->current_mute);
559 return wvolume->current_mute;
562 void mafw_gst_renderer_worker_volume_destroy(
563 MafwGstRendererWorkerVolume *wvolume)
565 g_return_if_fail(wvolume != NULL);
567 g_debug("disconnecting");
569 pa_ext_stream_restore_set_subscribe_cb(wvolume->context, NULL, NULL);
570 pa_context_set_state_callback(wvolume->context, _state_cb_destroy,
572 pa_context_disconnect(wvolume->context);
580 #include "mafw-gst-renderer-worker-volume.h"
583 #define G_LOG_DOMAIN "mafw-gst-renderer-worker-volume-fake"
585 struct _MafwGstRendererWorkerVolume {
586 MafwGstRendererWorkerVolumeChangedCb cb;
588 MafwGstRendererWorkerVolumeMuteCb mute_cb;
589 gpointer mute_user_data;
590 gdouble current_volume;
591 gboolean current_mute;
595 MafwGstRendererWorkerVolume *wvolume;
596 MafwGstRendererWorkerVolumeInitCb cb;
600 static gboolean _init_cb_closure(gpointer user_data)
602 InitCbClosure *closure = user_data;
604 if (closure->cb != NULL) {
605 closure->cb(closure->wvolume, closure->user_data);
612 void mafw_gst_renderer_worker_volume_init(GMainContext *main_context,
613 MafwGstRendererWorkerVolumeInitCb cb,
615 MafwGstRendererWorkerVolumeChangedCb
617 gpointer changed_user_data,
618 MafwGstRendererWorkerVolumeMuteCb
619 mute_cb, gpointer mute_user_data)
621 MafwGstRendererWorkerVolume *wvolume = NULL;
622 InitCbClosure *closure;
624 g_return_if_fail(cb != NULL);
626 g_debug("initializing volume manager");
628 wvolume = g_new0(MafwGstRendererWorkerVolume, 1);
630 wvolume->cb = changed_cb;
631 wvolume->user_data = changed_user_data;
632 wvolume->mute_cb = mute_cb;
633 wvolume->mute_user_data = mute_user_data;
634 wvolume->current_volume = 0.485;
636 closure = g_new0(InitCbClosure, 1);
637 closure->wvolume = wvolume;
639 closure->user_data = user_data;
640 g_idle_add(_init_cb_closure, closure);
643 void mafw_gst_renderer_worker_volume_set(MafwGstRendererWorkerVolume *wvolume,
644 gdouble volume, gboolean mute)
646 gboolean signal_volume, signal_mute;
648 g_return_if_fail(wvolume != NULL);
650 #ifndef MAFW_GST_RENDERER_ENABLE_MUTE
654 signal_volume = wvolume->current_volume != volume &&
656 signal_mute = wvolume->current_mute != mute && wvolume->mute_cb != NULL;
658 wvolume->current_volume = volume;
659 wvolume->current_mute = mute;
661 g_debug("volume set: %lf (mute %d)", volume, mute);
664 g_debug("signalling volume");
665 wvolume->cb(wvolume, volume, wvolume->user_data);
669 g_debug("signalling mute");
670 wvolume->mute_cb(wvolume, mute, wvolume->mute_user_data);
674 gdouble mafw_gst_renderer_worker_volume_get(
675 MafwGstRendererWorkerVolume *wvolume)
677 g_return_val_if_fail(wvolume != NULL, 0.0);
679 g_debug("getting volume; %lf", wvolume->current_volume);
681 return wvolume->current_volume;
684 gboolean mafw_gst_renderer_worker_volume_is_muted(
685 MafwGstRendererWorkerVolume *wvolume)
687 g_return_val_if_fail(wvolume != NULL, FALSE);
689 g_debug("getting mute; %d", wvolume->current_mute);
691 return wvolume->current_mute;
694 void mafw_gst_renderer_worker_volume_destroy(
695 MafwGstRendererWorkerVolume *wvolume)
697 g_return_if_fail(wvolume != NULL);