Kill DeclarativeQtCameraNotifications
[harmattan/cameraplus] / declarative / sounds.cpp
1 /*!
2  * This file is part of CameraPlus.
3  *
4  * Copyright (C) 2012-2013 Mohammed Sameer <msameer@foolab.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21 #include "sounds.h"
22 #include <QDebug>
23 #include "qtcamconfig.h"
24 #include <QMutex>
25 #include <QWaitCondition>
26 #include <QDBusServiceWatcher>
27 #include <QDBusConnection>
28 #include <contextsubscriber/contextproperty.h>
29
30 #define CAMERA_IMAGE_START_SOUND_ID  "camera-image-start"
31 #define CAMERA_IMAGE_END_SOUND_ID    "camera-image-end"
32 #define CAMERA_VIDEO_START_SOUND_ID  "camera-video-start"
33 #define CAMERA_VIDEO_STOP_SOUND_ID   "camera-video-stop"
34 #define CAMERA_FOCUS_SOUND_ID        "camera-focus"
35
36 // Odd, volume has to be a char *
37 #define CANBERRA_FULL_VOLUME         "0.0"
38 #define CANBERRA_HEADSET_VOLUME      "-24.0"
39 #define AUDIO_ROUTE_PROPERTY         "/com/nokia/policy/audio_route"
40 #define AUDIO_ROUTE_SPEAKERS         "ihf"
41
42 Sounds::Sounds(QObject *parent) :
43   QObject(parent),
44   m_muted(false),
45   m_ctx(0),
46   m_watcher(new QDBusServiceWatcher("org.pulseaudio.Server",
47                                     QDBusConnection::systemBus(),
48                                     QDBusServiceWatcher::WatchForOwnerChange)) {
49
50   QObject::connect(m_watcher,
51                    SIGNAL(serviceOwnerChanged(const QString&, const QString&, const QString&)),
52                    this,
53                    SLOT(serviceOwnerChanged(const QString&, const QString&, const QString&)));
54
55   // No idea why but canberra will not cache without that!!!
56   setenv("CANBERRA_EVENT_LOOKUP", "1", 1);
57
58   m_audioRoute = new ContextProperty(AUDIO_ROUTE_PROPERTY, this);
59   QObject::connect(m_audioRoute, SIGNAL(valueChanged()), this, SLOT(audioConnectionChanged()));
60   m_audioRoute->waitForSubscription(true);
61   audioConnectionChanged();
62 }
63
64 Sounds::~Sounds() {
65   if (m_ctx) {
66     ca_context_destroy(m_ctx);
67     m_ctx = 0;
68   }
69 }
70
71 void Sounds::serviceOwnerChanged(const QString& serviceName, const QString& oldOwner,
72                                  const QString& newOwner) {
73   Q_UNUSED(serviceName);
74   Q_UNUSED(oldOwner);
75
76   if (newOwner.isEmpty()) {
77     // pulse died:
78     if (m_ctx) {
79       ca_context_destroy(m_ctx);
80       m_ctx = 0;
81     }
82   }
83   else if (!newOwner.isEmpty()) {
84     reload();
85   }
86 }
87
88 void Sounds::playImageCaptureStartedSound() {
89   if (isMuted() || !m_ctx) {
90     return;
91   }
92
93   play(CAMERA_IMAGE_START_SOUND_ID);
94 }
95
96 void Sounds::playImageCaptureEndedSound() {
97   if (isMuted() || !m_ctx) {
98     return;
99   }
100
101   play(CAMERA_IMAGE_END_SOUND_ID);
102 }
103
104 void Sounds::playVideoRecordingStartedSound() {
105   if (isMuted() || !m_ctx) {
106     return;
107   }
108
109   playAndBlock(CAMERA_VIDEO_START_SOUND_ID);
110 }
111
112 void Sounds::playVideoRecordingEndedSound() {
113   if (isMuted() || !m_ctx) {
114     return;
115   }
116
117   play(CAMERA_VIDEO_STOP_SOUND_ID);
118 }
119
120 void Sounds::playAutoFocusAcquiredSound() {
121   if (isMuted() || !m_ctx) {
122     return;
123   }
124
125   play(CAMERA_FOCUS_SOUND_ID);
126 }
127
128 bool Sounds::isMuted() const {
129   return m_muted;
130 }
131
132 void Sounds::setMuted(bool mute) {
133   if (mute != m_muted) {
134     m_muted = mute;
135     emit muteChanged();
136   }
137 }
138
139 void Sounds::reload() {
140   if (m_ctx) {
141     ca_context_destroy(m_ctx);
142     m_ctx = 0;
143   }
144
145   int code = CA_SUCCESS;
146
147   code = ca_context_create(&m_ctx);
148   if (code != CA_SUCCESS) {
149     qWarning() << "Failed to create canberra context" << ca_strerror(code) << code;
150     return;
151   }
152
153   code = ca_context_set_driver(m_ctx, "pulse");
154   if (code != CA_SUCCESS) {
155     qWarning() << "Failed to set canberra driver to pulse audio" << ca_strerror(code) << code;
156   }
157
158   code = ca_context_change_props(m_ctx,
159                                  CA_PROP_MEDIA_ROLE, "camera-sound-effect",
160                                  NULL);
161   if (code != CA_SUCCESS) {
162     qWarning() << "Failed to set context properties" << ca_strerror(code) << code;
163   }
164
165   code = ca_context_open(m_ctx);
166   if (code != CA_SUCCESS) {
167     qWarning() << "Failed to open canberra context" << ca_strerror(code) << code;
168     ca_context_destroy(m_ctx);
169     m_ctx = 0;
170     return;
171   }
172
173   cache(m_imageCaptureStart, CAMERA_IMAGE_START_SOUND_ID);
174   cache(m_imageCaptureEnd, CAMERA_IMAGE_END_SOUND_ID);
175   cache(m_videoRecordingStart, CAMERA_VIDEO_START_SOUND_ID);
176   cache(m_videoRecordingEnd, CAMERA_VIDEO_STOP_SOUND_ID);
177   cache(m_autoFocusAcquired, CAMERA_FOCUS_SOUND_ID);
178 }
179
180 void Sounds::cache(const QString& path, const char *id) {
181   if (path.isEmpty()) {
182     return;
183   }
184
185   int code = ca_context_cache(m_ctx,
186                               CA_PROP_EVENT_ID, id,
187                               CA_PROP_MEDIA_FILENAME, path.toLocal8Bit().data(),
188                               CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
189                               NULL);
190   if (code != CA_SUCCESS) {
191     qWarning() << "Failed to cache" << path << ca_strerror(code) << code;
192   }
193 }
194
195 void Sounds::play(const char *id) {
196   if (!m_ctx) {
197     qWarning() << "Not connected to pulse audio";
198     return;
199   }
200
201   int code = ca_context_play(m_ctx, 0,
202                              CA_PROP_CANBERRA_VOLUME, m_volume.toAscii().constData(),
203                              CA_PROP_EVENT_ID, id,
204                              CA_PROP_MEDIA_ROLE, "camera-sound-effect",
205                              NULL);
206   if (code != CA_SUCCESS) {
207     qDebug() << "Failed to play sound" << ca_strerror(code) << code;
208   }
209 }
210
211 void ca_finish_callback(ca_context *c, uint32_t id, int error_code, void *userdata) {
212   Q_UNUSED(c);
213   Q_UNUSED(id);
214   Q_UNUSED(error_code);
215
216   QPair<QMutex *, QWaitCondition *> *data =
217     static_cast<QPair<QMutex *, QWaitCondition *> *>(userdata);
218
219   data->second->wakeAll();
220 }
221
222 void Sounds::playAndBlock(const char *id) {
223   QMutex mutex;
224   QWaitCondition cond;
225   ca_proplist *p = 0;
226   if (ca_proplist_create(&p) != CA_SUCCESS) {
227     qDebug() << "Failed to create proplist";
228     return;
229   }
230
231   ca_proplist_sets(p, CA_PROP_CANBERRA_VOLUME, CANBERRA_FULL_VOLUME);
232   ca_proplist_sets(p, CA_PROP_EVENT_ID, id);
233   ca_proplist_sets(p, CA_PROP_MEDIA_ROLE, "camera-sound-effect");
234
235   QPair<QMutex *, QWaitCondition *> data = qMakePair<QMutex *, QWaitCondition *>(&mutex, &cond);
236
237   mutex.lock();
238
239   int code = ca_context_play_full(m_ctx, 0, p, ca_finish_callback, &data);
240
241   if (code != CA_SUCCESS) {
242     qDebug() << "Failed to play sound" << ca_strerror(code) << code;
243     mutex.unlock();
244     ca_proplist_destroy(p);
245
246     return;
247   }
248
249   cond.wait(&mutex);
250   ca_proplist_destroy(p);
251   mutex.unlock();
252 }
253
254 void Sounds::audioConnectionChanged() {
255   if (m_audioRoute->value().toString() != AUDIO_ROUTE_SPEAKERS) {
256     m_volume = CANBERRA_HEADSET_VOLUME;
257   } else {
258     m_volume = CANBERRA_FULL_VOLUME;
259   }
260 }
261
262 QString Sounds::imageCaptureStart() const {
263   return m_imageCaptureStart;
264 }
265
266 void Sounds::setImageCaptureStart(const QString& path) {
267   if (path != m_imageCaptureStart) {
268     m_imageCaptureStart = path;
269     cache(m_imageCaptureStart, CAMERA_IMAGE_START_SOUND_ID);
270     emit imageCaptureStartChanged();
271   }
272 }
273
274 QString Sounds::imageCaptureEnd() const {
275   return m_imageCaptureEnd;
276 }
277
278 void Sounds::setImageCaptureEnd(const QString& path) {
279   if (path != m_imageCaptureEnd) {
280     m_imageCaptureEnd = path;
281     cache(m_imageCaptureEnd, CAMERA_IMAGE_END_SOUND_ID);
282     emit imageCaptureEndChanged();
283   }
284 }
285
286 QString Sounds::videoRecordingStart() const {
287   return m_videoRecordingStart;
288 }
289
290 void Sounds::setVideoRecordingStart(const QString& path) {
291   if (path != m_videoRecordingStart) {
292     m_videoRecordingStart = path;
293     cache(m_videoRecordingStart, CAMERA_VIDEO_START_SOUND_ID);
294     emit videoRecordingStartChanged();
295   }
296 }
297
298 QString Sounds::videoRecordingEnd() const {
299   return m_videoRecordingEnd;
300 }
301
302 void Sounds::setVideoRecordingEnd(const QString& path) {
303   if (path != m_videoRecordingEnd) {
304     m_videoRecordingEnd = path;
305     cache(m_videoRecordingEnd, CAMERA_VIDEO_STOP_SOUND_ID);
306     emit videoRecordingEndChanged();
307   }
308 }
309
310 QString Sounds::autoFocusAcquired() const {
311   return m_autoFocusAcquired;
312 }
313
314 void Sounds::setAutoFocusAcquired(const QString& path) {
315   if (path != m_autoFocusAcquired) {
316     m_autoFocusAcquired = path;
317     cache(m_autoFocusAcquired, CAMERA_FOCUS_SOUND_ID);
318     emit autoFocusAcquiredChanged();
319   }
320 }