summaryrefslogtreecommitdiffstats
path: root/src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp')
-rw-r--r--src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp473
1 files changed, 473 insertions, 0 deletions
diff --git a/src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp b/src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp
new file mode 100644
index 000000000..3b005e4a5
--- /dev/null
+++ b/src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp
@@ -0,0 +1,473 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qandroidcapturesession_p.h"
+
+#include "androidcamera_p.h"
+#include "qandroidcamerasession_p.h"
+#include "qaudioinput.h"
+#include "qaudiooutput.h"
+#include "androidmediaplayer_p.h"
+#include "androidmultimediautils_p.h"
+#include "qandroidmultimediautils_p.h"
+#include "qandroidvideooutput_p.h"
+#include "qandroidglobal_p.h"
+#include <private/qplatformaudioinput_p.h>
+#include <private/qplatformaudiooutput_p.h>
+#include <private/qmediarecorder_p.h>
+#include <private/qmediastoragelocation_p.h>
+#include <QtCore/qmimetype.h>
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+QAndroidCaptureSession::QAndroidCaptureSession()
+ : QObject()
+ , m_mediaRecorder(0)
+ , m_cameraSession(0)
+ , m_duration(0)
+ , m_state(QMediaRecorder::StoppedState)
+ , m_outputFormat(AndroidMediaRecorder::DefaultOutputFormat)
+ , m_audioEncoder(AndroidMediaRecorder::DefaultAudioEncoder)
+ , m_videoEncoder(AndroidMediaRecorder::DefaultVideoEncoder)
+{
+ m_notifyTimer.setInterval(1000);
+ connect(&m_notifyTimer, &QTimer::timeout, this, &QAndroidCaptureSession::updateDuration);
+}
+
+QAndroidCaptureSession::~QAndroidCaptureSession()
+{
+ stop();
+ m_mediaRecorder = nullptr;
+ if (m_audioInput && m_audioOutput)
+ AndroidMediaPlayer::stopSoundStreaming();
+}
+
+void QAndroidCaptureSession::setCameraSession(QAndroidCameraSession *cameraSession)
+{
+ if (m_cameraSession) {
+ disconnect(m_connOpenCamera);
+ disconnect(m_connActiveChangedCamera);
+ }
+
+ m_cameraSession = cameraSession;
+ if (m_cameraSession) {
+ m_connOpenCamera = connect(cameraSession, &QAndroidCameraSession::opened,
+ this, &QAndroidCaptureSession::onCameraOpened);
+ m_connActiveChangedCamera = connect(cameraSession, &QAndroidCameraSession::activeChanged,
+ this, [this](bool isActive) {
+ if (!isActive)
+ stop();
+ });
+ }
+}
+
+void QAndroidCaptureSession::setAudioInput(QPlatformAudioInput *input)
+{
+ if (m_audioInput == input)
+ return;
+
+ if (m_audioInput) {
+ disconnect(m_audioInputChanged);
+ }
+
+ m_audioInput = input;
+
+ if (m_audioInput) {
+ m_audioInputChanged = connect(m_audioInput->q, &QAudioInput::deviceChanged, this, [this]() {
+ if (m_state == QMediaRecorder::RecordingState)
+ m_mediaRecorder->setAudioInput(m_audioInput->device.id());
+ updateStreamingState();
+ });
+ }
+ updateStreamingState();
+}
+
+void QAndroidCaptureSession::setAudioOutput(QPlatformAudioOutput *output)
+{
+ if (m_audioOutput == output)
+ return;
+
+ if (m_audioOutput)
+ disconnect(m_audioOutputChanged);
+
+ m_audioOutput = output;
+
+ if (m_audioOutput) {
+ m_audioOutputChanged = connect(m_audioOutput->q, &QAudioOutput::deviceChanged, this,
+ [this] () {
+ AndroidMediaPlayer::setAudioOutput(m_audioOutput->device.id());
+ updateStreamingState();
+ });
+ AndroidMediaPlayer::setAudioOutput(m_audioOutput->device.id());
+ }
+ updateStreamingState();
+}
+
+void QAndroidCaptureSession::updateStreamingState()
+{
+ if (m_audioInput && m_audioOutput) {
+ AndroidMediaPlayer::startSoundStreaming(m_audioInput->device.id().toInt(),
+ m_audioOutput->device.id().toInt());
+ } else {
+ AndroidMediaPlayer::stopSoundStreaming();
+ }
+}
+
+QMediaRecorder::RecorderState QAndroidCaptureSession::state() const
+{
+ return m_state;
+}
+
+void QAndroidCaptureSession::setKeepAlive(bool keepAlive)
+{
+ if (m_cameraSession)
+ m_cameraSession->setKeepAlive(keepAlive);
+}
+
+
+void QAndroidCaptureSession::start(QMediaEncoderSettings &settings, const QUrl &outputLocation)
+{
+ if (m_state == QMediaRecorder::RecordingState)
+ return;
+
+ if (!m_cameraSession && !m_audioInput) {
+ updateError(QMediaRecorder::ResourceError, QLatin1String("No devices are set"));
+ return;
+ }
+
+ setKeepAlive(true);
+
+ const bool validCameraSession = m_cameraSession && m_cameraSession->camera();
+
+ if (validCameraSession && !qt_androidCheckCameraPermission()) {
+ updateError(QMediaRecorder::ResourceError, QLatin1String("Camera permission denied."));
+ setKeepAlive(false);
+ return;
+ }
+
+ if (m_audioInput && !qt_androidCheckMicrophonePermission()) {
+ updateError(QMediaRecorder::ResourceError, QLatin1String("Microphone permission denied."));
+ setKeepAlive(false);
+ return;
+ }
+
+ m_mediaRecorder = std::make_shared<AndroidMediaRecorder>();
+ connect(m_mediaRecorder.get(), &AndroidMediaRecorder::error, this,
+ &QAndroidCaptureSession::onError);
+ connect(m_mediaRecorder.get(), &AndroidMediaRecorder::info, this,
+ &QAndroidCaptureSession::onInfo);
+
+ applySettings(settings);
+
+ // Set audio/video sources
+ if (validCameraSession) {
+ m_cameraSession->camera()->stopPreviewSynchronous();
+ m_cameraSession->camera()->unlock();
+
+ m_mediaRecorder->setCamera(m_cameraSession->camera());
+ m_mediaRecorder->setVideoSource(AndroidMediaRecorder::Camera);
+ }
+
+ if (m_audioInput) {
+ m_mediaRecorder->setAudioInput(m_audioInput->device.id());
+ if (!m_mediaRecorder->isAudioSourceSet())
+ m_mediaRecorder->setAudioSource(AndroidMediaRecorder::DefaultAudioSource);
+ }
+
+ // Set output format
+ m_mediaRecorder->setOutputFormat(m_outputFormat);
+
+ // Set video encoder settings
+ if (validCameraSession) {
+ m_mediaRecorder->setVideoSize(settings.videoResolution());
+ m_mediaRecorder->setVideoFrameRate(qRound(settings.videoFrameRate()));
+ m_mediaRecorder->setVideoEncodingBitRate(settings.videoBitRate());
+ m_mediaRecorder->setVideoEncoder(m_videoEncoder);
+
+ // media recorder is also compensanting the mirror on front camera
+ auto rotation = m_cameraSession->currentCameraRotation();
+ if (m_cameraSession->camera()->getFacing() == AndroidCamera::CameraFacingFront)
+ rotation = (360 - rotation) % 360; // remove mirror compensation
+
+ m_mediaRecorder->setOrientationHint(rotation);
+ }
+
+ // Set audio encoder settings
+ if (m_audioInput) {
+ m_mediaRecorder->setAudioChannels(settings.audioChannelCount());
+ m_mediaRecorder->setAudioEncodingBitRate(settings.audioBitRate());
+ m_mediaRecorder->setAudioSamplingRate(settings.audioSampleRate());
+ m_mediaRecorder->setAudioEncoder(m_audioEncoder);
+ }
+
+ QString extension = settings.mimeType().preferredSuffix();
+ // Set output file
+ auto location = outputLocation.toString(QUrl::PreferLocalFile);
+ QString filePath = location;
+ if (QUrl(filePath).scheme() != QLatin1String("content")) {
+ filePath = QMediaStorageLocation::generateFileName(
+ location, m_cameraSession ? QStandardPaths::MoviesLocation
+ : QStandardPaths::MusicLocation, extension);
+ }
+
+ m_usedOutputLocation = QUrl::fromLocalFile(filePath);
+ m_outputLocationIsStandard = location.isEmpty() || QFileInfo(location).isRelative();
+ m_mediaRecorder->setOutputFile(filePath);
+
+ if (validCameraSession) {
+ m_cameraSession->disableRotation();
+ }
+
+ if (!m_mediaRecorder->prepare()) {
+ updateError(QMediaRecorder::FormatError,
+ QLatin1String("Unable to prepare the media recorder."));
+ restartViewfinder();
+
+ return;
+ }
+
+ if (!m_mediaRecorder->start()) {
+ updateError(QMediaRecorder::FormatError, QMediaRecorderPrivate::msgFailedStartRecording());
+ restartViewfinder();
+
+ return;
+ }
+
+ m_elapsedTime.start();
+ m_notifyTimer.start();
+ updateDuration();
+
+ if (validCameraSession) {
+ m_cameraSession->setReadyForCapture(false);
+
+ // Preview frame callback is cleared when setting up the camera with the media recorder.
+ // We need to reset it.
+ m_cameraSession->camera()->setupPreviewFrameCallback();
+ }
+
+ m_state = QMediaRecorder::RecordingState;
+ emit stateChanged(m_state);
+}
+
+void QAndroidCaptureSession::stop(bool error)
+{
+ if (m_state == QMediaRecorder::StoppedState || m_mediaRecorder == nullptr)
+ return;
+
+ m_mediaRecorder->stop();
+ m_notifyTimer.stop();
+ updateDuration();
+ m_elapsedTime.invalidate();
+
+ m_mediaRecorder = nullptr;
+
+ if (m_cameraSession && m_cameraSession->isActive()) {
+ // Viewport needs to be restarted after recording
+ restartViewfinder();
+ }
+
+ if (!error) {
+ // if the media is saved into the standard media location, register it
+ // with the Android media scanner so it appears immediately in apps
+ // such as the gallery.
+ if (m_outputLocationIsStandard)
+ AndroidMultimediaUtils::registerMediaFile(m_usedOutputLocation.toLocalFile());
+
+ emit actualLocationChanged(m_usedOutputLocation);
+ }
+
+ m_state = QMediaRecorder::StoppedState;
+ emit stateChanged(m_state);
+}
+
+qint64 QAndroidCaptureSession::duration() const
+{
+ return m_duration;
+}
+
+void QAndroidCaptureSession::applySettings(QMediaEncoderSettings &settings)
+{
+ // container settings
+ auto fileFormat = settings.mediaFormat().fileFormat();
+ if (!m_cameraSession && fileFormat == QMediaFormat::AAC) {
+ m_outputFormat = AndroidMediaRecorder::AAC_ADTS;
+ } else if (fileFormat == QMediaFormat::Ogg) {
+ m_outputFormat = AndroidMediaRecorder::OGG;
+ } else if (fileFormat == QMediaFormat::WebM) {
+ m_outputFormat = AndroidMediaRecorder::WEBM;
+// } else if (fileFormat == QLatin1String("3gp")) {
+// m_outputFormat = AndroidMediaRecorder::THREE_GPP;
+ } else {
+ // fallback to MP4
+ m_outputFormat = AndroidMediaRecorder::MPEG_4;
+ }
+
+ // audio settings
+ if (settings.audioChannelCount() <= 0)
+ settings.setAudioChannelCount(m_defaultSettings.audioChannels);
+ if (settings.audioBitRate() <= 0)
+ settings.setAudioBitRate(m_defaultSettings.audioBitRate);
+ if (settings.audioSampleRate() <= 0)
+ settings.setAudioSampleRate(m_defaultSettings.audioSampleRate);
+
+ if (settings.audioCodec() == QMediaFormat::AudioCodec::AAC)
+ m_audioEncoder = AndroidMediaRecorder::AAC;
+ else if (settings.audioCodec() == QMediaFormat::AudioCodec::Opus)
+ m_audioEncoder = AndroidMediaRecorder::OPUS;
+ else if (settings.audioCodec() == QMediaFormat::AudioCodec::Vorbis)
+ m_audioEncoder = AndroidMediaRecorder::VORBIS;
+ else
+ m_audioEncoder = m_defaultSettings.audioEncoder;
+
+
+ // video settings
+ if (m_cameraSession && m_cameraSession->camera()) {
+ if (settings.videoResolution().isEmpty()) {
+ settings.setVideoResolution(m_defaultSettings.videoResolution);
+ } else if (!m_supportedResolutions.contains(settings.videoResolution())) {
+ // if the requested resolution is not supported, find the closest one
+ QSize reqSize = settings.videoResolution();
+ int reqPixelCount = reqSize.width() * reqSize.height();
+ QList<int> supportedPixelCounts;
+ for (int i = 0; i < m_supportedResolutions.size(); ++i) {
+ const QSize &s = m_supportedResolutions.at(i);
+ supportedPixelCounts.append(s.width() * s.height());
+ }
+ int closestIndex = qt_findClosestValue(supportedPixelCounts, reqPixelCount);
+ settings.setVideoResolution(m_supportedResolutions.at(closestIndex));
+ }
+
+ if (settings.videoFrameRate() <= 0)
+ settings.setVideoFrameRate(m_defaultSettings.videoFrameRate);
+ if (settings.videoBitRate() <= 0)
+ settings.setVideoBitRate(m_defaultSettings.videoBitRate);
+
+ if (settings.videoCodec() == QMediaFormat::VideoCodec::H264)
+ m_videoEncoder = AndroidMediaRecorder::H264;
+ else if (settings.videoCodec() == QMediaFormat::VideoCodec::H265)
+ m_videoEncoder = AndroidMediaRecorder::HEVC;
+ else if (settings.videoCodec() == QMediaFormat::VideoCodec::MPEG4)
+ m_videoEncoder = AndroidMediaRecorder::MPEG_4_SP;
+ else
+ m_videoEncoder = m_defaultSettings.videoEncoder;
+
+ }
+}
+
+void QAndroidCaptureSession::restartViewfinder()
+{
+
+ setKeepAlive(false);
+
+ if (!m_cameraSession)
+ return;
+
+ if (m_cameraSession && m_cameraSession->camera()) {
+ m_cameraSession->camera()->reconnect();
+ m_cameraSession->camera()->stopPreviewSynchronous();
+ m_cameraSession->camera()->startPreview();
+ m_cameraSession->setReadyForCapture(true);
+ m_cameraSession->enableRotation();
+ }
+
+ m_mediaRecorder = nullptr;
+}
+
+void QAndroidCaptureSession::updateDuration()
+{
+ if (m_elapsedTime.isValid())
+ m_duration = m_elapsedTime.elapsed();
+
+ emit durationChanged(m_duration);
+}
+
+void QAndroidCaptureSession::onCameraOpened()
+{
+ m_supportedResolutions.clear();
+ m_supportedFramerates.clear();
+
+ // get supported resolutions from predefined profiles
+ for (int i = 0; i < 8; ++i) {
+ CaptureProfile profile = getProfile(i);
+ if (!profile.isNull) {
+ if (i == AndroidCamcorderProfile::QUALITY_HIGH)
+ m_defaultSettings = profile;
+
+ if (!m_supportedResolutions.contains(profile.videoResolution))
+ m_supportedResolutions.append(profile.videoResolution);
+ if (!m_supportedFramerates.contains(profile.videoFrameRate))
+ m_supportedFramerates.append(profile.videoFrameRate);
+ }
+ }
+
+ std::sort(m_supportedResolutions.begin(), m_supportedResolutions.end(), qt_sizeLessThan);
+ std::sort(m_supportedFramerates.begin(), m_supportedFramerates.end());
+
+ QMediaEncoderSettings defaultSettings;
+ applySettings(defaultSettings);
+ m_cameraSession->applyResolution(defaultSettings.videoResolution());
+}
+
+QAndroidCaptureSession::CaptureProfile QAndroidCaptureSession::getProfile(int id)
+{
+ CaptureProfile profile;
+ const bool hasProfile = AndroidCamcorderProfile::hasProfile(m_cameraSession->camera()->cameraId(),
+ AndroidCamcorderProfile::Quality(id));
+
+ if (hasProfile) {
+ AndroidCamcorderProfile camProfile = AndroidCamcorderProfile::get(m_cameraSession->camera()->cameraId(),
+ AndroidCamcorderProfile::Quality(id));
+
+ profile.outputFormat = AndroidMediaRecorder::OutputFormat(camProfile.getValue(AndroidCamcorderProfile::fileFormat));
+ profile.audioEncoder = AndroidMediaRecorder::AudioEncoder(camProfile.getValue(AndroidCamcorderProfile::audioCodec));
+ profile.audioBitRate = camProfile.getValue(AndroidCamcorderProfile::audioBitRate);
+ profile.audioChannels = camProfile.getValue(AndroidCamcorderProfile::audioChannels);
+ profile.audioSampleRate = camProfile.getValue(AndroidCamcorderProfile::audioSampleRate);
+ profile.videoEncoder = AndroidMediaRecorder::VideoEncoder(camProfile.getValue(AndroidCamcorderProfile::videoCodec));
+ profile.videoBitRate = camProfile.getValue(AndroidCamcorderProfile::videoBitRate);
+ profile.videoFrameRate = camProfile.getValue(AndroidCamcorderProfile::videoFrameRate);
+ profile.videoResolution = QSize(camProfile.getValue(AndroidCamcorderProfile::videoFrameWidth),
+ camProfile.getValue(AndroidCamcorderProfile::videoFrameHeight));
+
+ if (profile.outputFormat == AndroidMediaRecorder::MPEG_4)
+ profile.outputFileExtension = QStringLiteral("mp4");
+ else if (profile.outputFormat == AndroidMediaRecorder::THREE_GPP)
+ profile.outputFileExtension = QStringLiteral("3gp");
+ else if (profile.outputFormat == AndroidMediaRecorder::AMR_NB_Format)
+ profile.outputFileExtension = QStringLiteral("amr");
+ else if (profile.outputFormat == AndroidMediaRecorder::AMR_WB_Format)
+ profile.outputFileExtension = QStringLiteral("awb");
+
+ profile.isNull = false;
+ }
+
+ return profile;
+}
+
+void QAndroidCaptureSession::onError(int what, int extra)
+{
+ Q_UNUSED(what);
+ Q_UNUSED(extra);
+ stop(true);
+ updateError(QMediaRecorder::ResourceError, QLatin1String("Unknown error."));
+}
+
+void QAndroidCaptureSession::onInfo(int what, int extra)
+{
+ Q_UNUSED(extra);
+ if (what == 800) {
+ // MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
+ stop();
+ updateError(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum duration reached."));
+ } else if (what == 801) {
+ // MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED
+ stop();
+ updateError(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum file size reached."));
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qandroidcapturesession_p.cpp"