a45dbfc69b7aaaea1a90d7a9515122532beeaf3a
[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 <QDeclarativeInfo>
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
40 Sounds::Sounds(QObject *parent) :
41   QObject(parent),
42   m_muted(false),
43   m_ctx(0),
44   m_volume(Sounds::VolumeHigh),
45   m_watcher(new QDBusServiceWatcher("org.pulseaudio.Server",
46                                     QDBusConnection::systemBus(),
47                                     QDBusServiceWatcher::WatchForOwnerChange)) {
48
49   QObject::connect(m_watcher,
50                    SIGNAL(serviceOwnerChanged(const QString&, const QString&, const QString&)),
51                    this,
52                    SLOT(serviceOwnerChanged(const QString&, const QString&, const QString&)));
53
54   // No idea why but canberra will not cache without that!!!
55   setenv("CANBERRA_EVENT_LOOKUP", "1", 1);
56 }
57
58 Sounds::~Sounds() {
59   if (m_ctx) {
60     ca_context_destroy(m_ctx);
61     m_ctx = 0;
62   }
63 }
64
65 void Sounds::serviceOwnerChanged(const QString& serviceName, const QString& oldOwner,
66                                  const QString& newOwner) {
67   Q_UNUSED(serviceName);
68   Q_UNUSED(oldOwner);
69
70   if (newOwner.isEmpty()) {
71     // pulse died:
72     if (m_ctx) {
73       ca_context_destroy(m_ctx);
74       m_ctx = 0;
75     }
76   }
77   else if (!newOwner.isEmpty()) {
78     reload();
79   }
80 }
81
82 void Sounds::playImageCaptureStartedSound() {
83   if (isMuted() || !m_ctx) {
84     return;
85   }
86
87   play(CAMERA_IMAGE_START_SOUND_ID);
88 }
89
90 void Sounds::playImageCaptureEndedSound() {
91   if (isMuted() || !m_ctx) {
92     return;
93   }
94
95   play(CAMERA_IMAGE_END_SOUND_ID);
96 }
97
98 void Sounds::playVideoRecordingStartedSound() {
99   if (isMuted() || !m_ctx) {
100     return;
101   }
102
103   playAndBlock(CAMERA_VIDEO_START_SOUND_ID);
104 }
105
106 void Sounds::playVideoRecordingEndedSound() {
107   if (isMuted() || !m_ctx) {
108     return;
109   }
110
111   play(CAMERA_VIDEO_STOP_SOUND_ID);
112 }
113
114 void Sounds::playAutoFocusAcquiredSound() {
115   if (isMuted() || !m_ctx) {
116     return;
117   }
118
119   play(CAMERA_FOCUS_SOUND_ID);
120 }
121
122 bool Sounds::isMuted() const {
123   return m_muted;
124 }
125
126 void Sounds::setMuted(bool mute) {
127   if (mute != m_muted) {
128     m_muted = mute;
129     emit muteChanged();
130   }
131 }
132
133 void Sounds::reload() {
134   if (m_ctx) {
135     ca_context_destroy(m_ctx);
136     m_ctx = 0;
137   }
138
139   int code = CA_SUCCESS;
140
141   code = ca_context_create(&m_ctx);
142   if (code != CA_SUCCESS) {
143     qWarning() << "Failed to create canberra context" << ca_strerror(code) << code;
144     return;
145   }
146
147   code = ca_context_set_driver(m_ctx, "pulse");
148   if (code != CA_SUCCESS) {
149     qWarning() << "Failed to set canberra driver to pulse audio" << ca_strerror(code) << code;
150   }
151
152   code = ca_context_change_props(m_ctx,
153                                  CA_PROP_MEDIA_ROLE, "camera-sound-effect",
154                                  NULL);
155   if (code != CA_SUCCESS) {
156     qWarning() << "Failed to set context properties" << ca_strerror(code) << code;
157   }
158
159   code = ca_context_open(m_ctx);
160   if (code != CA_SUCCESS) {
161     qWarning() << "Failed to open canberra context" << ca_strerror(code) << code;
162     ca_context_destroy(m_ctx);
163     m_ctx = 0;
164     return;
165   }
166
167   cache(m_imageCaptureStart, CAMERA_IMAGE_START_SOUND_ID);
168   cache(m_imageCaptureEnd, CAMERA_IMAGE_END_SOUND_ID);
169   cache(m_videoRecordingStart, CAMERA_VIDEO_START_SOUND_ID);
170   cache(m_videoRecordingEnd, CAMERA_VIDEO_STOP_SOUND_ID);
171   cache(m_autoFocusAcquired, CAMERA_FOCUS_SOUND_ID);
172 }
173
174 void Sounds::cache(const QString& path, const char *id) {
175   if (path.isEmpty()) {
176     return;
177   }
178
179   int code = ca_context_cache(m_ctx,
180                               CA_PROP_EVENT_ID, id,
181                               CA_PROP_MEDIA_FILENAME, path.toLocal8Bit().data(),
182                               CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
183                               NULL);
184   if (code != CA_SUCCESS) {
185     qWarning() << "Failed to cache" << path << ca_strerror(code) << code;
186   }
187 }
188
189 void Sounds::play(const char *id) {
190   if (!m_ctx) {
191     qWarning() << "Not connected to pulse audio";
192     return;
193   }
194
195   const char *volume = m_volume == Sounds::VolumeLow ?
196     CANBERRA_HEADSET_VOLUME : CANBERRA_FULL_VOLUME;
197
198   int code = ca_context_play(m_ctx, 0,
199                              CA_PROP_CANBERRA_VOLUME, volume,
200                              CA_PROP_EVENT_ID, id,
201                              CA_PROP_MEDIA_ROLE, "camera-sound-effect",
202                              NULL);
203   if (code != CA_SUCCESS) {
204     qmlInfo(this) << "Failed to play sound" << ca_strerror(code) << code;
205   }
206 }
207
208 void ca_finish_callback(ca_context *c, uint32_t id, int error_code, void *userdata) {
209   Q_UNUSED(c);
210   Q_UNUSED(id);
211   Q_UNUSED(error_code);
212
213   QPair<QMutex *, QWaitCondition *> *data =
214     static_cast<QPair<QMutex *, QWaitCondition *> *>(userdata);
215
216   data->second->wakeAll();
217 }
218
219 void Sounds::playAndBlock(const char *id) {
220   QMutex mutex;
221   QWaitCondition cond;
222   ca_proplist *p = 0;
223   if (ca_proplist_create(&p) != CA_SUCCESS) {
224     qDebug() << "Failed to create proplist";
225     return;
226   }
227
228   ca_proplist_sets(p, CA_PROP_CANBERRA_VOLUME, CANBERRA_FULL_VOLUME);
229   ca_proplist_sets(p, CA_PROP_EVENT_ID, id);
230   ca_proplist_sets(p, CA_PROP_MEDIA_ROLE, "camera-sound-effect");
231
232   QPair<QMutex *, QWaitCondition *> data = qMakePair<QMutex *, QWaitCondition *>(&mutex, &cond);
233
234   mutex.lock();
235
236   int code = ca_context_play_full(m_ctx, 0, p, ca_finish_callback, &data);
237
238   if (code != CA_SUCCESS) {
239     qDebug() << "Failed to play sound" << ca_strerror(code) << code;
240     mutex.unlock();
241     ca_proplist_destroy(p);
242
243     return;
244   }
245
246   cond.wait(&mutex);
247   ca_proplist_destroy(p);
248   mutex.unlock();
249 }
250
251 QString Sounds::imageCaptureStart() const {
252   return m_imageCaptureStart;
253 }
254
255 void Sounds::setImageCaptureStart(const QString& path) {
256   if (path != m_imageCaptureStart) {
257     m_imageCaptureStart = path;
258     cache(m_imageCaptureStart, CAMERA_IMAGE_START_SOUND_ID);
259     emit imageCaptureStartChanged();
260   }
261 }
262
263 QString Sounds::imageCaptureEnd() const {
264   return m_imageCaptureEnd;
265 }
266
267 void Sounds::setImageCaptureEnd(const QString& path) {
268   if (path != m_imageCaptureEnd) {
269     m_imageCaptureEnd = path;
270     cache(m_imageCaptureEnd, CAMERA_IMAGE_END_SOUND_ID);
271     emit imageCaptureEndChanged();
272   }
273 }
274
275 QString Sounds::videoRecordingStart() const {
276   return m_videoRecordingStart;
277 }
278
279 void Sounds::setVideoRecordingStart(const QString& path) {
280   if (path != m_videoRecordingStart) {
281     m_videoRecordingStart = path;
282     cache(m_videoRecordingStart, CAMERA_VIDEO_START_SOUND_ID);
283     emit videoRecordingStartChanged();
284   }
285 }
286
287 QString Sounds::videoRecordingEnd() const {
288   return m_videoRecordingEnd;
289 }
290
291 void Sounds::setVideoRecordingEnd(const QString& path) {
292   if (path != m_videoRecordingEnd) {
293     m_videoRecordingEnd = path;
294     cache(m_videoRecordingEnd, CAMERA_VIDEO_STOP_SOUND_ID);
295     emit videoRecordingEndChanged();
296   }
297 }
298
299 QString Sounds::autoFocusAcquired() const {
300   return m_autoFocusAcquired;
301 }
302
303 void Sounds::setAutoFocusAcquired(const QString& path) {
304   if (path != m_autoFocusAcquired) {
305     m_autoFocusAcquired = path;
306     cache(m_autoFocusAcquired, CAMERA_FOCUS_SOUND_ID);
307     emit autoFocusAcquiredChanged();
308   }
309 }
310
311 Sounds::Volume Sounds::volume() const {
312   return m_volume;
313 }
314
315 void Sounds::setVolume(const Sounds::Volume& volume) {
316   if (m_volume != volume) {
317     m_volume = volume;
318     emit volumeChanged();
319   }
320 }