/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2016 Ruslan Baratov ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "androidcamera.h" #include "androidsurfacetexture.h" #include "androidsurfaceview.h" #include "qandroidmultimediautils.h" #include "qandroidglobal.h" #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE static const char QtCameraListenerClassName[] = "org/qtproject/qt5/android/multimedia/QtCameraListener"; typedef QHash CameraMap; Q_GLOBAL_STATIC(CameraMap, cameras) Q_GLOBAL_STATIC(QReadWriteLock, rwLock) static inline bool exceptionCheckAndClear(JNIEnv *env) { if (Q_UNLIKELY(env->ExceptionCheck())) { #ifdef QT_DEBUG env->ExceptionDescribe(); #endif // QT_DEBUG env->ExceptionClear(); return true; } return false; } static QRect areaToRect(jobject areaObj) { QJNIObjectPrivate area(areaObj); QJNIObjectPrivate rect = area.getObjectField("rect", "Landroid/graphics/Rect;"); return QRect(rect.getField("left"), rect.getField("top"), rect.callMethod("width"), rect.callMethod("height")); } static QJNIObjectPrivate rectToArea(const QRect &rect) { QJNIObjectPrivate jrect("android/graphics/Rect", "(IIII)V", rect.left(), rect.top(), rect.right(), rect.bottom()); QJNIObjectPrivate area("android/hardware/Camera$Area", "(Landroid/graphics/Rect;I)V", jrect.object(), 500); return area; } // native method for QtCameraLisener.java static void notifyAutoFocusComplete(JNIEnv* , jobject, int id, jboolean success) { QReadLocker locker(rwLock); const auto it = cameras->constFind(id); if (Q_UNLIKELY(it == cameras->cend())) return; Q_EMIT (*it)->autoFocusComplete(success); } static void notifyPictureExposed(JNIEnv* , jobject, int id) { QReadLocker locker(rwLock); const auto it = cameras->constFind(id); if (Q_UNLIKELY(it == cameras->cend())) return; Q_EMIT (*it)->pictureExposed(); } static void notifyPictureCaptured(JNIEnv *env, jobject, int id, jbyteArray data) { QReadLocker locker(rwLock); const auto it = cameras->constFind(id); if (Q_UNLIKELY(it == cameras->cend())) return; const int arrayLength = env->GetArrayLength(data); QByteArray bytes(arrayLength, Qt::Uninitialized); env->GetByteArrayRegion(data, 0, arrayLength, (jbyte*)bytes.data()); Q_EMIT (*it)->pictureCaptured(bytes); } static void notifyNewPreviewFrame(JNIEnv *env, jobject, int id, jbyteArray data, int width, int height, int format, int bpl) { QReadLocker locker(rwLock); const auto it = cameras->constFind(id); if (Q_UNLIKELY(it == cameras->cend())) return; const int arrayLength = env->GetArrayLength(data); if (arrayLength == 0) return; QByteArray bytes(arrayLength, Qt::Uninitialized); env->GetByteArrayRegion(data, 0, arrayLength, (jbyte*)bytes.data()); QVideoFrame frame(new QMemoryVideoBuffer(bytes, bpl), QSize(width, height), qt_pixelFormatFromAndroidImageFormat(AndroidCamera::ImageFormat(format))); Q_EMIT (*it)->newPreviewFrame(frame); } static void notifyFrameAvailable(JNIEnv *, jobject, int id) { QReadLocker locker(rwLock); const auto it = cameras->constFind(id); if (Q_UNLIKELY(it == cameras->cend())) return; (*it)->fetchLastPreviewFrame(); } class AndroidCameraPrivate : public QObject { Q_OBJECT public: AndroidCameraPrivate(); ~AndroidCameraPrivate(); Q_INVOKABLE bool init(int cameraId); Q_INVOKABLE void release(); Q_INVOKABLE bool lock(); Q_INVOKABLE bool unlock(); Q_INVOKABLE bool reconnect(); Q_INVOKABLE AndroidCamera::CameraFacing getFacing(); Q_INVOKABLE int getNativeOrientation(); Q_INVOKABLE QSize getPreferredPreviewSizeForVideo(); Q_INVOKABLE QList getSupportedPreviewSizes(); Q_INVOKABLE QList getSupportedPreviewFpsRange(); Q_INVOKABLE AndroidCamera::FpsRange getPreviewFpsRange(); Q_INVOKABLE void setPreviewFpsRange(int min, int max); Q_INVOKABLE AndroidCamera::ImageFormat getPreviewFormat(); Q_INVOKABLE void setPreviewFormat(AndroidCamera::ImageFormat fmt); Q_INVOKABLE QList getSupportedPreviewFormats(); Q_INVOKABLE QSize previewSize() const { return m_previewSize; } Q_INVOKABLE QSize getPreviewSize(); Q_INVOKABLE void updatePreviewSize(); Q_INVOKABLE bool setPreviewTexture(void *surfaceTexture); Q_INVOKABLE bool setPreviewDisplay(void *surfaceHolder); Q_INVOKABLE void setDisplayOrientation(int degrees); Q_INVOKABLE bool isZoomSupported(); Q_INVOKABLE int getMaxZoom(); Q_INVOKABLE QList getZoomRatios(); Q_INVOKABLE int getZoom(); Q_INVOKABLE void setZoom(int value); Q_INVOKABLE QString getFlashMode(); Q_INVOKABLE void setFlashMode(const QString &value); Q_INVOKABLE QString getFocusMode(); Q_INVOKABLE void setFocusMode(const QString &value); Q_INVOKABLE int getMaxNumFocusAreas(); Q_INVOKABLE QList getFocusAreas(); Q_INVOKABLE void setFocusAreas(const QList &areas); Q_INVOKABLE void autoFocus(); Q_INVOKABLE void cancelAutoFocus(); Q_INVOKABLE bool isAutoExposureLockSupported(); Q_INVOKABLE bool getAutoExposureLock(); Q_INVOKABLE void setAutoExposureLock(bool toggle); Q_INVOKABLE bool isAutoWhiteBalanceLockSupported(); Q_INVOKABLE bool getAutoWhiteBalanceLock(); Q_INVOKABLE void setAutoWhiteBalanceLock(bool toggle); Q_INVOKABLE int getExposureCompensation(); Q_INVOKABLE void setExposureCompensation(int value); Q_INVOKABLE float getExposureCompensationStep(); Q_INVOKABLE int getMinExposureCompensation(); Q_INVOKABLE int getMaxExposureCompensation(); Q_INVOKABLE QString getSceneMode(); Q_INVOKABLE void setSceneMode(const QString &value); Q_INVOKABLE QString getWhiteBalance(); Q_INVOKABLE void setWhiteBalance(const QString &value); Q_INVOKABLE void updateRotation(); Q_INVOKABLE QList getSupportedPictureSizes(); Q_INVOKABLE void setPictureSize(const QSize &size); Q_INVOKABLE void setJpegQuality(int quality); Q_INVOKABLE void startPreview(); Q_INVOKABLE void stopPreview(); Q_INVOKABLE void takePicture(); Q_INVOKABLE void setupPreviewFrameCallback(); Q_INVOKABLE void notifyNewFrames(bool notify); Q_INVOKABLE void fetchLastPreviewFrame(); Q_INVOKABLE void applyParameters(); Q_INVOKABLE QStringList callParametersStringListMethod(const QByteArray &methodName); int m_cameraId; QRecursiveMutex m_parametersMutex; QSize m_previewSize; int m_rotation; QJNIObjectPrivate m_info; QJNIObjectPrivate m_parameters; QJNIObjectPrivate m_camera; QJNIObjectPrivate m_cameraListener; Q_SIGNALS: void previewSizeChanged(); void previewStarted(); void previewFailedToStart(); void previewStopped(); void autoFocusStarted(); void whiteBalanceChanged(); void takePictureFailed(); void lastPreviewFrameFetched(const QVideoFrame &frame); }; AndroidCamera::AndroidCamera(AndroidCameraPrivate *d, QThread *worker) : QObject(), d_ptr(d), m_worker(worker) { qRegisterMetaType >(); qRegisterMetaType >(); qRegisterMetaType >(); qRegisterMetaType(); connect(d, &AndroidCameraPrivate::previewSizeChanged, this, &AndroidCamera::previewSizeChanged); connect(d, &AndroidCameraPrivate::previewStarted, this, &AndroidCamera::previewStarted); connect(d, &AndroidCameraPrivate::previewFailedToStart, this, &AndroidCamera::previewFailedToStart); connect(d, &AndroidCameraPrivate::previewStopped, this, &AndroidCamera::previewStopped); connect(d, &AndroidCameraPrivate::autoFocusStarted, this, &AndroidCamera::autoFocusStarted); connect(d, &AndroidCameraPrivate::whiteBalanceChanged, this, &AndroidCamera::whiteBalanceChanged); connect(d, &AndroidCameraPrivate::takePictureFailed, this, &AndroidCamera::takePictureFailed); connect(d, &AndroidCameraPrivate::lastPreviewFrameFetched, this, &AndroidCamera::lastPreviewFrameFetched); } AndroidCamera::~AndroidCamera() { Q_D(AndroidCamera); if (d->m_camera.isValid()) { release(); QWriteLocker locker(rwLock); cameras->remove(cameraId()); } m_worker->exit(); m_worker->wait(5000); } AndroidCamera *AndroidCamera::open(int cameraId) { if (!qt_androidRequestCameraPermission()) return nullptr; AndroidCameraPrivate *d = new AndroidCameraPrivate(); QThread *worker = new QThread; worker->start(); d->moveToThread(worker); connect(worker, &QThread::finished, d, &AndroidCameraPrivate::deleteLater); bool ok = true; QMetaObject::invokeMethod(d, "init", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ok), Q_ARG(int, cameraId)); if (!ok) { worker->quit(); worker->wait(5000); delete worker; return 0; } AndroidCamera *q = new AndroidCamera(d, worker); QWriteLocker locker(rwLock); cameras->insert(cameraId, q); return q; } int AndroidCamera::cameraId() const { Q_D(const AndroidCamera); return d->m_cameraId; } bool AndroidCamera::lock() { Q_D(AndroidCamera); bool ok = true; QMetaObject::invokeMethod(d, "lock", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ok)); return ok; } bool AndroidCamera::unlock() { Q_D(AndroidCamera); bool ok = true; QMetaObject::invokeMethod(d, "unlock", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ok)); return ok; } bool AndroidCamera::reconnect() { Q_D(AndroidCamera); bool ok = true; QMetaObject::invokeMethod(d, "reconnect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ok)); return ok; } void AndroidCamera::release() { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "release", Qt::BlockingQueuedConnection); } AndroidCamera::CameraFacing AndroidCamera::getFacing() { Q_D(AndroidCamera); return d->getFacing(); } int AndroidCamera::getNativeOrientation() { Q_D(AndroidCamera); return d->getNativeOrientation(); } QSize AndroidCamera::getPreferredPreviewSizeForVideo() { Q_D(AndroidCamera); return d->getPreferredPreviewSizeForVideo(); } QList AndroidCamera::getSupportedPreviewSizes() { Q_D(AndroidCamera); return d->getSupportedPreviewSizes(); } QList AndroidCamera::getSupportedPreviewFpsRange() { Q_D(AndroidCamera); return d->getSupportedPreviewFpsRange(); } AndroidCamera::FpsRange AndroidCamera::getPreviewFpsRange() { Q_D(AndroidCamera); return d->getPreviewFpsRange(); } void AndroidCamera::setPreviewFpsRange(FpsRange range) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setPreviewFpsRange", Q_ARG(int, range.min), Q_ARG(int, range.max)); } AndroidCamera::ImageFormat AndroidCamera::getPreviewFormat() { Q_D(AndroidCamera); return d->getPreviewFormat(); } void AndroidCamera::setPreviewFormat(ImageFormat fmt) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setPreviewFormat", Q_ARG(AndroidCamera::ImageFormat, fmt)); } QList AndroidCamera::getSupportedPreviewFormats() { Q_D(AndroidCamera); return d->getSupportedPreviewFormats(); } QSize AndroidCamera::previewSize() const { Q_D(const AndroidCamera); return d->m_previewSize; } QSize AndroidCamera::actualPreviewSize() { Q_D(AndroidCamera); return d->getPreviewSize(); } void AndroidCamera::setPreviewSize(const QSize &size) { Q_D(AndroidCamera); d->m_parametersMutex.lock(); bool areParametersValid = d->m_parameters.isValid(); d->m_parametersMutex.unlock(); if (!areParametersValid) return; d->m_previewSize = size; QMetaObject::invokeMethod(d, "updatePreviewSize"); } bool AndroidCamera::setPreviewTexture(AndroidSurfaceTexture *surfaceTexture) { Q_D(AndroidCamera); bool ok = true; QMetaObject::invokeMethod(d, "setPreviewTexture", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ok), Q_ARG(void *, surfaceTexture ? surfaceTexture->surfaceTexture() : 0)); return ok; } bool AndroidCamera::setPreviewDisplay(AndroidSurfaceHolder *surfaceHolder) { Q_D(AndroidCamera); bool ok = true; QMetaObject::invokeMethod(d, "setPreviewDisplay", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ok), Q_ARG(void *, surfaceHolder ? surfaceHolder->surfaceHolder() : 0)); return ok; } void AndroidCamera::setDisplayOrientation(int degrees) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setDisplayOrientation", Qt::QueuedConnection, Q_ARG(int, degrees)); } bool AndroidCamera::isZoomSupported() { Q_D(AndroidCamera); return d->isZoomSupported(); } int AndroidCamera::getMaxZoom() { Q_D(AndroidCamera); return d->getMaxZoom(); } QList AndroidCamera::getZoomRatios() { Q_D(AndroidCamera); return d->getZoomRatios(); } int AndroidCamera::getZoom() { Q_D(AndroidCamera); return d->getZoom(); } void AndroidCamera::setZoom(int value) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setZoom", Q_ARG(int, value)); } QStringList AndroidCamera::getSupportedFlashModes() { Q_D(AndroidCamera); return d->callParametersStringListMethod("getSupportedFlashModes"); } QString AndroidCamera::getFlashMode() { Q_D(AndroidCamera); return d->getFlashMode(); } void AndroidCamera::setFlashMode(const QString &value) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setFlashMode", Q_ARG(QString, value)); } QStringList AndroidCamera::getSupportedFocusModes() { Q_D(AndroidCamera); return d->callParametersStringListMethod("getSupportedFocusModes"); } QString AndroidCamera::getFocusMode() { Q_D(AndroidCamera); return d->getFocusMode(); } void AndroidCamera::setFocusMode(const QString &value) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setFocusMode", Q_ARG(QString, value)); } int AndroidCamera::getMaxNumFocusAreas() { Q_D(AndroidCamera); return d->getMaxNumFocusAreas(); } QList AndroidCamera::getFocusAreas() { Q_D(AndroidCamera); return d->getFocusAreas(); } void AndroidCamera::setFocusAreas(const QList &areas) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setFocusAreas", Q_ARG(QList, areas)); } void AndroidCamera::autoFocus() { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "autoFocus"); } void AndroidCamera::cancelAutoFocus() { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "cancelAutoFocus", Qt::QueuedConnection); } bool AndroidCamera::isAutoExposureLockSupported() { Q_D(AndroidCamera); return d->isAutoExposureLockSupported(); } bool AndroidCamera::getAutoExposureLock() { Q_D(AndroidCamera); return d->getAutoExposureLock(); } void AndroidCamera::setAutoExposureLock(bool toggle) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setAutoExposureLock", Q_ARG(bool, toggle)); } bool AndroidCamera::isAutoWhiteBalanceLockSupported() { Q_D(AndroidCamera); return d->isAutoWhiteBalanceLockSupported(); } bool AndroidCamera::getAutoWhiteBalanceLock() { Q_D(AndroidCamera); return d->getAutoWhiteBalanceLock(); } void AndroidCamera::setAutoWhiteBalanceLock(bool toggle) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setAutoWhiteBalanceLock", Q_ARG(bool, toggle)); } int AndroidCamera::getExposureCompensation() { Q_D(AndroidCamera); return d->getExposureCompensation(); } void AndroidCamera::setExposureCompensation(int value) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setExposureCompensation", Q_ARG(int, value)); } float AndroidCamera::getExposureCompensationStep() { Q_D(AndroidCamera); return d->getExposureCompensationStep(); } int AndroidCamera::getMinExposureCompensation() { Q_D(AndroidCamera); return d->getMinExposureCompensation(); } int AndroidCamera::getMaxExposureCompensation() { Q_D(AndroidCamera); return d->getMaxExposureCompensation(); } QStringList AndroidCamera::getSupportedSceneModes() { Q_D(AndroidCamera); return d->callParametersStringListMethod("getSupportedSceneModes"); } QString AndroidCamera::getSceneMode() { Q_D(AndroidCamera); return d->getSceneMode(); } void AndroidCamera::setSceneMode(const QString &value) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setSceneMode", Q_ARG(QString, value)); } QStringList AndroidCamera::getSupportedWhiteBalance() { Q_D(AndroidCamera); return d->callParametersStringListMethod("getSupportedWhiteBalance"); } QString AndroidCamera::getWhiteBalance() { Q_D(AndroidCamera); return d->getWhiteBalance(); } void AndroidCamera::setWhiteBalance(const QString &value) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setWhiteBalance", Q_ARG(QString, value)); } void AndroidCamera::setRotation(int rotation) { Q_D(AndroidCamera); //We need to do it here and not in worker class because we cache rotation d->m_parametersMutex.lock(); bool areParametersValid = d->m_parameters.isValid(); d->m_parametersMutex.unlock(); if (!areParametersValid) return; d->m_rotation = rotation; QMetaObject::invokeMethod(d, "updateRotation"); } int AndroidCamera::getRotation() const { Q_D(const AndroidCamera); return d->m_rotation; } QList AndroidCamera::getSupportedPictureSizes() { Q_D(AndroidCamera); return d->getSupportedPictureSizes(); } void AndroidCamera::setPictureSize(const QSize &size) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setPictureSize", Q_ARG(QSize, size)); } void AndroidCamera::setJpegQuality(int quality) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setJpegQuality", Q_ARG(int, quality)); } void AndroidCamera::takePicture() { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "takePicture", Qt::BlockingQueuedConnection); } void AndroidCamera::setupPreviewFrameCallback() { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "setupPreviewFrameCallback"); } void AndroidCamera::notifyNewFrames(bool notify) { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "notifyNewFrames", Q_ARG(bool, notify)); } void AndroidCamera::fetchLastPreviewFrame() { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "fetchLastPreviewFrame"); } QJNIObjectPrivate AndroidCamera::getCameraObject() { Q_D(AndroidCamera); return d->m_camera; } int AndroidCamera::getNumberOfCameras() { if (!qt_androidRequestCameraPermission()) return 0; return QJNIObjectPrivate::callStaticMethod("android/hardware/Camera", "getNumberOfCameras"); } void AndroidCamera::getCameraInfo(int id, AndroidCameraInfo *info) { Q_ASSERT(info); QJNIObjectPrivate cameraInfo("android/hardware/Camera$CameraInfo"); QJNIObjectPrivate::callStaticMethod("android/hardware/Camera", "getCameraInfo", "(ILandroid/hardware/Camera$CameraInfo;)V", id, cameraInfo.object()); AndroidCamera::CameraFacing facing = AndroidCamera::CameraFacing(cameraInfo.getField("facing")); // The orientation provided by Android is counter-clockwise, we need it clockwise info->orientation = (360 - cameraInfo.getField("orientation")) % 360; switch (facing) { case AndroidCamera::CameraFacingBack: info->name = QByteArray("back"); info->description = QStringLiteral("Rear-facing camera"); info->position = QCamera::BackFace; break; case AndroidCamera::CameraFacingFront: info->name = QByteArray("front"); info->description = QStringLiteral("Front-facing camera"); info->position = QCamera::FrontFace; break; default: break; } // Add a number to allow correct access to cameras on systems with two // (and more) front/back cameras if (id > 1) { info->name.append(QByteArray::number(id)); info->description.append(QString(" %1").arg(id)); } } void AndroidCamera::startPreview() { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "startPreview"); } void AndroidCamera::stopPreview() { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "stopPreview"); } void AndroidCamera::stopPreviewSynchronous() { Q_D(AndroidCamera); QMetaObject::invokeMethod(d, "stopPreview", Qt::BlockingQueuedConnection); } AndroidCameraPrivate::AndroidCameraPrivate() : QObject() { } AndroidCameraPrivate::~AndroidCameraPrivate() { } static qint32 s_activeCameras = 0; bool AndroidCameraPrivate::init(int cameraId) { m_cameraId = cameraId; QJNIEnvironmentPrivate env; const bool opened = s_activeCameras & (1 << cameraId); if (opened) return false; m_camera = QJNIObjectPrivate::callStaticObjectMethod("android/hardware/Camera", "open", "(I)Landroid/hardware/Camera;", cameraId); if (exceptionCheckAndClear(env) || !m_camera.isValid()) return false; m_cameraListener = QJNIObjectPrivate(QtCameraListenerClassName, "(I)V", m_cameraId); m_info = QJNIObjectPrivate("android/hardware/Camera$CameraInfo"); m_camera.callStaticMethod("android/hardware/Camera", "getCameraInfo", "(ILandroid/hardware/Camera$CameraInfo;)V", cameraId, m_info.object()); QJNIObjectPrivate params = m_camera.callObjectMethod("getParameters", "()Landroid/hardware/Camera$Parameters;"); m_parameters = QJNIObjectPrivate(params); s_activeCameras |= 1 << cameraId; return true; } void AndroidCameraPrivate::release() { m_previewSize = QSize(); m_parametersMutex.lock(); m_parameters = QJNIObjectPrivate(); m_parametersMutex.unlock(); if (m_camera.isValid()) { m_camera.callMethod("release"); s_activeCameras &= ~(1 << m_cameraId); } } bool AndroidCameraPrivate::lock() { QJNIEnvironmentPrivate env; m_camera.callMethod("lock"); return !exceptionCheckAndClear(env); } bool AndroidCameraPrivate::unlock() { QJNIEnvironmentPrivate env; m_camera.callMethod("unlock"); return !exceptionCheckAndClear(env); } bool AndroidCameraPrivate::reconnect() { QJNIEnvironmentPrivate env; m_camera.callMethod("reconnect"); return !exceptionCheckAndClear(env); } AndroidCamera::CameraFacing AndroidCameraPrivate::getFacing() { return AndroidCamera::CameraFacing(m_info.getField("facing")); } int AndroidCameraPrivate::getNativeOrientation() { return m_info.getField("orientation"); } QSize AndroidCameraPrivate::getPreferredPreviewSizeForVideo() { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return QSize(); QJNIObjectPrivate size = m_parameters.callObjectMethod("getPreferredPreviewSizeForVideo", "()Landroid/hardware/Camera$Size;"); if (!size.isValid()) return QSize(); return QSize(size.getField("width"), size.getField("height")); } QList AndroidCameraPrivate::getSupportedPreviewSizes() { QList list; const std::lock_guard locker(m_parametersMutex); if (m_parameters.isValid()) { QJNIObjectPrivate sizeList = m_parameters.callObjectMethod("getSupportedPreviewSizes", "()Ljava/util/List;"); int count = sizeList.callMethod("size"); for (int i = 0; i < count; ++i) { QJNIObjectPrivate size = sizeList.callObjectMethod("get", "(I)Ljava/lang/Object;", i); list.append(QSize(size.getField("width"), size.getField("height"))); } std::sort(list.begin(), list.end(), qt_sizeLessThan); } return list; } QList AndroidCameraPrivate::getSupportedPreviewFpsRange() { const std::lock_guard locker(m_parametersMutex); QJNIEnvironmentPrivate env; QList rangeList; if (m_parameters.isValid()) { QJNIObjectPrivate rangeListNative = m_parameters.callObjectMethod("getSupportedPreviewFpsRange", "()Ljava/util/List;"); int count = rangeListNative.callMethod("size"); rangeList.reserve(count); for (int i = 0; i < count; ++i) { QJNIObjectPrivate range = rangeListNative.callObjectMethod("get", "(I)Ljava/lang/Object;", i); jintArray jRange = static_cast(range.object()); jint* rangeArray = env->GetIntArrayElements(jRange, 0); AndroidCamera::FpsRange fpsRange; fpsRange.min = rangeArray[0]; fpsRange.max = rangeArray[1]; env->ReleaseIntArrayElements(jRange, rangeArray, 0); rangeList << fpsRange; } } return rangeList; } AndroidCamera::FpsRange AndroidCameraPrivate::getPreviewFpsRange() { const std::lock_guard locker(m_parametersMutex); QJNIEnvironmentPrivate env; AndroidCamera::FpsRange range; if (!m_parameters.isValid()) return range; jintArray jRangeArray = env->NewIntArray(2); m_parameters.callMethod("getPreviewFpsRange", "([I)V", jRangeArray); jint* jRangeElements = env->GetIntArrayElements(jRangeArray, 0); range.min = jRangeElements[0]; range.max = jRangeElements[1]; env->ReleaseIntArrayElements(jRangeArray, jRangeElements, 0); env->DeleteLocalRef(jRangeArray); return range; } void AndroidCameraPrivate::setPreviewFpsRange(int min, int max) { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return; QJNIEnvironmentPrivate env; m_parameters.callMethod("setPreviewFpsRange", "(II)V", min, max); exceptionCheckAndClear(env); } AndroidCamera::ImageFormat AndroidCameraPrivate::getPreviewFormat() { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return AndroidCamera::UnknownImageFormat; return AndroidCamera::ImageFormat(m_parameters.callMethod("getPreviewFormat")); } void AndroidCameraPrivate::setPreviewFormat(AndroidCamera::ImageFormat fmt) { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return; m_parameters.callMethod("setPreviewFormat", "(I)V", jint(fmt)); applyParameters(); } QList AndroidCameraPrivate::getSupportedPreviewFormats() { QList list; const std::lock_guard locker(m_parametersMutex); if (m_parameters.isValid()) { QJNIObjectPrivate formatList = m_parameters.callObjectMethod("getSupportedPreviewFormats", "()Ljava/util/List;"); int count = formatList.callMethod("size"); for (int i = 0; i < count; ++i) { QJNIObjectPrivate format = formatList.callObjectMethod("get", "(I)Ljava/lang/Object;", i); list.append(AndroidCamera::ImageFormat(format.callMethod("intValue"))); } } return list; } QSize AndroidCameraPrivate::getPreviewSize() { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return QSize(); QJNIObjectPrivate size = m_parameters.callObjectMethod("getPreviewSize", "()Landroid/hardware/Camera$Size;"); if (!size.isValid()) return QSize(); return QSize(size.getField("width"), size.getField("height")); } void AndroidCameraPrivate::updatePreviewSize() { const std::lock_guard locker(m_parametersMutex); if (m_previewSize.isValid()) { m_parameters.callMethod("setPreviewSize", "(II)V", m_previewSize.width(), m_previewSize.height()); applyParameters(); } emit previewSizeChanged(); } bool AndroidCameraPrivate::setPreviewTexture(void *surfaceTexture) { QJNIEnvironmentPrivate env; m_camera.callMethod("setPreviewTexture", "(Landroid/graphics/SurfaceTexture;)V", static_cast(surfaceTexture)); return !exceptionCheckAndClear(env); } bool AndroidCameraPrivate::setPreviewDisplay(void *surfaceHolder) { QJNIEnvironmentPrivate env; m_camera.callMethod("setPreviewDisplay", "(Landroid/view/SurfaceHolder;)V", static_cast(surfaceHolder)); return !exceptionCheckAndClear(env); } void AndroidCameraPrivate::setDisplayOrientation(int degrees) { m_camera.callMethod("setDisplayOrientation", "(I)V", degrees); } bool AndroidCameraPrivate::isZoomSupported() { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return false; return m_parameters.callMethod("isZoomSupported"); } int AndroidCameraPrivate::getMaxZoom() { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return 0; return m_parameters.callMethod("getMaxZoom"); } QList AndroidCameraPrivate::getZoomRatios() { const std::lock_guard locker(m_parametersMutex); QList ratios; if (m_parameters.isValid()) { QJNIObjectPrivate ratioList = m_parameters.callObjectMethod("getZoomRatios", "()Ljava/util/List;"); int count = ratioList.callMethod("size"); for (int i = 0; i < count; ++i) { QJNIObjectPrivate zoomRatio = ratioList.callObjectMethod("get", "(I)Ljava/lang/Object;", i); ratios.append(zoomRatio.callMethod("intValue")); } } return ratios; } int AndroidCameraPrivate::getZoom() { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return 0; return m_parameters.callMethod("getZoom"); } void AndroidCameraPrivate::setZoom(int value) { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return; m_parameters.callMethod("setZoom", "(I)V", value); applyParameters(); } QString AndroidCameraPrivate::getFlashMode() { const std::lock_guard locker(m_parametersMutex); QString value; if (m_parameters.isValid()) { QJNIObjectPrivate flashMode = m_parameters.callObjectMethod("getFlashMode", "()Ljava/lang/String;"); if (flashMode.isValid()) value = flashMode.toString(); } return value; } void AndroidCameraPrivate::setFlashMode(const QString &value) { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return; m_parameters.callMethod("setFlashMode", "(Ljava/lang/String;)V", QJNIObjectPrivate::fromString(value).object()); applyParameters(); } QString AndroidCameraPrivate::getFocusMode() { const std::lock_guard locker(m_parametersMutex); QString value; if (m_parameters.isValid()) { QJNIObjectPrivate focusMode = m_parameters.callObjectMethod("getFocusMode", "()Ljava/lang/String;"); if (focusMode.isValid()) value = focusMode.toString(); } return value; } void AndroidCameraPrivate::setFocusMode(const QString &value) { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return; m_parameters.callMethod("setFocusMode", "(Ljava/lang/String;)V", QJNIObjectPrivate::fromString(value).object()); applyParameters(); } int AndroidCameraPrivate::getMaxNumFocusAreas() { if (QtAndroidPrivate::androidSdkVersion() < 14) return 0; const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return 0; return m_parameters.callMethod("getMaxNumFocusAreas"); } QList AndroidCameraPrivate::getFocusAreas() { QList areas; if (QtAndroidPrivate::androidSdkVersion() < 14) return areas; const std::lock_guard locker(m_parametersMutex); if (m_parameters.isValid()) { QJNIObjectPrivate list = m_parameters.callObjectMethod("getFocusAreas", "()Ljava/util/List;"); if (list.isValid()) { int count = list.callMethod("size"); for (int i = 0; i < count; ++i) { QJNIObjectPrivate area = list.callObjectMethod("get", "(I)Ljava/lang/Object;", i); areas.append(areaToRect(area.object())); } } } return areas; } void AndroidCameraPrivate::setFocusAreas(const QList &areas) { if (QtAndroidPrivate::androidSdkVersion() < 14) return; const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return; QJNIObjectPrivate list; if (!areas.isEmpty()) { QJNIEnvironmentPrivate env; QJNIObjectPrivate arrayList("java/util/ArrayList", "(I)V", areas.size()); for (int i = 0; i < areas.size(); ++i) { arrayList.callMethod("add", "(Ljava/lang/Object;)Z", rectToArea(areas.at(i)).object()); exceptionCheckAndClear(env); } list = arrayList; } m_parameters.callMethod("setFocusAreas", "(Ljava/util/List;)V", list.object()); applyParameters(); } void AndroidCameraPrivate::autoFocus() { QJNIEnvironmentPrivate env; m_camera.callMethod("autoFocus", "(Landroid/hardware/Camera$AutoFocusCallback;)V", m_cameraListener.object()); if (!exceptionCheckAndClear(env)) emit autoFocusStarted(); } void AndroidCameraPrivate::cancelAutoFocus() { QJNIEnvironmentPrivate env; m_camera.callMethod("cancelAutoFocus"); exceptionCheckAndClear(env); } bool AndroidCameraPrivate::isAutoExposureLockSupported() { if (QtAndroidPrivate::androidSdkVersion() < 14) return false; const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return false; return m_parameters.callMethod("isAutoExposureLockSupported"); } bool AndroidCameraPrivate::getAutoExposureLock() { if (QtAndroidPrivate::androidSdkVersion() < 14) return false; const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return false; return m_parameters.callMethod("getAutoExposureLock"); } void AndroidCameraPrivate::setAutoExposureLock(bool toggle) { if (QtAndroidPrivate::androidSdkVersion() < 14) return; const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return; m_parameters.callMethod("setAutoExposureLock", "(Z)V", toggle); applyParameters(); } bool AndroidCameraPrivate::isAutoWhiteBalanceLockSupported() { if (QtAndroidPrivate::androidSdkVersion() < 14) return false; const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return false; return m_parameters.callMethod("isAutoWhiteBalanceLockSupported"); } bool AndroidCameraPrivate::getAutoWhiteBalanceLock() { if (QtAndroidPrivate::androidSdkVersion() < 14) return false; const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return false; return m_parameters.callMethod("getAutoWhiteBalanceLock"); } void AndroidCameraPrivate::setAutoWhiteBalanceLock(bool toggle) { if (QtAndroidPrivate::androidSdkVersion() < 14) return; const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return; m_parameters.callMethod("setAutoWhiteBalanceLock", "(Z)V", toggle); applyParameters(); } int AndroidCameraPrivate::getExposureCompensation() { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return 0; return m_parameters.callMethod("getExposureCompensation"); } void AndroidCameraPrivate::setExposureCompensation(int value) { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return; m_parameters.callMethod("setExposureCompensation", "(I)V", value); applyParameters(); } float AndroidCameraPrivate::getExposureCompensationStep() { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return 0; return m_parameters.callMethod("getExposureCompensationStep"); } int AndroidCameraPrivate::getMinExposureCompensation() { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return 0; return m_parameters.callMethod("getMinExposureCompensation"); } int AndroidCameraPrivate::getMaxExposureCompensation() { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return 0; return m_parameters.callMethod("getMaxExposureCompensation"); } QString AndroidCameraPrivate::getSceneMode() { const std::lock_guard locker(m_parametersMutex); QString value; if (m_parameters.isValid()) { QJNIObjectPrivate sceneMode = m_parameters.callObjectMethod("getSceneMode", "()Ljava/lang/String;"); if (sceneMode.isValid()) value = sceneMode.toString(); } return value; } void AndroidCameraPrivate::setSceneMode(const QString &value) { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return; m_parameters.callMethod("setSceneMode", "(Ljava/lang/String;)V", QJNIObjectPrivate::fromString(value).object()); applyParameters(); } QString AndroidCameraPrivate::getWhiteBalance() { const std::lock_guard locker(m_parametersMutex); QString value; if (m_parameters.isValid()) { QJNIObjectPrivate wb = m_parameters.callObjectMethod("getWhiteBalance", "()Ljava/lang/String;"); if (wb.isValid()) value = wb.toString(); } return value; } void AndroidCameraPrivate::setWhiteBalance(const QString &value) { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return; m_parameters.callMethod("setWhiteBalance", "(Ljava/lang/String;)V", QJNIObjectPrivate::fromString(value).object()); applyParameters(); emit whiteBalanceChanged(); } void AndroidCameraPrivate::updateRotation() { const std::lock_guard locker(m_parametersMutex); m_parameters.callMethod("setRotation", "(I)V", m_rotation); applyParameters(); } QList AndroidCameraPrivate::getSupportedPictureSizes() { const std::lock_guard locker(m_parametersMutex); QList list; if (m_parameters.isValid()) { QJNIObjectPrivate sizeList = m_parameters.callObjectMethod("getSupportedPictureSizes", "()Ljava/util/List;"); int count = sizeList.callMethod("size"); for (int i = 0; i < count; ++i) { QJNIObjectPrivate size = sizeList.callObjectMethod("get", "(I)Ljava/lang/Object;", i); list.append(QSize(size.getField("width"), size.getField("height"))); } std::sort(list.begin(), list.end(), qt_sizeLessThan); } return list; } void AndroidCameraPrivate::setPictureSize(const QSize &size) { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return; m_parameters.callMethod("setPictureSize", "(II)V", size.width(), size.height()); applyParameters(); } void AndroidCameraPrivate::setJpegQuality(int quality) { const std::lock_guard locker(m_parametersMutex); if (!m_parameters.isValid()) return; m_parameters.callMethod("setJpegQuality", "(I)V", quality); applyParameters(); } void AndroidCameraPrivate::startPreview() { QJNIEnvironmentPrivate env; setupPreviewFrameCallback(); m_camera.callMethod("startPreview"); if (exceptionCheckAndClear(env)) emit previewFailedToStart(); else emit previewStarted(); } void AndroidCameraPrivate::stopPreview() { QJNIEnvironmentPrivate env; // cancel any pending new frame notification m_cameraListener.callMethod("notifyWhenFrameAvailable", "(Z)V", false); m_camera.callMethod("stopPreview"); exceptionCheckAndClear(env); emit previewStopped(); } void AndroidCameraPrivate::takePicture() { QJNIEnvironmentPrivate env; // We must clear the preview callback before calling takePicture(), otherwise the call will // block and the camera server will be frozen until the next device restart... // That problem only happens on some devices and on the emulator m_cameraListener.callMethod("clearPreviewCallback", "(Landroid/hardware/Camera;)V", m_camera.object()); m_camera.callMethod("takePicture", "(Landroid/hardware/Camera$ShutterCallback;" "Landroid/hardware/Camera$PictureCallback;" "Landroid/hardware/Camera$PictureCallback;)V", m_cameraListener.object(), jobject(0), m_cameraListener.object()); if (exceptionCheckAndClear(env)) emit takePictureFailed(); } void AndroidCameraPrivate::setupPreviewFrameCallback() { m_cameraListener.callMethod("setupPreviewCallback", "(Landroid/hardware/Camera;)V", m_camera.object()); } void AndroidCameraPrivate::notifyNewFrames(bool notify) { m_cameraListener.callMethod("notifyNewFrames", "(Z)V", notify); } void AndroidCameraPrivate::fetchLastPreviewFrame() { QJNIEnvironmentPrivate env; QJNIObjectPrivate data = m_cameraListener.callObjectMethod("lastPreviewBuffer", "()[B"); if (!data.isValid()) { // If there's no buffer received yet, retry when the next one arrives m_cameraListener.callMethod("notifyWhenFrameAvailable", "(Z)V", true); return; } const int arrayLength = env->GetArrayLength(static_cast(data.object())); if (arrayLength == 0) return; QByteArray bytes(arrayLength, Qt::Uninitialized); env->GetByteArrayRegion(static_cast(data.object()), 0, arrayLength, reinterpret_cast(bytes.data())); const int width = m_cameraListener.callMethod("previewWidth"); const int height = m_cameraListener.callMethod("previewHeight"); const int format = m_cameraListener.callMethod("previewFormat"); const int bpl = m_cameraListener.callMethod("previewBytesPerLine"); QVideoFrame frame(new QMemoryVideoBuffer(bytes, bpl), QSize(width, height), qt_pixelFormatFromAndroidImageFormat(AndroidCamera::ImageFormat(format))); emit lastPreviewFrameFetched(frame); } void AndroidCameraPrivate::applyParameters() { QJNIEnvironmentPrivate env; m_camera.callMethod("setParameters", "(Landroid/hardware/Camera$Parameters;)V", m_parameters.object()); exceptionCheckAndClear(env); } QStringList AndroidCameraPrivate::callParametersStringListMethod(const QByteArray &methodName) { const std::lock_guard locker(m_parametersMutex); QStringList stringList; if (m_parameters.isValid()) { QJNIObjectPrivate list = m_parameters.callObjectMethod(methodName.constData(), "()Ljava/util/List;"); if (list.isValid()) { int count = list.callMethod("size"); for (int i = 0; i < count; ++i) { QJNIObjectPrivate string = list.callObjectMethod("get", "(I)Ljava/lang/Object;", i); stringList.append(string.toString()); } } } return stringList; } bool AndroidCamera::initJNI(JNIEnv *env) { jclass clazz = QJNIEnvironmentPrivate::findClass(QtCameraListenerClassName, env); static const JNINativeMethod methods[] = { {"notifyAutoFocusComplete", "(IZ)V", (void *)notifyAutoFocusComplete}, {"notifyPictureExposed", "(I)V", (void *)notifyPictureExposed}, {"notifyPictureCaptured", "(I[B)V", (void *)notifyPictureCaptured}, {"notifyNewPreviewFrame", "(I[BIIII)V", (void *)notifyNewPreviewFrame}, {"notifyFrameAvailable", "(I)V", (void *)notifyFrameAvailable} }; if (clazz && env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) != JNI_OK) { return false; } return true; } QT_END_NAMESPACE #include "androidcamera.moc"