11/* ***************************************************************************
22**
33** Copyright (C) 2016 The Qt Company Ltd.
4+ ** Copyright (C) 2016 Ruslan Baratov
45** Contact: https://www.qt.io/licensing/
56**
67** This file is part of the Qt Toolkit.
@@ -67,7 +68,6 @@ QAndroidCameraSession::QAndroidCameraSession(QObject *parent)
6768 , m_savedState(-1 )
6869 , m_status(QCamera::UnloadedStatus)
6970 , m_previewStarted(false )
70- , m_imageSettingsDirty(true )
7171 , m_captureDestination(QCameraImageCapture::CaptureToFile)
7272 , m_captureImageDriveMode(QCameraImageCapture::SingleImageCapture)
7373 , m_lastImageCaptureId(0 )
@@ -100,7 +100,7 @@ void QAndroidCameraSession::setCaptureMode(QCamera::CaptureModes mode)
100100 emit captureModeChanged (m_captureMode);
101101
102102 if (m_previewStarted && m_captureMode.testFlag (QCamera::CaptureStillImage))
103- adjustViewfinderSize (m_imageSettings .resolution ());
103+ applyViewfinderSettings (m_actualImageSettings .resolution ());
104104}
105105
106106bool QAndroidCameraSession::isCaptureModeSupported (QCamera::CaptureModes mode) const
@@ -223,7 +223,8 @@ void QAndroidCameraSession::close()
223223 m_readyForCapture = false ;
224224 m_currentImageCaptureId = -1 ;
225225 m_currentImageCaptureFileName.clear ();
226- m_imageSettingsDirty = true ;
226+ m_actualImageSettings = m_requestedImageSettings;
227+ m_actualViewfinderSettings = m_requestedViewfinderSettings;
227228
228229 m_camera->release ();
229230 delete m_camera;
@@ -251,37 +252,108 @@ void QAndroidCameraSession::setVideoOutput(QAndroidVideoOutput *output)
251252 }
252253}
253254
254- void QAndroidCameraSession::adjustViewfinderSize (const QSize &captureSize, bool restartPreview)
255+ void QAndroidCameraSession::setViewfinderSettings (const QCameraViewfinderSettings &settings)
256+ {
257+ if (m_requestedViewfinderSettings == settings)
258+ return ;
259+
260+ m_requestedViewfinderSettings = m_actualViewfinderSettings = settings;
261+
262+ if (m_readyForCapture)
263+ applyViewfinderSettings ();
264+ }
265+
266+ void QAndroidCameraSession::applyViewfinderSettings (const QSize &captureSize, bool restartPreview)
255267{
256268 if (!m_camera)
257269 return ;
258270
259- QSize currentViewfinderResolution = m_camera->previewSize ();
260- QSize adjustedViewfinderResolution;
271+ const QSize currentViewfinderResolution = m_camera->previewSize ();
272+ const AndroidCamera::ImageFormat currentPreviewFormat = m_camera->getPreviewFormat ();
273+ const AndroidCamera::FpsRange currentFpsRange = m_camera->getPreviewFpsRange ();
261274
262- if (m_captureMode.testFlag (QCamera::CaptureVideo) && m_camera->getPreferredPreviewSizeForVideo ().isEmpty ()) {
275+ // -- adjust resolution
276+ QSize adjustedViewfinderResolution;
277+ const bool validCaptureSize = captureSize.width () > 0 && captureSize.height () > 0 ;
278+ if (m_captureMode.testFlag (QCamera::CaptureVideo)
279+ && validCaptureSize
280+ && m_camera->getPreferredPreviewSizeForVideo ().isEmpty ()) {
263281 // According to the Android doc, if getPreferredPreviewSizeForVideo() returns null, it means
264282 // the preview size cannot be different from the capture size
265283 adjustedViewfinderResolution = captureSize;
266284 } else {
267- // search for viewfinder resolution with the same aspect ratio
268- const qreal aspectRatio = qreal (captureSize.width ()) / qreal (captureSize.height ());
269- QList<QSize> previewSizes = m_camera->getSupportedPreviewSizes ();
270- for (int i = previewSizes.count () - 1 ; i >= 0 ; --i) {
271- const QSize &size = previewSizes.at (i);
272- if (qAbs (aspectRatio - (qreal (size.width ()) / size.height ())) < 0.01 ) {
273- adjustedViewfinderResolution = size;
274- break ;
285+ qreal captureAspectRatio = 0 ;
286+ if (validCaptureSize)
287+ captureAspectRatio = qreal (captureSize.width ()) / qreal (captureSize.height ());
288+
289+ const QList<QSize> previewSizes = m_camera->getSupportedPreviewSizes ();
290+
291+ const QSize vfRes = m_requestedViewfinderSettings.resolution ();
292+ if (vfRes.width () > 0 && vfRes.height () > 0
293+ && (!validCaptureSize || qAbs (captureAspectRatio - (qreal (vfRes.width ()) / vfRes.height ())) < 0.01 )
294+ && previewSizes.contains (vfRes)) {
295+ adjustedViewfinderResolution = vfRes;
296+ } else if (validCaptureSize) {
297+ // search for viewfinder resolution with the same aspect ratio
298+ for (int i = previewSizes.count () - 1 ; i >= 0 ; --i) {
299+ const QSize &size = previewSizes.at (i);
300+ if (qAbs (captureAspectRatio - (qreal (size.width ()) / size.height ())) < 0.01 ) {
301+ adjustedViewfinderResolution = size;
302+ break ;
303+ }
304+ }
305+ if (!adjustedViewfinderResolution.isValid ()) {
306+ qWarning (" Cannot find a viewfinder resolution matching the capture aspect ratio." );
307+ return ;
275308 }
309+ } else {
310+ adjustedViewfinderResolution = previewSizes.last ();
276311 }
312+ }
313+ m_actualViewfinderSettings.setResolution (adjustedViewfinderResolution);
277314
278- if (!adjustedViewfinderResolution.isValid ()) {
279- qWarning (" Cannot find a viewfinder resolution matching the capture aspect ratio." );
280- return ;
315+ // -- adjust pixel format
316+
317+ AndroidCamera::ImageFormat adjustedPreviewFormat = AndroidCamera::NV21;
318+ if (m_requestedViewfinderSettings.pixelFormat () != QVideoFrame::Format_Invalid) {
319+ const AndroidCamera::ImageFormat f = AndroidImageFormatFromQtPixelFormat (m_requestedViewfinderSettings.pixelFormat ());
320+ if (f == AndroidCamera::UnknownImageFormat || !m_camera->getSupportedPreviewFormats ().contains (f))
321+ qWarning (" Unsupported viewfinder pixel format" );
322+ else
323+ adjustedPreviewFormat = f;
324+ }
325+ m_actualViewfinderSettings.setPixelFormat (QtPixelFormatFromAndroidImageFormat (adjustedPreviewFormat));
326+
327+ // -- adjust FPS
328+
329+ AndroidCamera::FpsRange adjustedFps = currentFpsRange;
330+ const AndroidCamera::FpsRange requestedFpsRange = AndroidCamera::FpsRange::makeFromQReal (m_requestedViewfinderSettings.minimumFrameRate (),
331+ m_requestedViewfinderSettings.maximumFrameRate ());
332+ if (requestedFpsRange.min > 0 || requestedFpsRange.max > 0 ) {
333+ int minDist = INT_MAX;
334+ const QList<AndroidCamera::FpsRange> supportedFpsRanges = m_camera->getSupportedPreviewFpsRange ();
335+ auto it = supportedFpsRanges.rbegin (), end = supportedFpsRanges.rend ();
336+ for (; it != end; ++it) {
337+ int dist = (requestedFpsRange.min > 0 ? qAbs (requestedFpsRange.min - it->min ) : 0 )
338+ + (requestedFpsRange.max > 0 ? qAbs (requestedFpsRange.max - it->max ) : 0 );
339+ if (dist < minDist) {
340+ minDist = dist;
341+ adjustedFps = *it;
342+ if (minDist == 0 )
343+ break ; // exact match
344+ }
281345 }
282346 }
347+ m_actualViewfinderSettings.setMinimumFrameRate (adjustedFps.getMinReal ());
348+ m_actualViewfinderSettings.setMaximumFrameRate (adjustedFps.getMaxReal ());
349+
350+ // -- Set values on camera
351+
352+ if (currentViewfinderResolution != adjustedViewfinderResolution
353+ || currentPreviewFormat != adjustedPreviewFormat
354+ || currentFpsRange.min != adjustedFps.min
355+ || currentFpsRange.max != adjustedFps.max ) {
283356
284- if (currentViewfinderResolution != adjustedViewfinderResolution) {
285357 if (m_videoOutput)
286358 m_videoOutput->setVideoSize (adjustedViewfinderResolution);
287359
@@ -290,13 +362,45 @@ void QAndroidCameraSession::adjustViewfinderSize(const QSize &captureSize, bool
290362 m_camera->stopPreview ();
291363
292364 m_camera->setPreviewSize (adjustedViewfinderResolution);
365+ m_camera->setPreviewFormat (adjustedPreviewFormat);
366+ m_camera->setPreviewFpsRange (adjustedFps);
293367
294368 // restart preview
295369 if (m_previewStarted && restartPreview)
296370 m_camera->startPreview ();
297371 }
298372}
299373
374+ QList<QSize> QAndroidCameraSession::getSupportedPreviewSizes () const
375+ {
376+ return m_camera ? m_camera->getSupportedPreviewSizes () : QList<QSize>();
377+ }
378+
379+ QList<QVideoFrame::PixelFormat> QAndroidCameraSession::getSupportedPixelFormats () const
380+ {
381+ QList<QVideoFrame::PixelFormat> formats;
382+
383+ if (!m_camera)
384+ return formats;
385+
386+ const QList<AndroidCamera::ImageFormat> nativeFormats = m_camera->getSupportedPreviewFormats ();
387+
388+ formats.reserve (nativeFormats.size ());
389+
390+ for (AndroidCamera::ImageFormat nativeFormat : nativeFormats) {
391+ QVideoFrame::PixelFormat format = QtPixelFormatFromAndroidImageFormat (nativeFormat);
392+ if (format != QVideoFrame::Format_Invalid)
393+ formats.append (format);
394+ }
395+
396+ return formats;
397+ }
398+
399+ QList<AndroidCamera::FpsRange> QAndroidCameraSession::getSupportedPreviewFpsRange () const
400+ {
401+ return m_camera ? m_camera->getSupportedPreviewFpsRange () : QList<AndroidCamera::FpsRange>();
402+ }
403+
300404bool QAndroidCameraSession::startPreview ()
301405{
302406 if (!m_camera)
@@ -323,7 +427,8 @@ bool QAndroidCameraSession::startPreview()
323427 emit statusChanged (m_status);
324428
325429 applyImageSettings ();
326- adjustViewfinderSize (m_imageSettings.resolution ());
430+ applyViewfinderSettings (m_captureMode.testFlag (QCamera::CaptureStillImage) ? m_actualImageSettings.resolution ()
431+ : QSize ());
327432
328433 AndroidMultimediaUtils::enableOrientationListener (true );
329434
@@ -357,19 +462,15 @@ void QAndroidCameraSession::stopPreview()
357462
358463void QAndroidCameraSession::setImageSettings (const QImageEncoderSettings &settings)
359464{
360- if (m_imageSettings == settings)
465+ if (m_requestedImageSettings == settings)
361466 return ;
362467
363- m_imageSettings = settings;
364- if (m_imageSettings.codec ().isEmpty ())
365- m_imageSettings.setCodec (QLatin1String (" jpeg" ));
366-
367- m_imageSettingsDirty = true ;
468+ m_requestedImageSettings = m_actualImageSettings = settings;
368469
369470 applyImageSettings ();
370471
371472 if (m_readyForCapture && m_captureMode.testFlag (QCamera::CaptureStillImage))
372- adjustViewfinderSize (m_imageSettings .resolution ());
473+ applyViewfinderSettings (m_actualImageSettings .resolution ());
373474}
374475
375476int QAndroidCameraSession::currentCameraRotation () const
@@ -426,15 +527,32 @@ void QAndroidCameraSession::setPreviewCallback(PreviewCallback *callback)
426527
427528void QAndroidCameraSession::applyImageSettings ()
428529{
429- if (!m_camera || !m_imageSettingsDirty )
530+ if (!m_camera)
430531 return ;
431532
432- const QSize requestedResolution = m_imageSettings. resolution ();
433- const QList<QSize> supportedResolutions = m_camera-> getSupportedPictureSizes ( );
533+ if (m_actualImageSettings. codec (). isEmpty ())
534+ m_actualImageSettings. setCodec ( QLatin1String ( " jpeg " ) );
434535
536+ const QSize requestedResolution = m_requestedImageSettings.resolution ();
537+ const QList<QSize> supportedResolutions = m_camera->getSupportedPictureSizes ();
435538 if (!requestedResolution.isValid ()) {
436- // if no resolution is set, use the highest supported one
437- m_imageSettings.setResolution (supportedResolutions.last ());
539+ // if the viewfinder resolution is explicitly set, pick the highest available capture
540+ // resolution with the same aspect ratio
541+ if (m_requestedViewfinderSettings.resolution ().isValid ()) {
542+ const QSize vfResolution = m_actualViewfinderSettings.resolution ();
543+ const qreal vfAspectRatio = qreal (vfResolution.width ()) / vfResolution.height ();
544+
545+ auto it = supportedResolutions.rbegin (), end = supportedResolutions.rend ();
546+ for (; it != end; ++it) {
547+ if (qAbs (vfAspectRatio - (qreal (it->width ()) / it->height ())) < 0.01 ) {
548+ m_actualImageSettings.setResolution (*it);
549+ break ;
550+ }
551+ }
552+ } else {
553+ // otherwise, use the highest supported one
554+ m_actualImageSettings.setResolution (supportedResolutions.last ());
555+ }
438556 } else if (!supportedResolutions.contains (requestedResolution)) {
439557 // if the requested resolution is not supported, find the closest one
440558 int reqPixelCount = requestedResolution.width () * requestedResolution.height ();
@@ -444,11 +562,12 @@ void QAndroidCameraSession::applyImageSettings()
444562 supportedPixelCounts.append (s.width () * s.height ());
445563 }
446564 int closestIndex = qt_findClosestValue (supportedPixelCounts, reqPixelCount);
447- m_imageSettings .setResolution (supportedResolutions.at (closestIndex));
565+ m_actualImageSettings .setResolution (supportedResolutions.at (closestIndex));
448566 }
567+ m_camera->setPictureSize (m_actualImageSettings.resolution ());
449568
450569 int jpegQuality = 100 ;
451- switch (m_imageSettings .quality ()) {
570+ switch (m_requestedImageSettings .quality ()) {
452571 case QMultimedia::VeryLowQuality:
453572 jpegQuality = 20 ;
454573 break ;
@@ -465,11 +584,7 @@ void QAndroidCameraSession::applyImageSettings()
465584 jpegQuality = 100 ;
466585 break ;
467586 }
468-
469- m_camera->setPictureSize (m_imageSettings.resolution ());
470587 m_camera->setJpegQuality (jpegQuality);
471-
472- m_imageSettingsDirty = false ;
473588}
474589
475590bool QAndroidCameraSession::isCaptureDestinationSupported (QCameraImageCapture::CaptureDestinations destination) const
@@ -531,7 +646,7 @@ int QAndroidCameraSession::capture(const QString &fileName)
531646 m_currentImageCaptureFileName = fileName;
532647
533648 applyImageSettings ();
534- adjustViewfinderSize (m_imageSettings .resolution ());
649+ applyViewfinderSettings (m_actualImageSettings .resolution ());
535650
536651 // adjust picture rotation depending on the device orientation
537652 m_camera->setRotation (currentCameraRotation ());
@@ -610,7 +725,7 @@ void QAndroidCameraSession::onCameraPictureCaptured(const QByteArray &data)
610725 QtConcurrent::run (this , &QAndroidCameraSession::processCapturedImage,
611726 m_currentImageCaptureId,
612727 data,
613- m_imageSettings .resolution (),
728+ m_actualImageSettings .resolution (),
614729 m_captureDestination,
615730 m_currentImageCaptureFileName);
616731 }
@@ -702,6 +817,42 @@ void QAndroidCameraSession::processCapturedImage(int id,
702817 }
703818}
704819
820+ QVideoFrame::PixelFormat QAndroidCameraSession::QtPixelFormatFromAndroidImageFormat (AndroidCamera::ImageFormat format)
821+ {
822+ switch (format) {
823+ case AndroidCamera::RGB565:
824+ return QVideoFrame::Format_RGB565;
825+ case AndroidCamera::NV21:
826+ return QVideoFrame::Format_NV21;
827+ case AndroidCamera::YUY2:
828+ return QVideoFrame::Format_YUYV;
829+ case AndroidCamera::JPEG:
830+ return QVideoFrame::Format_Jpeg;
831+ case AndroidCamera::YV12:
832+ return QVideoFrame::Format_YV12;
833+ default :
834+ return QVideoFrame::Format_Invalid;
835+ }
836+ }
837+
838+ AndroidCamera::ImageFormat QAndroidCameraSession::AndroidImageFormatFromQtPixelFormat (QVideoFrame::PixelFormat format)
839+ {
840+ switch (format) {
841+ case QVideoFrame::Format_RGB565:
842+ return AndroidCamera::RGB565;
843+ case QVideoFrame::Format_NV21:
844+ return AndroidCamera::NV21;
845+ case QVideoFrame::Format_YUYV:
846+ return AndroidCamera::YUY2;
847+ case QVideoFrame::Format_Jpeg:
848+ return AndroidCamera::JPEG;
849+ case QVideoFrame::Format_YV12:
850+ return AndroidCamera::YV12;
851+ default :
852+ return AndroidCamera::UnknownImageFormat;
853+ }
854+ }
855+
705856void QAndroidCameraSession::onVideoOutputReady (bool ready)
706857{
707858 if (ready && m_state == QCamera::ActiveState)
0 commit comments