From 13d7f835fad59fbda69bfa160e21f19d095ac8d5 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Fri, 29 Nov 2013 17:16:44 +0100 Subject: [PATCH 1/7] Android: fix camera viewfinder orientation. The camera sensor on Android devices might be in a different orientation than the device natural orientation. There is no API in Qt to know about the camera orientation, so correcting the viewfinder orientation is not possible without making guesses. This patch makes sure the viewfinder orientation always matches the device natural orientation. For example, if the camera is mounted in landscape, and the device natural orientation is portrait, the viewfinder will automatically be rotated 90 degrees counter-clockwise. Task-number: QTBUG-35086 Change-Id: Ia890872971d72657debe709f61edba27d09dec65 Reviewed-by: Christian Stromme --- .../mediacapture/qandroidcamerasession.cpp | 37 ++++++++++++++++--- .../src/mediacapture/qandroidcamerasession.h | 3 +- src/plugins/android/src/wrappers/jcamera.cpp | 12 ++++++ src/plugins/android/src/wrappers/jcamera.h | 4 ++ 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/plugins/android/src/mediacapture/qandroidcamerasession.cpp b/src/plugins/android/src/mediacapture/qandroidcamerasession.cpp index 3a50139c..5ff19d1c 100644 --- a/src/plugins/android/src/mediacapture/qandroidcamerasession.cpp +++ b/src/plugins/android/src/mediacapture/qandroidcamerasession.cpp @@ -92,6 +92,7 @@ QAndroidCameraSession::QAndroidCameraSession(QObject *parent) , m_selectedCamera(0) , m_camera(0) , m_nativeOrientation(0) + , m_previewOrientation(0) , m_videoOutput(0) , m_captureMode(QCamera::CaptureViewfinder) , m_state(QCamera::UnloadedState) @@ -183,10 +184,20 @@ bool QAndroidCameraSession::open() connect(m_camera, SIGNAL(pictureExposed()), this, SLOT(onCameraPictureExposed())); connect(m_camera, SIGNAL(pictureCaptured(QByteArray)), this, SLOT(onCameraPictureCaptured(QByteArray))); connect(m_camera, SIGNAL(previewFrameAvailable(QByteArray)), this, SLOT(onCameraPreviewFrameAvailable(QByteArray))); + m_nativeOrientation = m_camera->getNativeOrientation(); + + // Preview orientation will always match the device natural orientation + if (m_camera->getFacing() == JCamera::CameraFacingFront) + m_previewOrientation = 360 - m_nativeOrientation; + else + m_previewOrientation = m_nativeOrientation; + m_status = QCamera::LoadedStatus; + if (m_camera->getPreviewFormat() != JCamera::NV21) m_camera->setPreviewFormat(JCamera::NV21); + emit opened(); } else { m_status = QCamera::UnavailableStatus; @@ -257,8 +268,16 @@ void QAndroidCameraSession::adjustViewfinderSize(const QSize &captureSize, bool } if (m_camera->previewSize() != viewfinderResolution) { - if (m_videoOutput) - m_videoOutput->setVideoSize(viewfinderResolution); + if (m_videoOutput) { + QSize size = viewfinderResolution; + + // If the preview orientation is not the defaut one (0 or 180 degrees), + // we have to invert the output aspect ratio. + if (m_previewOrientation % 180) + size.transpose(); + + m_videoOutput->setVideoSize(size); + } // if preview is started, we have to stop it first before changing its size if (m_previewStarted && restartPreview) @@ -282,6 +301,7 @@ void QAndroidCameraSession::startPreview() applyImageSettings(); adjustViewfinderSize(m_imageSettings.resolution()); + m_camera->setDisplayOrientation(m_previewOrientation); if (m_videoOutput && m_videoOutput->isReady()) onVideoOutputReady(true); @@ -558,10 +578,11 @@ void QAndroidCameraSession::onCameraPreviewFrameAvailable(const QByteArray &data QtConcurrent::run(this, &QAndroidCameraSession::processPreviewImage, m_currentImageCaptureId, - data); + data, + m_camera->getRotation()); } -void QAndroidCameraSession::processPreviewImage(int id, const QByteArray &data) +void QAndroidCameraSession::processPreviewImage(int id, const QByteArray &data, int rotation) { QSize frameSize = m_camera->previewSize(); QImage preview(frameSize, QImage::Format_ARGB32); @@ -570,11 +591,17 @@ void QAndroidCameraSession::processPreviewImage(int id, const QByteArray &data) frameSize.width(), frameSize.height()); + QTransform transform; + // Preview display of front-facing cameras is flipped horizontally, but the frame data // we get here is not. Flip it ourselves if the camera is front-facing to match what the user // sees on the viewfinder. if (m_camera->getFacing() == JCamera::CameraFacingFront) - preview = preview.transformed(QTransform().scale(-1, 1)); + transform.scale(-1, 1); + + transform.rotate(rotation); + + preview = preview.transformed(transform); emit imageCaptured(id, preview); } diff --git a/src/plugins/android/src/mediacapture/qandroidcamerasession.h b/src/plugins/android/src/mediacapture/qandroidcamerasession.h index 897cf52d..17ea4171 100644 --- a/src/plugins/android/src/mediacapture/qandroidcamerasession.h +++ b/src/plugins/android/src/mediacapture/qandroidcamerasession.h @@ -124,7 +124,7 @@ private: void stopPreview(); void applyImageSettings(); - void processPreviewImage(int id, const QByteArray &data); + void processPreviewImage(int id, const QByteArray &data, int rotation); void processCapturedImage(int id, const QByteArray &data, const QSize &resolution, @@ -134,6 +134,7 @@ private: int m_selectedCamera; JCamera *m_camera; int m_nativeOrientation; + int m_previewOrientation; QAndroidVideoOutput *m_videoOutput; QCamera::CaptureModes m_captureMode; diff --git a/src/plugins/android/src/wrappers/jcamera.cpp b/src/plugins/android/src/wrappers/jcamera.cpp index 2d42ffbd..e69cb554 100644 --- a/src/plugins/android/src/wrappers/jcamera.cpp +++ b/src/plugins/android/src/wrappers/jcamera.cpp @@ -118,6 +118,7 @@ JCamera::JCamera(int cameraId, jobject cam) : QObject() , QJNIObjectPrivate(cam) , m_cameraId(cameraId) + , m_rotation(0) , m_hasAPI14(false) { if (isValid()) { @@ -205,6 +206,11 @@ int JCamera::getNativeOrientation() return m_info.getField("orientation"); } +void JCamera::setDisplayOrientation(int degrees) +{ + callMethod("setDisplayOrientation", "(I)V", degrees); +} + QSize JCamera::getPreferredPreviewSizeForVideo() { if (!m_parameters.isValid()) @@ -612,10 +618,16 @@ void JCamera::setRotation(int rotation) if (!m_parameters.isValid()) return; + m_rotation = rotation; m_parameters.callMethod("setRotation", "(I)V", rotation); applyParameters(); } +int JCamera::getRotation() const +{ + return m_rotation; +} + QList JCamera::getSupportedPictureSizes() { QList list; diff --git a/src/plugins/android/src/wrappers/jcamera.h b/src/plugins/android/src/wrappers/jcamera.h index 464ca3cb..7c47ec7a 100644 --- a/src/plugins/android/src/wrappers/jcamera.h +++ b/src/plugins/android/src/wrappers/jcamera.h @@ -82,6 +82,8 @@ public: CameraFacing getFacing(); int getNativeOrientation(); + void setDisplayOrientation(int degrees); + QSize getPreferredPreviewSizeForVideo(); QList getSupportedPreviewSizes(); @@ -136,6 +138,7 @@ public: void setWhiteBalance(const QString &value); void setRotation(int rotation); + int getRotation() const; QList getSupportedPictureSizes(); void setPictureSize(const QSize &size); @@ -174,6 +177,7 @@ private: QJNIObjectPrivate m_parameters; QSize m_previewSize; + int m_rotation; bool m_hasAPI14; }; From 9ceb1c083e4f6911ace1af3c9dfa6b4dd9036586 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Thu, 5 Dec 2013 17:47:49 +0100 Subject: [PATCH 2/7] Use the backend to notify the QCamera state changes. We were emitting the stateChanged() signal no matter what was actually done by the backend. QCamera now emits this signal only if the backend actually changed the state. Task-number: QTBUG-35070 Change-Id: I9f32361cd815dbcc366ad30033692f9ae732ec46 Reviewed-by: Christian Stromme --- src/multimedia/camera/qcamera.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/multimedia/camera/qcamera.cpp b/src/multimedia/camera/qcamera.cpp index 5b0f0d10..e91a0318 100644 --- a/src/multimedia/camera/qcamera.cpp +++ b/src/multimedia/camera/qcamera.cpp @@ -175,8 +175,6 @@ void QCameraPrivate::_q_error(int error, const QString &errorString) void QCameraPrivate::setState(QCamera::State newState) { - Q_Q(QCamera); - unsetError(); if (!control) { @@ -184,13 +182,8 @@ void QCameraPrivate::setState(QCamera::State newState) return; } - if (state == newState) - return; - restartPending = false; - state = newState; - control->setState(state); - emit q->stateChanged(state); + control->setState(newState); } void QCameraPrivate::_q_updateState(QCamera::State newState) @@ -203,7 +196,6 @@ void QCameraPrivate::_q_updateState(QCamera::State newState) return; if (newState != state) { - qDebug() << "Camera state changed:" << newState; state = newState; emit q->stateChanged(state); } From 966d4bc86f02de83b621ef972c418352e84f6d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Str=C3=B8mme?= Date: Thu, 28 Nov 2013 15:07:21 +0100 Subject: [PATCH 3/7] Android add support for qrc in mediaplayer. [ChangeLog][QtMultimedia][Android] Enable mediaplayer to read files from the Qt Resource system. Task-number: QTBUG-31422 Change-Id: Iaaefb35566b623b78b9f8aa22daf6ac92fb35e67 Reviewed-by: Yoann Lopes --- .../mediaplayer/qandroidmediaplayercontrol.cpp | 15 ++++++++++++--- .../src/mediaplayer/qandroidmediaplayercontrol.h | 2 ++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp index 54c04e5c..ce73263d 100644 --- a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp +++ b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp @@ -218,10 +218,19 @@ void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent, return; } - const QString uri = mediaContent.canonicalUrl().toString(); + const QUrl url = mediaContent.canonicalUrl(); + QString mediaPath; + if (url.scheme() == QLatin1String("qrc")) { + const QString path = url.toString().mid(3); + mTempFile.reset(QTemporaryFile::createNativeFile(path)); + if (!mTempFile.isNull()) + mediaPath = QLatin1String("file://") + mTempFile->fileName(); + } else { + mediaPath = url.toString(); + } - if (!uri.isEmpty()) - mMediaPlayer->setDataSource(uri); + if (!mediaPath.isEmpty()) + mMediaPlayer->setDataSource(mediaPath); else setMediaStatus(QMediaPlayer::NoMedia); diff --git a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h index ef1d325e..fadac3c1 100644 --- a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h +++ b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h @@ -45,6 +45,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE @@ -114,6 +115,7 @@ private: QMediaPlayer::State mPendingState; qint64 mPendingPosition; bool mPendingSetMedia; + QScopedPointer mTempFile; void setState(QMediaPlayer::State state); void setMediaStatus(QMediaPlayer::MediaStatus status); From 51b7864bb8879ea181da33fbeb36080dbed623ea Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Mon, 9 Dec 2013 17:07:27 +0100 Subject: [PATCH 4/7] Android: automatically deploy playlist plugins. Include the playlistformats directory for automatic plugin deployment. Change-Id: Ie61a4b1c1637a493e1bdb7354b33060728ccabda Reviewed-by: Christian Stromme --- src/multimedia/multimedia.pro | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/multimedia/multimedia.pro b/src/multimedia/multimedia.pro index 63c3f19b..e5334937 100644 --- a/src/multimedia/multimedia.pro +++ b/src/multimedia/multimedia.pro @@ -68,7 +68,8 @@ ANDROID_FEATURES += \ MODULE_PLUGIN_TYPES = \ mediaservice \ audio \ - video/videonode + video/videonode \ + playlistformats win32: LIBS_PRIVATE += -luuid From 653041db1dbceb8e404949d98b6a34e6d05cbe1f Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Thu, 5 Dec 2013 15:55:06 +0100 Subject: [PATCH 5/7] WMF: fix some crashes. Task-number: QTBUG-30827 Change-Id: Iafc53f7095ad9c086b3982afb2af045c54497dc9 Reviewed-by: Christian Stromme --- src/plugins/wmf/player/mfplayersession.cpp | 8 +++++--- src/plugins/wmf/player/mfvideorenderercontrol.cpp | 3 +++ src/plugins/wmf/sourceresolver.cpp | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/plugins/wmf/player/mfplayersession.cpp b/src/plugins/wmf/player/mfplayersession.cpp index 8e0235e9..c324116e 100644 --- a/src/plugins/wmf/player/mfplayersession.cpp +++ b/src/plugins/wmf/player/mfplayersession.cpp @@ -411,6 +411,8 @@ MFPlayerSession::MFPlayerSession(MFPlayerService *playerService) , m_rateSupport(0) , m_volumeControl(0) , m_netsourceStatistics(0) + , m_duration(0) + , m_sourceResolver(0) , m_hCloseEvent(0) , m_closing(false) , m_pendingRate(1) @@ -536,7 +538,7 @@ void MFPlayerSession::load(const QMediaContent &media, QIODevice *stream) clear(); QMediaResourceList resources = media.resources(); - if (m_status == QMediaPlayer::LoadingMedia) + if (m_status == QMediaPlayer::LoadingMedia && m_sourceResolver) m_sourceResolver->cancel(); if (resources.isEmpty() && !stream) { @@ -581,7 +583,7 @@ void MFPlayerSession::handleSourceError(long hr) void MFPlayerSession::handleMediaSourceReady() { - if (QMediaPlayer::LoadingMedia != m_status) + if (QMediaPlayer::LoadingMedia != m_status || !m_sourceResolver) return; #ifdef DEBUG_MEDIAFOUNDATION qDebug() << "handleMediaSourceReady"; @@ -1786,7 +1788,7 @@ void MFPlayerSession::handleSessionEvent(IMFMediaEvent *sessionEvent) { HRESULT hrStatus = S_OK; HRESULT hr = sessionEvent->GetStatus(&hrStatus); - if (FAILED(hr)) { + if (FAILED(hr) || !m_session) { sessionEvent->Release(); return; } diff --git a/src/plugins/wmf/player/mfvideorenderercontrol.cpp b/src/plugins/wmf/player/mfvideorenderercontrol.cpp index 83768c8e..e10cebb2 100644 --- a/src/plugins/wmf/player/mfvideorenderercontrol.cpp +++ b/src/plugins/wmf/player/mfvideorenderercontrol.cpp @@ -1085,6 +1085,9 @@ namespace HRESULT onDispatchWorkItem(IMFAsyncResult* pAsyncResult) { QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + HRESULT hr = S_OK; IUnknown *pState = NULL; hr = pAsyncResult->GetState(&pState); diff --git a/src/plugins/wmf/sourceresolver.cpp b/src/plugins/wmf/sourceresolver.cpp index 3f39671f..9ac126bd 100644 --- a/src/plugins/wmf/sourceresolver.cpp +++ b/src/plugins/wmf/sourceresolver.cpp @@ -197,7 +197,7 @@ void SourceResolver::load(QMediaResourceList& resources, QIODevice* stream) #ifdef TEST_STREAMING //Testing stream function if (url.scheme() == QLatin1String("file")) { - stream = new QFile(url.path().mid(1), this); + stream = new QFile(url.path().mid(1)); if (stream->open(QIODevice::ReadOnly)) { m_stream = new MFStream(stream, true); hr = m_sourceResolver->BeginCreateObjectFromByteStream( @@ -217,7 +217,7 @@ void SourceResolver::load(QMediaResourceList& resources, QIODevice* stream) if (url.scheme() == QLatin1String("qrc")) { // If the canonical URL refers to a Qt resource, open with QFile and use // the stream playback capability to play. - stream = new QFile(QLatin1Char(':') + url.path(), this); + stream = new QFile(QLatin1Char(':') + url.path()); if (stream->open(QIODevice::ReadOnly)) { m_stream = new MFStream(stream, true); hr = m_sourceResolver->BeginCreateObjectFromByteStream( From 29ded57cef22d0a6d92f813d0a77637623d7b670 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Mon, 9 Dec 2013 14:09:40 +0100 Subject: [PATCH 6/7] WMF: remove dead code. Change-Id: Ib3b595263e64336cc5d0482edd890e7b4e534927 Reviewed-by: Christian Stromme --- src/plugins/wmf/player/mfplayersession.cpp | 333 --------------------- 1 file changed, 333 deletions(-) diff --git a/src/plugins/wmf/player/mfplayersession.cpp b/src/plugins/wmf/player/mfplayersession.cpp index c324116e..04b46ec0 100644 --- a/src/plugins/wmf/player/mfplayersession.cpp +++ b/src/plugins/wmf/player/mfplayersession.cpp @@ -68,339 +68,6 @@ #include //#define DEBUG_MEDIAFOUNDATION -//#define TEST_STREAMING - -namespace -{ - //MFStream is added for supporting QIODevice type of media source. - //It is used to delegate invocations from media foundation(through IMFByteStream) to QIODevice. - class MFStream : public QObject, public IMFByteStream - { - Q_OBJECT - public: - MFStream(QIODevice *stream, bool ownStream) - : m_cRef(1) - , m_stream(stream) - , m_ownStream(ownStream) - , m_currentReadResult(0) - { - //Move to the thread of the stream object - //to make sure invocations on stream - //are happened in the same thread of stream object - this->moveToThread(stream->thread()); - connect(stream, SIGNAL(readyRead()), this, SLOT(handleReadyRead())); - } - - ~MFStream() - { - if (m_currentReadResult) - m_currentReadResult->Release(); - if (m_ownStream) - m_stream->deleteLater(); - } - - //from IUnknown - STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject) - { - if (!ppvObject) - return E_POINTER; - if (riid == IID_IMFByteStream) { - *ppvObject = static_cast(this); - } else if (riid == IID_IUnknown) { - *ppvObject = static_cast(this); - } else { - *ppvObject = NULL; - return E_NOINTERFACE; - } - AddRef(); - return S_OK; - } - - STDMETHODIMP_(ULONG) AddRef(void) - { - return InterlockedIncrement(&m_cRef); - } - - STDMETHODIMP_(ULONG) Release(void) - { - LONG cRef = InterlockedDecrement(&m_cRef); - if (cRef == 0) { - this->deleteLater(); - } - return cRef; - } - - - //from IMFByteStream - STDMETHODIMP GetCapabilities(DWORD *pdwCapabilities) - { - if (!pdwCapabilities) - return E_INVALIDARG; - *pdwCapabilities = MFBYTESTREAM_IS_READABLE; - if (!m_stream->isSequential()) - *pdwCapabilities |= MFBYTESTREAM_IS_SEEKABLE; - return S_OK; - } - - STDMETHODIMP GetLength(QWORD *pqwLength) - { - if (!pqwLength) - return E_INVALIDARG; - QMutexLocker locker(&m_mutex); - *pqwLength = QWORD(m_stream->size()); - return S_OK; - } - - STDMETHODIMP SetLength(QWORD) - { - return E_NOTIMPL; - } - - STDMETHODIMP GetCurrentPosition(QWORD *pqwPosition) - { - if (!pqwPosition) - return E_INVALIDARG; - QMutexLocker locker(&m_mutex); - *pqwPosition = m_stream->pos(); - return S_OK; - } - - STDMETHODIMP SetCurrentPosition(QWORD qwPosition) - { - QMutexLocker locker(&m_mutex); - //SetCurrentPosition may happend during the BeginRead/EndRead pair, - //refusing to execute SetCurrentPosition during that time seems to be - //the simplest workable solution - if (m_currentReadResult) - return S_FALSE; - - bool seekOK = m_stream->seek(qint64(qwPosition)); - if (seekOK) - return S_OK; - else - return S_FALSE; - } - - STDMETHODIMP IsEndOfStream(BOOL *pfEndOfStream) - { - if (!pfEndOfStream) - return E_INVALIDARG; - QMutexLocker locker(&m_mutex); - *pfEndOfStream = m_stream->atEnd() ? TRUE : FALSE; - return S_OK; - } - - STDMETHODIMP Read(BYTE *pb, ULONG cb, ULONG *pcbRead) - { - QMutexLocker locker(&m_mutex); - qint64 read = m_stream->read((char*)(pb), qint64(cb)); - if (pcbRead) - *pcbRead = ULONG(read); - return S_OK; - } - - STDMETHODIMP BeginRead(BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback, - IUnknown *punkState) - { - if (!pCallback || !pb) - return E_INVALIDARG; - - Q_ASSERT(m_currentReadResult == NULL); - - AsyncReadState *state = new (std::nothrow) AsyncReadState(pb, cb); - if (state == NULL) - return E_OUTOFMEMORY; - - HRESULT hr = MFCreateAsyncResult(state, pCallback, punkState, &m_currentReadResult); - state->Release(); - if (FAILED(hr)) - return hr; - - QCoreApplication::postEvent(this, new QEvent(QEvent::User)); - return hr; - } - - STDMETHODIMP EndRead(IMFAsyncResult* pResult, ULONG *pcbRead) - { - if (!pcbRead) - return E_INVALIDARG; - IUnknown *pUnk; - pResult->GetObject(&pUnk); - AsyncReadState *state = static_cast(pUnk); - *pcbRead = state->bytesRead(); - pUnk->Release(); - - m_currentReadResult->Release(); - m_currentReadResult = NULL; - - return S_OK; - } - - STDMETHODIMP Write(const BYTE *, ULONG, ULONG *) - { - return E_NOTIMPL; - } - - STDMETHODIMP BeginWrite(const BYTE *, ULONG , - IMFAsyncCallback *, - IUnknown *) - { - return E_NOTIMPL; - } - - STDMETHODIMP EndWrite(IMFAsyncResult *, - ULONG *) - { - return E_NOTIMPL; - } - - STDMETHODIMP Seek( - MFBYTESTREAM_SEEK_ORIGIN SeekOrigin, - LONGLONG llSeekOffset, - DWORD, - QWORD *pqwCurrentPosition) - { - QMutexLocker locker(&m_mutex); - if (m_currentReadResult) - return S_FALSE; - - qint64 pos = qint64(llSeekOffset); - switch (SeekOrigin) { - case msoCurrent: - pos += m_stream->pos(); - break; - } - bool seekOK = m_stream->seek(pos); - if (*pqwCurrentPosition) - *pqwCurrentPosition = pos; - if (seekOK) - return S_OK; - else - return S_FALSE; - } - - STDMETHODIMP Flush() - { - return E_NOTIMPL; - } - - STDMETHODIMP Close() - { - QMutexLocker locker(&m_mutex); - if (m_ownStream) - m_stream->close(); - return S_OK; - } - - private: - //AsyncReadState is a helper class used in BeginRead for asynchronous operation - //to record some BeginRead parameters, so these parameters could be - //used later when actually executing the read operation in another thread. - class AsyncReadState : public IUnknown - { - public: - AsyncReadState(BYTE *pb, ULONG cb) - : m_cRef(1) - , m_pb(pb) - , m_cb(cb) - , m_cbRead(0) - { - } - - //from IUnknown - STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject) - { - if (!ppvObject) - return E_POINTER; - - if (riid == IID_IUnknown) { - *ppvObject = static_cast(this); - } else { - *ppvObject = NULL; - return E_NOINTERFACE; - } - AddRef(); - return S_OK; - } - - STDMETHODIMP_(ULONG) AddRef(void) - { - return InterlockedIncrement(&m_cRef); - } - - STDMETHODIMP_(ULONG) Release(void) - { - LONG cRef = InterlockedDecrement(&m_cRef); - if (cRef == 0) - delete this; - // For thread safety, return a temporary variable. - return cRef; - } - - BYTE* pb() const { return m_pb; } - ULONG cb() const { return m_cb; } - ULONG bytesRead() const { return m_cbRead; } - - void setBytesRead(ULONG cbRead) { m_cbRead = cbRead; } - - private: - long m_cRef; - BYTE *m_pb; - ULONG m_cb; - ULONG m_cbRead; - }; - - long m_cRef; - QIODevice *m_stream; - bool m_ownStream; - DWORD m_workQueueId; - QMutex m_mutex; - - void doRead() - { - bool readDone = true; - IUnknown *pUnk = NULL; - HRESULT hr = m_currentReadResult->GetObject(&pUnk); - if (SUCCEEDED(hr)) { - //do actual read - AsyncReadState *state = static_cast(pUnk); - ULONG cbRead; - Read(state->pb(), state->cb() - state->bytesRead(), &cbRead); - pUnk->Release(); - - state->setBytesRead(cbRead + state->bytesRead()); - if (state->cb() > state->bytesRead() && !m_stream->atEnd()) { - readDone = false; - } - } - - if (readDone) { - //now inform the original caller - m_currentReadResult->SetStatus(hr); - MFInvokeCallback(m_currentReadResult); - } - } - - - private Q_SLOTS: - void handleReadyRead() - { - doRead(); - } - - protected: - void customEvent(QEvent *event) - { - if (event->type() != QEvent::User) { - QObject::customEvent(event); - return; - } - doRead(); - } - IMFAsyncResult *m_currentReadResult; - }; -} - MFPlayerSession::MFPlayerSession(MFPlayerService *playerService) : m_playerService(playerService) From ecce937a054e4a96597cb13c40d216bddbd45891 Mon Sep 17 00:00:00 2001 From: Denis Kormalev Date: Thu, 5 Dec 2013 17:09:07 +0400 Subject: [PATCH 7/7] Android: fix camera preview At some(?) devices we can't rely on one shot preview callback because it receives data only after we start new previewing session. And this cause situation when imageCaptured signal is never emitted. This fix applies preview callback with already allocated buffers and collects all preview images in these buffers. When we capture image - we can simply fetch last preview image (if there was any) from c++ part. Task-number: QTBUG-34993 Change-Id: I608750c344ca3c089f4673df4907e0f47e57e2ba Reviewed-by: Yoann Lopes --- .../qt5/android/multimedia/QtCamera.java | 72 +++++++++++++++++-- .../mediacapture/qandroidcamerasession.cpp | 20 ++---- .../src/mediacapture/qandroidcamerasession.h | 1 - src/plugins/android/src/wrappers/jcamera.cpp | 37 +++++----- src/plugins/android/src/wrappers/jcamera.h | 6 +- 5 files changed, 92 insertions(+), 44 deletions(-) diff --git a/src/plugins/android/jar/src/org/qtproject/qt5/android/multimedia/QtCamera.java b/src/plugins/android/jar/src/org/qtproject/qt5/android/multimedia/QtCamera.java index 3d891196..4aa07b71 100644 --- a/src/plugins/android/jar/src/org/qtproject/qt5/android/multimedia/QtCamera.java +++ b/src/plugins/android/jar/src/org/qtproject/qt5/android/multimedia/QtCamera.java @@ -42,8 +42,11 @@ package org.qtproject.qt5.android.multimedia; import android.hardware.Camera; +import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.util.Log; +import java.lang.Math; +import java.util.concurrent.locks.ReentrantLock; public class QtCamera implements Camera.ShutterCallback, Camera.PictureCallback, @@ -52,6 +55,11 @@ public class QtCamera implements Camera.ShutterCallback, { private int m_cameraId = -1; private Camera m_camera = null; + private byte[] m_cameraPreviewFirstBuffer = null; + private byte[] m_cameraPreviewSecondBuffer = null; + private int m_actualPreviewBuffer = 0; + private final ReentrantLock m_buffersLock = new ReentrantLock(); + private boolean m_isReleased = false; private static final String TAG = "Qt Camera"; @@ -97,6 +105,7 @@ public class QtCamera implements Camera.ShutterCallback, public void release() { + m_isReleased = true; m_camera.release(); } @@ -134,6 +143,22 @@ public class QtCamera implements Camera.ShutterCallback, public void startPreview() { + Camera.Size previewSize = m_camera.getParameters().getPreviewSize(); + double bytesPerPixel = ImageFormat.getBitsPerPixel(m_camera.getParameters().getPreviewFormat()) / 8.0; + int bufferSizeNeeded = (int)Math.ceil(bytesPerPixel*previewSize.width*previewSize.height); + + //We need to clear preview buffers queue here, but there is no method to do it + //Though just resetting preview callback do the trick + m_camera.setPreviewCallback(null); + m_buffersLock.lock(); + if (m_cameraPreviewFirstBuffer == null || m_cameraPreviewFirstBuffer.length < bufferSizeNeeded) + m_cameraPreviewFirstBuffer = new byte[bufferSizeNeeded]; + if (m_cameraPreviewSecondBuffer == null || m_cameraPreviewSecondBuffer.length < bufferSizeNeeded) + m_cameraPreviewSecondBuffer = new byte[bufferSizeNeeded]; + addCallbackBuffer(); + m_buffersLock.unlock(); + m_camera.setPreviewCallbackWithBuffer(this); + m_camera.startPreview(); } @@ -152,11 +177,6 @@ public class QtCamera implements Camera.ShutterCallback, m_camera.cancelAutoFocus(); } - public void requestPreviewFrame() - { - m_camera.setOneShotPreviewCallback(this); - } - public void takePicture() { try { @@ -166,6 +186,37 @@ public class QtCamera implements Camera.ShutterCallback, } } + public byte[] lockAndFetchPreviewBuffer() + { + //This method should always be followed by unlockPreviewBuffer() + //This method is not just a getter. It also marks last preview as already seen one. + //We should reset actualBuffer flag here to make sure we will not use old preview with future captures + byte[] result = null; + m_buffersLock.lock(); + if (m_actualPreviewBuffer == 1) + result = m_cameraPreviewFirstBuffer; + else if (m_actualPreviewBuffer == 2) + result = m_cameraPreviewSecondBuffer; + m_actualPreviewBuffer = 0; + return result; + } + + public void unlockPreviewBuffer() + { + if (m_buffersLock.isHeldByCurrentThread()) + m_buffersLock.unlock(); + } + + private void addCallbackBuffer() + { + if (m_isReleased) + return; + + m_camera.addCallbackBuffer((m_actualPreviewBuffer == 1) + ? m_cameraPreviewSecondBuffer + : m_cameraPreviewFirstBuffer); + } + @Override public void onShutter() { @@ -181,7 +232,15 @@ public class QtCamera implements Camera.ShutterCallback, @Override public void onPreviewFrame(byte[] data, Camera camera) { - notifyPreviewFrame(m_cameraId, data); + m_buffersLock.lock(); + if (data == m_cameraPreviewFirstBuffer) + m_actualPreviewBuffer = 1; + else if (data == m_cameraPreviewSecondBuffer) + m_actualPreviewBuffer = 2; + else + m_actualPreviewBuffer = 0; + addCallbackBuffer(); + m_buffersLock.unlock(); } @Override @@ -193,5 +252,4 @@ public class QtCamera implements Camera.ShutterCallback, private static native void notifyAutoFocusComplete(int id, boolean success); private static native void notifyPictureExposed(int id); private static native void notifyPictureCaptured(int id, byte[] data); - private static native void notifyPreviewFrame(int id, byte[] data); } diff --git a/src/plugins/android/src/mediacapture/qandroidcamerasession.cpp b/src/plugins/android/src/mediacapture/qandroidcamerasession.cpp index 5ff19d1c..55065cb4 100644 --- a/src/plugins/android/src/mediacapture/qandroidcamerasession.cpp +++ b/src/plugins/android/src/mediacapture/qandroidcamerasession.cpp @@ -183,7 +183,6 @@ bool QAndroidCameraSession::open() if (m_camera) { connect(m_camera, SIGNAL(pictureExposed()), this, SLOT(onCameraPictureExposed())); connect(m_camera, SIGNAL(pictureCaptured(QByteArray)), this, SLOT(onCameraPictureCaptured(QByteArray))); - connect(m_camera, SIGNAL(previewFrameAvailable(QByteArray)), this, SLOT(onCameraPreviewFrameAvailable(QByteArray))); m_nativeOrientation = m_camera->getNativeOrientation(); @@ -484,7 +483,6 @@ int QAndroidCameraSession::capture(const QString &fileName) // adjust picture rotation depending on the device orientation m_camera->setRotation(currentCameraRotation()); - m_camera->requestPreviewFrame(); m_camera->takePicture(); } else { //: Drive mode is the camera's shutter mode, for example single shot, continuos exposure, etc. @@ -509,6 +507,13 @@ void QAndroidCameraSession::onCameraPictureExposed() return; emit imageExposed(m_currentImageCaptureId); + QByteArray lastFrame = m_camera->fetchLastPreviewFrame(); + if (lastFrame.size()) { + QtConcurrent::run(this, &QAndroidCameraSession::processPreviewImage, + m_currentImageCaptureId, + lastFrame, + m_camera->getRotation()); + } } void QAndroidCameraSession::onCameraPictureCaptured(const QByteArray &data) @@ -571,17 +576,6 @@ void QAndroidCameraSession::processCapturedImage(int id, } } -void QAndroidCameraSession::onCameraPreviewFrameAvailable(const QByteArray &data) -{ - if (m_captureCanceled || m_readyForCapture) - return; - - QtConcurrent::run(this, &QAndroidCameraSession::processPreviewImage, - m_currentImageCaptureId, - data, - m_camera->getRotation()); -} - void QAndroidCameraSession::processPreviewImage(int id, const QByteArray &data, int rotation) { QSize frameSize = m_camera->previewSize(); diff --git a/src/plugins/android/src/mediacapture/qandroidcamerasession.h b/src/plugins/android/src/mediacapture/qandroidcamerasession.h index 17ea4171..61d8c1a1 100644 --- a/src/plugins/android/src/mediacapture/qandroidcamerasession.h +++ b/src/plugins/android/src/mediacapture/qandroidcamerasession.h @@ -114,7 +114,6 @@ private Q_SLOTS: void onCameraPictureExposed(); void onCameraPictureCaptured(const QByteArray &data); - void onCameraPreviewFrameAvailable(const QByteArray &data); private: bool open(); diff --git a/src/plugins/android/src/wrappers/jcamera.cpp b/src/plugins/android/src/wrappers/jcamera.cpp index e69cb554..5712ae35 100644 --- a/src/plugins/android/src/wrappers/jcamera.cpp +++ b/src/plugins/android/src/wrappers/jcamera.cpp @@ -102,18 +102,6 @@ static void notifyPictureCaptured(JNIEnv *env, jobject, int id, jbyteArray data) } } -static void notifyPreviewFrame(JNIEnv *env, jobject, int id, jbyteArray data) -{ - JCamera *obj = g_objectMap.value(id, 0); - if (obj) { - QByteArray bytes; - int arrayLength = env->GetArrayLength(data); - bytes.resize(arrayLength); - env->GetByteArrayRegion(data, 0, arrayLength, (jbyte*)bytes.data()); - Q_EMIT obj->previewFrameAvailable(bytes); - } -} - JCamera::JCamera(int cameraId, jobject cam) : QObject() , QJNIObjectPrivate(cam) @@ -667,16 +655,28 @@ void JCamera::setJpegQuality(int quality) applyParameters(); } -void JCamera::requestPreviewFrame() -{ - callMethod("requestPreviewFrame"); -} - void JCamera::takePicture() { callMethod("takePicture"); } +QByteArray JCamera::fetchLastPreviewFrame() +{ + QJNIEnvironmentPrivate env; + QJNIObjectPrivate dataObj = callObjectMethod("lockAndFetchPreviewBuffer", "()[B"); + if (!dataObj.object()) { + callMethod("unlockPreviewBuffer"); + return QByteArray(); + } + jbyteArray data = static_cast(dataObj.object()); + QByteArray bytes; + int arrayLength = env->GetArrayLength(data); + bytes.resize(arrayLength); + env->GetByteArrayRegion(data, 0, arrayLength, (jbyte*)bytes.data()); + callMethod("unlockPreviewBuffer"); + return bytes; +} + void JCamera::startPreview() { callMethod("startPreview"); @@ -720,8 +720,7 @@ QStringList JCamera::callStringListMethod(const char *methodName) static JNINativeMethod methods[] = { {"notifyAutoFocusComplete", "(IZ)V", (void *)notifyAutoFocusComplete}, {"notifyPictureExposed", "(I)V", (void *)notifyPictureExposed}, - {"notifyPictureCaptured", "(I[B)V", (void *)notifyPictureCaptured}, - {"notifyPreviewFrame", "(I[B)V", (void *)notifyPreviewFrame} + {"notifyPictureCaptured", "(I[B)V", (void *)notifyPictureCaptured} }; bool JCamera::initJNI(JNIEnv *env) diff --git a/src/plugins/android/src/wrappers/jcamera.h b/src/plugins/android/src/wrappers/jcamera.h index 7c47ec7a..6bba9840 100644 --- a/src/plugins/android/src/wrappers/jcamera.h +++ b/src/plugins/android/src/wrappers/jcamera.h @@ -147,10 +147,10 @@ public: void startPreview(); void stopPreview(); - void requestPreviewFrame(); - void takePicture(); + QByteArray fetchLastPreviewFrame(); + static bool initJNI(JNIEnv *env); Q_SIGNALS: @@ -161,8 +161,6 @@ Q_SIGNALS: void whiteBalanceChanged(); - void previewFrameAvailable(const QByteArray &data); - void pictureExposed(); void pictureCaptured(const QByteArray &data);