Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
6#include "androidcamera_p.h"
8#include "qaudioinput.h"
9#include "qaudiooutput.h"
14#include "qandroidglobal_p.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 <QtCore/qmimetype.h>
20
21#include <algorithm>
22
24
26 : QObject()
27 , m_mediaRecorder(0)
28 , m_cameraSession(0)
29 , m_duration(0)
30 , m_state(QMediaRecorder::StoppedState)
31 , m_outputFormat(AndroidMediaRecorder::DefaultOutputFormat)
32 , m_audioEncoder(AndroidMediaRecorder::DefaultAudioEncoder)
33 , m_videoEncoder(AndroidMediaRecorder::DefaultVideoEncoder)
34{
35 m_notifyTimer.setInterval(1000);
36 connect(&m_notifyTimer, &QTimer::timeout, this, &QAndroidCaptureSession::updateDuration);
37}
38
40{
41 stop();
42 m_mediaRecorder = nullptr;
43 if (m_audioInput && m_audioOutput)
45}
46
48{
49 if (m_cameraSession) {
50 disconnect(m_connOpenCamera);
51 disconnect(m_connActiveChangedCamera);
52 }
53
54 m_cameraSession = cameraSession;
55 if (m_cameraSession) {
56 m_connOpenCamera = connect(cameraSession, &QAndroidCameraSession::opened,
57 this, &QAndroidCaptureSession::onCameraOpened);
58 m_connActiveChangedCamera = connect(cameraSession, &QAndroidCameraSession::activeChanged,
59 this, [this](bool isActive) {
60 if (!isActive)
61 stop();
62 });
63 }
64}
65
67{
68 if (m_audioInput == input)
69 return;
70
71 if (m_audioInput) {
72 disconnect(m_audioInputChanged);
73 }
74
75 m_audioInput = input;
76
77 if (m_audioInput) {
78 m_audioInputChanged = connect(m_audioInput->q, &QAudioInput::deviceChanged, this, [this]() {
79 if (m_state == QMediaRecorder::RecordingState)
80 m_mediaRecorder->setAudioInput(m_audioInput->device.id());
81 updateStreamingState();
82 });
83 }
84 updateStreamingState();
85}
86
88{
89 if (m_audioOutput == output)
90 return;
91
92 if (m_audioOutput)
93 disconnect(m_audioOutputChanged);
94
95 m_audioOutput = output;
96
97 if (m_audioOutput) {
98 m_audioOutputChanged = connect(m_audioOutput->q, &QAudioOutput::deviceChanged, this,
99 [this] () {
100 AndroidMediaPlayer::setAudioOutput(m_audioOutput->device.id());
101 updateStreamingState();
102 });
104 }
105 updateStreamingState();
106}
107
108void QAndroidCaptureSession::updateStreamingState()
109{
110 if (m_audioInput && m_audioOutput) {
112 m_audioOutput->device.id().toInt());
113 } else {
115 }
116}
117
122
123void QAndroidCaptureSession::setKeepAlive(bool keepAlive)
124{
125 if (m_cameraSession)
126 m_cameraSession->setKeepAlive(keepAlive);
127}
128
129
131{
132 if (m_state == QMediaRecorder::RecordingState)
133 return;
134
135 if (!m_cameraSession && !m_audioInput) {
137 return;
138 }
139
140 setKeepAlive(true);
141
142 const bool validCameraSession = m_cameraSession && m_cameraSession->camera();
143
144 if (validCameraSession && !qt_androidCheckCameraPermission()) {
145 updateError(QMediaRecorder::ResourceError, QLatin1String("Camera permission denied."));
146 setKeepAlive(false);
147 return;
148 }
149
150 if (m_audioInput && !qt_androidCheckMicrophonePermission()) {
151 updateError(QMediaRecorder::ResourceError, QLatin1String("Microphone permission denied."));
152 setKeepAlive(false);
153 return;
154 }
155
156 m_mediaRecorder = std::make_shared<AndroidMediaRecorder>();
157 connect(m_mediaRecorder.get(), &AndroidMediaRecorder::error, this,
158 &QAndroidCaptureSession::onError);
159 connect(m_mediaRecorder.get(), &AndroidMediaRecorder::info, this,
160 &QAndroidCaptureSession::onInfo);
161
162 applySettings(settings);
163
164 // Set audio/video sources
165 if (validCameraSession) {
166 m_cameraSession->camera()->stopPreviewSynchronous();
167 m_cameraSession->camera()->unlock();
168
169 m_mediaRecorder->setCamera(m_cameraSession->camera());
170 m_mediaRecorder->setVideoSource(AndroidMediaRecorder::Camera);
171 }
172
173 if (m_audioInput) {
174 m_mediaRecorder->setAudioInput(m_audioInput->device.id());
175 if (!m_mediaRecorder->isAudioSourceSet())
176 m_mediaRecorder->setAudioSource(AndroidMediaRecorder::DefaultAudioSource);
177 }
178
179 // Set output format
180 m_mediaRecorder->setOutputFormat(m_outputFormat);
181
182 // Set video encoder settings
183 if (validCameraSession) {
184 m_mediaRecorder->setVideoSize(settings.videoResolution());
185 m_mediaRecorder->setVideoFrameRate(qRound(settings.videoFrameRate()));
186 m_mediaRecorder->setVideoEncodingBitRate(settings.videoBitRate());
187 m_mediaRecorder->setVideoEncoder(m_videoEncoder);
188
189 // media recorder is also compensanting the mirror on front camera
190 auto rotation = m_cameraSession->currentCameraRotation();
191 if (m_cameraSession->camera()->getFacing() == AndroidCamera::CameraFacingFront)
192 rotation = (360 - rotation) % 360; // remove mirror compensation
193
194 m_mediaRecorder->setOrientationHint(rotation);
195 }
196
197 // Set audio encoder settings
198 if (m_audioInput) {
199 m_mediaRecorder->setAudioChannels(settings.audioChannelCount());
200 m_mediaRecorder->setAudioEncodingBitRate(settings.audioBitRate());
201 m_mediaRecorder->setAudioSamplingRate(settings.audioSampleRate());
202 m_mediaRecorder->setAudioEncoder(m_audioEncoder);
203 }
204
205 QString extension = settings.mimeType().preferredSuffix();
206 // Set output file
207 auto location = outputLocation.toString(QUrl::PreferLocalFile);
208 QString filePath = location;
209 if (QUrl(filePath).scheme() != QLatin1String("content")) {
213 }
214
215 m_usedOutputLocation = QUrl::fromLocalFile(filePath);
216 m_outputLocationIsStandard = location.isEmpty() || QFileInfo(location).isRelative();
217 m_mediaRecorder->setOutputFile(filePath);
218
219 if (validCameraSession) {
220 m_cameraSession->disableRotation();
221 }
222
223 if (!m_mediaRecorder->prepare()) {
225 QLatin1String("Unable to prepare the media recorder."));
226 restartViewfinder();
227
228 return;
229 }
230
231 if (!m_mediaRecorder->start()) {
233 restartViewfinder();
234
235 return;
236 }
237
238 m_elapsedTime.start();
239 m_notifyTimer.start();
240 updateDuration();
241
242 if (validCameraSession) {
243 m_cameraSession->setReadyForCapture(false);
244
245 // Preview frame callback is cleared when setting up the camera with the media recorder.
246 // We need to reset it.
247 m_cameraSession->camera()->setupPreviewFrameCallback();
248 }
249
251 emit stateChanged(m_state);
252}
253
255{
256 if (m_state == QMediaRecorder::StoppedState || m_mediaRecorder == nullptr)
257 return;
258
259 m_mediaRecorder->stop();
260 m_notifyTimer.stop();
261 updateDuration();
262 m_elapsedTime.invalidate();
263
264 m_mediaRecorder = nullptr;
265
266 if (m_cameraSession && m_cameraSession->isActive()) {
267 // Viewport needs to be restarted after recording
268 restartViewfinder();
269 }
270
271 if (!error) {
272 // if the media is saved into the standard media location, register it
273 // with the Android media scanner so it appears immediately in apps
274 // such as the gallery.
275 if (m_outputLocationIsStandard)
277
278 emit actualLocationChanged(m_usedOutputLocation);
279 }
280
282 emit stateChanged(m_state);
283}
284
286{
287 return m_duration;
288}
289
290void QAndroidCaptureSession::applySettings(QMediaEncoderSettings &settings)
291{
292 // container settings
293 auto fileFormat = settings.mediaFormat().fileFormat();
294 if (!m_cameraSession && fileFormat == QMediaFormat::AAC) {
295 m_outputFormat = AndroidMediaRecorder::AAC_ADTS;
296 } else if (fileFormat == QMediaFormat::Ogg) {
297 m_outputFormat = AndroidMediaRecorder::OGG;
298 } else if (fileFormat == QMediaFormat::WebM) {
299 m_outputFormat = AndroidMediaRecorder::WEBM;
300// } else if (fileFormat == QLatin1String("3gp")) {
301// m_outputFormat = AndroidMediaRecorder::THREE_GPP;
302 } else {
303 // fallback to MP4
304 m_outputFormat = AndroidMediaRecorder::MPEG_4;
305 }
306
307 // audio settings
308 if (settings.audioChannelCount() <= 0)
309 settings.setAudioChannelCount(m_defaultSettings.audioChannels);
310 if (settings.audioBitRate() <= 0)
311 settings.setAudioBitRate(m_defaultSettings.audioBitRate);
312 if (settings.audioSampleRate() <= 0)
313 settings.setAudioSampleRate(m_defaultSettings.audioSampleRate);
314
315 if (settings.audioCodec() == QMediaFormat::AudioCodec::AAC)
316 m_audioEncoder = AndroidMediaRecorder::AAC;
317 else if (settings.audioCodec() == QMediaFormat::AudioCodec::Opus)
318 m_audioEncoder = AndroidMediaRecorder::OPUS;
319 else if (settings.audioCodec() == QMediaFormat::AudioCodec::Vorbis)
320 m_audioEncoder = AndroidMediaRecorder::VORBIS;
321 else
322 m_audioEncoder = m_defaultSettings.audioEncoder;
323
324
325 // video settings
326 if (m_cameraSession && m_cameraSession->camera()) {
327 if (settings.videoResolution().isEmpty()) {
328 settings.setVideoResolution(m_defaultSettings.videoResolution);
329 } else if (!m_supportedResolutions.contains(settings.videoResolution())) {
330 // if the requested resolution is not supported, find the closest one
331 QSize reqSize = settings.videoResolution();
332 int reqPixelCount = reqSize.width() * reqSize.height();
333 QList<int> supportedPixelCounts;
334 for (int i = 0; i < m_supportedResolutions.size(); ++i) {
335 const QSize &s = m_supportedResolutions.at(i);
336 supportedPixelCounts.append(s.width() * s.height());
337 }
338 int closestIndex = qt_findClosestValue(supportedPixelCounts, reqPixelCount);
339 settings.setVideoResolution(m_supportedResolutions.at(closestIndex));
340 }
341
342 if (settings.videoFrameRate() <= 0)
343 settings.setVideoFrameRate(m_defaultSettings.videoFrameRate);
344 if (settings.videoBitRate() <= 0)
345 settings.setVideoBitRate(m_defaultSettings.videoBitRate);
346
347 if (settings.videoCodec() == QMediaFormat::VideoCodec::H264)
348 m_videoEncoder = AndroidMediaRecorder::H264;
349 else if (settings.videoCodec() == QMediaFormat::VideoCodec::H265)
350 m_videoEncoder = AndroidMediaRecorder::HEVC;
351 else if (settings.videoCodec() == QMediaFormat::VideoCodec::MPEG4)
352 m_videoEncoder = AndroidMediaRecorder::MPEG_4_SP;
353 else
354 m_videoEncoder = m_defaultSettings.videoEncoder;
355
356 }
357}
358
359void QAndroidCaptureSession::restartViewfinder()
360{
361
362 setKeepAlive(false);
363
364 if (!m_cameraSession)
365 return;
366
367 if (m_cameraSession && m_cameraSession->camera()) {
368 m_cameraSession->camera()->reconnect();
369 m_cameraSession->camera()->stopPreviewSynchronous();
370 m_cameraSession->camera()->startPreview();
371 m_cameraSession->setReadyForCapture(true);
372 m_cameraSession->enableRotation();
373 }
374
375 m_mediaRecorder = nullptr;
376}
377
378void QAndroidCaptureSession::updateDuration()
379{
380 if (m_elapsedTime.isValid())
381 m_duration = m_elapsedTime.elapsed();
382
383 emit durationChanged(m_duration);
384}
385
386void QAndroidCaptureSession::onCameraOpened()
387{
388 m_supportedResolutions.clear();
389 m_supportedFramerates.clear();
390
391 // get supported resolutions from predefined profiles
392 for (int i = 0; i < 8; ++i) {
393 CaptureProfile profile = getProfile(i);
394 if (!profile.isNull) {
396 m_defaultSettings = profile;
397
398 if (!m_supportedResolutions.contains(profile.videoResolution))
399 m_supportedResolutions.append(profile.videoResolution);
400 if (!m_supportedFramerates.contains(profile.videoFrameRate))
401 m_supportedFramerates.append(profile.videoFrameRate);
402 }
403 }
404
405 std::sort(m_supportedResolutions.begin(), m_supportedResolutions.end(), qt_sizeLessThan);
406 std::sort(m_supportedFramerates.begin(), m_supportedFramerates.end());
407
408 QMediaEncoderSettings defaultSettings;
409 applySettings(defaultSettings);
410 m_cameraSession->applyResolution(defaultSettings.videoResolution());
411}
412
413QAndroidCaptureSession::CaptureProfile QAndroidCaptureSession::getProfile(int id)
414{
415 CaptureProfile profile;
416 const bool hasProfile = AndroidCamcorderProfile::hasProfile(m_cameraSession->camera()->cameraId(),
418
419 if (hasProfile) {
420 AndroidCamcorderProfile camProfile = AndroidCamcorderProfile::get(m_cameraSession->camera()->cameraId(),
422
423 profile.outputFormat = AndroidMediaRecorder::OutputFormat(camProfile.getValue(AndroidCamcorderProfile::fileFormat));
424 profile.audioEncoder = AndroidMediaRecorder::AudioEncoder(camProfile.getValue(AndroidCamcorderProfile::audioCodec));
425 profile.audioBitRate = camProfile.getValue(AndroidCamcorderProfile::audioBitRate);
426 profile.audioChannels = camProfile.getValue(AndroidCamcorderProfile::audioChannels);
427 profile.audioSampleRate = camProfile.getValue(AndroidCamcorderProfile::audioSampleRate);
428 profile.videoEncoder = AndroidMediaRecorder::VideoEncoder(camProfile.getValue(AndroidCamcorderProfile::videoCodec));
429 profile.videoBitRate = camProfile.getValue(AndroidCamcorderProfile::videoBitRate);
430 profile.videoFrameRate = camProfile.getValue(AndroidCamcorderProfile::videoFrameRate);
431 profile.videoResolution = QSize(camProfile.getValue(AndroidCamcorderProfile::videoFrameWidth),
432 camProfile.getValue(AndroidCamcorderProfile::videoFrameHeight));
433
434 if (profile.outputFormat == AndroidMediaRecorder::MPEG_4)
435 profile.outputFileExtension = QStringLiteral("mp4");
436 else if (profile.outputFormat == AndroidMediaRecorder::THREE_GPP)
437 profile.outputFileExtension = QStringLiteral("3gp");
438 else if (profile.outputFormat == AndroidMediaRecorder::AMR_NB_Format)
439 profile.outputFileExtension = QStringLiteral("amr");
440 else if (profile.outputFormat == AndroidMediaRecorder::AMR_WB_Format)
441 profile.outputFileExtension = QStringLiteral("awb");
442
443 profile.isNull = false;
444 }
445
446 return profile;
447}
448
449void QAndroidCaptureSession::onError(int what, int extra)
450{
451 Q_UNUSED(what);
452 Q_UNUSED(extra);
453 stop(true);
455}
456
457void QAndroidCaptureSession::onInfo(int what, int extra)
458{
459 Q_UNUSED(extra);
460 if (what == 800) {
461 // MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
462 stop();
463 updateError(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum duration reached."));
464 } else if (what == 801) {
465 // MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED
466 stop();
467 updateError(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum file size reached."));
468 }
469}
470
472
473#include "moc_qandroidcapturesession_p.cpp"
bool isActive
static bool hasProfile(jint cameraId, Quality quality)
static AndroidCamcorderProfile get(jint cameraId, Quality quality)
void setupPreviewFrameCallback()
void stopPreviewSynchronous()
CameraFacing getFacing()
int cameraId() const
static void stopSoundStreaming()
static bool setAudioOutput(const QByteArray &deviceId)
static void startSoundStreaming(const int inputId, const int outputId)
void error(int what, int extra)
void info(int what, int extra)
static void registerMediaFile(const QString &file)
void activeChanged(bool)
void applyResolution(const QSize &captureSize=QSize(), bool restartPreview=true)
AndroidCamera * camera() const
void setKeepAlive(bool keepAlive)
void stateChanged(QMediaRecorder::RecorderState state)
QMediaRecorder::RecorderState state() const
void start(QMediaEncoderSettings &settings, const QUrl &outputLocation)
void actualLocationChanged(const QUrl &location)
void setAudioInput(QPlatformAudioInput *input)
void setAudioOutput(QPlatformAudioOutput *output)
void durationChanged(qint64 position)
void updateError(int error, const QString &errorString)
void setCameraSession(QAndroidCameraSession *cameraSession=0)
QByteArray id
\qmlproperty string QtMultimedia::audioDevice::id
void deviceChanged()
void deviceChanged()
int toInt(bool *ok=nullptr, int base=10) const
Returns the byte array converted to an int using base base, which is ten by default.
void invalidate() noexcept
Marks this QElapsedTimer object as invalid.
qint64 elapsed() const noexcept
Returns the number of milliseconds since this QElapsedTimer was last started.
void start() noexcept
\typealias QElapsedTimer::Duration Synonym for std::chrono::nanoseconds.
bool isValid() const noexcept
Returns false if the timer has never been started or invalidated by a call to invalidate().
bool isRelative() const
Returns true if the file system entry's path is relative, otherwise returns false (that is,...
qsizetype size() const noexcept
Definition qlist.h:397
iterator end()
Definition qlist.h:626
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
iterator begin()
Definition qlist.h:625
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
static QString msgFailedStartRecording()
\inmodule QtMultimedia
RecorderState
\qmlproperty enumeration QtMultimedia::MediaRecorder::recorderState
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
void setInterval(int msec)
Definition qtimer.cpp:579
void stop()
Stops the timer.
Definition qtimer.cpp:267
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
\inmodule QtCore
Definition qurl.h:94
static QUrl fromLocalFile(const QString &localfile)
Returns a QUrl representation of localFile, interpreted as a local file.
Definition qurl.cpp:3368
QString scheme() const
Returns the scheme of the URL.
Definition qurl.cpp:1991
@ PreferLocalFile
Definition qurl.h:114
QString toString(FormattingOptions options=FormattingOptions(PrettyDecoded)) const
Returns a string representation of the URL.
Definition qurl.cpp:2831
QString toLocalFile() const
Returns the path of this URL formatted as a local file path.
Definition qurl.cpp:3425
void extension()
[6]
Definition dialogs.cpp:230
Q_MULTIMEDIA_EXPORT QString generateFileName(const QString &requestedName, QStandardPaths::StandardLocation type, const QString &extension)
Combined button and popup list for selecting options.
QT_BEGIN_NAMESPACE int qt_findClosestValue(const QList< int > &list, int value)
bool qt_androidCheckMicrophonePermission()
bool qt_androidCheckCameraPermission()
bool qt_sizeLessThan(const QSize &s1, const QSize &s2)
DBusConnection const char DBusError * error
QMediaFormat::FileFormat fileFormat
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:327
GLint location
GLdouble s
[6]
Definition qopenglext.h:235
GLenum GLenum GLenum input
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
#define emit
#define Q_UNUSED(x)
long long qint64
Definition qtypes.h:60
QT_BEGIN_NAMESPACE typedef uchar * output
QSettings settings("MySoft", "Star Runner")
[0]
myObject disconnect()
[26]
bool contains(const AT &t) const noexcept
Definition qlist.h:45