Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// Copyright (C) 2016 Ruslan Baratov
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "androidcamera_p.h"
#include "androidsurfacetexture_p.h"
#include "androidsurfaceview_p.h"
#include "qandroidmultimediautils_p.h"
#include "qandroidglobal_p.h"
#include <private/qvideoframe_p.h>
#include <qhash.h>
#include <qstringlist.h>
#include <qdebug.h>
#include <QtCore/qthread.h>
#include <QtCore/qreadwritelock.h>
#include <QtCore/qmutex.h>
#include <QtMultimedia/private/qmemoryvideobuffer_p.h>
#include <QtMultimedia/private/qmultimedia_ranges_p.h>
#include <QtCore/qcoreapplication.h>
#include <mutex>
QT_BEGIN_NAMESPACE
namespace ranges = QtMultimediaPrivate::ranges;
Q_STATIC_LOGGING_CATEGORY(lcAndroidCamera, "qt.multimedia.android.camera");
static const char QtCameraListenerClassName[] = "org/qtproject/qt/android/multimedia/QtCameraListener";
typedef QHash<int, AndroidCamera *> CameraMap;
Q_GLOBAL_STATIC(CameraMap, cameras)
Q_GLOBAL_STATIC(QReadWriteLock, rwLock)
static QRect areaToRect(jobject areaObj)
{
QJniObject area(areaObj);
QJniObject rect = area.getObjectField("rect", "Landroid/graphics/Rect;");
return QRect(rect.getField<jint>("left"),
rect.getField<jint>("top"),
rect.callMethod<jint>("width"),
rect.callMethod<jint>("height"));
}
static QJniObject rectToArea(const QRect &rect)
{
QJniObject jrect("android/graphics/Rect",
"(IIII)V",
rect.left(), rect.top(), rect.right(), rect.bottom());
QJniObject 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())) {
qCWarning(lcAndroidCamera) << "Could not obtain camera!";
return;
}
AndroidCamera *camera = (*it);
const int arrayLength = env->GetArrayLength(data);
QByteArray bytes(arrayLength, Qt::Uninitialized);
env->GetByteArrayRegion(data, 0, arrayLength, reinterpret_cast<jbyte *>(bytes.data()));
auto parameters = camera->getParametersObject();
QJniObject size =
parameters.callObjectMethod("getPictureSize", "()Landroid/hardware/Camera$Size;");
if (!size.isValid()) {
qCWarning(lcAndroidCamera) << "Picture Size is not valid!";
return;
}
QSize pictureSize(size.getField<jint>("width"), size.getField<jint>("height"));
auto format = AndroidCamera::ImageFormat(parameters.callMethod<jint>("getPictureFormat"));
if (format == AndroidCamera::ImageFormat::UnknownImageFormat) {
qCWarning(lcAndroidCamera) << "Android Camera Image Format is UnknownImageFormat!";
return;
}
int bytesPerLine = 0;
switch (format) {
case AndroidCamera::ImageFormat::YV12:
bytesPerLine = (pictureSize.width() + 15) & ~15;
break;
case AndroidCamera::ImageFormat::NV21:
bytesPerLine = pictureSize.width();
break;
case AndroidCamera::ImageFormat::RGB565:
case AndroidCamera::ImageFormat::YUY2:
bytesPerLine = pictureSize.width() * 2;
break;
default:
bytesPerLine = -1;
}
auto pictureFormat = qt_pixelFormatFromAndroidImageFormat(format);
Q_EMIT camera->pictureCaptured(bytes, pictureFormat, pictureSize, bytesPerLine);
}
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());
QVideoFrameFormat frameFormat(
QSize(width, height),
qt_pixelFormatFromAndroidImageFormat(AndroidCamera::ImageFormat(format)));
QVideoFrame frame = QVideoFramePrivate::createFrame(
std::make_unique<QMemoryVideoBuffer>(std::move(bytes), bpl), std::move(frameFormat));
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<QSize> getSupportedPreviewSizes();
static QList<QSize> getSupportedPreviewSizes(QJniObject &parameters);
Q_INVOKABLE QList<AndroidCamera::FpsRange> getSupportedPreviewFpsRange();
Q_INVOKABLE AndroidCamera::FpsRange getPreviewFpsRange();
static AndroidCamera::FpsRange getPreviewFpsRange(QJniObject &parameters);
Q_INVOKABLE void setPreviewFpsRange(int min, int max);
Q_INVOKABLE AndroidCamera::ImageFormat getPreviewFormat();
Q_INVOKABLE void setPreviewFormat(AndroidCamera::ImageFormat fmt);
Q_INVOKABLE QList<AndroidCamera::ImageFormat> getSupportedPreviewFormats();
static QList<AndroidCamera::ImageFormat> getSupportedPreviewFormats(QJniObject &parameters);
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<int> 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<QRect> getFocusAreas();
Q_INVOKABLE void setFocusAreas(const QList<QRect> &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<QSize> getSupportedPictureSizes();
Q_INVOKABLE QList<QSize> getSupportedVideoSizes();
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;
QJniObject m_info;
QJniObject m_parameters;
QJniObject m_camera;
QJniObject 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)
{
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_androidCheckCameraPermission())
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<QSize> AndroidCamera::getSupportedPreviewSizes()
{
Q_D(AndroidCamera);
return d->getSupportedPreviewSizes();
}
QList<AndroidCamera::FpsRange> 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::ImageFormat> 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<int> 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<QRect> AndroidCamera::getFocusAreas()
{
Q_D(AndroidCamera);
return d->getFocusAreas();
}
void AndroidCamera::setFocusAreas(const QList<QRect> &areas)
{
Q_D(AndroidCamera);
QMetaObject::invokeMethod(d, "setFocusAreas", Q_ARG(QList<QRect>, 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<QSize> AndroidCamera::getSupportedPictureSizes()
{
Q_D(AndroidCamera);
return d->getSupportedPictureSizes();
}
QList<QSize> AndroidCamera::getSupportedVideoSizes()
{
Q_D(AndroidCamera);
return d->getSupportedVideoSizes();
}
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");
}
QJniObject AndroidCamera::getCameraObject()
{
Q_D(AndroidCamera);
return d->m_camera;
}
int AndroidCamera::getNumberOfCameras()
{
if (!qt_androidCheckCameraPermission())
return 0;
return QJniObject::callStaticMethod<jint>("android/hardware/Camera",
"getNumberOfCameras");
}
void AndroidCamera::getCameraInfo(int id, QCameraDevicePrivate *info)
{
Q_ASSERT(info);
QJniObject cameraInfo("android/hardware/Camera$CameraInfo");
QJniObject::callStaticMethod<void>("android/hardware/Camera",
"getCameraInfo",
"(ILandroid/hardware/Camera$CameraInfo;)V",
id, cameraInfo.object());
AndroidCamera::CameraFacing facing = AndroidCamera::CameraFacing(cameraInfo.getField<jint>("facing"));
// The orientation provided by Android is counter-clockwise, we need it clockwise
info->orientation = (360 - cameraInfo.getField<jint>("orientation")) % 360;
switch (facing) {
case AndroidCamera::CameraFacingBack:
info->id = QByteArray("back");
info->description = QStringLiteral("Rear-facing camera");
info->position = QCameraDevice::BackFace;
info->isDefault = true;
break;
case AndroidCamera::CameraFacingFront:
info->id = QByteArray("front");
info->description = QStringLiteral("Front-facing camera");
info->position = QCameraDevice::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->id.append(QByteArray::number(id));
info->description.append(QStringLiteral(" %1").arg(id));
}
}
QVideoFrameFormat::PixelFormat AndroidCamera::QtPixelFormatFromAndroidImageFormat(AndroidCamera::ImageFormat format)
{
switch (format) {
case AndroidCamera::NV21:
return QVideoFrameFormat::Format_NV21;
case AndroidCamera::YUY2:
return QVideoFrameFormat::Format_YUYV;
case AndroidCamera::JPEG:
return QVideoFrameFormat::Format_Jpeg;
case AndroidCamera::YV12:
return QVideoFrameFormat::Format_YV12;
default:
return QVideoFrameFormat::Format_Invalid;
}
}
AndroidCamera::ImageFormat AndroidCamera::AndroidImageFormatFromQtPixelFormat(QVideoFrameFormat::PixelFormat format)
{
switch (format) {
case QVideoFrameFormat::Format_NV21:
return AndroidCamera::NV21;
case QVideoFrameFormat::Format_YUYV:
return AndroidCamera::YUY2;
case QVideoFrameFormat::Format_Jpeg:
return AndroidCamera::JPEG;
case QVideoFrameFormat::Format_YV12:
return AndroidCamera::YV12;
default:
return AndroidCamera::UnknownImageFormat;
}
}
QList<QCameraFormat> AndroidCamera::getSupportedFormats()
{
QList<QCameraFormat> formats;
AndroidCamera::FpsRange range = getPreviewFpsRange();
for (const auto &previewSize : getSupportedVideoSizes()) {
for (const auto &previewFormat : getSupportedPreviewFormats()) {
QCameraFormatPrivate * format = new QCameraFormatPrivate();
format->pixelFormat = QtPixelFormatFromAndroidImageFormat(previewFormat);
format->resolution = previewSize;
format->minFrameRate = range.min;
format->maxFrameRate = range.max;
formats.append(format->create());
}
}
return formats;
}
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);
}
QJniObject AndroidCamera::getParametersObject()
{
Q_D(AndroidCamera);
return d->m_parameters;
}
AndroidCameraPrivate::AndroidCameraPrivate()
: QObject()
{
}
AndroidCameraPrivate::~AndroidCameraPrivate()
{
}
static qint32 s_activeCameras = 0;
bool AndroidCameraPrivate::init(int cameraId)
{
m_cameraId = cameraId;
QJniEnvironment env;
const bool opened = s_activeCameras & (1 << cameraId);
if (opened)
return false;
m_camera = QJniObject::callStaticObjectMethod("android/hardware/Camera",
"open",
"(I)Landroid/hardware/Camera;",
cameraId);
if (!m_camera.isValid())
return false;
m_cameraListener = QJniObject(QtCameraListenerClassName, "(I)V", m_cameraId);
m_info = QJniObject("android/hardware/Camera$CameraInfo");
m_camera.callStaticMethod<void>("android/hardware/Camera",
"getCameraInfo",
"(ILandroid/hardware/Camera$CameraInfo;)V",
cameraId,
m_info.object());
QJniObject params = m_camera.callObjectMethod("getParameters",
"()Landroid/hardware/Camera$Parameters;");
m_parameters = QJniObject(params);
s_activeCameras |= 1 << cameraId;
return true;
}
void AndroidCameraPrivate::release()
{
m_previewSize = QSize();
m_parametersMutex.lock();
m_parameters = QJniObject();
m_parametersMutex.unlock();
if (m_camera.isValid()) {
m_camera.callMethod<void>("release");
s_activeCameras &= ~(1 << m_cameraId);
}
}
bool AndroidCameraPrivate::lock()
{
QJniEnvironment env;
auto methodId = env->GetMethodID(m_camera.objectClass(), "lock", "()V");
env->CallVoidMethod(m_camera.object(), methodId);
if (env.checkAndClearExceptions())
return false;
return true;
}
bool AndroidCameraPrivate::unlock()
{
QJniEnvironment env;
auto methodId = env->GetMethodID(m_camera.objectClass(), "unlock", "()V");
env->CallVoidMethod(m_camera.object(), methodId);
if (env.checkAndClearExceptions())
return false;
return true;
}
bool AndroidCameraPrivate::reconnect()
{
QJniEnvironment env;
auto methodId = env->GetMethodID(m_camera.objectClass(), "reconnect", "()V");
env->CallVoidMethod(m_camera.object(), methodId);
if (env.checkAndClearExceptions())
return false;
return true;
}
AndroidCamera::CameraFacing AndroidCameraPrivate::getFacing()
{
return AndroidCamera::CameraFacing(m_info.getField<jint>("facing"));
}
int AndroidCameraPrivate::getNativeOrientation()
{
return m_info.getField<jint>("orientation");
}
QSize AndroidCameraPrivate::getPreferredPreviewSizeForVideo()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return QSize();
QJniObject size = m_parameters.callObjectMethod("getPreferredPreviewSizeForVideo",
"()Landroid/hardware/Camera$Size;");
if (!size.isValid())
return QSize();
return QSize(size.getField<jint>("width"), size.getField<jint>("height"));
}
QList<QSize> AndroidCameraPrivate::getSupportedPreviewSizes()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
return getSupportedPreviewSizes(m_parameters);
}
QList<QSize> AndroidCameraPrivate::getSupportedPreviewSizes(QJniObject &parameters)
{
QList<QSize> list;
if (parameters.isValid()) {
QJniObject sizeList = parameters.callObjectMethod("getSupportedPreviewSizes",
"()Ljava/util/List;");
int count = sizeList.callMethod<jint>("size");
for (int i = 0; i < count; ++i) {
QJniObject size = sizeList.callObjectMethod("get",
"(I)Ljava/lang/Object;",
i);
list.append(QSize(size.getField<jint>("width"), size.getField<jint>("height")));
}
ranges::sort(list, qt_sizeLessThan);
}
return list;
}
QList<AndroidCamera::FpsRange> AndroidCameraPrivate::getSupportedPreviewFpsRange()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
QJniEnvironment env;
QList<AndroidCamera::FpsRange> rangeList;
if (m_parameters.isValid()) {
QJniObject rangeListNative = m_parameters.callObjectMethod("getSupportedPreviewFpsRange",
"()Ljava/util/List;");
int count = rangeListNative.callMethod<jint>("size");
rangeList.reserve(count);
for (int i = 0; i < count; ++i) {
QJniObject range = rangeListNative.callObjectMethod("get",
"(I)Ljava/lang/Object;",
i);
jintArray jRange = static_cast<jintArray>(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<QRecursiveMutex> locker(m_parametersMutex);
return getPreviewFpsRange(m_parameters);
}
AndroidCamera::FpsRange AndroidCameraPrivate::getPreviewFpsRange(QJniObject &parameters)
{
QJniEnvironment env;
AndroidCamera::FpsRange range;
if (!parameters.isValid())
return range;
jintArray jRangeArray = env->NewIntArray(2);
parameters.callMethod<void>("getPreviewFpsRange", "([I)V", jRangeArray);
jint* jRangeElements = env->GetIntArrayElements(jRangeArray, 0);
// Android Camera API returns values scaled by 1000, so divide here to report
// normal values for Qt
range.min = jRangeElements[0] / 1000;
range.max = jRangeElements[1] / 1000;
env->ReleaseIntArrayElements(jRangeArray, jRangeElements, 0);
env->DeleteLocalRef(jRangeArray);
return range;
}
void AndroidCameraPrivate::setPreviewFpsRange(int min, int max)
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return;
// Android Camera API returns values scaled by 1000, so multiply here to
// give Android API the scale is expects
m_parameters.callMethod<void>("setPreviewFpsRange", "(II)V", min * 1000, max * 1000);
}
AndroidCamera::ImageFormat AndroidCameraPrivate::getPreviewFormat()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return AndroidCamera::UnknownImageFormat;
return AndroidCamera::ImageFormat(m_parameters.callMethod<jint>("getPreviewFormat"));
}
void AndroidCameraPrivate::setPreviewFormat(AndroidCamera::ImageFormat fmt)
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return;
m_parameters.callMethod<void>("setPreviewFormat", "(I)V", jint(fmt));
applyParameters();
}
QList<AndroidCamera::ImageFormat> AndroidCameraPrivate::getSupportedPreviewFormats()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
return getSupportedPreviewFormats(m_parameters);
}
QList<AndroidCamera::ImageFormat> AndroidCameraPrivate::getSupportedPreviewFormats(QJniObject &parameters)
{
QList<AndroidCamera::ImageFormat> list;
if (parameters.isValid()) {
QJniObject formatList = parameters.callObjectMethod("getSupportedPreviewFormats",
"()Ljava/util/List;");
int count = formatList.callMethod<jint>("size");
for (int i = 0; i < count; ++i) {
QJniObject format = formatList.callObjectMethod("get",
"(I)Ljava/lang/Object;",
i);
list.append(AndroidCamera::ImageFormat(format.callMethod<jint>("intValue")));
}
}
return list;
}
QSize AndroidCameraPrivate::getPreviewSize()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return QSize();
QJniObject size = m_parameters.callObjectMethod("getPreviewSize",
"()Landroid/hardware/Camera$Size;");
if (!size.isValid())
return QSize();
return QSize(size.getField<jint>("width"), size.getField<jint>("height"));
}
void AndroidCameraPrivate::updatePreviewSize()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (m_previewSize.isValid()) {
m_parameters.callMethod<void>("setPreviewSize", "(II)V", m_previewSize.width(), m_previewSize.height());
applyParameters();
}
Q_EMIT previewSizeChanged();
}
bool AndroidCameraPrivate::setPreviewTexture(void *surfaceTexture)
{
QJniEnvironment env;
auto methodId = env->GetMethodID(m_camera.objectClass(), "setPreviewTexture",
"(Landroid/graphics/SurfaceTexture;)V");
env->CallVoidMethod(m_camera.object(), methodId, static_cast<jobject>(surfaceTexture));
if (env.checkAndClearExceptions())
return false;
return true;
}
bool AndroidCameraPrivate::setPreviewDisplay(void *surfaceHolder)
{
QJniEnvironment env;
auto methodId = env->GetMethodID(m_camera.objectClass(), "setPreviewDisplay",
"(Landroid/view/SurfaceHolder;)V");
env->CallVoidMethod(m_camera.object(), methodId, static_cast<jobject>(surfaceHolder));
if (env.checkAndClearExceptions())
return false;
return true;
}
void AndroidCameraPrivate::setDisplayOrientation(int degrees)
{
m_camera.callMethod<void>("setDisplayOrientation", "(I)V", degrees);
m_cameraListener.callMethod<void>("setPhotoRotation", "(I)V", degrees);
}
bool AndroidCameraPrivate::isZoomSupported()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return false;
return m_parameters.callMethod<jboolean>("isZoomSupported");
}
int AndroidCameraPrivate::getMaxZoom()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return 0;
return m_parameters.callMethod<jint>("getMaxZoom");
}
QList<int> AndroidCameraPrivate::getZoomRatios()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
QList<int> ratios;
if (m_parameters.isValid()) {
QJniObject ratioList = m_parameters.callObjectMethod("getZoomRatios",
"()Ljava/util/List;");
int count = ratioList.callMethod<jint>("size");
for (int i = 0; i < count; ++i) {
QJniObject zoomRatio = ratioList.callObjectMethod("get",
"(I)Ljava/lang/Object;",
i);
ratios.append(zoomRatio.callMethod<jint>("intValue"));
}
}
return ratios;
}
int AndroidCameraPrivate::getZoom()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return 0;
return m_parameters.callMethod<jint>("getZoom");
}
void AndroidCameraPrivate::setZoom(int value)
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return;
m_parameters.callMethod<void>("setZoom", "(I)V", value);
applyParameters();
}
QString AndroidCameraPrivate::getFlashMode()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
QString value;
if (m_parameters.isValid()) {
QJniObject 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<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return;
m_parameters.callMethod<void>("setFlashMode",
"(Ljava/lang/String;)V",
QJniObject::fromString(value).object());
applyParameters();
}
QString AndroidCameraPrivate::getFocusMode()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
QString value;
if (m_parameters.isValid()) {
QJniObject 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<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return;
m_parameters.callMethod<void>("setFocusMode",
"(Ljava/lang/String;)V",
QJniObject::fromString(value).object());
applyParameters();
}
int AndroidCameraPrivate::getMaxNumFocusAreas()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return 0;
return m_parameters.callMethod<jint>("getMaxNumFocusAreas");
}
QList<QRect> AndroidCameraPrivate::getFocusAreas()
{
QList<QRect> areas;
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (m_parameters.isValid()) {
QJniObject list = m_parameters.callObjectMethod("getFocusAreas",
"()Ljava/util/List;");
if (list.isValid()) {
int count = list.callMethod<jint>("size");
for (int i = 0; i < count; ++i) {
QJniObject area = list.callObjectMethod("get",
"(I)Ljava/lang/Object;",
i);
areas.append(areaToRect(area.object()));
}
}
}
return areas;
}
void AndroidCameraPrivate::setFocusAreas(const QList<QRect> &areas)
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid() || areas.isEmpty())
return;
QJniObject list;
if (!areas.isEmpty()) {
QJniEnvironment env;
QJniObject arrayList("java/util/ArrayList", "(I)V", areas.size());
for (int i = 0; i < areas.size(); ++i) {
arrayList.callMethod<jboolean>("add",
"(Ljava/lang/Object;)Z",
rectToArea(areas.at(i)).object());
}
list = arrayList;
}
m_parameters.callMethod<void>("setFocusAreas", "(Ljava/util/List;)V", list.object());
applyParameters();
}
void AndroidCameraPrivate::autoFocus()
{
QJniEnvironment env;
auto methodId = env->GetMethodID(m_camera.objectClass(), "autoFocus",
"(Landroid/hardware/Camera$AutoFocusCallback;)V");
env->CallVoidMethod(m_camera.object(), methodId, m_cameraListener.object());
if (!env.checkAndClearExceptions())
Q_EMIT autoFocusStarted();
}
void AndroidCameraPrivate::cancelAutoFocus()
{
QJniEnvironment env;
m_camera.callMethod<void>("cancelAutoFocus");
}
bool AndroidCameraPrivate::isAutoExposureLockSupported()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return false;
return m_parameters.callMethod<jboolean>("isAutoExposureLockSupported");
}
bool AndroidCameraPrivate::getAutoExposureLock()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return false;
return m_parameters.callMethod<jboolean>("getAutoExposureLock");
}
void AndroidCameraPrivate::setAutoExposureLock(bool toggle)
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return;
m_parameters.callMethod<void>("setAutoExposureLock", "(Z)V", toggle);
applyParameters();
}
bool AndroidCameraPrivate::isAutoWhiteBalanceLockSupported()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return false;
return m_parameters.callMethod<jboolean>("isAutoWhiteBalanceLockSupported");
}
bool AndroidCameraPrivate::getAutoWhiteBalanceLock()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return false;
return m_parameters.callMethod<jboolean>("getAutoWhiteBalanceLock");
}
void AndroidCameraPrivate::setAutoWhiteBalanceLock(bool toggle)
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return;
m_parameters.callMethod<void>("setAutoWhiteBalanceLock", "(Z)V", toggle);
applyParameters();
}
int AndroidCameraPrivate::getExposureCompensation()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return 0;
return m_parameters.callMethod<jint>("getExposureCompensation");
}
void AndroidCameraPrivate::setExposureCompensation(int value)
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return;
m_parameters.callMethod<void>("setExposureCompensation", "(I)V", value);
applyParameters();
}
float AndroidCameraPrivate::getExposureCompensationStep()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return 0;
return m_parameters.callMethod<jfloat>("getExposureCompensationStep");
}
int AndroidCameraPrivate::getMinExposureCompensation()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return 0;
return m_parameters.callMethod<jint>("getMinExposureCompensation");
}
int AndroidCameraPrivate::getMaxExposureCompensation()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return 0;
return m_parameters.callMethod<jint>("getMaxExposureCompensation");
}
QString AndroidCameraPrivate::getSceneMode()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
QString value;
if (m_parameters.isValid()) {
QJniObject 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<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return;
m_parameters.callMethod<void>("setSceneMode",
"(Ljava/lang/String;)V",
QJniObject::fromString(value).object());
applyParameters();
}
QString AndroidCameraPrivate::getWhiteBalance()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
QString value;
if (m_parameters.isValid()) {
QJniObject 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<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return;
m_parameters.callMethod<void>("setWhiteBalance",
"(Ljava/lang/String;)V",
QJniObject::fromString(value).object());
applyParameters();
Q_EMIT whiteBalanceChanged();
}
void AndroidCameraPrivate::updateRotation()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
m_parameters.callMethod<void>("setRotation", "(I)V", m_rotation);
applyParameters();
}
QList<QSize> AndroidCameraPrivate::getSupportedPictureSizes()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
QList<QSize> list;
if (m_parameters.isValid()) {
QJniObject sizeList = m_parameters.callObjectMethod("getSupportedPictureSizes",
"()Ljava/util/List;");
int count = sizeList.callMethod<jint>("size");
for (int i = 0; i < count; ++i) {
QJniObject size = sizeList.callObjectMethod("get",
"(I)Ljava/lang/Object;",
i);
list.append(QSize(size.getField<jint>("width"), size.getField<jint>("height")));
}
ranges::sort(list, qt_sizeLessThan);
}
return list;
}
QList<QSize> AndroidCameraPrivate::getSupportedVideoSizes()
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
QList<QSize> list;
if (m_parameters.isValid()) {
QJniObject sizeList = m_parameters.callObjectMethod("getSupportedVideoSizes",
"()Ljava/util/List;");
if (!sizeList.isValid())
return list;
int count = sizeList.callMethod<jint>("size");
for (int i = 0; i < count; ++i) {
const QJniObject size = sizeList.callObjectMethod("get", "(I)Ljava/lang/Object;", i);
if (size.isValid())
list.append(QSize(size.getField<jint>("width"), size.getField<jint>("height")));
}
ranges::sort(list, qt_sizeLessThan);
}
return list;
}
void AndroidCameraPrivate::setPictureSize(const QSize &size)
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return;
m_parameters.callMethod<void>("setPictureSize", "(II)V", size.width(), size.height());
applyParameters();
}
void AndroidCameraPrivate::setJpegQuality(int quality)
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
if (!m_parameters.isValid())
return;
m_parameters.callMethod<void>("setJpegQuality", "(I)V", quality);
applyParameters();
}
void AndroidCameraPrivate::startPreview()
{
setupPreviewFrameCallback();
QJniEnvironment env;
auto methodId = env->GetMethodID(m_camera.objectClass(), "startPreview", "()V");
env->CallVoidMethod(m_camera.object(), methodId);
if (env.checkAndClearExceptions())
Q_EMIT previewFailedToStart();
else
Q_EMIT previewStarted();
}
void AndroidCameraPrivate::stopPreview()
{
// cancel any pending new frame notification
m_cameraListener.callMethod<void>("notifyWhenFrameAvailable", "(Z)V", false);
m_camera.callMethod<void>("stopPreview");
Q_EMIT previewStopped();
}
void AndroidCameraPrivate::takePicture()
{
// 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<void>("clearPreviewCallback", "(Landroid/hardware/Camera;)V", m_camera.object());
QJniEnvironment env;
auto methodId = env->GetMethodID(m_camera.objectClass(), "takePicture",
"(Landroid/hardware/Camera$ShutterCallback;"
"Landroid/hardware/Camera$PictureCallback;"
"Landroid/hardware/Camera$PictureCallback;)V");
env->CallVoidMethod(m_camera.object(), methodId, m_cameraListener.object(),
jobject(0), m_cameraListener.object());
if (env.checkAndClearExceptions())
Q_EMIT takePictureFailed();
}
void AndroidCameraPrivate::setupPreviewFrameCallback()
{
m_cameraListener.callMethod<void>("setupPreviewCallback", "(Landroid/hardware/Camera;)V", m_camera.object());
}
void AndroidCameraPrivate::notifyNewFrames(bool notify)
{
m_cameraListener.callMethod<void>("notifyNewFrames", "(Z)V", notify);
}
void AndroidCameraPrivate::fetchLastPreviewFrame()
{
QJniEnvironment env;
QJniObject 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<void>("notifyWhenFrameAvailable", "(Z)V", true);
return;
}
const int arrayLength = env->GetArrayLength(static_cast<jbyteArray>(data.object()));
if (arrayLength == 0)
return;
QByteArray bytes(arrayLength, Qt::Uninitialized);
env->GetByteArrayRegion(static_cast<jbyteArray>(data.object()),
0,
arrayLength,
reinterpret_cast<jbyte *>(bytes.data()));
const int width = m_cameraListener.callMethod<jint>("previewWidth");
const int height = m_cameraListener.callMethod<jint>("previewHeight");
const int format = m_cameraListener.callMethod<jint>("previewFormat");
const int bpl = m_cameraListener.callMethod<jint>("previewBytesPerLine");
QVideoFrameFormat frameFormat(
QSize(width, height),
qt_pixelFormatFromAndroidImageFormat(AndroidCamera::ImageFormat(format)));
QVideoFrame frame = QVideoFramePrivate::createFrame(
std::make_unique<QMemoryVideoBuffer>(std::move(bytes), bpl), std::move(frameFormat));
Q_EMIT lastPreviewFrameFetched(frame);
}
void AndroidCameraPrivate::applyParameters()
{
QJniEnvironment env;
m_camera.callMethod<void>("setParameters",
"(Landroid/hardware/Camera$Parameters;)V",
m_parameters.object());
}
QStringList AndroidCameraPrivate::callParametersStringListMethod(const QByteArray &methodName)
{
const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex);
QStringList stringList;
if (m_parameters.isValid()) {
QJniObject list = m_parameters.callObjectMethod(methodName.constData(),
"()Ljava/util/List;");
if (list.isValid()) {
int count = list.callMethod<jint>("size");
for (int i = 0; i < count; ++i) {
QJniObject string = list.callObjectMethod("get",
"(I)Ljava/lang/Object;",
i);
stringList.append(string.toString());
}
}
}
return stringList;
}
bool AndroidCamera::registerNativeMethods()
{
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}
};
const int size = std::size(methods);
return QJniEnvironment().registerNativeMethods(QtCameraListenerClassName, methods, size);
}
QT_END_NAMESPACE
#include "androidcamera.moc"
#include "moc_androidcamera_p.cpp"