From 038e22078b3ba665ae9519bf5c630b86064b301e Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Tue, 29 Sep 2015 16:13:53 +0200 Subject: AVFoundation: fix camera capture previews. Generate the preview from a viewfinder frame and not from the final JPG image. In addition, the preview is now rotated to always be in the same orientation as the device at the time of capture. Task-number: QTBUG-46971 Change-Id: I48851225738e50fbd89c2f94904bac366303a9ad Reviewed-by: Christian Stromme --- .../avfoundation/camera/avfimagecapturecontrol.mm | 90 ++++++++++++++-------- 1 file changed, 56 insertions(+), 34 deletions(-) (limited to 'src/plugins/avfoundation/camera/avfimagecapturecontrol.mm') diff --git a/src/plugins/avfoundation/camera/avfimagecapturecontrol.mm b/src/plugins/avfoundation/camera/avfimagecapturecontrol.mm index c28ccef8e..4703f8727 100644 --- a/src/plugins/avfoundation/camera/avfimagecapturecontrol.mm +++ b/src/plugins/avfoundation/camera/avfimagecapturecontrol.mm @@ -33,14 +33,15 @@ #include "avfcameradebug.h" #include "avfimagecapturecontrol.h" -#include "avfcamerasession.h" #include "avfcameraservice.h" #include "avfcameracontrol.h" #include #include #include +#include #include +#include QT_USE_NAMESPACE @@ -65,6 +66,10 @@ AVFImageCaptureControl::AVFImageCaptureControl(AVFCameraService *service, QObjec connect(m_cameraControl, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateReadyStatus())); connect(m_session, SIGNAL(readyToConfigureConnections()), SLOT(updateCaptureConnection())); + + connect(m_session, &AVFCameraSession::newViewfinderFrame, + this, &AVFImageCaptureControl::onNewViewfinderFrame, + Qt::DirectConnection); } AVFImageCaptureControl::~AVFImageCaptureControl() @@ -106,9 +111,18 @@ int AVFImageCaptureControl::capture(const QString &fileName) qDebugCamera() << "Capture image to" << actualFileName; - int captureId = m_lastCaptureId; + CaptureRequest request = { m_lastCaptureId, new QSemaphore }; + m_requestsMutex.lock(); + m_captureRequests.enqueue(request); + m_requestsMutex.unlock(); + [m_stillImageOutput captureStillImageAsynchronouslyFromConnection:m_videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) { + + // Wait for the preview to be generated before saving the JPEG + request.previewReady->acquire(); + delete request.previewReady; + if (error) { QStringList messageParts; messageParts << QString::fromUtf8([[error localizedDescription] UTF8String]); @@ -119,64 +133,72 @@ int AVFImageCaptureControl::capture(const QString &fileName) qDebugCamera() << "Image capture failed:" << errorMessage; QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, - Q_ARG(int, captureId), + Q_ARG(int, request.captureId), Q_ARG(int, QCameraImageCapture::ResourceError), Q_ARG(QString, errorMessage)); } else { - qDebugCamera() << "Image captured:" << actualFileName; - //we can't find the exact time the image is exposed, - //but image capture is very fast on desktop, so emit it here - QMetaObject::invokeMethod(this, "imageExposed", Qt::QueuedConnection, - Q_ARG(int, captureId)); + qDebugCamera() << "Image capture completed:" << actualFileName; NSData *nsJpgData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer]; QByteArray jpgData = QByteArray::fromRawData((const char *)[nsJpgData bytes], [nsJpgData length]); - //Generate snap preview as downscalled image - { - QBuffer buffer(&jpgData); - QImageReader imageReader(&buffer); - QSize imgSize = imageReader.size(); - int downScaleSteps = 0; - while (imgSize.width() > 800 && downScaleSteps < 8) { - imgSize.rwidth() /= 2; - imgSize.rheight() /= 2; - downScaleSteps++; - } - - imageReader.setScaledSize(imgSize); - QImage snapPreview = imageReader.read(); - - QMetaObject::invokeMethod(this, "imageCaptured", Qt::QueuedConnection, - Q_ARG(int, captureId), - Q_ARG(QImage, snapPreview)); - } - - qDebugCamera() << "Image captured" << actualFileName; - QFile f(actualFileName); if (f.open(QFile::WriteOnly)) { if (f.write(jpgData) != -1) { QMetaObject::invokeMethod(this, "imageSaved", Qt::QueuedConnection, - Q_ARG(int, captureId), + Q_ARG(int, request.captureId), Q_ARG(QString, actualFileName)); } else { QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, - Q_ARG(int, captureId), + Q_ARG(int, request.captureId), Q_ARG(int, QCameraImageCapture::OutOfSpaceError), Q_ARG(QString, f.errorString())); } } else { QString errorMessage = tr("Could not open destination file:\n%1").arg(actualFileName); QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, - Q_ARG(int, captureId), + Q_ARG(int, request.captureId), Q_ARG(int, QCameraImageCapture::ResourceError), Q_ARG(QString, errorMessage)); } } }]; - return captureId; + return request.captureId; +} + +void AVFImageCaptureControl::onNewViewfinderFrame(const QVideoFrame &frame) +{ + QMutexLocker locker(&m_requestsMutex); + + if (m_captureRequests.isEmpty()) + return; + + CaptureRequest request = m_captureRequests.dequeue(); + Q_EMIT imageExposed(request.captureId); + + QtConcurrent::run(this, &AVFImageCaptureControl::makeCapturePreview, + request, + frame, + m_session->activeCameraInfo(), + m_orientationHandler.currentOrientation()); +} + +void AVFImageCaptureControl::makeCapturePreview(CaptureRequest request, + const QVideoFrame &frame, + AVFCameraInfo cameraInfo, + int screenOrientation) +{ + QTransform transform; + screenOrientation = 360 - screenOrientation; + if (cameraInfo.position == QCamera::FrontFace) + transform.rotate((screenOrientation + cameraInfo.orientation) % 360); + else + transform.rotate((screenOrientation + (360 - cameraInfo.orientation)) % 360); + + Q_EMIT imageCaptured(request.captureId, qt_imageFromVideoFrame(frame).transformed(transform)); + + request.previewReady->release(); } void AVFImageCaptureControl::cancelCapture() -- cgit v1.2.3