Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qandroidcapturesession.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
8#include "qaudioinput.h"
9#include "qaudiooutput.h"
15#include <private/qplatformaudioinput_p.h>
16#include <private/qplatformaudiooutput_p.h>
17#include <private/qmediarecorder_p.h>
18#include <private/qmediastoragelocation_p.h>
19#include <QtMultimedia/private/qmultimedia_ranges_p.h>
20
21#include <algorithm>
22
23QT_BEGIN_NAMESPACE
24namespace ranges = QtMultimediaPrivate::ranges;
25
26QAndroidCaptureSession::QAndroidCaptureSession()
27 : QObject()
28 , m_mediaRecorder(0)
29 , m_cameraSession(0)
30 , m_duration(0)
31 , m_state(QMediaRecorder::StoppedState)
33 , m_audioEncoder(AndroidMediaRecorder::DefaultAudioEncoder)
35{
36 m_notifyTimer.setInterval(1000);
37 connect(&m_notifyTimer, &QTimer::timeout, this, &QAndroidCaptureSession::updateDuration);
38}
39
41{
42 stop();
43 m_mediaRecorder = nullptr;
44 if (m_audioInput && m_audioOutput)
46}
47
49{
50 if (m_cameraSession) {
51 disconnect(m_connOpenCamera);
52 disconnect(m_connActiveChangedCamera);
53 }
54
55 m_cameraSession = cameraSession;
56 if (m_cameraSession) {
57 m_connOpenCamera = connect(cameraSession, &QAndroidCameraSession::opened,
58 this, &QAndroidCaptureSession::onCameraOpened);
59 m_connActiveChangedCamera = connect(cameraSession, &QAndroidCameraSession::activeChanged,
60 this, [this](bool isActive) {
61 if (!isActive)
62 stop();
63 });
64 }
65}
66
67void QAndroidCaptureSession::setAudioInput(QPlatformAudioInput *input)
68{
69 if (m_audioInput == input)
70 return;
71
72 if (m_audioInput) {
73 disconnect(m_audioInputChanged);
74 }
75
76 m_audioInput = input;
77
78 if (m_audioInput) {
79 m_audioInputChanged = connect(m_audioInput->q, &QAudioInput::deviceChanged, this, [this]() {
80 if (m_state == QMediaRecorder::RecordingState)
81 m_mediaRecorder->setAudioInput(m_audioInput->device.id());
82 updateStreamingState();
83 });
84 }
85 updateStreamingState();
86}
87
88void QAndroidCaptureSession::setAudioOutput(QPlatformAudioOutput *output)
89{
90 if (m_audioOutput == output)
91 return;
92
93 if (m_audioOutput)
94 disconnect(m_audioOutputChanged);
95
96 m_audioOutput = output;
97
98 if (m_audioOutput) {
99 m_audioOutputChanged = connect(m_audioOutput->q, &QAudioOutput::deviceChanged, this,
100 [this] () {
101 AndroidMediaPlayer::setAudioOutput(m_audioOutput->device.id());
102 updateStreamingState();
103 });
104 AndroidMediaPlayer::setAudioOutput(m_audioOutput->device.id());
105 }
106 updateStreamingState();
107}
108
109void QAndroidCaptureSession::updateStreamingState()
110{
111 if (m_audioInput && m_audioOutput) {
112 AndroidMediaPlayer::startSoundStreaming(m_audioInput->device.id().toInt(),
113 m_audioOutput->device.id().toInt());
114 } else {
116 }
117}
118
119QMediaRecorder::RecorderState QAndroidCaptureSession::state() const
120{
121 return m_state;
122}
123
124void QAndroidCaptureSession::setKeepAlive(bool keepAlive)
125{
126 if (m_cameraSession)
127 m_cameraSession->setKeepAlive(keepAlive);
128}
129
130
131void QAndroidCaptureSession::start(QMediaEncoderSettings &settings, const QUrl &outputLocation)
132{
133 if (m_state == QMediaRecorder::RecordingState)
134 return;
135
136 if (!m_cameraSession && !m_audioInput) {
137 updateError(QMediaRecorder::ResourceError, QLatin1String("No devices are set"));
138 return;
139 }
140
141 setKeepAlive(true);
142
143 const bool validCameraSession = m_cameraSession && m_cameraSession->camera();
144
145 if (validCameraSession && !qt_androidCheckCameraPermission()) {
146 updateError(QMediaRecorder::ResourceError, QLatin1String("Camera permission denied."));
147 setKeepAlive(false);
148 return;
149 }
150
151 if (m_audioInput && !qt_androidCheckMicrophonePermission()) {
152 updateError(QMediaRecorder::ResourceError, QLatin1String("Microphone permission denied."));
153 setKeepAlive(false);
154 return;
155 }
156
157 m_mediaRecorder = std::make_shared<AndroidMediaRecorder>();
158 connect(m_mediaRecorder.get(), &AndroidMediaRecorder::error, this,
159 &QAndroidCaptureSession::onError);
160 connect(m_mediaRecorder.get(), &AndroidMediaRecorder::info, this,
161 &QAndroidCaptureSession::onInfo);
162
163 applySettings(settings);
164
165 // Set audio/video sources
166 if (validCameraSession) {
168 m_cameraSession->camera()->unlock();
169
170 m_mediaRecorder->setCamera(m_cameraSession->camera());
171 m_mediaRecorder->setVideoSource(AndroidMediaRecorder::Camera);
172 }
173
174 if (m_audioInput) {
175 m_mediaRecorder->setAudioInput(m_audioInput->device.id());
176 if (!m_mediaRecorder->isAudioSourceSet())
177 m_mediaRecorder->setAudioSource(AndroidMediaRecorder::DefaultAudioSource);
178 }
179
180 // Set output format
181 m_mediaRecorder->setOutputFormat(m_outputFormat);
182
183 // Set video encoder settings
184 if (validCameraSession) {
185 m_mediaRecorder->setVideoSize(settings.videoResolution());
186 m_mediaRecorder->setVideoFrameRate(qRound(settings.videoFrameRate()));
187 m_mediaRecorder->setVideoEncodingBitRate(settings.videoBitRate());
188 m_mediaRecorder->setVideoEncoder(m_videoEncoder);
189
190 // media recorder is also compensanting the mirror on front camera
191 auto rotation = m_cameraSession->currentCameraRotation();
192 if (m_cameraSession->camera()->getFacing() == AndroidCamera::CameraFacingFront)
193 rotation = (360 - rotation) % 360; // remove mirror compensation
194
195 m_mediaRecorder->setOrientationHint(rotation);
196 }
197
198 // Set audio encoder settings
199 if (m_audioInput) {
200 m_mediaRecorder->setAudioChannels(settings.audioChannelCount());
201 m_mediaRecorder->setAudioEncodingBitRate(settings.audioBitRate());
202 m_mediaRecorder->setAudioSamplingRate(settings.audioSampleRate());
203 m_mediaRecorder->setAudioEncoder(m_audioEncoder);
204 }
205
206 QString extension = settings.preferredSuffix();
207 // Set output file
208 auto location = outputLocation.toString(QUrl::PreferLocalFile);
209 QString filePath = location;
210 if (QUrl(filePath).scheme() != QLatin1String("content")) {
211 filePath = QMediaStorageLocation::generateFileName(
212 location, m_cameraSession ? QStandardPaths::MoviesLocation
213 : QStandardPaths::MusicLocation, extension);
214 }
215
216 m_usedOutputLocation = QUrl::fromLocalFile(filePath);
217 m_outputLocationIsStandard = location.isEmpty() || QFileInfo(location).isRelative();
218 m_mediaRecorder->setOutputFile(filePath);
219
220 if (validCameraSession) {
221 m_cameraSession->disableRotation();
222 }
223
224 if (!m_mediaRecorder->prepare()) {
225 updateError(QMediaRecorder::FormatError,
226 QLatin1String("Unable to prepare the media recorder."));
227 restartViewfinder();
228
229 return;
230 }
231
232 if (!m_mediaRecorder->start()) {
233 updateError(QMediaRecorder::FormatError, QMediaRecorderPrivate::msgFailedStartRecording());
234 restartViewfinder();
235
236 return;
237 }
238
239 m_elapsedTime.start();
240 m_notifyTimer.start();
241 updateDuration();
242
243 if (validCameraSession) {
244 m_cameraSession->setReadyForCapture(false);
245
246 // Preview frame callback is cleared when setting up the camera with the media recorder.
247 // We need to reset it.
249 }
250
251 m_state = QMediaRecorder::RecordingState;
252 emit stateChanged(m_state);
253}
254
256{
257 if (m_state == QMediaRecorder::StoppedState || m_mediaRecorder == nullptr)
258 return;
259
260 m_mediaRecorder->stop();
261 m_notifyTimer.stop();
262 updateDuration();
263 m_elapsedTime.invalidate();
264
265 m_mediaRecorder = nullptr;
266
267 if (m_cameraSession && m_cameraSession->isActive()) {
268 // Viewport needs to be restarted after recording
269 restartViewfinder();
270 }
271
272 if (!error) {
273 // if the media is saved into the standard media location, register it
274 // with the Android media scanner so it appears immediately in apps
275 // such as the gallery.
276 if (m_outputLocationIsStandard)
277 AndroidMultimediaUtils::registerMediaFile(m_usedOutputLocation.toLocalFile());
278
279 emit actualLocationChanged(m_usedOutputLocation);
280 }
281
282 m_state = QMediaRecorder::StoppedState;
283 emit stateChanged(m_state);
284}
285
287{
288 return m_duration;
289}
290
291void QAndroidCaptureSession::applySettings(QMediaEncoderSettings &settings)
292{
293 // container settings
294 auto fileFormat = settings.mediaFormat().fileFormat();
295 if (!m_cameraSession && fileFormat == QMediaFormat::AAC) {
296 m_outputFormat = AndroidMediaRecorder::AAC_ADTS;
297 } else if (fileFormat == QMediaFormat::Ogg) {
298 m_outputFormat = AndroidMediaRecorder::OGG;
299 } else if (fileFormat == QMediaFormat::WebM) {
300 m_outputFormat = AndroidMediaRecorder::WEBM;
301// } else if (fileFormat == QLatin1String("3gp")) {
302// m_outputFormat = AndroidMediaRecorder::THREE_GPP;
303 } else {
304 // fallback to MP4
305 m_outputFormat = AndroidMediaRecorder::MPEG_4;
306 }
307
308 // audio settings
309 if (settings.audioChannelCount() <= 0)
310 settings.setAudioChannelCount(m_defaultSettings.audioChannels);
311 if (settings.audioBitRate() <= 0)
312 settings.setAudioBitRate(m_defaultSettings.audioBitRate);
313 if (settings.audioSampleRate() <= 0)
314 settings.setAudioSampleRate(m_defaultSettings.audioSampleRate);
315
316 if (settings.audioCodec() == QMediaFormat::AudioCodec::AAC)
317 m_audioEncoder = AndroidMediaRecorder::AAC;
318 else if (settings.audioCodec() == QMediaFormat::AudioCodec::Opus)
319 m_audioEncoder = AndroidMediaRecorder::OPUS;
320 else if (settings.audioCodec() == QMediaFormat::AudioCodec::Vorbis)
321 m_audioEncoder = AndroidMediaRecorder::VORBIS;
322 else
323 m_audioEncoder = m_defaultSettings.audioEncoder;
324
325
326 // video settings
327 if (m_cameraSession && m_cameraSession->camera()) {
328 if (settings.videoResolution().isEmpty()) {
329 settings.setVideoResolution(m_defaultSettings.videoResolution);
330 } else if (!m_supportedResolutions.contains(settings.videoResolution())) {
331 // if the requested resolution is not supported, find the closest one
332 QSize reqSize = settings.videoResolution();
333 int reqPixelCount = reqSize.width() * reqSize.height();
334 QList<int> supportedPixelCounts;
335 for (int i = 0; i < m_supportedResolutions.size(); ++i) {
336 const QSize &s = m_supportedResolutions.at(i);
337 supportedPixelCounts.append(s.width() * s.height());
338 }
339 int closestIndex = qt_findClosestValue(supportedPixelCounts, reqPixelCount);
340 settings.setVideoResolution(m_supportedResolutions.at(closestIndex));
341 }
342
343 if (settings.videoFrameRate() <= 0)
344 settings.setVideoFrameRate(m_defaultSettings.videoFrameRate);
345 if (settings.videoBitRate() <= 0)
346 settings.setVideoBitRate(m_defaultSettings.videoBitRate);
347
348 if (settings.videoCodec() == QMediaFormat::VideoCodec::H264)
349 m_videoEncoder = AndroidMediaRecorder::H264;
350 else if (settings.videoCodec() == QMediaFormat::VideoCodec::H265)
351 m_videoEncoder = AndroidMediaRecorder::HEVC;
352 else if (settings.videoCodec() == QMediaFormat::VideoCodec::MPEG4)
353 m_videoEncoder = AndroidMediaRecorder::MPEG_4_SP;
354 else
355 m_videoEncoder = m_defaultSettings.videoEncoder;
356
357 }
358}
359
360void QAndroidCaptureSession::restartViewfinder()
361{
362
363 setKeepAlive(false);
364
365 if (!m_cameraSession)
366 return;
367
368 if (m_cameraSession && m_cameraSession->camera()) {
369 m_cameraSession->camera()->reconnect();
371 m_cameraSession->camera()->startPreview();
372 m_cameraSession->setReadyForCapture(true);
373 m_cameraSession->enableRotation();
374 }
375
376 m_mediaRecorder = nullptr;
377}
378
379void QAndroidCaptureSession::updateDuration()
380{
381 if (m_elapsedTime.isValid())
382 m_duration = m_elapsedTime.elapsed();
383
384 emit durationChanged(m_duration);
385}
386
387void QAndroidCaptureSession::onCameraOpened()
388{
389 m_supportedResolutions.clear();
390 m_supportedFramerates.clear();
391
392 // get supported resolutions from predefined profiles
393 for (int i = 0; i < 8; ++i) {
394 CaptureProfile profile = getProfile(i);
395 if (!profile.isNull) {
396 if (i == AndroidCamcorderProfile::QUALITY_HIGH)
397 m_defaultSettings = profile;
398
399 if (!m_supportedResolutions.contains(profile.videoResolution))
400 m_supportedResolutions.append(profile.videoResolution);
401 if (!m_supportedFramerates.contains(profile.videoFrameRate))
402 m_supportedFramerates.append(profile.videoFrameRate);
403 }
404 }
405
406 ranges::sort(m_supportedResolutions, qt_sizeLessThan);
407 ranges::sort(m_supportedFramerates);
408
409 QMediaEncoderSettings defaultSettings;
410 applySettings(defaultSettings);
411 m_cameraSession->applyResolution(defaultSettings.videoResolution());
412}
413
414QAndroidCaptureSession::CaptureProfile QAndroidCaptureSession::getProfile(int id)
415{
416 CaptureProfile profile;
417 const bool hasProfile = AndroidCamcorderProfile::hasProfile(m_cameraSession->camera()->cameraId(),
419
420 if (hasProfile) {
421 AndroidCamcorderProfile camProfile = AndroidCamcorderProfile::get(m_cameraSession->camera()->cameraId(),
423
425 profile.audioEncoder = AndroidMediaRecorder::AudioEncoder(camProfile.getValue(AndroidCamcorderProfile::audioCodec));
426 profile.audioBitRate = camProfile.getValue(AndroidCamcorderProfile::audioBitRate);
427 profile.audioChannels = camProfile.getValue(AndroidCamcorderProfile::audioChannels);
428 profile.audioSampleRate = camProfile.getValue(AndroidCamcorderProfile::audioSampleRate);
430 profile.videoBitRate = camProfile.getValue(AndroidCamcorderProfile::videoBitRate);
431 profile.videoFrameRate = camProfile.getValue(AndroidCamcorderProfile::videoFrameRate);
432 profile.videoResolution = QSize(camProfile.getValue(AndroidCamcorderProfile::videoFrameWidth),
434
435 if (profile.outputFormat == AndroidMediaRecorder::MPEG_4)
436 profile.outputFileExtension = QStringLiteral("mp4");
437 else if (profile.outputFormat == AndroidMediaRecorder::THREE_GPP)
438 profile.outputFileExtension = QStringLiteral("3gp");
439 else if (profile.outputFormat == AndroidMediaRecorder::AMR_NB_Format)
440 profile.outputFileExtension = QStringLiteral("amr");
441 else if (profile.outputFormat == AndroidMediaRecorder::AMR_WB_Format)
442 profile.outputFileExtension = QStringLiteral("awb");
443
444 profile.isNull = false;
445 }
446
447 return profile;
448}
449
450void QAndroidCaptureSession::onError(int what, int extra)
451{
452 Q_UNUSED(what);
453 Q_UNUSED(extra);
454 stop(true);
455 updateError(QMediaRecorder::ResourceError, QLatin1String("Unknown error."));
456}
457
458void QAndroidCaptureSession::onInfo(int what, int extra)
459{
460 Q_UNUSED(extra);
461 if (what == 800) {
462 // MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
463 stop();
464 updateError(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum duration reached."));
465 } else if (what == 801) {
466 // MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED
467 stop();
468 updateError(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum file size reached."));
469 }
470}
471
472QT_END_NAMESPACE
473
474#include "moc_qandroidcapturesession_p.cpp"
int getValue(Field field) const
void setupPreviewFrameCallback()
void stopPreviewSynchronous()
int cameraId() const
static void stopSoundStreaming()
static void startSoundStreaming(const int inputId, const int outputId)
void info(int what, int extra)
AndroidCamera * camera() const
void setKeepAlive(bool keepAlive)
void start(QMediaEncoderSettings &settings, const QUrl &outputLocation)
void setCameraSession(QAndroidCameraSession *cameraSession=0)
\inmodule QtCore
Definition qsize.h:27
bool qt_androidCheckCameraPermission()