Added DUI control panel applet
[mafwsubrenderer] / qmafw-gst-subtitles-renderer / src / MafwGstRendererVolume.cpp
1 #include "MafwGstRendererVolume.h"
2
3 #include <dbus/dbus.h>
4 #include <dbusconnectioneventloop.h>
5
6 #include <QDebug>
7 #include <QTimer>
8
9 // The default PulseAudioMainVolume DBus API socket
10 #define DEFAULT_ADDRESS "unix:path=/var/run/pulse/dbus-socket"
11 #define ROLE = "x-maemo";
12
13 #define PULSE_CORE_PATH "/org/pulseaudio/core1"
14 #define STREAM_RESTORE_PATH "/org/pulseaudio/stream_restore1"
15 #define STREAM_RESTORE_IF "org.PulseAudio.Ext.StreamRestore1"
16 #define GET_ENTRY_METHOD "GetEntryByName"
17 #define STREAM_RESTORE_IF_ENTRY STREAM_RESTORE_IF ".RestoreEntry"
18 #define STREAM_RESTORE_VOLUME_SIGNAL STREAM_RESTORE_IF_ENTRY ".VolumeUpdated"
19
20 #define ENTRY_NAME "sink-input-by-media-role:x-maemo"
21 //# 100% volume in Pulseaudio's native volume units.
22 const uint VOLUME_NORM = 0x10000;
23 //Place of mono channel at pulse audio's channel position enumeration
24 const uint VOLUME_CHANNEL_MONO = 0;
25 //Delay for attempting to reconnect to pulseaudio
26 const uint PULSE_RESTART_DELAY = 2000;
27
28 /********************************************************************
29  * MafwGstRendererVolume::MafwGstRendererVolume
30  ********************************************************************/
31 MafwGstRendererVolume::MafwGstRendererVolume(): m_currentVolume(0), m_pendingVolumeValue(0),
32                             m_dbusConnection(0), m_objectPath(QString()), m_pendingCall(0)
33 {
34     qDebug() << __PRETTY_FUNCTION__;
35
36     connectToPulseAudio();
37 }
38
39 /********************************************************************
40  * MafwGstRendererVolume::connectToPulseAudio
41  ********************************************************************/
42 void MafwGstRendererVolume::connectToPulseAudio()
43 {
44     qDebug() << __PRETTY_FUNCTION__;
45     DBusError error;
46     QByteArray address = qgetenv("PULSE_DBUS_SERVER");
47
48     if (address.isEmpty())
49     {
50         address = QByteArray(DEFAULT_ADDRESS);
51     }
52
53     dbus_error_init (&error);
54
55     if (m_dbusConnection)
56     {
57         DBUSConnectionEventLoop::removeConnection(m_dbusConnection);
58         dbus_connection_unref(m_dbusConnection);
59         m_dbusConnection = 0;
60     }
61
62     m_dbusConnection = dbus_connection_open (address.constData(), &error);
63
64     if (dbus_error_is_set(&error))
65     {
66         qCritical() << "Unable to open dbus connection to pulse audio:" << error.message;
67         dbus_error_free (&error);        
68         QTimer::singleShot(PULSE_RESTART_DELAY, this, SLOT(connectToPulseAudio()));
69     }
70     else
71     {
72         if (DBUSConnectionEventLoop::addConnection(m_dbusConnection))
73         {
74         dbus_connection_add_filter (
75             m_dbusConnection,
76             (DBusHandleMessageFunction) handleIncomingMessages,
77             (void *) this, NULL);
78
79         getRestoreEntryForMediaRole();
80         }
81         else
82         {
83             qCritical() << "DBUSConnectionEventLoop failure";
84             dbus_connection_unref(m_dbusConnection);
85             m_dbusConnection = 0;
86         }
87     }
88 }
89
90 /********************************************************************
91  * MafwGstRendererVolume::~MafwGstRendererVolume()
92  ********************************************************************/
93 MafwGstRendererVolume::~MafwGstRendererVolume()
94 {
95     if(m_pendingCall)
96     {
97         dbus_pending_call_cancel(m_pendingCall);
98     }
99     DBUSConnectionEventLoop::removeConnection(m_dbusConnection);
100     dbus_connection_unref(m_dbusConnection);
101 }
102 /********************************************************************
103  * We need the object path for the RestoreEntry of the "x-maemo" role.
104  * GetEntryByName with parameter "sink-input-by-media-role:x-maemo" is used
105  * for that.
106  ********************************************************************/
107 void MafwGstRendererVolume::getRestoreEntryForMediaRole()
108 {    
109     qDebug() << __PRETTY_FUNCTION__;
110     DBusMessage *msg;
111     DBusError    error;
112
113     dbus_error_init (&error);
114
115     msg = dbus_message_new_method_call(0,
116                                        STREAM_RESTORE_PATH,
117                                        STREAM_RESTORE_IF,
118                                        GET_ENTRY_METHOD);
119     const char *name = ENTRY_NAME;
120     dbus_message_append_args (msg,
121                               DBUS_TYPE_STRING, &name,
122                               DBUS_TYPE_INVALID);
123
124
125     DBusPendingCall *pending = 0;
126     //Return value taken for satisfying coverity tool
127     bool outOfMemory = dbus_connection_send_with_reply( m_dbusConnection, msg, &pending, -1 );
128     Q_UNUSED(outOfMemory);
129     m_pendingCall = pending;
130
131     if (pending)
132     {        
133         qDebug() << __PRETTY_FUNCTION__ << "pending call sent!";
134         dbus_pending_call_set_notify( pending,
135                                       (DBusPendingCallNotifyFunction) getEntryReply,
136                                       (void *)this,
137                                       NULL );
138         dbus_message_unref(msg);
139     }
140     qDebug() << __PRETTY_FUNCTION__ << "exit";
141 }
142
143 /********************************************************************
144  * Now we have the RestoreEntry object path for "x-maemo". Next we just
145  * need to start listening for the VolumeUpdated signals from that object,
146  * and use the Volume property for controlling the role volume.
147  ********************************************************************/
148 void MafwGstRendererVolume::getEntryReply(DBusPendingCall *pending, MafwGstRendererVolume *self)
149 {
150     qDebug() << __PRETTY_FUNCTION__;
151     self->m_pendingCall = 0;
152     DBusMessage *reply;
153     DBusError error;
154
155     dbus_error_init(&error);
156
157     reply = dbus_pending_call_steal_reply(pending);
158
159     if (dbus_set_error_from_message(&error, reply))
160     {
161         qWarning() << "Unable to get volume from pulse audio:" << error.message;
162         dbus_error_free (&error);
163         //Try to reconnect
164         QTimer::singleShot(0, self, SLOT(connectToPulseAudio()));
165     }
166     else if (reply && (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN))
167     {
168         const char *object_path;
169         bool argError = dbus_message_get_args(reply, &error, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID);
170         if (!argError)
171         {
172             qWarning() << "Unable to get volume from pulse audio:" << error.message;
173             dbus_error_free (&error);
174             //Try to reconnect
175             QTimer::singleShot(0, self, SLOT(connectToPulseAudio()));
176         }
177
178         qDebug() << __PRETTY_FUNCTION__ << "Object path: " << object_path;
179         self->m_objectPath = object_path;
180
181         if (self->m_pendingVolumeValue > 0)
182         {
183             qDebug() << __PRETTY_FUNCTION__ << "setting volume to level " << self->m_pendingVolumeValue;
184             self->listenVolumeSignals();
185             self->setVolume(self->m_pendingVolumeValue);
186             self->m_pendingVolumeValue = 0;
187         }
188         else
189         {
190             qDebug() << __PRETTY_FUNCTION__ << "getting initial volume level.";
191             DBusMessage *msg;
192             DBusError    error2;
193             dbus_error_init (&error2);
194
195             msg = dbus_message_new_method_call(0,
196                                                object_path,
197                                                DBUS_INTERFACE_PROPERTIES,
198                                                "Get");
199             const char* interface = STREAM_RESTORE_IF_ENTRY;
200             const char* property = "Volume";
201
202             dbus_message_append_args (msg,
203                                       DBUS_TYPE_STRING, &interface,
204                                       DBUS_TYPE_STRING, &property,
205                                       DBUS_TYPE_INVALID);
206
207             DBusPendingCall *pending = 0;
208             //Return value taken for satisfying coverity tool
209             bool outOfMemory = dbus_connection_send_with_reply( self->m_dbusConnection, msg, &pending, -1 );
210             Q_UNUSED(outOfMemory);
211             self->m_pendingCall = pending;
212             if (pending)
213             {
214                 qDebug() << __PRETTY_FUNCTION__ << "pending call sent!";
215                 dbus_pending_call_set_notify( pending,
216                                               (DBusPendingCallNotifyFunction) volumeReply,
217                                               (void *)self,
218                                               NULL );
219                 dbus_message_unref(msg);
220             }
221         }
222     }
223     dbus_message_unref (reply);
224 }
225
226 /********************************************************************
227  * MafwGstRendererVolume::volumeReply
228  ********************************************************************/
229 void MafwGstRendererVolume::volumeReply(DBusPendingCall *pending, MafwGstRendererVolume *self)
230 {
231     self->m_pendingCall = 0;
232     Q_UNUSED (self);
233     qDebug() << __PRETTY_FUNCTION__;
234
235     DBusMessage *reply;
236     reply = dbus_pending_call_steal_reply(pending);
237
238     DBusError error;
239     dbus_error_init(&error);
240
241     if (dbus_set_error_from_message(&error, reply))
242     {
243         qWarning() << "MafwGstRendererVolume: Unable to get volume from pulse audio:" << error.message;
244         dbus_error_free (&error);
245     }
246     else
247     {
248         DBusMessageIter iter, array_iterator;
249         dbus_message_iter_init (reply, &iter);
250         if(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT)
251         {
252             dbus_message_iter_recurse (&iter, &array_iterator);
253         }
254
255         if (dbus_message_iter_get_arg_type (&array_iterator) == DBUS_TYPE_ARRAY)
256         {
257             self->readVolumeFromStruct(&array_iterator);
258             Q_EMIT self->volumeChanged(self->m_currentVolume);
259         }
260         else
261         {
262             qCritical("Unable to get initial volume from Pulse Audio!!");
263         }
264     }
265     dbus_message_unref (reply);
266     self->listenVolumeSignals();
267 }
268
269 /********************************************************************
270  * MafwGstRendererVolume::listenVolumeSignals
271  ********************************************************************/
272 void MafwGstRendererVolume::listenVolumeSignals()
273 {    
274     qDebug() << __PRETTY_FUNCTION__;
275
276     DBusMessage *message = 0;
277     char *signal = (char *) STREAM_RESTORE_VOLUME_SIGNAL;
278     char           **emptyarray = { 0 };
279
280     message = dbus_message_new_method_call (0,
281                                             PULSE_CORE_PATH,
282                                             0,
283                                             "ListenForSignal");
284
285     dbus_message_append_args (message,
286                               DBUS_TYPE_STRING, &signal,
287                               DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &emptyarray, 0,
288                               DBUS_TYPE_INVALID);
289
290     dbus_connection_send (m_dbusConnection, message, NULL);
291     dbus_connection_flush (m_dbusConnection);
292     dbus_message_unref (message);
293 }
294
295 /********************************************************************
296  * MafwGstRendererVolume::handleIncomingMessages
297  ********************************************************************/
298 void MafwGstRendererVolume::handleIncomingMessages (DBusConnection* conn,
299                     DBusMessage* message,
300                     MafwGstRendererVolume* self)
301 {    
302     qDebug(__PRETTY_FUNCTION__);
303     Q_UNUSED (conn);
304
305     if (message &&
306         dbus_message_has_member (message, "VolumeUpdated") &&
307         dbus_message_has_interface(message, STREAM_RESTORE_IF_ENTRY) &&
308         dbus_message_has_path(message, self->m_objectPath.toAscii()))
309     {
310         qDebug() << "MafwGstRendererVolume: VolumeUpdated signal received.";
311         DBusError error;
312         dbus_error_init(&error);
313
314         if (dbus_set_error_from_message(&error, message))
315         {
316             qWarning() << "Got volume error from pulse audio:" << error.message;
317             dbus_error_free (&error);
318         }
319
320         DBusMessageIter iter;
321         dbus_message_iter_init (message, &iter);
322
323         if (self->readVolumeFromStruct(&iter))
324         {
325             Q_EMIT self->volumeChanged(self->m_currentVolume);
326         }
327     }
328     //When a connection is disconnected, you are guaranteed to get a signal "Disconnected" from the interface DBUS_INTERFACE_LOCAL, path DBUS_PATH_LOCAL.
329     else if (message &&
330              dbus_message_has_member (message, "Disconnected") &&
331              dbus_message_get_interface(message) == QString(DBUS_INTERFACE_LOCAL) &&
332              dbus_message_get_path(message) == QString(DBUS_PATH_LOCAL))
333     {
334         qWarning("MafwGstRendererVolume: Connection with pulse audio is disconnected!");
335         QTimer::singleShot(PULSE_RESTART_DELAY, self, SLOT(connectToPulseAudio()));
336         qDebug("MafwGstRendererVolume: Trying to reconnect.");
337     }
338
339 }
340
341 /********************************************************************
342  * There is a dbus variant containing array of structures. Each structure
343  * contains channels postion and the volume level of this position
344  ********************************************************************/
345 bool MafwGstRendererVolume::readVolumeFromStruct(DBusMessageIter *iterator)
346 {    
347     qDebug(__PRETTY_FUNCTION__);
348     bool volumeChanged = false;
349     DBusMessageIter struct_iterator;
350     dbus_message_iter_recurse (iterator, &struct_iterator);
351     int volume = -1;
352
353     while (dbus_message_iter_get_arg_type(&struct_iterator) == DBUS_TYPE_STRUCT)
354     {
355         DBusMessageIter variant;
356         dbus_message_iter_recurse (&struct_iterator, &variant);
357         int channel, value = 0;
358
359         if (dbus_message_iter_get_arg_type (&variant) == DBUS_TYPE_UINT32)
360         {
361             dbus_message_iter_get_basic (&variant, &channel);
362             qDebug("Channel %d", channel);
363             dbus_message_iter_next (&variant);
364             dbus_message_iter_get_basic (&variant, &value);
365
366             value = qRound((float)value / (float)VOLUME_NORM * 100);
367
368             //We're interested in all the channels that stream-restore happens to give you.
369             //We have to somehow map between a single volume value and per-channel volume.
370             //The usual way to do the mapping is just to use the loudest channel as
371             //the overall volume
372             if (value > volume)
373             {
374                 uint newVolume = value;
375                 if (newVolume != m_currentVolume)
376                 {
377                     m_currentVolume = newVolume;
378                     volumeChanged = true;
379                     qDebug("MafwGstRendererVolume: current volume level has changed: %d", m_currentVolume);
380                 }
381             }
382
383             if (m_currentVolume > 100) //MAFW volume has range 0-100
384             {
385                 qWarning("MafwGstRendererVolume: Pulse audio signals volume level which is out-of range!");
386                 m_currentVolume = 100;
387                 break;
388             }
389         }
390         else
391         {
392             qWarning("MafwGstRendererVolume: Invalid volume value from pulse audio!");
393         }
394         dbus_message_iter_next (&struct_iterator);
395     }
396     return volumeChanged;
397 }
398
399 /********************************************************************
400  * MafwGstRendererVolume::getVolume
401  ********************************************************************/
402 uint MafwGstRendererVolume::getVolume()
403 {
404     qDebug(__PRETTY_FUNCTION__);
405     return m_currentVolume;
406 }
407
408 /********************************************************************
409  * Incoming value has range 0-99 pulseaudio uses values 0-VOLUME_NORM
410  ********************************************************************/
411 bool MafwGstRendererVolume::setVolume (uint value)
412 {
413     bool success = true;
414     qDebug("MafwGstRendererVolume::setVolume (uint %d)", value);
415     if (m_objectPath.isEmpty())
416     {
417         qDebug() << "MafwGstRendererVolume: Can not set volume yet. Waiting for RestoreEntry object path";
418         m_pendingVolumeValue = value;
419         return success;
420     }
421
422     const char* interface = STREAM_RESTORE_IF_ENTRY;
423     const char* property = "Volume";
424     if (value > 100) //MAFW volume has range 0-100
425     {
426         qWarning("MafwGstRendererVolume: Trying to set volume level which is out-of range!");
427         value = (uint)100;
428     }
429     uint nativeValue = (((float)value / (float)100) * VOLUME_NORM);
430     DBusMessage* message;
431     message = dbus_message_new_method_call(0,
432                                            m_objectPath.toAscii(),
433                                            DBUS_INTERFACE_PROPERTIES,
434                                            "Set");
435
436     if ( dbus_message_append_args(message,
437                                  DBUS_TYPE_STRING, &interface,
438                                  DBUS_TYPE_STRING, &property,
439                                  DBUS_TYPE_INVALID))
440     {
441         //Compose the dbus variant containing an array of struct of pair
442         //of unsigned integers va(uu)
443         DBusMessageIter argument_iterator, variant_iterator, array_iterator, struct_iterator;
444         dbus_message_iter_init_append (message, &argument_iterator);
445         dbus_message_iter_open_container (&argument_iterator,
446                                           DBUS_TYPE_VARIANT,
447                                           "a(uu)",
448                                           &variant_iterator);
449         dbus_message_iter_open_container (&variant_iterator,
450                                           DBUS_TYPE_ARRAY,
451                                           "(uu)",
452                                           &array_iterator);
453         dbus_message_iter_open_container (&array_iterator,
454                                           DBUS_TYPE_STRUCT,
455                                           NULL,
456                                           &struct_iterator);
457
458         dbus_message_iter_append_basic (&struct_iterator, DBUS_TYPE_UINT32, &VOLUME_CHANNEL_MONO);
459         dbus_message_iter_append_basic (&struct_iterator, DBUS_TYPE_UINT32, &nativeValue);
460
461         dbus_message_iter_close_container (&array_iterator, &struct_iterator);
462         dbus_message_iter_close_container (&variant_iterator, &array_iterator);
463         dbus_message_iter_close_container (&argument_iterator, &variant_iterator);
464
465         dbus_connection_send (m_dbusConnection, message, NULL);
466         dbus_connection_flush (m_dbusConnection);
467     }
468     else
469     {
470         qWarning("Cannot set volume!");
471         success = false;
472     }
473     dbus_message_unref (message);
474
475     return success;
476 }