Updated copyright year
[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_conf(0),
47   m_watcher(new QDBusServiceWatcher("org.pulseaudio.Server",
48                                     QDBusConnection::systemBus(),
49                                     QDBusServiceWatcher::WatchForOwnerChange)) {
50
51   QObject::connect(m_watcher,
52                    SIGNAL(serviceOwnerChanged(const QString&, const QString&, const QString&)),
53                    this,
54                    SLOT(serviceOwnerChanged(const QString&, const QString&, const QString&)));
55
56   // No idea why but canberra will not cache without that!!!
57   setenv("CANBERRA_EVENT_LOOKUP", "1", 1);
58
59   m_audioRoute = new ContextProperty(AUDIO_ROUTE_PROPERTY, this);
60   QObject::connect(m_audioRoute, SIGNAL(valueChanged()), this, SLOT(audioConnectionChanged()));
61   m_audioRoute->waitForSubscription(true);
62   audioConnectionChanged();
63 }
64
65 Sounds::~Sounds() {
66   if (m_ctx) {
67     ca_context_destroy(m_ctx);
68     m_ctx = 0;
69   }
70 }
71
72 void Sounds::setConfig(QtCamConfig *conf) {
73   m_conf = conf;
74 }
75
76 void Sounds::serviceOwnerChanged(const QString& serviceName, const QString& oldOwner,
77                                  const QString& newOwner) {
78   Q_UNUSED(serviceName);
79   Q_UNUSED(oldOwner);
80
81   if (newOwner.isEmpty()) {
82     // pulse died:
83     if (m_ctx) {
84       ca_context_destroy(m_ctx);
85       m_ctx = 0;
86     }
87   }
88   else if (!newOwner.isEmpty()) {
89     reload();
90   }
91 }
92
93 void Sounds::imageCaptureStarted() {
94   if (isMuted() || !m_ctx) {
95     return;
96   }
97
98   play(CAMERA_IMAGE_START_SOUND_ID);
99 }
100
101 void Sounds::imageCaptureEnded() {
102   if (isMuted() || !m_ctx) {
103     return;
104   }
105
106   play(CAMERA_IMAGE_END_SOUND_ID);
107 }
108
109 void Sounds::videoRecordingStarted() {
110   if (isMuted() || !m_ctx) {
111     return;
112   }
113
114   playAndBlock(CAMERA_VIDEO_START_SOUND_ID);
115 }
116
117 void Sounds::videoRecordingEnded() {
118   if (isMuted() || !m_ctx) {
119     return;
120   }
121
122   play(CAMERA_VIDEO_STOP_SOUND_ID);
123 }
124
125 void Sounds::autoFocusAcquired() {
126   if (isMuted() || !m_ctx) {
127     return;
128   }
129
130   play(CAMERA_FOCUS_SOUND_ID);
131 }
132
133 bool Sounds::isMuted() const {
134   return m_muted;
135 }
136
137 void Sounds::setMuted(bool mute) {
138   if (mute != m_muted) {
139     m_muted = mute;
140     emit muteChanged();
141   }
142 }
143
144 void Sounds::reload() {
145   if (m_ctx) {
146     ca_context_destroy(m_ctx);
147     m_ctx = 0;
148   }
149
150   int code = CA_SUCCESS;
151
152   code = ca_context_create(&m_ctx);
153   if (code != CA_SUCCESS) {
154     qWarning() << "Failed to create canberra context" << ca_strerror(code) << code;
155     return;
156   }
157
158   code = ca_context_set_driver(m_ctx, "pulse");
159   if (code != CA_SUCCESS) {
160     qWarning() << "Failed to set canberra driver to pulse audio" << ca_strerror(code) << code;
161   }
162
163   code = ca_context_change_props(m_ctx,
164                                  CA_PROP_MEDIA_ROLE, "camera-sound-effect",
165                                  NULL);
166   if (code != CA_SUCCESS) {
167     qWarning() << "Failed to set context properties" << ca_strerror(code) << code;
168   }
169
170   code = ca_context_open(m_ctx);
171   if (code != CA_SUCCESS) {
172     qWarning() << "Failed to open canberra context" << ca_strerror(code) << code;
173     ca_context_destroy(m_ctx);
174     m_ctx = 0;
175     return;
176   }
177
178   cache(m_conf->imageCaptureStartedSound(), CAMERA_IMAGE_START_SOUND_ID);
179   cache(m_conf->imageCaptureEndedSound(), CAMERA_IMAGE_END_SOUND_ID);
180   cache(m_conf->videoRecordingStartedSound(), CAMERA_VIDEO_START_SOUND_ID);
181   cache(m_conf->videoRecordingEndedSound(), CAMERA_VIDEO_STOP_SOUND_ID);
182   cache(m_conf->autoFocusAcquiredSound(), CAMERA_FOCUS_SOUND_ID);
183 }
184
185 void Sounds::cache(const QString& path, const char *id) {
186   if (path.isEmpty()) {
187     return;
188   }
189
190   int code = ca_context_cache(m_ctx,
191                               CA_PROP_EVENT_ID, id,
192                               CA_PROP_MEDIA_FILENAME, path.toLocal8Bit().data(),
193                               CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
194                               NULL);
195   if (code != CA_SUCCESS) {
196     qWarning() << "Failed to cache" << path << ca_strerror(code) << code;
197   }
198 }
199
200 void Sounds::play(const char *id) {
201   if (!m_ctx) {
202     qWarning() << "Not connected to pulse audio";
203     return;
204   }
205
206   int code = ca_context_play(m_ctx, 0,
207                              CA_PROP_CANBERRA_VOLUME, m_volume.toAscii().constData(),
208                              CA_PROP_EVENT_ID, id,
209                              CA_PROP_MEDIA_ROLE, "camera-sound-effect",
210                              NULL);
211   if (code != CA_SUCCESS) {
212     qDebug() << "Failed to play sound" << ca_strerror(code) << code;
213   }
214 }
215
216 void ca_finish_callback(ca_context *c, uint32_t id, int error_code, void *userdata) {
217   Q_UNUSED(c);
218   Q_UNUSED(id);
219   Q_UNUSED(error_code);
220
221   QPair<QMutex *, QWaitCondition *> *data =
222     static_cast<QPair<QMutex *, QWaitCondition *> *>(userdata);
223
224   data->second->wakeAll();
225 }
226
227 void Sounds::playAndBlock(const char *id) {
228   QMutex mutex;
229   QWaitCondition cond;
230   ca_proplist *p = 0;
231   if (ca_proplist_create(&p) != CA_SUCCESS) {
232     qDebug() << "Failed to create proplist";
233     return;
234   }
235
236   ca_proplist_sets(p, CA_PROP_CANBERRA_VOLUME, CANBERRA_FULL_VOLUME);
237   ca_proplist_sets(p, CA_PROP_EVENT_ID, id);
238   ca_proplist_sets(p, CA_PROP_MEDIA_ROLE, "camera-sound-effect");
239
240   QPair<QMutex *, QWaitCondition *> data = qMakePair<QMutex *, QWaitCondition *>(&mutex, &cond);
241
242   mutex.lock();
243
244   int code = ca_context_play_full(m_ctx, 0, p, ca_finish_callback, &data);
245
246   if (code != CA_SUCCESS) {
247     qDebug() << "Failed to play sound" << ca_strerror(code) << code;
248     mutex.unlock();
249     ca_proplist_destroy(p);
250
251     return;
252   }
253
254   cond.wait(&mutex);
255   ca_proplist_destroy(p);
256   mutex.unlock();
257 }
258
259 void Sounds::audioConnectionChanged() {
260   if (m_audioRoute->value().toString() != AUDIO_ROUTE_SPEAKERS) {
261     m_volume = CANBERRA_HEADSET_VOLUME;
262   } else {
263     m_volume = CANBERRA_FULL_VOLUME;
264   }
265 }