/* * This file is a part of MAFW * * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. * * Contact: Visa Smolander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifndef MAFW_GST_RENDERER_DISABLE_PULSE_VOLUME #include #include #include #include #include "mafw-gst-renderer-worker-volume.h" #include "config.h" #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "mafw-gst-renderer-worker-volume" #define MAFW_GST_RENDERER_WORKER_VOLUME_SERVER NULL #define MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PROPERTY "PULSE_PROP_media.role" #define MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PREFIX "sink-input-by-media-role:" #define MAFW_GST_RENDERER_WORKER_VOLUME_ROLE "x-maemo" #define MAFW_GST_RENDERER_WORKER_SET_TIMEOUT 200 struct _MafwGstRendererWorkerVolume { pa_glib_mainloop *mainloop; pa_context *context; gdouble pulse_volume; gboolean pulse_mute; MafwGstRendererWorkerVolumeChangedCb cb; gpointer user_data; MafwGstRendererWorkerVolumeMuteCb mute_cb; gpointer mute_user_data; gdouble current_volume; gboolean current_mute; gboolean pending_operation; gdouble pending_operation_volume; gboolean pending_operation_mute; guint change_request_id; pa_operation *pa_operation; }; typedef struct { MafwGstRendererWorkerVolume *wvolume; MafwGstRendererWorkerVolumeInitCb cb; gpointer user_data; } InitCbClosure; #define _pa_volume_to_per_one(volume) \ ((guint) ((((gdouble)(volume) / (gdouble) PA_VOLUME_NORM) + \ (gdouble) 0.005) * (gdouble) 100.0) / (gdouble) 100.0) #define _pa_volume_from_per_one(volume) \ ((pa_volume_t)((gdouble)(volume) * (gdouble) PA_VOLUME_NORM)) #define _pa_operation_running(wvolume) \ (wvolume->pa_operation != NULL && \ pa_operation_get_state(wvolume->pa_operation) == PA_OPERATION_RUNNING) static void _state_cb_init(pa_context *c, void *data); static gchar *_get_client_name(void) { gchar buf[PATH_MAX]; gchar *name = NULL; if (pa_get_binary_name(buf, sizeof(buf))) name = g_strdup_printf("mafw-gst-renderer[%s]", buf); else name = g_strdup("mafw-gst-renderer"); return name; } static void _ext_stream_restore_read_cb(pa_context *c, const pa_ext_stream_restore2_info *i, int eol, void *userdata) { MafwGstRendererWorkerVolume *wvolume = userdata; gdouble volume; gboolean mute; if (eol < 0) { g_critical("eol parameter should not be < 1. " "Discarding volume event"); return; } if (i == NULL || strcmp(i->name, MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PREFIX MAFW_GST_RENDERER_WORKER_VOLUME_ROLE) != 0) { return; } volume = _pa_volume_to_per_one(pa_cvolume_max(&i->volume)); mute = i->mute != 0 ? TRUE : FALSE; if (_pa_operation_running(wvolume) || (wvolume->pending_operation && (wvolume->pending_operation_volume != volume #ifdef MAFW_GST_RENDERER_ENABLE_MUTE || wvolume->pending_operation_mute != mute #endif ))) { g_debug("volume notification, but operation running, ignoring"); return; } wvolume->pulse_volume = volume; wvolume->pulse_mute = mute; /* EMIT VOLUME */ g_debug("ext stream volume is %lf (mute: %d) for role %s in device %s", wvolume->pulse_volume, wvolume->pulse_mute, i->name, i->device); if (!wvolume->pending_operation && wvolume->pulse_volume != wvolume->current_volume) { wvolume->current_volume = wvolume->pulse_volume; if (wvolume->cb != NULL) { g_debug("signalling volume"); wvolume->cb(wvolume, wvolume->current_volume, wvolume->user_data); } } #ifdef MAFW_GST_RENDERER_ENABLE_MUTE if (!wvolume->pending_operation && wvolume->pulse_mute != wvolume->current_mute) { wvolume->current_mute = wvolume->pulse_mute; if (wvolume->mute_cb != NULL) { g_debug("signalling mute"); wvolume->mute_cb(wvolume, wvolume->current_mute, wvolume->mute_user_data); } } #endif wvolume->pending_operation = FALSE; } static void _destroy_context(MafwGstRendererWorkerVolume *wvolume) { if (wvolume->pa_operation != NULL) { if (pa_operation_get_state(wvolume->pa_operation) == PA_OPERATION_RUNNING) { pa_operation_cancel(wvolume->pa_operation); } pa_operation_unref(wvolume->pa_operation); wvolume->pa_operation = NULL; } pa_context_unref(wvolume->context); } static InitCbClosure *_init_cb_closure_new(MafwGstRendererWorkerVolume *wvolume, MafwGstRendererWorkerVolumeInitCb cb, gpointer user_data) { InitCbClosure *closure; closure = g_new(InitCbClosure, 1); closure->wvolume = wvolume; closure->cb = cb; closure->user_data = user_data; return closure; } static void _connect(gpointer user_data) { gchar *name = NULL; pa_mainloop_api *api = NULL; InitCbClosure *closure = user_data; MafwGstRendererWorkerVolume *wvolume = closure->wvolume; name = _get_client_name(); /* get the mainloop api and create a context */ api = pa_glib_mainloop_get_api(wvolume->mainloop); wvolume->context = pa_context_new(api, name); g_assert(wvolume->context != NULL); /* register some essential callbacks */ pa_context_set_state_callback(wvolume->context, _state_cb_init, closure); g_debug("connecting to pulse"); g_assert(pa_context_connect(wvolume->context, MAFW_GST_RENDERER_WORKER_VOLUME_SERVER, PA_CONTEXT_NOAUTOSPAWN | PA_CONTEXT_NOFAIL, NULL) >= 0); g_free(name); } static gboolean _reconnect(gpointer user_data) { InitCbClosure *closure = user_data; MafwGstRendererWorkerVolume *wvolume = closure->wvolume; g_warning("got disconnected from pulse, reconnecting"); _destroy_context(wvolume); _connect(user_data); return FALSE; } static void _state_cb(pa_context *c, void *data) { MafwGstRendererWorkerVolume *wvolume = data; pa_context_state_t state; state = pa_context_get_state(c); switch (state) { case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: { InitCbClosure *closure; closure = _init_cb_closure_new(wvolume, NULL, NULL); g_idle_add(_reconnect, closure); break; } case PA_CONTEXT_READY: { pa_operation *o; o = pa_ext_stream_restore2_read(c, _ext_stream_restore_read_cb, wvolume); g_assert(o != NULL); pa_operation_unref(o); break; } default: break; } } static void _ext_stream_restore_read_cb_init(pa_context *c, const pa_ext_stream_restore2_info *i, int eol, void *userdata) { InitCbClosure *closure = userdata; if (eol < 0) { g_critical("eol parameter should not be < 1"); } if (i == NULL || strcmp(i->name, MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PREFIX MAFW_GST_RENDERER_WORKER_VOLUME_ROLE) != 0) return; closure->wvolume->pulse_volume = _pa_volume_to_per_one(pa_cvolume_max(&i->volume)); closure->wvolume->pulse_mute = i->mute != 0 ? TRUE : FALSE; closure->wvolume->current_volume = closure->wvolume->pulse_volume; closure->wvolume->current_mute = closure->wvolume->pulse_mute; /* NOT EMIT VOLUME, BUT DEBUG */ g_debug("ext stream volume is %lf (mute: %d) for role %s in device %s", closure->wvolume->pulse_volume, i->mute, i->name, i->device); if (closure->cb != NULL) { g_debug("initialized: returning volume manager"); closure->cb(closure->wvolume, closure->user_data); } else { if (closure->wvolume->cb != NULL) { g_debug("signalling volume after reconnection"); closure->wvolume->cb(closure->wvolume, closure->wvolume->current_volume, closure->wvolume->user_data); } if (closure->wvolume->mute_cb != NULL) { g_debug("signalling mute after reconnection"); closure->wvolume->mute_cb(closure->wvolume, closure->wvolume-> current_mute, closure->wvolume-> mute_user_data); } } pa_context_set_state_callback(closure->wvolume->context, _state_cb, closure->wvolume); g_free(closure); } static void _ext_stream_restore_subscribe_cb(pa_context *c, void *userdata) { pa_operation *o; o = pa_ext_stream_restore2_read(c, _ext_stream_restore_read_cb, userdata); g_assert(o != NULL); pa_operation_unref(o); } static void _state_cb_init(pa_context *c, void *data) { InitCbClosure *closure = data; MafwGstRendererWorkerVolume *wvolume = closure->wvolume; pa_context_state_t state; state = pa_context_get_state(c); g_debug("state: %d", state); switch (state) { case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: g_critical("Connection to pulse failed, reconnection in 1 " "second"); g_timeout_add_seconds(1, _reconnect, closure); break; case PA_CONTEXT_READY: { pa_operation *o; g_debug("PA_CONTEXT_READY"); o = pa_ext_stream_restore2_read(c, _ext_stream_restore_read_cb_init, closure); g_assert(o != NULL); pa_operation_unref(o); pa_ext_stream_restore_set_subscribe_cb( c, _ext_stream_restore_subscribe_cb, wvolume); o = pa_ext_stream_restore_subscribe(c, 1, NULL, NULL); g_assert(o != NULL); pa_operation_unref(o); break; } default: break; } } static gboolean _destroy_idle(gpointer data) { MafwGstRendererWorkerVolume *wvolume = data; g_debug("destroying"); _destroy_context(wvolume); pa_glib_mainloop_free(wvolume->mainloop); g_free(wvolume); return FALSE; } static void _state_cb_destroy(pa_context *c, void *data) { pa_context_state_t state; state = pa_context_get_state(c); switch (state) { case PA_CONTEXT_TERMINATED: g_idle_add(_destroy_idle, data); break; case PA_CONTEXT_FAILED: g_error("Unexpected problem in volume management"); break; default: break; } } static void _success_cb(pa_context *c, int success, void *userdata) { if (success == 0) { g_critical("Setting volume to pulse operation failed"); } } static void _remove_set_timeout(MafwGstRendererWorkerVolume *wvolume) { if (wvolume->change_request_id != 0) { g_source_remove(wvolume->change_request_id); } wvolume->change_request_id = 0; } static gboolean _set_timeout(gpointer data) { pa_ext_stream_restore2_info info; pa_ext_stream_restore2_info *infos[1]; MafwGstRendererWorkerVolume *wvolume = data; if (wvolume->pending_operation) { g_debug("setting volume ignored as there is still a pending " "operation. Waiting till next iteration"); } else if (wvolume->pulse_volume != wvolume->current_volume #ifdef MAFW_GST_RENDERER_ENABLE_MUTE || wvolume->pulse_mute != wvolume->current_mute #endif ) { info.name = MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PREFIX MAFW_GST_RENDERER_WORKER_VOLUME_ROLE; info.channel_map.channels = 1; info.channel_map.map[0] = PA_CHANNEL_POSITION_MONO; info.device = NULL; info.volume_is_absolute = TRUE; infos[0] = &info; info.mute = wvolume->current_mute; pa_cvolume_init(&info.volume); pa_cvolume_set(&info.volume, info.channel_map.channels, _pa_volume_from_per_one(wvolume-> current_volume)); g_debug("setting volume to %lf and mute to %d", wvolume->current_volume, wvolume->current_mute); if (wvolume->pa_operation != NULL) { pa_operation_unref(wvolume->pa_operation); } wvolume->pending_operation = TRUE; wvolume->pending_operation_volume = wvolume->current_volume; wvolume->pending_operation_mute = wvolume->current_mute; wvolume->pa_operation = pa_ext_stream_restore2_write( wvolume->context, PA_UPDATE_REPLACE, (const pa_ext_stream_restore2_info* const *)infos, 1, TRUE, _success_cb, wvolume); if (wvolume->pa_operation == NULL) { g_critical("NULL operation when writing volume to " "pulse"); _remove_set_timeout(wvolume); } } else { g_debug("removing volume timeout"); _remove_set_timeout(wvolume); } return wvolume->change_request_id != 0; } void mafw_gst_renderer_worker_volume_init(GMainContext *main_context, MafwGstRendererWorkerVolumeInitCb cb, gpointer user_data, MafwGstRendererWorkerVolumeChangedCb changed_cb, gpointer changed_user_data, MafwGstRendererWorkerVolumeMuteCb mute_cb, gpointer mute_user_data) { MafwGstRendererWorkerVolume *wvolume = NULL; InitCbClosure *closure; g_return_if_fail(cb != NULL); g_assert(g_setenv(MAFW_GST_RENDERER_WORKER_VOLUME_ROLE_PROPERTY, MAFW_GST_RENDERER_WORKER_VOLUME_ROLE, FALSE)); g_debug("initializing volume manager"); wvolume = g_new0(MafwGstRendererWorkerVolume, 1); wvolume->pulse_volume = 1.0; wvolume->pulse_mute = FALSE; wvolume->cb = changed_cb; wvolume->user_data = changed_user_data; wvolume->mute_cb = mute_cb; wvolume->mute_user_data = mute_user_data; wvolume->mainloop = pa_glib_mainloop_new(main_context); g_assert(wvolume->mainloop != NULL); closure = _init_cb_closure_new(wvolume, cb, user_data); _connect(closure); } void mafw_gst_renderer_worker_volume_set(MafwGstRendererWorkerVolume *wvolume, gdouble volume, gboolean mute) { gboolean signal_volume, signal_mute; g_return_if_fail(wvolume != NULL); g_return_if_fail(pa_context_get_state(wvolume->context) == PA_CONTEXT_READY); #ifndef MAFW_GST_RENDERER_ENABLE_MUTE mute = FALSE; #endif signal_volume = wvolume->current_volume != volume && wvolume->cb != NULL; signal_mute = wvolume->current_mute != mute && wvolume->mute_cb != NULL; wvolume->current_volume = volume; wvolume->current_mute = mute; g_debug("volume set: %lf (mute %d)", volume, mute); if (signal_volume) { g_debug("signalling volume"); wvolume->cb(wvolume, volume, wvolume->user_data); } if (signal_mute) { g_debug("signalling mute"); wvolume->mute_cb(wvolume, mute, wvolume->mute_user_data); } if ((signal_mute || signal_volume) && wvolume->change_request_id == 0) { wvolume->change_request_id = g_timeout_add(MAFW_GST_RENDERER_WORKER_SET_TIMEOUT, _set_timeout, wvolume); _set_timeout(wvolume); } } gdouble mafw_gst_renderer_worker_volume_get( MafwGstRendererWorkerVolume *wvolume) { g_return_val_if_fail(wvolume != NULL, 0.0); g_debug("getting volume; %lf", wvolume->current_volume); return wvolume->current_volume; } gboolean mafw_gst_renderer_worker_volume_is_muted( MafwGstRendererWorkerVolume *wvolume) { g_return_val_if_fail(wvolume != NULL, FALSE); g_debug("getting mute; %d", wvolume->current_mute); return wvolume->current_mute; } void mafw_gst_renderer_worker_volume_destroy( MafwGstRendererWorkerVolume *wvolume) { g_return_if_fail(wvolume != NULL); g_debug("disconnecting"); pa_ext_stream_restore_set_subscribe_cb(wvolume->context, NULL, NULL); pa_context_set_state_callback(wvolume->context, _state_cb_destroy, wvolume); pa_context_disconnect(wvolume->context); } #else #include "mafw-gst-renderer-worker-volume.h" #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "mafw-gst-renderer-worker-volume-fake" struct _MafwGstRendererWorkerVolume { MafwGstRendererWorkerVolumeChangedCb cb; gpointer user_data; MafwGstRendererWorkerVolumeMuteCb mute_cb; gpointer mute_user_data; gdouble current_volume; gboolean current_mute; }; typedef struct { MafwGstRendererWorkerVolume *wvolume; MafwGstRendererWorkerVolumeInitCb cb; gpointer user_data; } InitCbClosure; static gboolean _init_cb_closure(gpointer user_data) { InitCbClosure *closure = user_data; if (closure->cb != NULL) { closure->cb(closure->wvolume, closure->user_data); } g_free(closure); return FALSE; } void mafw_gst_renderer_worker_volume_init(GMainContext *main_context, MafwGstRendererWorkerVolumeInitCb cb, gpointer user_data, MafwGstRendererWorkerVolumeChangedCb changed_cb, gpointer changed_user_data, MafwGstRendererWorkerVolumeMuteCb mute_cb, gpointer mute_user_data) { MafwGstRendererWorkerVolume *wvolume = NULL; InitCbClosure *closure; g_return_if_fail(cb != NULL); g_debug("initializing volume manager"); wvolume = g_new0(MafwGstRendererWorkerVolume, 1); wvolume->cb = changed_cb; wvolume->user_data = changed_user_data; wvolume->mute_cb = mute_cb; wvolume->mute_user_data = mute_user_data; wvolume->current_volume = 0.485; closure = g_new0(InitCbClosure, 1); closure->wvolume = wvolume; closure->cb = cb; closure->user_data = user_data; g_idle_add(_init_cb_closure, closure); } void mafw_gst_renderer_worker_volume_set(MafwGstRendererWorkerVolume *wvolume, gdouble volume, gboolean mute) { gboolean signal_volume, signal_mute; g_return_if_fail(wvolume != NULL); #ifndef MAFW_GST_RENDERER_ENABLE_MUTE mute = FALSE; #endif signal_volume = wvolume->current_volume != volume && wvolume->cb != NULL; signal_mute = wvolume->current_mute != mute && wvolume->mute_cb != NULL; wvolume->current_volume = volume; wvolume->current_mute = mute; g_debug("volume set: %lf (mute %d)", volume, mute); if (signal_volume) { g_debug("signalling volume"); wvolume->cb(wvolume, volume, wvolume->user_data); } if (signal_mute) { g_debug("signalling mute"); wvolume->mute_cb(wvolume, mute, wvolume->mute_user_data); } } gdouble mafw_gst_renderer_worker_volume_get( MafwGstRendererWorkerVolume *wvolume) { g_return_val_if_fail(wvolume != NULL, 0.0); g_debug("getting volume; %lf", wvolume->current_volume); return wvolume->current_volume; } gboolean mafw_gst_renderer_worker_volume_is_muted( MafwGstRendererWorkerVolume *wvolume) { g_return_val_if_fail(wvolume != NULL, FALSE); g_debug("getting mute; %d", wvolume->current_mute); return wvolume->current_mute; } void mafw_gst_renderer_worker_volume_destroy( MafwGstRendererWorkerVolume *wvolume) { g_return_if_fail(wvolume != NULL); g_free(wvolume); } #endif