12#include <qvideosink.h>
13#include <QtConcurrent/qtconcurrentrun.h>
15#include <qguiapplication.h>
18#include <qvideoframe.h>
19#include <private/qplatformimagecapture_p.h>
20#include <private/qplatformvideosink_p.h>
21#include <private/qmemoryvideobuffer_p.h>
22#include <private/qcameradevice_p.h>
23#include <private/qmediastoragelocation_p.h>
24#include <private/qvideoframe_p.h>
25#include <QImageWriter>
29Q_GLOBAL_STATIC(QList<QCameraDevice>, g_availableCameras)
31QAndroidCameraSession::QAndroidCameraSession(QObject *parent)
37 , m_previewStarted(
false)
38 , m_readyForCapture(
false)
39 , m_currentImageCaptureId(-1)
40 , m_previewCallback(0)
44 connect(
qApp, &QGuiApplication::applicationStateChanged,
45 this, &QAndroidCameraSession::onApplicationStateChanged);
47 auto screen =
qApp->primaryScreen();
49 connect(screen, &QScreen::orientationChanged,
this,
50 &QAndroidCameraSession::updateOrientation);
56QAndroidCameraSession::~QAndroidCameraSession()
59 disconnect(m_retryPreviewConnection);
75void QAndroidCameraSession::setActive(
bool active)
77 if (m_active == active)
82 if (active &&
qApp->applicationState() == Qt::ApplicationInactive) {
83 m_isStateSaved =
true;
84 m_savedState = active;
88 m_isStateSaved =
false;
90 setActiveHelper(m_active);
91 emit activeChanged(m_active);
94void QAndroidCameraSession::setActiveHelper(
bool active)
100 if (!m_camera && !open()) {
101 emit error(QCamera::CameraError, QStringLiteral(
"Failed to open camera"));
108void QAndroidCameraSession::updateAvailableCameras()
110 g_availableCameras->clear();
112 const int numCameras = AndroidCamera::getNumberOfCameras();
113 for (
int i = 0; i < numCameras; ++i) {
114 QCameraDevicePrivate *info =
new QCameraDevicePrivate;
115 AndroidCamera::getCameraInfo(i, info);
117 if (!info->id.isEmpty()) {
119 AndroidCamera *camera = AndroidCamera::open(i);
122 info->videoFormats = camera->getSupportedFormats();
123 info->photoResolutions = camera->getSupportedPictureSizes();
127 g_availableCameras->append(info->create());
132const QList<QCameraDevice> &QAndroidCameraSession::availableCameras()
134 if (g_availableCameras->isEmpty())
135 updateAvailableCameras();
137 return *g_availableCameras;
140bool QAndroidCameraSession::open()
144 m_camera = AndroidCamera::open(m_selectedCamera);
147 connect(m_camera, &AndroidCamera::pictureExposed,
148 this, &QAndroidCameraSession::onCameraPictureExposed);
149 connect(m_camera, &AndroidCamera::lastPreviewFrameFetched,
150 this, &QAndroidCameraSession::onLastPreviewFrameFetched,
151 Qt::DirectConnection);
152 connect(m_camera, &AndroidCamera::newPreviewFrame,
153 this, &QAndroidCameraSession::onNewPreviewFrame,
154 Qt::DirectConnection);
155 connect(m_camera, &AndroidCamera::pictureCaptured,
156 this, &QAndroidCameraSession::onCameraPictureCaptured);
157 connect(m_camera, &AndroidCamera::previewStarted,
158 this, &QAndroidCameraSession::onCameraPreviewStarted);
159 connect(m_camera, &AndroidCamera::previewStopped,
160 this, &QAndroidCameraSession::onCameraPreviewStopped);
161 connect(m_camera, &AndroidCamera::previewFailedToStart,
162 this, &QAndroidCameraSession::onCameraPreviewFailedToStart);
163 connect(m_camera, &AndroidCamera::takePictureFailed,
164 this, &QAndroidCameraSession::onCameraTakePictureFailed);
166 if (m_camera->getPreviewFormat() != AndroidCamera::NV21)
167 m_camera->setPreviewFormat(AndroidCamera::NV21);
169 m_camera->notifyNewFrames(m_previewCallback);
175 return m_camera != 0;
178void QAndroidCameraSession::close()
185 m_readyForCapture =
false;
186 m_currentImageCaptureId = -1;
187 m_currentImageCaptureFileName.clear();
188 m_actualImageSettings = m_requestedImageSettings;
197void QAndroidCameraSession::setVideoOutput(QAndroidVideoOutput *output)
200 m_videoOutput->stop();
201 m_videoOutput->reset();
205 m_videoOutput = output;
206 if (m_videoOutput->isReady()) {
207 onVideoOutputReady(
true);
209 connect(m_videoOutput, &QAndroidVideoOutput::readyChanged,
210 this, &QAndroidCameraSession::onVideoOutputReady);
217void QAndroidCameraSession::setCameraFormat(
const QCameraFormat &format)
219 m_requestedFpsRange.min = format.minFrameRate();
220 m_requestedFpsRange.max = format.maxFrameRate();
221 m_requestedPixelFromat = AndroidCamera::AndroidImageFormatFromQtPixelFormat(format.pixelFormat());
223 m_requestedImageSettings.setResolution(format.resolution());
224 m_actualImageSettings.setResolution(format.resolution());
225 if (m_readyForCapture)
226 applyResolution(m_actualImageSettings.resolution());
229void QAndroidCameraSession::applyResolution(
const QSize &captureSize,
bool restartPreview)
234 const QSize currentViewfinderResolution = m_camera->previewSize();
235 const AndroidCamera::ImageFormat currentPreviewFormat = m_camera->getPreviewFormat();
236 const AndroidCamera::FpsRange currentFpsRange = m_camera->getPreviewFpsRange();
239 QSize adjustedViewfinderResolution;
240 const QList<QSize> previewSizes = m_camera->getSupportedPreviewSizes();
242 const bool validCaptureSize = captureSize.width() > 0 && captureSize.height() > 0;
244 && m_camera->getPreferredPreviewSizeForVideo().isEmpty()) {
247 adjustedViewfinderResolution = captureSize;
249 qreal captureAspectRatio = 0;
250 if (validCaptureSize)
251 captureAspectRatio = qreal(captureSize.width()) / qreal(captureSize.height());
253 if (validCaptureSize) {
255 qreal minAspectDiff = 1;
256 QSize closestResolution;
257 for (
int i = previewSizes.count() - 1; i >= 0; --i) {
258 const QSize &size = previewSizes.at(i);
259 const qreal sizeAspect = qreal(size.width()) / size.height();
260 if (qFuzzyCompare(captureAspectRatio, sizeAspect)) {
261 adjustedViewfinderResolution = size;
263 }
else if (minAspectDiff > qAbs(sizeAspect - captureAspectRatio)) {
264 closestResolution = size;
265 minAspectDiff = qAbs(sizeAspect - captureAspectRatio);
268 if (!adjustedViewfinderResolution.isValid()) {
269 qWarning(
"Cannot find a viewfinder resolution matching the capture aspect ratio.");
270 if (closestResolution.isValid()) {
271 adjustedViewfinderResolution = closestResolution;
272 qWarning(
"Using closest viewfinder resolution.");
278 adjustedViewfinderResolution = previewSizes.last();
284 AndroidCamera::ImageFormat adjustedPreviewFormat = m_requestedPixelFromat;
285 if (adjustedPreviewFormat == AndroidCamera::UnknownImageFormat)
286 adjustedPreviewFormat = AndroidCamera::NV21;
290 AndroidCamera::FpsRange adjustedFps = m_requestedFpsRange;
291 if (adjustedFps.min == 0 || adjustedFps.max == 0)
292 adjustedFps = currentFpsRange;
297 QSize cameraOutputResolution = adjustedViewfinderResolution;
298 QSize videoOutputResolution = adjustedViewfinderResolution;
299 QSize currentVideoOutputResolution = m_videoOutput ? m_videoOutput->getVideoSize() : QSize(0, 0);
300 const int rotation = currentCameraRotation();
302 if (rotation == 90 || rotation == 270) {
303 videoOutputResolution.transpose();
304 if (previewSizes.contains(cameraOutputResolution.transposed()))
305 cameraOutputResolution.transpose();
308 if (currentViewfinderResolution != cameraOutputResolution
309 || (m_videoOutput && currentVideoOutputResolution != videoOutputResolution)
310 || currentPreviewFormat != adjustedPreviewFormat || currentFpsRange.min != adjustedFps.min
311 || currentFpsRange.max != adjustedFps.max) {
313 m_videoOutput->setVideoSize(videoOutputResolution);
317 if (m_previewStarted && restartPreview)
318 m_camera->stopPreview();
320 m_camera->setPreviewSize(cameraOutputResolution);
321 m_camera->setPreviewFormat(adjustedPreviewFormat);
322 m_camera->setPreviewFpsRange(adjustedFps);
325 if (m_previewStarted && restartPreview)
326 m_camera->startPreview();
330QList<QSize> QAndroidCameraSession::getSupportedPreviewSizes()
const
332 return m_camera ? m_camera->getSupportedPreviewSizes() : QList<QSize>();
335QList<QVideoFrameFormat::PixelFormat> QAndroidCameraSession::getSupportedPixelFormats()
const
337 QList<QVideoFrameFormat::PixelFormat> formats;
342 const QList<AndroidCamera::ImageFormat> nativeFormats = m_camera->getSupportedPreviewFormats();
344 formats.reserve(nativeFormats.size());
346 for (AndroidCamera::ImageFormat nativeFormat : nativeFormats) {
347 QVideoFrameFormat::PixelFormat format = AndroidCamera::QtPixelFormatFromAndroidImageFormat(nativeFormat);
348 if (format != QVideoFrameFormat::Format_Invalid)
349 formats.append(format);
355QList<AndroidCamera::FpsRange> QAndroidCameraSession::getSupportedPreviewFpsRange()
const
357 return m_camera ? m_camera->getSupportedPreviewFpsRange() : QList<AndroidCamera::FpsRange>();
361bool QAndroidCameraSession::startPreview()
363 if (!m_camera || !m_videoOutput)
366 if (m_previewStarted)
369 if (!m_videoOutput->isReady())
372 Q_ASSERT(m_videoOutput->surfaceTexture() || m_videoOutput->surfaceHolder());
374 if ((m_videoOutput->surfaceTexture() && !m_camera->setPreviewTexture(m_videoOutput->surfaceTexture()))
375 || (m_videoOutput->surfaceHolder() && !m_camera->setPreviewDisplay(m_videoOutput->surfaceHolder())))
378 applyResolution(m_actualImageSettings.resolution());
380 AndroidMultimediaUtils::enableOrientationListener(
true);
383 m_camera->startPreview();
384 m_previewStarted =
true;
385 m_videoOutput->start();
390QSize QAndroidCameraSession::getDefaultResolution()
const
392 const bool hasHighQualityProfile = AndroidCamcorderProfile::hasProfile(
393 m_camera->cameraId(),
394 AndroidCamcorderProfile::Quality(AndroidCamcorderProfile::QUALITY_HIGH));
396 if (hasHighQualityProfile) {
397 const AndroidCamcorderProfile camProfile = AndroidCamcorderProfile::get(
398 m_camera->cameraId(),
399 AndroidCamcorderProfile::Quality(AndroidCamcorderProfile::QUALITY_HIGH));
401 return QSize(camProfile.getValue(AndroidCamcorderProfile::videoFrameWidth),
402 camProfile.getValue(AndroidCamcorderProfile::videoFrameHeight));
407void QAndroidCameraSession::stopPreview()
409 if (!m_camera || !m_previewStarted)
412 AndroidMultimediaUtils::enableOrientationListener(
false);
414 m_camera->stopPreview();
415 m_camera->setPreviewSize(QSize());
416 m_camera->setPreviewTexture(0);
417 m_camera->setPreviewDisplay(0);
420 m_videoOutput->stop();
422 m_previewStarted =
false;
425void QAndroidCameraSession::setImageSettings(
const QImageEncoderSettings &settings)
427 if (m_requestedImageSettings == settings)
430 m_requestedImageSettings = m_actualImageSettings = settings;
432 applyImageSettings();
434 if (m_readyForCapture)
435 applyResolution(m_actualImageSettings.resolution());
438void QAndroidCameraSession::enableRotation()
440 m_rotationEnabled =
true;
443void QAndroidCameraSession::disableRotation()
445 m_rotationEnabled =
false;
448void QAndroidCameraSession::updateOrientation()
450 if (!m_camera || !m_rotationEnabled)
453 m_camera->setDisplayOrientation(currentCameraRotation());
454 applyResolution(m_actualImageSettings.resolution());
458int QAndroidCameraSession::currentCameraRotation()
const
463 auto screen = QGuiApplication::primaryScreen();
464 auto screenOrientation = screen->orientation();
465 if (screenOrientation == Qt::PrimaryOrientation)
466 screenOrientation = screen->primaryOrientation();
468 int deviceOrientation = 0;
469 switch (screenOrientation) {
470 case Qt::PrimaryOrientation:
471 case Qt::PortraitOrientation:
473 case Qt::LandscapeOrientation:
474 deviceOrientation = 90;
476 case Qt::InvertedPortraitOrientation:
477 deviceOrientation = 180;
479 case Qt::InvertedLandscapeOrientation:
480 deviceOrientation = 270;
484 int nativeCameraOrientation = m_camera->getNativeOrientation();
488 if (m_camera->getFacing() == AndroidCamera::CameraFacingFront) {
489 rotation = (nativeCameraOrientation + deviceOrientation) % 360;
490 rotation = (360 - rotation) % 360;
492 rotation = (nativeCameraOrientation - deviceOrientation + 360) % 360;
497void QAndroidCameraSession::setPreviewFormat(AndroidCamera::ImageFormat format)
499 if (format == AndroidCamera::UnknownImageFormat)
502 m_camera->setPreviewFormat(format);
505void QAndroidCameraSession::setPreviewCallback(PreviewCallback *callback)
507 m_videoFrameCallbackMutex.lock();
508 m_previewCallback = callback;
510 m_camera->notifyNewFrames(m_previewCallback);
511 m_videoFrameCallbackMutex.unlock();
514void QAndroidCameraSession::applyImageSettings()
520 m_actualImageSettings.setFormat(QImageCapture::JPEG);
522 const QSize requestedResolution = m_requestedImageSettings.resolution();
523 const QList<QSize> supportedResolutions = m_camera->getSupportedPictureSizes();
524 if (!requestedResolution.isValid()) {
525 m_actualImageSettings.setResolution(getDefaultResolution());
526 }
else if (!supportedResolutions.contains(requestedResolution)) {
528 int reqPixelCount = requestedResolution.width() * requestedResolution.height();
529 QList<
int> supportedPixelCounts;
530 for (
int i = 0; i < supportedResolutions.size(); ++i) {
531 const QSize &s = supportedResolutions.at(i);
532 supportedPixelCounts.append(s.width() * s.height());
534 int closestIndex = qt_findClosestValue(supportedPixelCounts, reqPixelCount);
535 m_actualImageSettings.setResolution(supportedResolutions.at(closestIndex));
537 m_camera->setPictureSize(m_actualImageSettings.resolution());
539 int jpegQuality = 100;
540 switch (m_requestedImageSettings.quality()) {
541 case QImageCapture::VeryLowQuality:
544 case QImageCapture::LowQuality:
547 case QImageCapture::NormalQuality:
550 case QImageCapture::HighQuality:
553 case QImageCapture::VeryHighQuality:
557 m_camera->setJpegQuality(jpegQuality);
560bool QAndroidCameraSession::isReadyForCapture()
const
562 return isActive() && m_readyForCapture;
565void QAndroidCameraSession::setReadyForCapture(
bool ready)
567 if (m_readyForCapture == ready)
570 m_readyForCapture = ready;
571 emit readyForCaptureChanged(ready);
574int QAndroidCameraSession::captureImage()
576 const int newImageCaptureId = m_currentImageCaptureId + 1;
578 if (!isReadyForCapture()) {
579 emit imageCaptureError(newImageCaptureId, QImageCapture::NotReadyError,
580 QPlatformImageCapture::msgCameraNotReady());
581 return newImageCaptureId;
584 setReadyForCapture(
false);
586 m_currentImageCaptureId = newImageCaptureId;
588 applyResolution(m_actualImageSettings.resolution());
589 m_camera->takePicture();
591 return m_currentImageCaptureId;
594int QAndroidCameraSession::capture(
const QString &fileName)
596 m_currentImageCaptureFileName = fileName;
597 m_imageCaptureToBuffer =
false;
598 return captureImage();
601int QAndroidCameraSession::captureToBuffer()
603 m_currentImageCaptureFileName.clear();
604 m_imageCaptureToBuffer =
true;
605 return captureImage();
608void QAndroidCameraSession::onCameraTakePictureFailed()
610 emit imageCaptureError(m_currentImageCaptureId, QImageCapture::ResourceError,
611 tr(
"Failed to capture image"));
614 m_camera->startPreview();
617void QAndroidCameraSession::onCameraPictureExposed()
622 emit imageExposed(m_currentImageCaptureId);
623 m_camera->fetchLastPreviewFrame();
626void QAndroidCameraSession::onLastPreviewFrameFetched(
const QVideoFrame &frame)
633 (
void)QtConcurrent::run(&QAndroidCameraSession::processPreviewImage,
this,
634 m_currentImageCaptureId, frame, currentCameraRotation());
637void QAndroidCameraSession::processPreviewImage(
int id,
const QVideoFrame &frame,
int rotation)
642 QTransform transform;
643 transform.rotate(rotation);
645 if (m_camera->getFacing() == AndroidCamera::CameraFacingFront)
646 transform.scale(-1, 1);
648 emit imageCaptured(id, frame.toImage().transformed(transform));
651void QAndroidCameraSession::onNewPreviewFrame(
const QVideoFrame &frame)
656 m_videoFrameCallbackMutex.lock();
658 if (m_previewCallback)
659 m_previewCallback->onFrameAvailable(frame);
661 m_videoFrameCallbackMutex.unlock();
664void QAndroidCameraSession::onCameraPictureCaptured(
const QByteArray &bytes,
665 QVideoFrameFormat::PixelFormat format, QSize size,
int bytesPerLine)
667 if (m_imageCaptureToBuffer) {
668 processCapturedImageToBuffer(m_currentImageCaptureId, bytes, format, size, bytesPerLine);
671 (
void)QtConcurrent::run(&QAndroidCameraSession::processCapturedImage,
this,
672 m_currentImageCaptureId, bytes, m_currentImageCaptureFileName);
677 m_camera->startPreview();
680void QAndroidCameraSession::onCameraPreviewStarted()
682 setReadyForCapture(
true);
685void QAndroidCameraSession::onCameraPreviewFailedToStart()
688 Q_EMIT error(QCamera::CameraError, tr(
"Camera preview failed to start."));
690 AndroidMultimediaUtils::enableOrientationListener(
false);
691 m_camera->setPreviewSize(QSize());
692 m_camera->setPreviewTexture(0);
694 m_videoOutput->stop();
695 m_videoOutput->reset();
697 m_previewStarted =
false;
700 setReadyForCapture(
false);
704void QAndroidCameraSession::onCameraPreviewStopped()
706 if (!m_previewStarted)
708 setReadyForCapture(
false);
711void QAndroidCameraSession::processCapturedImage(
int id,
const QByteArray &bytes,
const QString &fileName)
713 const QString actualFileName = QMediaStorageLocation::generateFileName(
714 fileName, QStandardPaths::PicturesLocation, QLatin1String(
"jpg"));
715 QFile writer(actualFileName);
716 if (!writer.open(QIODeviceBase::WriteOnly)) {
717 const QString errorMessage = tr(
"File is not available: %1").arg(writer.errorString());
718 emit imageCaptureError(id, QImageCapture::Error::ResourceError, errorMessage);
722 if (writer.write(bytes) < 0) {
723 const QString errorMessage = tr(
"Could not save to file: %1").arg(writer.errorString());
724 emit imageCaptureError(id, QImageCapture::Error::ResourceError, errorMessage);
729 if (fileName.isEmpty() || QFileInfo(fileName).isRelative())
730 AndroidMultimediaUtils::registerMediaFile(actualFileName);
732 emit imageSaved(id, actualFileName);
735void QAndroidCameraSession::processCapturedImageToBuffer(
int id,
const QByteArray &bytes,
736 QVideoFrameFormat::PixelFormat format, QSize size,
int bytesPerLine)
738 QVideoFrame frame = QVideoFramePrivate::createFrame(
739 std::make_unique<QMemoryVideoBuffer>(bytes, bytesPerLine),
740 QVideoFrameFormat(size, format));
741 emit imageAvailable(id, frame);
744void QAndroidCameraSession::onVideoOutputReady(
bool ready)
746 if (ready && m_active)
750void QAndroidCameraSession::onApplicationStateChanged()
753 switch (QGuiApplication::applicationState()) {
754 case Qt::ApplicationInactive:
755 if (!m_keepActive && m_active) {
756 m_savedState = m_active;
758 m_isStateSaved =
true;
761 case Qt::ApplicationActive:
762 if (m_isStateSaved) {
763 setActive(m_savedState);
764 m_isStateSaved =
false;
772void QAndroidCameraSession::setKeepAlive(
bool keepAlive)
774 m_keepActive = keepAlive;
777void QAndroidCameraSession::setVideoSink(QVideoSink *sink)
783 disconnect(m_retryPreviewConnection);
788 m_retryPreviewConnection =
789 connect(m_sink->platformVideoSink(), &QPlatformVideoSink::rhiChanged,
this, [&]()
795 }, Qt::DirectConnection);
797 delete m_textureOutput;
798 m_textureOutput =
nullptr;
800 m_textureOutput =
new QAndroidTextureVideoOutput(m_sink,
this);
803 setVideoOutput(m_textureOutput);
808#include "moc_qandroidcamerasession_p.cpp"