Added qmafw-gst-subtitles-renderer-0.0.55 for Meego Harmattan 1.2
[mafwsubrenderer] / qmafw-gst-subtitles-renderer / src / MafwGstRendererVolume.cpp
diff --git a/qmafw-gst-subtitles-renderer/src/MafwGstRendererVolume.cpp b/qmafw-gst-subtitles-renderer/src/MafwGstRendererVolume.cpp
new file mode 100644 (file)
index 0000000..daf3b95
--- /dev/null
@@ -0,0 +1,476 @@
+#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;
+}