Merge lp:~phablet-team/qtubuntu-camera/fix-trust-prompt-bugs into lp:qtubuntu-camera/stable
- fix-trust-prompt-bugs
- Merge into stable
Status: | Merged |
---|---|
Approved by: | Jim Hodapp |
Approved revision: | 153 |
Merged at revision: | 149 |
Proposed branch: | lp:~phablet-team/qtubuntu-camera/fix-trust-prompt-bugs |
Merge into: | lp:qtubuntu-camera/stable |
Diff against target: |
595 lines (+152/-136) 7 files modified
debian/changelog (+3/-3) debian/control (+1/-1) src/aalmediarecordercontrol.cpp (+115/-86) src/aalmediarecordercontrol.h (+9/-5) src/audiocapture.cpp (+13/-26) src/audiocapture.h (+6/-10) unittests/stubs/audiocapture_stub.cpp (+5/-5) |
To merge this branch: | bzr merge lp:~phablet-team/qtubuntu-camera/fix-trust-prompt-bugs |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jim Hodapp (community) | code | Approve | |
Review via email: mp+268776@code.launchpad.net |
Commit message
Refactor media recorder code to:
- ensure proper cleanups
- prevent double start/stop of recordings
- allow for soundless recordings
- do not start recording before microphone access is allowed/forbidden by user
Description of the change
Refactor media recorder code to:
- ensure proper cleanups
- prevent double start/stop of recordings
- allow for soundless recordings
- do not start recording before microphone access is allowed/forbidden by user
Fixes:
Bug #1487126: Camera/mic trust: initial video corrupt
Bug #1487131: Camera/mic trust: mic denial in camera-app freezes camera
Bug #1487159: revoking mic permission in system-settings, camera appears to record but video is unplayable
- 149. By Florian Boucault
-
Refactor media recorder code to:
- ensure proper cleanups
- prevent double start/stop of recordings
- allow for soundless recordings
- do not start recording before microphone access is allowed/forbidden by user - 150. By Florian Boucault
-
Slightly clearer if/else
- 151. By Jim Hodapp
-
Fix package version and libqtubuntu-
media-signals- dev dependency version.
- 152. By Florian Boucault
-
Small status fix
- 153. By Florian Boucault
-
Reset m_audioCaptureA
vailable in cleanup.
Florian Boucault (fboucault) : | # |
- 154. By Florian Boucault
-
In the case of a timeout error signalled by pulseaudio when trying to use the microphone, be smart and prevent any further recording.
Jim Hodapp (jhodapp) : | # |
Preview Diff
1 | === modified file 'debian/changelog' | |||
2 | --- debian/changelog 2015-07-10 18:12:33 +0000 | |||
3 | +++ debian/changelog 2015-08-21 20:10:16 +0000 | |||
4 | @@ -1,4 +1,4 @@ | |||
6 | 1 | qtubuntu-camera (0.3.3+15.10.20150710-0ubuntu1) wily; urgency=medium | 1 | qtubuntu-camera (0.3.3+15.04.20150710-0ubuntu1) vivid; urgency=medium |
7 | 2 | 2 | ||
8 | 3 | [ CI Train Bot ] | 3 | [ CI Train Bot ] |
9 | 4 | * New rebuild forced. | 4 | * New rebuild forced. |
10 | @@ -9,7 +9,7 @@ | |||
11 | 9 | 9 | ||
12 | 10 | -- CI Train Bot <ci-train-bot@canonical.com> Fri, 10 Jul 2015 18:12:33 +0000 | 10 | -- CI Train Bot <ci-train-bot@canonical.com> Fri, 10 Jul 2015 18:12:33 +0000 |
13 | 11 | 11 | ||
15 | 12 | qtubuntu-camera (0.3.3+15.10.20150706-0ubuntu1) wily; urgency=medium | 12 | qtubuntu-camera (0.3.3+15.04.20150706-0ubuntu1) vivid; urgency=medium |
16 | 13 | 13 | ||
17 | 14 | [ Alberto Aguirre ] | 14 | [ Alberto Aguirre ] |
18 | 15 | * Remove dependency on platform-api. Use QScreen instead to obtain | 15 | * Remove dependency on platform-api. Use QScreen instead to obtain |
19 | @@ -20,7 +20,7 @@ | |||
20 | 20 | 20 | ||
21 | 21 | -- CI Train Bot <ci-train-bot@canonical.com> Mon, 06 Jul 2015 18:22:45 +0000 | 21 | -- CI Train Bot <ci-train-bot@canonical.com> Mon, 06 Jul 2015 18:22:45 +0000 |
22 | 22 | 22 | ||
24 | 23 | qtubuntu-camera (0.3.3+15.10.20150629-0ubuntu1) wily; urgency=medium | 23 | qtubuntu-camera (0.3.3+15.04.20150629-0ubuntu1) vivid; urgency=medium |
25 | 24 | 24 | ||
26 | 25 | [ Florian Boucault ] | 25 | [ Florian Boucault ] |
27 | 26 | * Write GPS location even if no altitude is given. (LP: #1447689) | 26 | * Write GPS location even if no altitude is given. (LP: #1447689) |
28 | 27 | 27 | ||
29 | === modified file 'debian/control' | |||
30 | --- debian/control 2015-07-10 18:07:53 +0000 | |||
31 | +++ debian/control 2015-08-21 20:10:16 +0000 | |||
32 | @@ -8,7 +8,7 @@ | |||
33 | 8 | libhybris-dev (>= 0.1.0+git20131207+e452e83-0ubuntu34), | 8 | libhybris-dev (>= 0.1.0+git20131207+e452e83-0ubuntu34), |
34 | 9 | libpulse-dev, | 9 | libpulse-dev, |
35 | 10 | libqt5opengl5-dev, | 10 | libqt5opengl5-dev, |
37 | 11 | libqtubuntu-media-signals-dev (>=0.3+15.10.20150618.1-0ubuntu1), | 11 | libqtubuntu-media-signals-dev (>=0.3+15.04.20141104-0ubuntu1), |
38 | 12 | pkg-config, | 12 | pkg-config, |
39 | 13 | qt5-default, | 13 | qt5-default, |
40 | 14 | qtmultimedia5-dev, | 14 | qtmultimedia5-dev, |
41 | 15 | 15 | ||
42 | === modified file 'src/aalmediarecordercontrol.cpp' | |||
43 | --- src/aalmediarecordercontrol.cpp 2015-01-26 16:21:39 +0000 | |||
44 | +++ src/aalmediarecordercontrol.cpp 2015-08-21 20:10:16 +0000 | |||
45 | @@ -25,7 +25,6 @@ | |||
46 | 25 | #include <QDebug> | 25 | #include <QDebug> |
47 | 26 | #include <QFile> | 26 | #include <QFile> |
48 | 27 | #include <QFileInfo> | 27 | #include <QFileInfo> |
49 | 28 | #include <QThread> | ||
50 | 29 | #include <QTimer> | 28 | #include <QTimer> |
51 | 30 | 29 | ||
52 | 31 | #include <hybris/camera/camera_compatibility_layer.h> | 30 | #include <hybris/camera/camera_compatibility_layer.h> |
53 | @@ -65,7 +64,7 @@ | |||
54 | 65 | m_currentState(QMediaRecorder::StoppedState), | 64 | m_currentState(QMediaRecorder::StoppedState), |
55 | 66 | m_currentStatus(QMediaRecorder::UnloadedStatus), | 65 | m_currentStatus(QMediaRecorder::UnloadedStatus), |
56 | 67 | m_recordingTimer(0), | 66 | m_recordingTimer(0), |
58 | 68 | m_workerThread(0) | 67 | m_audioCaptureAvailable(false) |
59 | 69 | { | 68 | { |
60 | 70 | } | 69 | } |
61 | 71 | 70 | ||
62 | @@ -83,6 +82,8 @@ | |||
63 | 83 | << errno << ")"; | 82 | << errno << ")"; |
64 | 84 | } | 83 | } |
65 | 85 | deleteRecorder(); | 84 | deleteRecorder(); |
66 | 85 | m_audioCaptureThread.quit(); | ||
67 | 86 | m_audioCaptureThread.wait(); | ||
68 | 86 | } | 87 | } |
69 | 87 | 88 | ||
70 | 88 | /*! | 89 | /*! |
71 | @@ -158,76 +159,44 @@ | |||
72 | 158 | /*! | 159 | /*! |
73 | 159 | * \brief Starts the main microphone reader/writer loop in AudioCapture (run) | 160 | * \brief Starts the main microphone reader/writer loop in AudioCapture (run) |
74 | 160 | */ | 161 | */ |
76 | 161 | void AalMediaRecorderControl::onStartThread() | 162 | void AalMediaRecorderControl::startAudioCaptureThread() |
77 | 162 | { | 163 | { |
82 | 163 | qDebug() << "Starting microphone reader/writer worker thread"; | 164 | qDebug() << "Starting microphone reader/writer thread"; |
83 | 164 | // Start the microphone read/write worker thread | 165 | // Start the microphone read/write thread |
84 | 165 | m_workerThread->start(); | 166 | m_audioCaptureThread.start(); |
85 | 166 | Q_EMIT startWorkerThread(); | 167 | Q_EMIT audioCaptureThreadStarted(); |
86 | 167 | } | 168 | } |
87 | 168 | 169 | ||
88 | 169 | /*! | 170 | /*! |
89 | 170 | * \brief AalMediaRecorderControl::init makes sure the mediarecorder is | 171 | * \brief AalMediaRecorderControl::init makes sure the mediarecorder is |
90 | 171 | * initialized | 172 | * initialized |
91 | 172 | */ | 173 | */ |
93 | 173 | void AalMediaRecorderControl::initRecorder() | 174 | bool AalMediaRecorderControl::initRecorder() |
94 | 174 | { | 175 | { |
95 | 175 | if (m_mediaRecorder == 0) { | 176 | if (m_mediaRecorder == 0) { |
96 | 176 | m_mediaRecorder = android_media_new_recorder(); | 177 | m_mediaRecorder = android_media_new_recorder(); |
97 | 177 | |||
98 | 178 | m_audioCapture = new AudioCapture(m_mediaRecorder); | ||
99 | 179 | m_workerThread = new QThread; | ||
100 | 180 | |||
101 | 181 | if (m_audioCapture == 0) { | ||
102 | 182 | qWarning() << "Unable to create new audio capture, audio recording won't function"; | ||
103 | 183 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "Unable to create new audio capture, audio recording won't function"); | ||
104 | 184 | } | ||
105 | 185 | else | ||
106 | 186 | { | ||
107 | 187 | bool ret = false; | ||
108 | 188 | |||
109 | 189 | // Make sure that m_audioCapture is executed within the m_workerThread affinity | ||
110 | 190 | m_audioCapture->moveToThread(m_workerThread); | ||
111 | 191 | |||
112 | 192 | // Finished signal is for when the workerThread is completed. Important to connect this so that | ||
113 | 193 | // resources are cleaned up in the proper order and not leaked | ||
114 | 194 | ret = connect(m_audioCapture, SIGNAL(finished()), m_workerThread, SLOT(quit())); | ||
115 | 195 | if (!ret) | ||
116 | 196 | qWarning() << "Failed to connect quit() to the m_audioCapture finished signal"; | ||
117 | 197 | ret = connect(m_audioCapture, SIGNAL(finished()), m_audioCapture, SLOT(deleteLater())); | ||
118 | 198 | if (!ret) | ||
119 | 199 | qWarning() << "Failed to connect deleteLater() to the m_audioCapture finished signal"; | ||
120 | 200 | // Clean up the worker thread after we finish recording | ||
121 | 201 | ret = connect(m_workerThread, SIGNAL(finished()), m_workerThread, SLOT(deleteLater())); | ||
122 | 202 | if (!ret) | ||
123 | 203 | qWarning() << "Failed to connect deleteLater() to the m_workerThread finished signal"; | ||
124 | 204 | // startWorkerThread signal comes from an Android layer callback that resides down in | ||
125 | 205 | // the AudioRecordHybris class | ||
126 | 206 | ret = connect(this, SIGNAL(startWorkerThread()), m_audioCapture, SLOT(run())); | ||
127 | 207 | if (!ret) | ||
128 | 208 | qWarning() << "Failed to connect run() to the local startWorkerThread signal"; | ||
129 | 209 | |||
130 | 210 | // Call onStartThreadCb when the reader side of the named pipe has been setup | ||
131 | 211 | m_audioCapture->init(&AalMediaRecorderControl::onStartThreadCb, this); | ||
132 | 212 | } | ||
133 | 213 | |||
134 | 214 | if (m_mediaRecorder == 0) { | 178 | if (m_mediaRecorder == 0) { |
135 | 215 | qWarning() << "Unable to create new media recorder"; | 179 | qWarning() << "Unable to create new media recorder"; |
136 | 216 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "Unable to create new media recorder"); | 180 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "Unable to create new media recorder"); |
137 | 181 | return false; | ||
138 | 182 | } | ||
139 | 183 | |||
140 | 184 | int audioInitError = initAudioCapture(); | ||
141 | 185 | if (audioInitError == 0) { | ||
142 | 186 | m_audioCaptureAvailable = true; | ||
143 | 217 | } else { | 187 | } else { |
147 | 218 | setStatus(QMediaRecorder::LoadedStatus); | 188 | m_audioCaptureAvailable = false; |
148 | 219 | android_recorder_set_error_cb(m_mediaRecorder, &AalMediaRecorderControl::errorCB, this); | 189 | if (audioInitError == AudioCapture::AUDIO_CAPTURE_TIMEOUT_ERROR) { |
149 | 220 | android_camera_unlock(m_service->androidControl()); | 190 | deleteRecorder(); |
150 | 191 | return false; | ||
151 | 192 | } | ||
152 | 221 | } | 193 | } |
162 | 222 | } | 194 | |
163 | 223 | 195 | android_recorder_set_error_cb(m_mediaRecorder, &AalMediaRecorderControl::errorCB, this); | |
164 | 224 | if (m_recordingTimer == 0) { | 196 | android_camera_unlock(m_service->androidControl()); |
165 | 225 | m_recordingTimer = new QTimer(this); | 197 | } |
166 | 226 | m_recordingTimer->setInterval(DURATION_UPDATE_INTERVAL); | 198 | |
167 | 227 | m_recordingTimer->setSingleShot(false); | 199 | return true; |
159 | 228 | QObject::connect(m_recordingTimer, SIGNAL(timeout()), | ||
160 | 229 | this, SLOT(updateDuration())); | ||
161 | 230 | } | ||
168 | 231 | } | 200 | } |
169 | 232 | 201 | ||
170 | 233 | /*! | 202 | /*! |
171 | @@ -236,6 +205,8 @@ | |||
172 | 236 | */ | 205 | */ |
173 | 237 | void AalMediaRecorderControl::deleteRecorder() | 206 | void AalMediaRecorderControl::deleteRecorder() |
174 | 238 | { | 207 | { |
175 | 208 | deleteAudioCapture(); | ||
176 | 209 | |||
177 | 239 | if (m_mediaRecorder == 0) | 210 | if (m_mediaRecorder == 0) |
178 | 240 | return; | 211 | return; |
179 | 241 | 212 | ||
180 | @@ -245,6 +216,43 @@ | |||
181 | 245 | setStatus(QMediaRecorder::UnloadedStatus); | 216 | setStatus(QMediaRecorder::UnloadedStatus); |
182 | 246 | } | 217 | } |
183 | 247 | 218 | ||
184 | 219 | int AalMediaRecorderControl::initAudioCapture() | ||
185 | 220 | { | ||
186 | 221 | // setting up audio recording; m_audioCapture is executed within the m_workerThread affinity | ||
187 | 222 | m_audioCapture = new AudioCapture(m_mediaRecorder); | ||
188 | 223 | int audioInitError = m_audioCapture->setupMicrophoneStream(); | ||
189 | 224 | if (audioInitError != 0) | ||
190 | 225 | { | ||
191 | 226 | qWarning() << "Failed to setup PulseAudio microphone recording stream"; | ||
192 | 227 | delete m_audioCapture; | ||
193 | 228 | m_audioCapture = 0; | ||
194 | 229 | } else { | ||
195 | 230 | m_audioCapture->moveToThread(&m_audioCaptureThread); | ||
196 | 231 | |||
197 | 232 | // startWorkerThread signal comes from an Android layer callback that resides down in | ||
198 | 233 | // the AudioRecordHybris class | ||
199 | 234 | connect(this, SIGNAL(audioCaptureThreadStarted()), m_audioCapture, SLOT(run())); | ||
200 | 235 | |||
201 | 236 | // Call recorderReadAudioCallback when the reader side of the named pipe has been setup | ||
202 | 237 | m_audioCapture->init(&AalMediaRecorderControl::recorderReadAudioCallback, this); | ||
203 | 238 | } | ||
204 | 239 | return audioInitError; | ||
205 | 240 | } | ||
206 | 241 | |||
207 | 242 | void AalMediaRecorderControl::deleteAudioCapture() | ||
208 | 243 | { | ||
209 | 244 | if (m_audioCapture == 0) | ||
210 | 245 | return; | ||
211 | 246 | |||
212 | 247 | m_audioCapture->stopCapture(); | ||
213 | 248 | m_audioCaptureThread.quit(); | ||
214 | 249 | m_audioCaptureThread.wait(); | ||
215 | 250 | |||
216 | 251 | delete m_audioCapture; | ||
217 | 252 | m_audioCapture = 0; | ||
218 | 253 | m_audioCaptureAvailable = false; | ||
219 | 254 | } | ||
220 | 255 | |||
221 | 248 | /*! | 256 | /*! |
222 | 249 | * \brief AalMediaRecorderControl::errorCB handles errors from the android layer | 257 | * \brief AalMediaRecorderControl::errorCB handles errors from the android layer |
223 | 250 | * \param context | 258 | * \param context |
224 | @@ -285,10 +293,7 @@ | |||
225 | 285 | 293 | ||
226 | 286 | switch (state) { | 294 | switch (state) { |
227 | 287 | case QMediaRecorder::RecordingState: { | 295 | case QMediaRecorder::RecordingState: { |
232 | 288 | int ret = startRecording(); | 296 | startRecording(); |
229 | 289 | if (ret == -1) { | ||
230 | 290 | setStatus(QMediaRecorder::LoadedStatus); | ||
231 | 291 | } | ||
233 | 292 | break; | 297 | break; |
234 | 293 | } | 298 | } |
235 | 294 | case QMediaRecorder::StoppedState: { | 299 | case QMediaRecorder::StoppedState: { |
236 | @@ -349,17 +354,21 @@ | |||
237 | 349 | return RECORDER_INITIALIZATION_ERROR; | 354 | return RECORDER_INITIALIZATION_ERROR; |
238 | 350 | } | 355 | } |
239 | 351 | 356 | ||
240 | 357 | if (m_currentStatus != QMediaRecorder::UnloadedStatus) { | ||
241 | 358 | qWarning() << "Can't start a recording while another one is in progess"; | ||
242 | 359 | return RECORDER_NOT_AVAILABLE_ERROR; | ||
243 | 360 | } | ||
244 | 361 | |||
245 | 362 | setStatus(QMediaRecorder::LoadingStatus); | ||
246 | 363 | |||
247 | 352 | m_duration = 0; | 364 | m_duration = 0; |
248 | 353 | Q_EMIT durationChanged(m_duration); | 365 | Q_EMIT durationChanged(m_duration); |
249 | 354 | 366 | ||
253 | 355 | initRecorder(); | 367 | if (!initRecorder()) { |
254 | 356 | if (m_mediaRecorder == 0) { | 368 | setStatus(QMediaRecorder::UnloadedStatus); |
252 | 357 | deleteRecorder(); | ||
255 | 358 | return RECORDER_NOT_AVAILABLE_ERROR; | 369 | return RECORDER_NOT_AVAILABLE_ERROR; |
256 | 359 | } | 370 | } |
257 | 360 | 371 | ||
258 | 361 | setStatus(QMediaRecorder::StartingStatus); | ||
259 | 362 | |||
260 | 363 | QVideoEncoderSettings videoSettings = m_service->videoEncoderControl()->videoSettings(); | 372 | QVideoEncoderSettings videoSettings = m_service->videoEncoderControl()->videoSettings(); |
261 | 364 | 373 | ||
262 | 365 | int ret; | 374 | int ret; |
263 | @@ -369,12 +378,15 @@ | |||
264 | 369 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "android_recorder_setCamera() failed\n"); | 378 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "android_recorder_setCamera() failed\n"); |
265 | 370 | return RECORDER_INITIALIZATION_ERROR; | 379 | return RECORDER_INITIALIZATION_ERROR; |
266 | 371 | } | 380 | } |
273 | 372 | //state initial / idle | 381 | // state initial / idle |
274 | 373 | ret = android_recorder_setAudioSource(m_mediaRecorder, ANDROID_AUDIO_SOURCE_CAMCORDER); | 382 | if (m_audioCaptureAvailable) { |
275 | 374 | if (ret < 0) { | 383 | ret = android_recorder_setAudioSource(m_mediaRecorder, ANDROID_AUDIO_SOURCE_CAMCORDER); |
276 | 375 | deleteRecorder(); | 384 | if (ret < 0) { |
277 | 376 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "android_recorder_setAudioSource() failed"); | 385 | deleteRecorder(); |
278 | 377 | return RECORDER_INITIALIZATION_ERROR; | 386 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "android_recorder_setAudioSource() failed"); |
279 | 387 | return RECORDER_INITIALIZATION_ERROR; | ||
280 | 388 | } | ||
281 | 389 | |||
282 | 378 | } | 390 | } |
283 | 379 | ret = android_recorder_setVideoSource(m_mediaRecorder, ANDROID_VIDEO_SOURCE_CAMERA); | 391 | ret = android_recorder_setVideoSource(m_mediaRecorder, ANDROID_VIDEO_SOURCE_CAMERA); |
284 | 380 | if (ret < 0) { | 392 | if (ret < 0) { |
285 | @@ -382,19 +394,21 @@ | |||
286 | 382 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "android_recorder_setVideoSource() failed"); | 394 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "android_recorder_setVideoSource() failed"); |
287 | 383 | return RECORDER_INITIALIZATION_ERROR; | 395 | return RECORDER_INITIALIZATION_ERROR; |
288 | 384 | } | 396 | } |
290 | 385 | //state initialized | 397 | // state initialized |
291 | 386 | ret = android_recorder_setOutputFormat(m_mediaRecorder, ANDROID_OUTPUT_FORMAT_MPEG_4); | 398 | ret = android_recorder_setOutputFormat(m_mediaRecorder, ANDROID_OUTPUT_FORMAT_MPEG_4); |
292 | 387 | if (ret < 0) { | 399 | if (ret < 0) { |
293 | 388 | deleteRecorder(); | 400 | deleteRecorder(); |
294 | 389 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "android_recorder_setOutputFormat() failed"); | 401 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "android_recorder_setOutputFormat() failed"); |
295 | 390 | return RECORDER_INITIALIZATION_ERROR; | 402 | return RECORDER_INITIALIZATION_ERROR; |
296 | 391 | } | 403 | } |
303 | 392 | //state DataSourceConfigured | 404 | // state DataSourceConfigured |
304 | 393 | ret = android_recorder_setAudioEncoder(m_mediaRecorder, ANDROID_AUDIO_ENCODER_AAC); | 405 | if (m_audioCaptureAvailable) { |
305 | 394 | if (ret < 0) { | 406 | ret = android_recorder_setAudioEncoder(m_mediaRecorder, ANDROID_AUDIO_ENCODER_AAC); |
306 | 395 | deleteRecorder(); | 407 | if (ret < 0) { |
307 | 396 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "android_recorder_setAudioEncoder() failed"); | 408 | deleteRecorder(); |
308 | 397 | return RECORDER_INITIALIZATION_ERROR; | 409 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "android_recorder_setAudioEncoder() failed"); |
309 | 410 | return RECORDER_INITIALIZATION_ERROR; | ||
310 | 411 | } | ||
311 | 398 | } | 412 | } |
312 | 399 | // FIXME set codec from settings | 413 | // FIXME set codec from settings |
313 | 400 | ret = android_recorder_setVideoEncoder(m_mediaRecorder, ANDROID_VIDEO_ENCODER_H264); | 414 | ret = android_recorder_setVideoEncoder(m_mediaRecorder, ANDROID_VIDEO_ENCODER_H264); |
314 | @@ -466,7 +480,11 @@ | |||
315 | 466 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "android_recorder_prepare() failed"); | 480 | Q_EMIT error(RECORDER_INITIALIZATION_ERROR, "android_recorder_prepare() failed"); |
316 | 467 | return RECORDER_INITIALIZATION_ERROR; | 481 | return RECORDER_INITIALIZATION_ERROR; |
317 | 468 | } | 482 | } |
319 | 469 | //state prepared | 483 | |
320 | 484 | setStatus(QMediaRecorder::LoadedStatus); | ||
321 | 485 | setStatus(QMediaRecorder::StartingStatus); | ||
322 | 486 | |||
323 | 487 | // state prepared | ||
324 | 470 | ret = android_recorder_start(m_mediaRecorder); | 488 | ret = android_recorder_start(m_mediaRecorder); |
325 | 471 | if (ret < 0) { | 489 | if (ret < 0) { |
326 | 472 | close(m_outfd); | 490 | close(m_outfd); |
327 | @@ -481,6 +499,13 @@ | |||
328 | 481 | 499 | ||
329 | 482 | setStatus(QMediaRecorder::RecordingStatus); | 500 | setStatus(QMediaRecorder::RecordingStatus); |
330 | 483 | 501 | ||
331 | 502 | if (m_recordingTimer == 0) { | ||
332 | 503 | m_recordingTimer = new QTimer(this); | ||
333 | 504 | m_recordingTimer->setInterval(DURATION_UPDATE_INTERVAL); | ||
334 | 505 | m_recordingTimer->setSingleShot(false); | ||
335 | 506 | QObject::connect(m_recordingTimer, SIGNAL(timeout()), | ||
336 | 507 | this, SLOT(updateDuration())); | ||
337 | 508 | } | ||
338 | 484 | m_recordingTimer->start(); | 509 | m_recordingTimer->start(); |
339 | 485 | 510 | ||
340 | 486 | return 0; | 511 | return 0; |
341 | @@ -496,8 +521,9 @@ | |||
342 | 496 | qWarning() << "Can't stop recording properly, m_mediaRecorder is NULL"; | 521 | qWarning() << "Can't stop recording properly, m_mediaRecorder is NULL"; |
343 | 497 | return; | 522 | return; |
344 | 498 | } | 523 | } |
347 | 499 | if (m_audioCapture == 0) { | 524 | |
348 | 500 | qWarning() << "Can't stop recording properly, m_audioCapture is NULL"; | 525 | if (m_currentStatus != QMediaRecorder::RecordingStatus) { |
349 | 526 | qWarning() << "Can't stop a recording that has not started"; | ||
350 | 501 | return; | 527 | return; |
351 | 502 | } | 528 | } |
352 | 503 | 529 | ||
353 | @@ -514,7 +540,9 @@ | |||
354 | 514 | // NOTE: This must come after the android_recorder_stop call, otherwise the | 540 | // NOTE: This must come after the android_recorder_stop call, otherwise the |
355 | 515 | // RecordThread instance will block the MPEG4Writer pthread_join when trying to | 541 | // RecordThread instance will block the MPEG4Writer pthread_join when trying to |
356 | 516 | // cleanly stop recording. | 542 | // cleanly stop recording. |
358 | 517 | m_audioCapture->stopCapture(); | 543 | if (m_audioCapture != 0) { |
359 | 544 | m_audioCapture->stopCapture(); | ||
360 | 545 | } | ||
361 | 518 | 546 | ||
362 | 519 | android_recorder_reset(m_mediaRecorder); | 547 | android_recorder_reset(m_mediaRecorder); |
363 | 520 | 548 | ||
364 | @@ -542,9 +570,10 @@ | |||
365 | 542 | android_recorder_setParameters(m_mediaRecorder, param.toLocal8Bit().data()); | 570 | android_recorder_setParameters(m_mediaRecorder, param.toLocal8Bit().data()); |
366 | 543 | } | 571 | } |
367 | 544 | 572 | ||
369 | 545 | void AalMediaRecorderControl::onStartThreadCb(void *context) | 573 | void AalMediaRecorderControl::recorderReadAudioCallback(void *context) |
370 | 546 | { | 574 | { |
371 | 547 | AalMediaRecorderControl *thiz = static_cast<AalMediaRecorderControl*>(context); | 575 | AalMediaRecorderControl *thiz = static_cast<AalMediaRecorderControl*>(context); |
374 | 548 | if (thiz != NULL) | 576 | if (thiz != NULL) { |
375 | 549 | thiz->onStartThread(); | 577 | thiz->startAudioCaptureThread(); |
376 | 578 | } | ||
377 | 550 | } | 579 | } |
378 | 551 | 580 | ||
379 | === modified file 'src/aalmediarecordercontrol.h' | |||
380 | --- src/aalmediarecordercontrol.h 2015-01-23 02:49:03 +0000 | |||
381 | +++ src/aalmediarecordercontrol.h 2015-08-21 20:10:16 +0000 | |||
382 | @@ -21,6 +21,7 @@ | |||
383 | 21 | #include <QMediaRecorderControl> | 21 | #include <QMediaRecorderControl> |
384 | 22 | #include <QSize> | 22 | #include <QSize> |
385 | 23 | #include <QUrl> | 23 | #include <QUrl> |
386 | 24 | #include <QThread> | ||
387 | 24 | 25 | ||
388 | 25 | #include <stdint.h> | 26 | #include <stdint.h> |
389 | 26 | 27 | ||
390 | @@ -58,23 +59,25 @@ | |||
391 | 58 | virtual void setMuted(bool muted); | 59 | virtual void setMuted(bool muted); |
392 | 59 | virtual void setState(QMediaRecorder::State state); | 60 | virtual void setState(QMediaRecorder::State state); |
393 | 60 | virtual void setVolume(qreal gain); | 61 | virtual void setVolume(qreal gain); |
395 | 61 | void onStartThread(); | 62 | void startAudioCaptureThread(); |
396 | 62 | 63 | ||
397 | 63 | signals: | 64 | signals: |
399 | 64 | void startWorkerThread(); | 65 | void audioCaptureThreadStarted(); |
400 | 65 | 66 | ||
401 | 66 | private Q_SLOTS: | 67 | private Q_SLOTS: |
402 | 67 | virtual void updateDuration(); | 68 | virtual void updateDuration(); |
403 | 68 | void handleError(); | 69 | void handleError(); |
404 | 70 | void deleteAudioCapture(); | ||
405 | 69 | 71 | ||
406 | 70 | private: | 72 | private: |
408 | 71 | void initRecorder(); | 73 | bool initRecorder(); |
409 | 72 | void deleteRecorder(); | 74 | void deleteRecorder(); |
410 | 75 | int initAudioCapture(); | ||
411 | 73 | void setStatus(QMediaRecorder::Status status); | 76 | void setStatus(QMediaRecorder::Status status); |
412 | 74 | int startRecording(); | 77 | int startRecording(); |
413 | 75 | void stopRecording(); | 78 | void stopRecording(); |
414 | 76 | void setParameter(const QString ¶meter, int value); | 79 | void setParameter(const QString ¶meter, int value); |
416 | 77 | static void onStartThreadCb(void *context); | 80 | static void recorderReadAudioCallback(void *context); |
417 | 78 | 81 | ||
418 | 79 | AalCameraService *m_service; | 82 | AalCameraService *m_service; |
419 | 80 | MediaRecorderWrapper *m_mediaRecorder; | 83 | MediaRecorderWrapper *m_mediaRecorder; |
420 | @@ -85,7 +88,8 @@ | |||
421 | 85 | QMediaRecorder::State m_currentState; | 88 | QMediaRecorder::State m_currentState; |
422 | 86 | QMediaRecorder::Status m_currentStatus; | 89 | QMediaRecorder::Status m_currentStatus; |
423 | 87 | QTimer *m_recordingTimer; | 90 | QTimer *m_recordingTimer; |
425 | 88 | QThread *m_workerThread; | 91 | QThread m_audioCaptureThread; |
426 | 92 | bool m_audioCaptureAvailable; | ||
427 | 89 | 93 | ||
428 | 90 | static const int RECORDER_GENERAL_ERROR = -1; | 94 | static const int RECORDER_GENERAL_ERROR = -1; |
429 | 91 | static const int RECORDER_NOT_AVAILABLE_ERROR = -2; | 95 | static const int RECORDER_NOT_AVAILABLE_ERROR = -2; |
430 | 92 | 96 | ||
431 | === modified file 'src/audiocapture.cpp' | |||
432 | --- src/audiocapture.cpp 2014-12-03 17:08:38 +0000 | |||
433 | +++ src/audiocapture.cpp 2015-08-21 20:10:16 +0000 | |||
434 | @@ -37,6 +37,8 @@ | |||
435 | 37 | 37 | ||
436 | 38 | AudioCapture::~AudioCapture() | 38 | AudioCapture::~AudioCapture() |
437 | 39 | { | 39 | { |
438 | 40 | android_recorder_set_audio_read_cb(m_mediaRecorder, NULL, NULL); | ||
439 | 41 | |||
440 | 40 | if (m_audioPipe >= 0) | 42 | if (m_audioPipe >= 0) |
441 | 41 | close(m_audioPipe); | 43 | close(m_audioPipe); |
442 | 42 | if (m_paStream != NULL) | 44 | if (m_paStream != NULL) |
443 | @@ -46,10 +48,10 @@ | |||
444 | 46 | /*! | 48 | /*! |
445 | 47 | * \brief Initializes AudioCapture so that it's ready to read microphone data from Pulseaudio | 49 | * \brief Initializes AudioCapture so that it's ready to read microphone data from Pulseaudio |
446 | 48 | */ | 50 | */ |
448 | 49 | bool AudioCapture::init(StartWorkerThreadCb cb, void *context) | 51 | bool AudioCapture::init(RecorderReadAudioCallback callback, void *context) |
449 | 50 | { | 52 | { |
452 | 51 | // The MediaRecorderLayer will call method (cb) when it's ready to encode a new audio buffer | 53 | // The MediaRecorderLayer will call method (callback) when it's ready to encode a new audio buffer |
453 | 52 | android_recorder_set_audio_read_cb(m_mediaRecorder, cb, context); | 54 | android_recorder_set_audio_read_cb(m_mediaRecorder, callback, context); |
454 | 53 | 55 | ||
455 | 54 | return true; | 56 | return true; |
456 | 55 | } | 57 | } |
457 | @@ -68,17 +70,12 @@ | |||
458 | 68 | */ | 70 | */ |
459 | 69 | void AudioCapture::run() | 71 | void AudioCapture::run() |
460 | 70 | { | 72 | { |
461 | 73 | m_flagExit = false; | ||
462 | 71 | qDebug() << __PRETTY_FUNCTION__; | 74 | qDebug() << __PRETTY_FUNCTION__; |
463 | 72 | 75 | ||
464 | 73 | int bytesWritten = 0, bytesRead = 0; | 76 | int bytesWritten = 0, bytesRead = 0; |
465 | 74 | const size_t readSize = sizeof(m_audioBuf); | 77 | const size_t readSize = sizeof(m_audioBuf); |
466 | 75 | 78 | ||
467 | 76 | if (!setupMicrophoneStream()) | ||
468 | 77 | { | ||
469 | 78 | qWarning() << "Failed to setup PulseAudio microphone recording stream"; | ||
470 | 79 | return; | ||
471 | 80 | } | ||
472 | 81 | |||
473 | 82 | if (!setupPipe()) | 79 | if (!setupPipe()) |
474 | 83 | { | 80 | { |
475 | 84 | qWarning() << "Failed to open /dev/socket/micshm, cannot write data to pipe"; | 81 | qWarning() << "Failed to open /dev/socket/micshm, cannot write data to pipe"; |
476 | @@ -101,8 +98,6 @@ | |||
477 | 101 | pa_simple_free(m_paStream); | 98 | pa_simple_free(m_paStream); |
478 | 102 | m_paStream = NULL; | 99 | m_paStream = NULL; |
479 | 103 | } | 100 | } |
480 | 104 | |||
481 | 105 | Q_EMIT finished(); | ||
482 | 106 | } | 101 | } |
483 | 107 | 102 | ||
484 | 108 | /*! | 103 | /*! |
485 | @@ -122,21 +117,9 @@ | |||
486 | 122 | } | 117 | } |
487 | 123 | 118 | ||
488 | 124 | /*! | 119 | /*! |
489 | 125 | * \brief Signals AalMediaRecorderControl to start the main thread loop. | ||
490 | 126 | * \detail This is necessary due to thread contexts. Starting of the main thread loop | ||
491 | 127 | * for AudioCapture must be done in the main thread context and not in the AudioCapture | ||
492 | 128 | * thread context, otherwise the loop start signal will never be seen. | ||
493 | 129 | */ | ||
494 | 130 | void AudioCapture::startThreadLoop() | ||
495 | 131 | { | ||
496 | 132 | Q_EMIT startThread(); | ||
497 | 133 | qDebug() << "Emitted startThread(), should start reading from mic"; | ||
498 | 134 | } | ||
499 | 135 | |||
500 | 136 | /*! | ||
501 | 137 | * \brief Sets up the Pulseaudio microphone input channel | 120 | * \brief Sets up the Pulseaudio microphone input channel |
502 | 138 | */ | 121 | */ |
504 | 139 | bool AudioCapture::setupMicrophoneStream() | 122 | int AudioCapture::setupMicrophoneStream() |
505 | 140 | { | 123 | { |
506 | 141 | // FIXME: Get these parameters more dynamically from the control | 124 | // FIXME: Get these parameters more dynamically from the control |
507 | 142 | static const pa_sample_spec ss = { | 125 | static const pa_sample_spec ss = { |
508 | @@ -150,10 +133,14 @@ | |||
509 | 150 | if (m_paStream == NULL) | 133 | if (m_paStream == NULL) |
510 | 151 | { | 134 | { |
511 | 152 | qWarning() << "Failed to open a PulseAudio channel to read the microphone: " << pa_strerror(error); | 135 | qWarning() << "Failed to open a PulseAudio channel to read the microphone: " << pa_strerror(error); |
513 | 153 | return false; | 136 | if (error == PA_ERR_TIMEOUT) { |
514 | 137 | return AUDIO_CAPTURE_TIMEOUT_ERROR; | ||
515 | 138 | } else { | ||
516 | 139 | return AUDIO_CAPTURE_GENERAL_ERROR; | ||
517 | 140 | } | ||
518 | 154 | } | 141 | } |
519 | 155 | 142 | ||
521 | 156 | return true; | 143 | return 0; |
522 | 157 | } | 144 | } |
523 | 158 | 145 | ||
524 | 159 | /*! | 146 | /*! |
525 | 160 | 147 | ||
526 | === modified file 'src/audiocapture.h' | |||
527 | --- src/audiocapture.h 2014-07-30 14:50:39 +0000 | |||
528 | +++ src/audiocapture.h 2015-08-21 20:10:16 +0000 | |||
529 | @@ -34,28 +34,24 @@ | |||
530 | 34 | { | 34 | { |
531 | 35 | Q_OBJECT | 35 | Q_OBJECT |
532 | 36 | 36 | ||
534 | 37 | typedef void (*StartWorkerThreadCb)(void *context); | 37 | typedef void (*RecorderReadAudioCallback)(void *context); |
535 | 38 | public: | 38 | public: |
536 | 39 | static const int AUDIO_CAPTURE_GENERAL_ERROR = -1; | ||
537 | 40 | static const int AUDIO_CAPTURE_TIMEOUT_ERROR = -2; | ||
538 | 41 | |||
539 | 39 | explicit AudioCapture(MediaRecorderWrapper *mediaRecorder); | 42 | explicit AudioCapture(MediaRecorderWrapper *mediaRecorder); |
540 | 40 | ~AudioCapture(); | 43 | ~AudioCapture(); |
541 | 41 | 44 | ||
543 | 42 | bool init(StartWorkerThreadCb cb, void *context); | 45 | bool init(RecorderReadAudioCallback callback, void *context); |
544 | 43 | /* Terminates the Pulseaudio reader/writer QThread */ | 46 | /* Terminates the Pulseaudio reader/writer QThread */ |
545 | 47 | int setupMicrophoneStream(); | ||
546 | 44 | void stopCapture(); | 48 | void stopCapture(); |
547 | 45 | 49 | ||
548 | 46 | signals: | ||
549 | 47 | void startThread(); | ||
550 | 48 | void finished(); | ||
551 | 49 | |||
552 | 50 | public Q_SLOTS: | 50 | public Q_SLOTS: |
553 | 51 | void run(); | 51 | void run(); |
554 | 52 | 52 | ||
555 | 53 | private Q_SLOTS: | ||
556 | 54 | void startThreadLoop(); | ||
557 | 55 | |||
558 | 56 | private: | 53 | private: |
559 | 57 | int readMicrophone(); | 54 | int readMicrophone(); |
560 | 58 | bool setupMicrophoneStream(); | ||
561 | 59 | bool setupPipe(); | 55 | bool setupPipe(); |
562 | 60 | ssize_t loopWrite(int fd, const void *data, size_t len); | 56 | ssize_t loopWrite(int fd, const void *data, size_t len); |
563 | 61 | int writeDataToPipe(); | 57 | int writeDataToPipe(); |
564 | 62 | 58 | ||
565 | === modified file 'unittests/stubs/audiocapture_stub.cpp' | |||
566 | --- unittests/stubs/audiocapture_stub.cpp 2014-07-30 14:50:39 +0000 | |||
567 | +++ unittests/stubs/audiocapture_stub.cpp 2015-08-21 20:10:16 +0000 | |||
568 | @@ -29,7 +29,7 @@ | |||
569 | 29 | { | 29 | { |
570 | 30 | } | 30 | } |
571 | 31 | 31 | ||
573 | 32 | bool AudioCapture::init(StartWorkerThreadCb cb, void *context) | 32 | bool AudioCapture::init(RecorderReadAudioCallback cb, void *context) |
574 | 33 | { | 33 | { |
575 | 34 | Q_UNUSED(cb); | 34 | Q_UNUSED(cb); |
576 | 35 | Q_UNUSED(context); | 35 | Q_UNUSED(context); |
577 | @@ -40,14 +40,14 @@ | |||
578 | 40 | { | 40 | { |
579 | 41 | } | 41 | } |
580 | 42 | 42 | ||
581 | 43 | int AudioCapture::setupMicrophoneStream() | ||
582 | 44 | { | ||
583 | 45 | } | ||
584 | 46 | |||
585 | 43 | void AudioCapture::run() | 47 | void AudioCapture::run() |
586 | 44 | { | 48 | { |
587 | 45 | } | 49 | } |
588 | 46 | 50 | ||
589 | 47 | void AudioCapture::startThreadLoop() | ||
590 | 48 | { | ||
591 | 49 | } | ||
592 | 50 | |||
593 | 51 | int AudioCapture::readMicrophone() | 51 | int AudioCapture::readMicrophone() |
594 | 52 | { | 52 | { |
595 | 53 | return 0; | 53 | return 0; |
A few comments/questions inline.