--- /dev/null
+#include "MafwGstRendererVolume.h"
+
+#include <dbus/dbus.h>
+#include <dbusconnectioneventloop.h>
+
+#include <QDebug>
+#include <QTimer>
+
+// The default PulseAudioMainVolume DBus API socket
+#define DEFAULT_ADDRESS "unix:path=/var/run/pulse/dbus-socket"
+#define ROLE = "x-maemo";
+
+#define PULSE_CORE_PATH "/org/pulseaudio/core1"
+#define STREAM_RESTORE_PATH "/org/pulseaudio/stream_restore1"
+#define STREAM_RESTORE_IF "org.PulseAudio.Ext.StreamRestore1"
+#define GET_ENTRY_METHOD "GetEntryByName"
+#define STREAM_RESTORE_IF_ENTRY STREAM_RESTORE_IF ".RestoreEntry"
+#define STREAM_RESTORE_VOLUME_SIGNAL STREAM_RESTORE_IF_ENTRY ".VolumeUpdated"
+
+#define ENTRY_NAME "sink-input-by-media-role:x-maemo"
+//# 100% volume in Pulseaudio's native volume units.
+const uint VOLUME_NORM = 0x10000;
+//Place of mono channel at pulse audio's channel position enumeration
+const uint VOLUME_CHANNEL_MONO = 0;
+//Delay for attempting to reconnect to pulseaudio
+const uint PULSE_RESTART_DELAY = 2000;
+
+/********************************************************************
+ * MafwGstRendererVolume::MafwGstRendererVolume
+ ********************************************************************/
+MafwGstRendererVolume::MafwGstRendererVolume(): m_currentVolume(0), m_pendingVolumeValue(0),
+ m_dbusConnection(0), m_objectPath(QString()), m_pendingCall(0)
+{
+ qDebug() << __PRETTY_FUNCTION__;
+
+ connectToPulseAudio();
+}
+
+/********************************************************************
+ * MafwGstRendererVolume::connectToPulseAudio
+ ********************************************************************/
+void MafwGstRendererVolume::connectToPulseAudio()
+{
+ qDebug() << __PRETTY_FUNCTION__;
+ DBusError error;
+ QByteArray address = qgetenv("PULSE_DBUS_SERVER");
+
+ if (address.isEmpty())
+ {
+ address = QByteArray(DEFAULT_ADDRESS);
+ }
+
+ dbus_error_init (&error);
+
+ if (m_dbusConnection)
+ {
+ DBUSConnectionEventLoop::removeConnection(m_dbusConnection);
+ dbus_connection_unref(m_dbusConnection);
+ m_dbusConnection = 0;
+ }
+
+ m_dbusConnection = dbus_connection_open (address.constData(), &error);
+
+ if (dbus_error_is_set(&error))
+ {
+ qCritical() << "Unable to open dbus connection to pulse audio:" << error.message;
+ dbus_error_free (&error);
+ QTimer::singleShot(PULSE_RESTART_DELAY, this, SLOT(connectToPulseAudio()));
+ }
+ else
+ {
+ if (DBUSConnectionEventLoop::addConnection(m_dbusConnection))
+ {
+ dbus_connection_add_filter (
+ m_dbusConnection,
+ (DBusHandleMessageFunction) handleIncomingMessages,
+ (void *) this, NULL);
+
+ getRestoreEntryForMediaRole();
+ }
+ else
+ {
+ qCritical() << "DBUSConnectionEventLoop failure";
+ dbus_connection_unref(m_dbusConnection);
+ m_dbusConnection = 0;
+ }
+ }
+}
+
+/********************************************************************
+ * MafwGstRendererVolume::~MafwGstRendererVolume()
+ ********************************************************************/
+MafwGstRendererVolume::~MafwGstRendererVolume()
+{
+ if(m_pendingCall)
+ {
+ dbus_pending_call_cancel(m_pendingCall);
+ }
+ DBUSConnectionEventLoop::removeConnection(m_dbusConnection);
+ dbus_connection_unref(m_dbusConnection);
+}
+/********************************************************************
+ * We need the object path for the RestoreEntry of the "x-maemo" role.
+ * GetEntryByName with parameter "sink-input-by-media-role:x-maemo" is used
+ * for that.
+ ********************************************************************/
+void MafwGstRendererVolume::getRestoreEntryForMediaRole()
+{
+ qDebug() << __PRETTY_FUNCTION__;
+ DBusMessage *msg;
+ DBusError error;
+
+ dbus_error_init (&error);
+
+ msg = dbus_message_new_method_call(0,
+ STREAM_RESTORE_PATH,
+ STREAM_RESTORE_IF,
+ GET_ENTRY_METHOD);
+ const char *name = ENTRY_NAME;
+ dbus_message_append_args (msg,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID);
+
+
+ DBusPendingCall *pending = 0;
+ //Return value taken for satisfying coverity tool
+ bool outOfMemory = dbus_connection_send_with_reply( m_dbusConnection, msg, &pending, -1 );
+ Q_UNUSED(outOfMemory);
+ m_pendingCall = pending;
+
+ if (pending)
+ {
+ qDebug() << __PRETTY_FUNCTION__ << "pending call sent!";
+ dbus_pending_call_set_notify( pending,
+ (DBusPendingCallNotifyFunction) getEntryReply,
+ (void *)this,
+ NULL );
+ dbus_message_unref(msg);
+ }
+ qDebug() << __PRETTY_FUNCTION__ << "exit";
+}
+
+/********************************************************************
+ * Now we have the RestoreEntry object path for "x-maemo". Next we just
+ * need to start listening for the VolumeUpdated signals from that object,
+ * and use the Volume property for controlling the role volume.
+ ********************************************************************/
+void MafwGstRendererVolume::getEntryReply(DBusPendingCall *pending, MafwGstRendererVolume *self)
+{
+ qDebug() << __PRETTY_FUNCTION__;
+ self->m_pendingCall = 0;
+ DBusMessage *reply;
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ reply = dbus_pending_call_steal_reply(pending);
+
+ if (dbus_set_error_from_message(&error, reply))
+ {
+ qWarning() << "Unable to get volume from pulse audio:" << error.message;
+ dbus_error_free (&error);
+ //Try to reconnect
+ QTimer::singleShot(0, self, SLOT(connectToPulseAudio()));
+ }
+ else if (reply && (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN))
+ {
+ const char *object_path;
+ bool argError = dbus_message_get_args(reply, &error, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID);
+ if (!argError)
+ {
+ qWarning() << "Unable to get volume from pulse audio:" << error.message;
+ dbus_error_free (&error);
+ //Try to reconnect
+ QTimer::singleShot(0, self, SLOT(connectToPulseAudio()));
+ }
+
+ qDebug() << __PRETTY_FUNCTION__ << "Object path: " << object_path;
+ self->m_objectPath = object_path;
+
+ if (self->m_pendingVolumeValue > 0)
+ {
+ qDebug() << __PRETTY_FUNCTION__ << "setting volume to level " << self->m_pendingVolumeValue;
+ self->listenVolumeSignals();
+ self->setVolume(self->m_pendingVolumeValue);
+ self->m_pendingVolumeValue = 0;
+ }
+ else
+ {
+ qDebug() << __PRETTY_FUNCTION__ << "getting initial volume level.";
+ DBusMessage *msg;
+ DBusError error2;
+ dbus_error_init (&error2);
+
+ msg = dbus_message_new_method_call(0,
+ object_path,
+ DBUS_INTERFACE_PROPERTIES,
+ "Get");
+ const char* interface = STREAM_RESTORE_IF_ENTRY;
+ const char* property = "Volume";
+
+ dbus_message_append_args (msg,
+ DBUS_TYPE_STRING, &interface,
+ DBUS_TYPE_STRING, &property,
+ DBUS_TYPE_INVALID);
+
+ DBusPendingCall *pending = 0;
+ //Return value taken for satisfying coverity tool
+ bool outOfMemory = dbus_connection_send_with_reply( self->m_dbusConnection, msg, &pending, -1 );
+ Q_UNUSED(outOfMemory);
+ self->m_pendingCall = pending;
+ if (pending)
+ {
+ qDebug() << __PRETTY_FUNCTION__ << "pending call sent!";
+ dbus_pending_call_set_notify( pending,
+ (DBusPendingCallNotifyFunction) volumeReply,
+ (void *)self,
+ NULL );
+ dbus_message_unref(msg);
+ }
+ }
+ }
+ dbus_message_unref (reply);
+}
+
+/********************************************************************
+ * MafwGstRendererVolume::volumeReply
+ ********************************************************************/
+void MafwGstRendererVolume::volumeReply(DBusPendingCall *pending, MafwGstRendererVolume *self)
+{
+ self->m_pendingCall = 0;
+ Q_UNUSED (self);
+ qDebug() << __PRETTY_FUNCTION__;
+
+ DBusMessage *reply;
+ reply = dbus_pending_call_steal_reply(pending);
+
+ DBusError error;
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, reply))
+ {
+ qWarning() << "MafwGstRendererVolume: Unable to get volume from pulse audio:" << error.message;
+ dbus_error_free (&error);
+ }
+ else
+ {
+ DBusMessageIter iter, array_iterator;
+ dbus_message_iter_init (reply, &iter);
+ if(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT)
+ {
+ dbus_message_iter_recurse (&iter, &array_iterator);
+ }
+
+ if (dbus_message_iter_get_arg_type (&array_iterator) == DBUS_TYPE_ARRAY)
+ {
+ self->readVolumeFromStruct(&array_iterator);
+ Q_EMIT self->volumeChanged(self->m_currentVolume);
+ }
+ else
+ {
+ qCritical("Unable to get initial volume from Pulse Audio!!");
+ }
+ }
+ dbus_message_unref (reply);
+ self->listenVolumeSignals();
+}
+
+/********************************************************************
+ * MafwGstRendererVolume::listenVolumeSignals
+ ********************************************************************/
+void MafwGstRendererVolume::listenVolumeSignals()
+{
+ qDebug() << __PRETTY_FUNCTION__;
+
+ DBusMessage *message = 0;
+ char *signal = (char *) STREAM_RESTORE_VOLUME_SIGNAL;
+ char **emptyarray = { 0 };
+
+ message = dbus_message_new_method_call (0,
+ PULSE_CORE_PATH,
+ 0,
+ "ListenForSignal");
+
+ dbus_message_append_args (message,
+ DBUS_TYPE_STRING, &signal,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &emptyarray, 0,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send (m_dbusConnection, message, NULL);
+ dbus_connection_flush (m_dbusConnection);
+ dbus_message_unref (message);
+}
+
+/********************************************************************
+ * MafwGstRendererVolume::handleIncomingMessages
+ ********************************************************************/
+void MafwGstRendererVolume::handleIncomingMessages (DBusConnection* conn,
+ DBusMessage* message,
+ MafwGstRendererVolume* self)
+{
+ qDebug(__PRETTY_FUNCTION__);
+ Q_UNUSED (conn);
+
+ if (message &&
+ dbus_message_has_member (message, "VolumeUpdated") &&
+ dbus_message_has_interface(message, STREAM_RESTORE_IF_ENTRY) &&
+ dbus_message_has_path(message, self->m_objectPath.toAscii()))
+ {
+ qDebug() << "MafwGstRendererVolume: VolumeUpdated signal received.";
+ DBusError error;
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, message))
+ {
+ qWarning() << "Got volume error from pulse audio:" << error.message;
+ dbus_error_free (&error);
+ }
+
+ DBusMessageIter iter;
+ dbus_message_iter_init (message, &iter);
+
+ if (self->readVolumeFromStruct(&iter))
+ {
+ Q_EMIT self->volumeChanged(self->m_currentVolume);
+ }
+ }
+ //When a connection is disconnected, you are guaranteed to get a signal "Disconnected" from the interface DBUS_INTERFACE_LOCAL, path DBUS_PATH_LOCAL.
+ else if (message &&
+ dbus_message_has_member (message, "Disconnected") &&
+ dbus_message_get_interface(message) == QString(DBUS_INTERFACE_LOCAL) &&
+ dbus_message_get_path(message) == QString(DBUS_PATH_LOCAL))
+ {
+ qWarning("MafwGstRendererVolume: Connection with pulse audio is disconnected!");
+ QTimer::singleShot(PULSE_RESTART_DELAY, self, SLOT(connectToPulseAudio()));
+ qDebug("MafwGstRendererVolume: Trying to reconnect.");
+ }
+
+}
+
+/********************************************************************
+ * There is a dbus variant containing array of structures. Each structure
+ * contains channels postion and the volume level of this position
+ ********************************************************************/
+bool MafwGstRendererVolume::readVolumeFromStruct(DBusMessageIter *iterator)
+{
+ qDebug(__PRETTY_FUNCTION__);
+ bool volumeChanged = false;
+ DBusMessageIter struct_iterator;
+ dbus_message_iter_recurse (iterator, &struct_iterator);
+ int volume = -1;
+
+ while (dbus_message_iter_get_arg_type(&struct_iterator) == DBUS_TYPE_STRUCT)
+ {
+ DBusMessageIter variant;
+ dbus_message_iter_recurse (&struct_iterator, &variant);
+ int channel, value = 0;
+
+ if (dbus_message_iter_get_arg_type (&variant) == DBUS_TYPE_UINT32)
+ {
+ dbus_message_iter_get_basic (&variant, &channel);
+ qDebug("Channel %d", channel);
+ dbus_message_iter_next (&variant);
+ dbus_message_iter_get_basic (&variant, &value);
+
+ value = qRound((float)value / (float)VOLUME_NORM * 100);
+
+ //We're interested in all the channels that stream-restore happens to give you.
+ //We have to somehow map between a single volume value and per-channel volume.
+ //The usual way to do the mapping is just to use the loudest channel as
+ //the overall volume
+ if (value > volume)
+ {
+ uint newVolume = value;
+ if (newVolume != m_currentVolume)
+ {
+ m_currentVolume = newVolume;
+ volumeChanged = true;
+ qDebug("MafwGstRendererVolume: current volume level has changed: %d", m_currentVolume);
+ }
+ }
+
+ if (m_currentVolume > 100) //MAFW volume has range 0-100
+ {
+ qWarning("MafwGstRendererVolume: Pulse audio signals volume level which is out-of range!");
+ m_currentVolume = 100;
+ break;
+ }
+ }
+ else
+ {
+ qWarning("MafwGstRendererVolume: Invalid volume value from pulse audio!");
+ }
+ dbus_message_iter_next (&struct_iterator);
+ }
+ return volumeChanged;
+}
+
+/********************************************************************
+ * MafwGstRendererVolume::getVolume
+ ********************************************************************/
+uint MafwGstRendererVolume::getVolume()
+{
+ qDebug(__PRETTY_FUNCTION__);
+ return m_currentVolume;
+}
+
+/********************************************************************
+ * Incoming value has range 0-99 pulseaudio uses values 0-VOLUME_NORM
+ ********************************************************************/
+bool MafwGstRendererVolume::setVolume (uint value)
+{
+ bool success = true;
+ qDebug("MafwGstRendererVolume::setVolume (uint %d)", value);
+ if (m_objectPath.isEmpty())
+ {
+ qDebug() << "MafwGstRendererVolume: Can not set volume yet. Waiting for RestoreEntry object path";
+ m_pendingVolumeValue = value;
+ return success;
+ }
+
+ const char* interface = STREAM_RESTORE_IF_ENTRY;
+ const char* property = "Volume";
+ if (value > 100) //MAFW volume has range 0-100
+ {
+ qWarning("MafwGstRendererVolume: Trying to set volume level which is out-of range!");
+ value = (uint)100;
+ }
+ uint nativeValue = (((float)value / (float)100) * VOLUME_NORM);
+ DBusMessage* message;
+ message = dbus_message_new_method_call(0,
+ m_objectPath.toAscii(),
+ DBUS_INTERFACE_PROPERTIES,
+ "Set");
+
+ if ( dbus_message_append_args(message,
+ DBUS_TYPE_STRING, &interface,
+ DBUS_TYPE_STRING, &property,
+ DBUS_TYPE_INVALID))
+ {
+ //Compose the dbus variant containing an array of struct of pair
+ //of unsigned integers va(uu)
+ DBusMessageIter argument_iterator, variant_iterator, array_iterator, struct_iterator;
+ dbus_message_iter_init_append (message, &argument_iterator);
+ dbus_message_iter_open_container (&argument_iterator,
+ DBUS_TYPE_VARIANT,
+ "a(uu)",
+ &variant_iterator);
+ dbus_message_iter_open_container (&variant_iterator,
+ DBUS_TYPE_ARRAY,
+ "(uu)",
+ &array_iterator);
+ dbus_message_iter_open_container (&array_iterator,
+ DBUS_TYPE_STRUCT,
+ NULL,
+ &struct_iterator);
+
+ dbus_message_iter_append_basic (&struct_iterator, DBUS_TYPE_UINT32, &VOLUME_CHANNEL_MONO);
+ dbus_message_iter_append_basic (&struct_iterator, DBUS_TYPE_UINT32, &nativeValue);
+
+ dbus_message_iter_close_container (&array_iterator, &struct_iterator);
+ dbus_message_iter_close_container (&variant_iterator, &array_iterator);
+ dbus_message_iter_close_container (&argument_iterator, &variant_iterator);
+
+ dbus_connection_send (m_dbusConnection, message, NULL);
+ dbus_connection_flush (m_dbusConnection);
+ }
+ else
+ {
+ qWarning("Cannot set volume!");
+ success = false;
+ }
+ dbus_message_unref (message);
+
+ return success;
+}