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 <christian.stromme@theqtcompany.com>
This commit is contained in:
@@ -70,6 +70,7 @@ public:
|
|||||||
static int defaultCameraIndex();
|
static int defaultCameraIndex();
|
||||||
static const QList<AVFCameraInfo> &availableCameraDevices();
|
static const QList<AVFCameraInfo> &availableCameraDevices();
|
||||||
static AVFCameraInfo cameraDeviceInfo(const QByteArray &device);
|
static AVFCameraInfo cameraDeviceInfo(const QByteArray &device);
|
||||||
|
AVFCameraInfo activeCameraInfo() const { return m_activeCameraInfo; }
|
||||||
|
|
||||||
void setVideoOutput(AVFCameraRendererControl *output);
|
void setVideoOutput(AVFCameraRendererControl *output);
|
||||||
AVCaptureSession *captureSession() const { return m_captureSession; }
|
AVCaptureSession *captureSession() const { return m_captureSession; }
|
||||||
@@ -93,10 +94,12 @@ public Q_SLOTS:
|
|||||||
void processSessionStopped();
|
void processSessionStopped();
|
||||||
|
|
||||||
void onCameraFrameFetched(const QVideoFrame &frame);
|
void onCameraFrameFetched(const QVideoFrame &frame);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void readyToConfigureConnections();
|
void readyToConfigureConnections();
|
||||||
void stateChanged(QCamera::State newState);
|
void stateChanged(QCamera::State newState);
|
||||||
void activeChanged(bool);
|
void activeChanged(bool);
|
||||||
|
void newViewfinderFrame(const QVideoFrame &frame);
|
||||||
void error(int error, const QString &errorString);
|
void error(int error, const QString &errorString);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -107,6 +110,7 @@ private:
|
|||||||
|
|
||||||
static int m_defaultCameraIndex;
|
static int m_defaultCameraIndex;
|
||||||
static QList<AVFCameraInfo> m_cameraDevices;
|
static QList<AVFCameraInfo> m_cameraDevices;
|
||||||
|
AVFCameraInfo m_activeCameraInfo;
|
||||||
|
|
||||||
AVFCameraService *m_service;
|
AVFCameraService *m_service;
|
||||||
AVFCameraRendererControl *m_videoOutput;
|
AVFCameraRendererControl *m_videoOutput;
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ void AVFCameraSession::attachVideoInputDevice()
|
|||||||
[m_captureSession removeInput:m_videoInput];
|
[m_captureSession removeInput:m_videoInput];
|
||||||
[m_videoInput release];
|
[m_videoInput release];
|
||||||
m_videoInput = 0;
|
m_videoInput = 0;
|
||||||
|
m_activeCameraInfo = AVFCameraInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
AVCaptureDevice *videoDevice = m_service->videoDeviceControl()->createCaptureDevice();
|
AVCaptureDevice *videoDevice = m_service->videoDeviceControl()->createCaptureDevice();
|
||||||
@@ -346,6 +347,7 @@ void AVFCameraSession::attachVideoInputDevice()
|
|||||||
qWarning() << "Failed to create video device input";
|
qWarning() << "Failed to create video device input";
|
||||||
} else {
|
} else {
|
||||||
if ([m_captureSession canAddInput:m_videoInput]) {
|
if ([m_captureSession canAddInput:m_videoInput]) {
|
||||||
|
m_activeCameraInfo = m_cameraDevices.at(m_service->videoDeviceControl()->selectedDevice());
|
||||||
[m_videoInput retain];
|
[m_videoInput retain];
|
||||||
[m_captureSession addInput:m_videoInput];
|
[m_captureSession addInput:m_videoInput];
|
||||||
} else {
|
} else {
|
||||||
@@ -414,6 +416,8 @@ FourCharCode AVFCameraSession::defaultCodec()
|
|||||||
|
|
||||||
void AVFCameraSession::onCameraFrameFetched(const QVideoFrame &frame)
|
void AVFCameraSession::onCameraFrameFetched(const QVideoFrame &frame)
|
||||||
{
|
{
|
||||||
|
Q_EMIT newViewfinderFrame(frame);
|
||||||
|
|
||||||
m_videoProbesMutex.lock();
|
m_videoProbesMutex.lock();
|
||||||
QSet<AVFMediaVideoProbeControl *>::const_iterator i = m_videoProbes.constBegin();
|
QSet<AVFMediaVideoProbeControl *>::const_iterator i = m_videoProbes.constBegin();
|
||||||
while (i != m_videoProbes.constEnd()) {
|
while (i != m_videoProbes.constEnd()) {
|
||||||
|
|||||||
@@ -36,19 +36,24 @@
|
|||||||
|
|
||||||
#import <AVFoundation/AVFoundation.h>
|
#import <AVFoundation/AVFoundation.h>
|
||||||
|
|
||||||
|
#include <QtCore/qqueue.h>
|
||||||
|
#include <QtCore/qsemaphore.h>
|
||||||
#include <QtMultimedia/qcameraimagecapturecontrol.h>
|
#include <QtMultimedia/qcameraimagecapturecontrol.h>
|
||||||
|
#include <private/qvideooutputorientationhandler_p.h>
|
||||||
|
#include "avfcamerasession.h"
|
||||||
#include "avfstoragelocation.h"
|
#include "avfstoragelocation.h"
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
class AVFCameraSession;
|
|
||||||
class AVFCameraService;
|
|
||||||
class AVFCameraControl;
|
|
||||||
|
|
||||||
class AVFImageCaptureControl : public QCameraImageCaptureControl
|
class AVFImageCaptureControl : public QCameraImageCaptureControl
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
struct CaptureRequest {
|
||||||
|
int captureId;
|
||||||
|
QSemaphore *previewReady;
|
||||||
|
};
|
||||||
|
|
||||||
AVFImageCaptureControl(AVFCameraService *service, QObject *parent = 0);
|
AVFImageCaptureControl(AVFCameraService *service, QObject *parent = 0);
|
||||||
~AVFImageCaptureControl();
|
~AVFImageCaptureControl();
|
||||||
|
|
||||||
@@ -64,8 +69,11 @@ public:
|
|||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void updateCaptureConnection();
|
void updateCaptureConnection();
|
||||||
void updateReadyStatus();
|
void updateReadyStatus();
|
||||||
|
void onNewViewfinderFrame(const QVideoFrame &frame);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void makeCapturePreview(CaptureRequest request, const QVideoFrame &frame, AVFCameraInfo cameraInfo, int screenOrientation);
|
||||||
|
|
||||||
AVFCameraSession *m_session;
|
AVFCameraSession *m_session;
|
||||||
AVFCameraControl *m_cameraControl;
|
AVFCameraControl *m_cameraControl;
|
||||||
bool m_ready;
|
bool m_ready;
|
||||||
@@ -73,8 +81,14 @@ private:
|
|||||||
AVCaptureStillImageOutput *m_stillImageOutput;
|
AVCaptureStillImageOutput *m_stillImageOutput;
|
||||||
AVCaptureConnection *m_videoConnection;
|
AVCaptureConnection *m_videoConnection;
|
||||||
AVFStorageLocation m_storageLocation;
|
AVFStorageLocation m_storageLocation;
|
||||||
|
QVideoOutputOrientationHandler m_orientationHandler;
|
||||||
|
|
||||||
|
QMutex m_requestsMutex;
|
||||||
|
QQueue<CaptureRequest> m_captureRequests;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_TYPEINFO(AVFImageCaptureControl::CaptureRequest, Q_PRIMITIVE_TYPE);
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -33,14 +33,15 @@
|
|||||||
|
|
||||||
#include "avfcameradebug.h"
|
#include "avfcameradebug.h"
|
||||||
#include "avfimagecapturecontrol.h"
|
#include "avfimagecapturecontrol.h"
|
||||||
#include "avfcamerasession.h"
|
|
||||||
#include "avfcameraservice.h"
|
#include "avfcameraservice.h"
|
||||||
#include "avfcameracontrol.h"
|
#include "avfcameracontrol.h"
|
||||||
|
|
||||||
#include <QtCore/qurl.h>
|
#include <QtCore/qurl.h>
|
||||||
#include <QtCore/qfile.h>
|
#include <QtCore/qfile.h>
|
||||||
#include <QtCore/qbuffer.h>
|
#include <QtCore/qbuffer.h>
|
||||||
|
#include <QtConcurrent/qtconcurrentrun.h>
|
||||||
#include <QtGui/qimagereader.h>
|
#include <QtGui/qimagereader.h>
|
||||||
|
#include <private/qvideoframe_p.h>
|
||||||
|
|
||||||
QT_USE_NAMESPACE
|
QT_USE_NAMESPACE
|
||||||
|
|
||||||
@@ -65,6 +66,10 @@ AVFImageCaptureControl::AVFImageCaptureControl(AVFCameraService *service, QObjec
|
|||||||
connect(m_cameraControl, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateReadyStatus()));
|
connect(m_cameraControl, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateReadyStatus()));
|
||||||
|
|
||||||
connect(m_session, SIGNAL(readyToConfigureConnections()), SLOT(updateCaptureConnection()));
|
connect(m_session, SIGNAL(readyToConfigureConnections()), SLOT(updateCaptureConnection()));
|
||||||
|
|
||||||
|
connect(m_session, &AVFCameraSession::newViewfinderFrame,
|
||||||
|
this, &AVFImageCaptureControl::onNewViewfinderFrame,
|
||||||
|
Qt::DirectConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
AVFImageCaptureControl::~AVFImageCaptureControl()
|
AVFImageCaptureControl::~AVFImageCaptureControl()
|
||||||
@@ -106,9 +111,18 @@ int AVFImageCaptureControl::capture(const QString &fileName)
|
|||||||
|
|
||||||
qDebugCamera() << "Capture image to" << actualFileName;
|
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
|
[m_stillImageOutput captureStillImageAsynchronouslyFromConnection:m_videoConnection
|
||||||
completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
|
completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
|
||||||
|
|
||||||
|
// Wait for the preview to be generated before saving the JPEG
|
||||||
|
request.previewReady->acquire();
|
||||||
|
delete request.previewReady;
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
QStringList messageParts;
|
QStringList messageParts;
|
||||||
messageParts << QString::fromUtf8([[error localizedDescription] UTF8String]);
|
messageParts << QString::fromUtf8([[error localizedDescription] UTF8String]);
|
||||||
@@ -119,64 +133,72 @@ int AVFImageCaptureControl::capture(const QString &fileName)
|
|||||||
qDebugCamera() << "Image capture failed:" << errorMessage;
|
qDebugCamera() << "Image capture failed:" << errorMessage;
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
|
||||||
Q_ARG(int, captureId),
|
Q_ARG(int, request.captureId),
|
||||||
Q_ARG(int, QCameraImageCapture::ResourceError),
|
Q_ARG(int, QCameraImageCapture::ResourceError),
|
||||||
Q_ARG(QString, errorMessage));
|
Q_ARG(QString, errorMessage));
|
||||||
} else {
|
} else {
|
||||||
qDebugCamera() << "Image captured:" << actualFileName;
|
qDebugCamera() << "Image capture completed:" << 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));
|
|
||||||
|
|
||||||
NSData *nsJpgData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
|
NSData *nsJpgData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
|
||||||
QByteArray jpgData = QByteArray::fromRawData((const char *)[nsJpgData bytes], [nsJpgData length]);
|
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);
|
QFile f(actualFileName);
|
||||||
if (f.open(QFile::WriteOnly)) {
|
if (f.open(QFile::WriteOnly)) {
|
||||||
if (f.write(jpgData) != -1) {
|
if (f.write(jpgData) != -1) {
|
||||||
QMetaObject::invokeMethod(this, "imageSaved", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(this, "imageSaved", Qt::QueuedConnection,
|
||||||
Q_ARG(int, captureId),
|
Q_ARG(int, request.captureId),
|
||||||
Q_ARG(QString, actualFileName));
|
Q_ARG(QString, actualFileName));
|
||||||
} else {
|
} else {
|
||||||
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
|
||||||
Q_ARG(int, captureId),
|
Q_ARG(int, request.captureId),
|
||||||
Q_ARG(int, QCameraImageCapture::OutOfSpaceError),
|
Q_ARG(int, QCameraImageCapture::OutOfSpaceError),
|
||||||
Q_ARG(QString, f.errorString()));
|
Q_ARG(QString, f.errorString()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
QString errorMessage = tr("Could not open destination file:\n%1").arg(actualFileName);
|
QString errorMessage = tr("Could not open destination file:\n%1").arg(actualFileName);
|
||||||
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
|
||||||
Q_ARG(int, captureId),
|
Q_ARG(int, request.captureId),
|
||||||
Q_ARG(int, QCameraImageCapture::ResourceError),
|
Q_ARG(int, QCameraImageCapture::ResourceError),
|
||||||
Q_ARG(QString, errorMessage));
|
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()
|
void AVFImageCaptureControl::cancelCapture()
|
||||||
|
|||||||
Reference in New Issue
Block a user