diff options
Diffstat (limited to 'src/plugins/android/src/wrappers/jni')
12 files changed, 570 insertions, 88 deletions
diff --git a/src/plugins/android/src/wrappers/jni/androidcamera.cpp b/src/plugins/android/src/wrappers/jni/androidcamera.cpp index a4acbd8f9..23200462e 100644 --- a/src/plugins/android/src/wrappers/jni/androidcamera.cpp +++ b/src/plugins/android/src/wrappers/jni/androidcamera.cpp @@ -33,20 +33,24 @@ #include "androidcamera.h" #include "androidsurfacetexture.h" +#include "androidsurfaceview.h" #include "qandroidmultimediautils.h" #include <qstringlist.h> #include <qdebug.h> -#include <qmutex.h> #include <QtCore/private/qjnihelpers_p.h> #include <QtCore/qthread.h> +#include <QtCore/qreadwritelock.h> +#include <QtCore/qmutex.h> +#include <QtMultimedia/private/qmemoryvideobuffer_p.h> QT_BEGIN_NAMESPACE static const char QtCameraListenerClassName[] = "org/qtproject/qt5/android/multimedia/QtCameraListener"; -static QMutex g_cameraMapMutex; -typedef QMap<int, AndroidCamera *> CameraMap; -Q_GLOBAL_STATIC(CameraMap, g_cameraMap) + +typedef QHash<int, AndroidCamera *> CameraMap; +Q_GLOBAL_STATIC(CameraMap, cameras) +Q_GLOBAL_STATIC(QReadWriteLock, rwLock) static inline bool exceptionCheckAndClear(JNIEnv *env) { @@ -88,43 +92,57 @@ static QJNIObjectPrivate rectToArea(const QRect &rect) // native method for QtCameraLisener.java static void notifyAutoFocusComplete(JNIEnv* , jobject, int id, jboolean success) { - QMutexLocker locker(&g_cameraMapMutex); - AndroidCamera *obj = g_cameraMap->value(id, 0); - if (obj) - Q_EMIT obj->autoFocusComplete(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) { - QMutexLocker locker(&g_cameraMapMutex); - AndroidCamera *obj = g_cameraMap->value(id, 0); - if (obj) - Q_EMIT obj->pictureExposed(); + 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) { - QMutexLocker locker(&g_cameraMapMutex); - AndroidCamera *obj = g_cameraMap->value(id, 0); - if (obj) { - const int arrayLength = env->GetArrayLength(data); - QByteArray bytes(arrayLength, Qt::Uninitialized); - env->GetByteArrayRegion(data, 0, arrayLength, (jbyte*)bytes.data()); - Q_EMIT obj->pictureCaptured(bytes); - } + 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) +static void notifyNewPreviewFrame(JNIEnv *env, jobject, int id, jbyteArray data, + int width, int height, int format, int bpl) { - QMutexLocker locker(&g_cameraMapMutex); - AndroidCamera *obj = g_cameraMap->value(id, 0); - if (obj) { - const int arrayLength = env->GetArrayLength(data); - QByteArray bytes(arrayLength, Qt::Uninitialized); - env->GetByteArrayRegion(data, 0, arrayLength, (jbyte*)bytes.data()); + QReadLocker locker(rwLock); + const auto it = cameras->constFind(id); + if (Q_UNLIKELY(it == cameras->cend())) + return; - Q_EMIT obj->newPreviewFrame(bytes, width, height); - } + 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); } class AndroidCameraPrivate : public QObject @@ -149,10 +167,12 @@ public: Q_INVOKABLE AndroidCamera::ImageFormat getPreviewFormat(); Q_INVOKABLE void setPreviewFormat(AndroidCamera::ImageFormat fmt); + Q_INVOKABLE QList<AndroidCamera::ImageFormat> getSupportedPreviewFormats(); Q_INVOKABLE QSize previewSize() const { return m_previewSize; } Q_INVOKABLE void updatePreviewSize(); Q_INVOKABLE bool setPreviewTexture(void *surfaceTexture); + Q_INVOKABLE bool setPreviewDisplay(void *surfaceHolder); Q_INVOKABLE bool isZoomSupported(); Q_INVOKABLE int getMaxZoom(); @@ -224,13 +244,16 @@ public: Q_SIGNALS: void previewSizeChanged(); void previewStarted(); + void previewFailedToStart(); void previewStopped(); void autoFocusStarted(); void whiteBalanceChanged(); - void lastPreviewFrameFetched(const QByteArray &preview, int width, int height); + void takePictureFailed(); + + void lastPreviewFrameFetched(const QVideoFrame &frame); }; AndroidCamera::AndroidCamera(AndroidCameraPrivate *d, QThread *worker) @@ -242,12 +265,15 @@ AndroidCamera::AndroidCamera(AndroidCameraPrivate *d, QThread *worker) qRegisterMetaType<QList<int> >(); qRegisterMetaType<QList<QSize> >(); qRegisterMetaType<QList<QRect> >(); + qRegisterMetaType<ImageFormat>(); 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); } @@ -255,12 +281,11 @@ AndroidCamera::~AndroidCamera() { Q_D(AndroidCamera); if (d->m_camera.isValid()) { - g_cameraMapMutex.lock(); - g_cameraMap->remove(d->m_cameraId); - g_cameraMapMutex.unlock(); + release(); + QWriteLocker locker(rwLock); + cameras->remove(cameraId()); } - release(); m_worker->exit(); m_worker->wait(5000); } @@ -283,9 +308,9 @@ AndroidCamera *AndroidCamera::open(int cameraId) } AndroidCamera *q = new AndroidCamera(d, worker); - g_cameraMapMutex.lock(); - g_cameraMap->insert(cameraId, q); - g_cameraMapMutex.unlock(); + QWriteLocker locker(rwLock); + cameras->insert(cameraId, q); + return q; } @@ -361,6 +386,12 @@ void AndroidCamera::setPreviewFormat(ImageFormat fmt) 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); @@ -392,6 +423,18 @@ bool AndroidCamera::setPreviewTexture(AndroidSurfaceTexture *surfaceTexture) 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; +} + bool AndroidCamera::isZoomSupported() { Q_D(AndroidCamera); @@ -706,6 +749,12 @@ void AndroidCamera::stopPreview() QMetaObject::invokeMethod(d, "stopPreview"); } +void AndroidCamera::stopPreviewSynchronous() +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "stopPreview", Qt::BlockingQueuedConnection); +} + AndroidCameraPrivate::AndroidCameraPrivate() : QObject(), m_parametersMutex(QMutex::Recursive) @@ -827,7 +876,7 @@ AndroidCamera::ImageFormat AndroidCameraPrivate::getPreviewFormat() QMutexLocker parametersLocker(&m_parametersMutex); if (!m_parameters.isValid()) - return AndroidCamera::Unknown; + return AndroidCamera::UnknownImageFormat; return AndroidCamera::ImageFormat(m_parameters.callMethod<jint>("getPreviewFormat")); } @@ -843,6 +892,27 @@ void AndroidCameraPrivate::setPreviewFormat(AndroidCamera::ImageFormat fmt) applyParameters(); } +QList<AndroidCamera::ImageFormat> AndroidCameraPrivate::getSupportedPreviewFormats() +{ + QList<AndroidCamera::ImageFormat> list; + + QMutexLocker parametersLocker(&m_parametersMutex); + + if (m_parameters.isValid()) { + QJNIObjectPrivate formatList = m_parameters.callObjectMethod("getSupportedPreviewFormats", + "()Ljava/util/List;"); + int count = formatList.callMethod<jint>("size"); + for (int i = 0; i < count; ++i) { + QJNIObjectPrivate format = formatList.callObjectMethod("get", + "(I)Ljava/lang/Object;", + i); + list.append(AndroidCamera::ImageFormat(format.callMethod<jint>("intValue"))); + } + } + + return list; +} + void AndroidCameraPrivate::updatePreviewSize() { QMutexLocker parametersLocker(&m_parametersMutex); @@ -864,6 +934,15 @@ bool AndroidCameraPrivate::setPreviewTexture(void *surfaceTexture) return !exceptionCheckAndClear(env); } +bool AndroidCameraPrivate::setPreviewDisplay(void *surfaceHolder) +{ + QJNIEnvironmentPrivate env; + m_camera.callMethod<void>("setPreviewDisplay", + "(Landroid/view/SurfaceHolder;)V", + static_cast<jobject>(surfaceHolder)); + return !exceptionCheckAndClear(env); +} + bool AndroidCameraPrivate::isZoomSupported() { QMutexLocker parametersLocker(&m_parametersMutex); @@ -1057,15 +1136,21 @@ void AndroidCameraPrivate::setFocusAreas(const QList<QRect> &areas) void AndroidCameraPrivate::autoFocus() { + QJNIEnvironmentPrivate env; + m_camera.callMethod<void>("autoFocus", "(Landroid/hardware/Camera$AutoFocusCallback;)V", m_cameraListener.object()); - emit autoFocusStarted(); + + if (!exceptionCheckAndClear(env)) + emit autoFocusStarted(); } void AndroidCameraPrivate::cancelAutoFocus() { + QJNIEnvironmentPrivate env; m_camera.callMethod<void>("cancelAutoFocus"); + exceptionCheckAndClear(env); } bool AndroidCameraPrivate::isAutoExposureLockSupported() @@ -1314,25 +1399,40 @@ void AndroidCameraPrivate::setJpegQuality(int quality) void AndroidCameraPrivate::startPreview() { + QJNIEnvironmentPrivate env; + setupPreviewFrameCallback(); m_camera.callMethod<void>("startPreview"); - emit previewStarted(); + + if (exceptionCheckAndClear(env)) + emit previewFailedToStart(); + else + emit previewStarted(); } void AndroidCameraPrivate::stopPreview() { + QJNIEnvironmentPrivate env; + m_camera.callMethod<void>("stopPreview"); + + exceptionCheckAndClear(env); emit previewStopped(); } void AndroidCameraPrivate::takePicture() { + QJNIEnvironmentPrivate env; + m_camera.callMethod<void>("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() @@ -1354,15 +1454,25 @@ void AndroidCameraPrivate::fetchLastPreviewFrame() 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())); - emit lastPreviewFrameFetched(bytes, - m_cameraListener.callMethod<jint>("previewWidth"), - m_cameraListener.callMethod<jint>("previewHeight")); + 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"); + + QVideoFrame frame(new QMemoryVideoBuffer(bytes, bpl), + QSize(width, height), + qt_pixelFormatFromAndroidImageFormat(AndroidCamera::ImageFormat(format))); + + emit lastPreviewFrameFetched(frame); } void AndroidCameraPrivate::applyParameters() @@ -1407,7 +1517,7 @@ bool AndroidCamera::initJNI(JNIEnv *env) {"notifyAutoFocusComplete", "(IZ)V", (void *)notifyAutoFocusComplete}, {"notifyPictureExposed", "(I)V", (void *)notifyPictureExposed}, {"notifyPictureCaptured", "(I[B)V", (void *)notifyPictureCaptured}, - {"notifyNewPreviewFrame", "(I[BII)V", (void *)notifyNewPreviewFrame} + {"notifyNewPreviewFrame", "(I[BIIII)V", (void *)notifyNewPreviewFrame} }; if (clazz && env->RegisterNatives(clazz, diff --git a/src/plugins/android/src/wrappers/jni/androidcamera.h b/src/plugins/android/src/wrappers/jni/androidcamera.h index 7a8ae8b23..a5e0294c0 100644 --- a/src/plugins/android/src/wrappers/jni/androidcamera.h +++ b/src/plugins/android/src/wrappers/jni/androidcamera.h @@ -46,6 +46,7 @@ class QThread; class AndroidCameraPrivate; class AndroidSurfaceTexture; +class AndroidSurfaceHolder; struct AndroidCameraInfo { @@ -67,7 +68,7 @@ public: }; enum ImageFormat { // same values as in android.graphics.ImageFormat Java class - Unknown = 0, + UnknownImageFormat = 0, RGB565 = 4, NV16 = 16, NV21 = 17, @@ -95,10 +96,12 @@ public: ImageFormat getPreviewFormat(); void setPreviewFormat(ImageFormat fmt); + QList<ImageFormat> getSupportedPreviewFormats(); QSize previewSize() const; void setPreviewSize(const QSize &size); bool setPreviewTexture(AndroidSurfaceTexture *surfaceTexture); + bool setPreviewDisplay(AndroidSurfaceHolder *surfaceHolder); bool isZoomSupported(); int getMaxZoom(); @@ -152,6 +155,7 @@ public: void startPreview(); void stopPreview(); + void stopPreviewSynchronous(); void takePicture(); @@ -168,6 +172,7 @@ public: Q_SIGNALS: void previewSizeChanged(); void previewStarted(); + void previewFailedToStart(); void previewStopped(); void autoFocusStarted(); @@ -175,10 +180,11 @@ Q_SIGNALS: void whiteBalanceChanged(); + void takePictureFailed(); void pictureExposed(); void pictureCaptured(const QByteArray &data); - void lastPreviewFrameFetched(const QByteArray &preview, int width, int height); - void newPreviewFrame(const QByteArray &frame, int width, int height); + void lastPreviewFrameFetched(const QVideoFrame &frame); + void newPreviewFrame(const QVideoFrame &frame); private: AndroidCamera(AndroidCameraPrivate *d, QThread *worker); @@ -188,6 +194,8 @@ private: QScopedPointer<QThread> m_worker; }; +Q_DECLARE_METATYPE(AndroidCamera::ImageFormat) + QT_END_NAMESPACE #endif // ANDROIDCAMERA_H diff --git a/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.cpp b/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.cpp index 56ac0e0ac..f67428b6e 100644 --- a/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.cpp +++ b/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.cpp @@ -60,6 +60,7 @@ AndroidMediaMetadataRetriever::AndroidMediaMetadataRetriever() AndroidMediaMetadataRetriever::~AndroidMediaMetadataRetriever() { + release(); } QString AndroidMediaMetadataRetriever::extractMetadata(MetadataKey key) diff --git a/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.h b/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.h index 01a98490b..1b4a09bb7 100644 --- a/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.h +++ b/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.h @@ -71,10 +71,10 @@ public: ~AndroidMediaMetadataRetriever(); QString extractMetadata(MetadataKey key); - void release(); bool setDataSource(const QUrl &url); private: + void release(); QJNIObjectPrivate m_metadataRetriever; }; diff --git a/src/plugins/android/src/wrappers/jni/androidmediaplayer.cpp b/src/plugins/android/src/wrappers/jni/androidmediaplayer.cpp index 8fbecbc73..3267d838b 100644 --- a/src/plugins/android/src/wrappers/jni/androidmediaplayer.cpp +++ b/src/plugins/android/src/wrappers/jni/androidmediaplayer.cpp @@ -37,29 +37,34 @@ #include <QtCore/private/qjni_p.h> #include <QtCore/private/qjnihelpers_p.h> #include "androidsurfacetexture.h" -#include <QMap> +#include <QVector> +#include <QReadWriteLock> static const char QtAndroidMediaPlayerClassName[] = "org/qtproject/qt5/android/multimedia/QtAndroidMediaPlayer"; -typedef QMap<jlong, AndroidMediaPlayer *> MediaPlayerMap; -Q_GLOBAL_STATIC(MediaPlayerMap, mediaPlayers) +typedef QVector<AndroidMediaPlayer *> MediaPlayerList; +Q_GLOBAL_STATIC(MediaPlayerList, mediaPlayers) +Q_GLOBAL_STATIC(QReadWriteLock, rwLock) QT_BEGIN_NAMESPACE AndroidMediaPlayer::AndroidMediaPlayer() : QObject() { - + QWriteLocker locker(rwLock); const jlong id = reinterpret_cast<jlong>(this); mMediaPlayer = QJNIObjectPrivate(QtAndroidMediaPlayerClassName, "(Landroid/app/Activity;J)V", QtAndroidPrivate::activity(), id); - (*mediaPlayers)[id] = this; + mediaPlayers->append(this); } AndroidMediaPlayer::~AndroidMediaPlayer() { - mediaPlayers->remove(reinterpret_cast<jlong>(this)); + QWriteLocker locker(rwLock); + const int i = mediaPlayers->indexOf(this); + Q_ASSERT(i != -1); + mediaPlayers->remove(i); } void AndroidMediaPlayer::release() @@ -154,66 +159,72 @@ static void onErrorNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlon { Q_UNUSED(env); Q_UNUSED(thiz); - AndroidMediaPlayer *const mp = (*mediaPlayers)[id]; - if (!mp) + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) return; - Q_EMIT mp->error(what, extra); + Q_EMIT (*mediaPlayers)[i]->error(what, extra); } static void onBufferingUpdateNative(JNIEnv *env, jobject thiz, jint percent, jlong id) { Q_UNUSED(env); Q_UNUSED(thiz); - AndroidMediaPlayer *const mp = (*mediaPlayers)[id]; - if (!mp) + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) return; - Q_EMIT mp->bufferingChanged(percent); + Q_EMIT (*mediaPlayers)[i]->bufferingChanged(percent); } static void onProgressUpdateNative(JNIEnv *env, jobject thiz, jint progress, jlong id) { Q_UNUSED(env); Q_UNUSED(thiz); - AndroidMediaPlayer *const mp = (*mediaPlayers)[id]; - if (!mp) + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) return; - Q_EMIT mp->progressChanged(progress); + Q_EMIT (*mediaPlayers)[i]->progressChanged(progress); } static void onDurationChangedNative(JNIEnv *env, jobject thiz, jint duration, jlong id) { Q_UNUSED(env); Q_UNUSED(thiz); - AndroidMediaPlayer *const mp = (*mediaPlayers)[id]; - if (!mp) + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) return; - Q_EMIT mp->durationChanged(duration); + Q_EMIT (*mediaPlayers)[i]->durationChanged(duration); } static void onInfoNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlong id) { Q_UNUSED(env); Q_UNUSED(thiz); - AndroidMediaPlayer *const mp = (*mediaPlayers)[id]; - if (!mp) + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) return; - Q_EMIT mp->info(what, extra); + Q_EMIT (*mediaPlayers)[i]->info(what, extra); } static void onStateChangedNative(JNIEnv *env, jobject thiz, jint state, jlong id) { Q_UNUSED(env); Q_UNUSED(thiz); - AndroidMediaPlayer *const mp = (*mediaPlayers)[id]; - if (!mp) + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) return; - Q_EMIT mp->stateChanged(state); + Q_EMIT (*mediaPlayers)[i]->stateChanged(state); } static void onVideoSizeChangedNative(JNIEnv *env, @@ -224,11 +235,12 @@ static void onVideoSizeChangedNative(JNIEnv *env, { Q_UNUSED(env); Q_UNUSED(thiz); - AndroidMediaPlayer *const mp = (*mediaPlayers)[id]; - if (!mp) + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) return; - Q_EMIT mp->videoSizeChanged(width, height); + Q_EMIT (*mediaPlayers)[i]->videoSizeChanged(width, height); } bool AndroidMediaPlayer::initJNI(JNIEnv *env) diff --git a/src/plugins/android/src/wrappers/jni/androidmediarecorder.cpp b/src/plugins/android/src/wrappers/jni/androidmediarecorder.cpp index fa32f31ef..34063056f 100644 --- a/src/plugins/android/src/wrappers/jni/androidmediarecorder.cpp +++ b/src/plugins/android/src/wrappers/jni/androidmediarecorder.cpp @@ -34,6 +34,8 @@ #include "androidmediarecorder.h" #include "androidcamera.h" +#include "androidsurfacetexture.h" +#include "androidsurfaceview.h" #include <QtCore/private/qjni_p.h> #include <qmap.h> @@ -339,6 +341,41 @@ void AndroidMediaRecorder::setOutputFile(const QString &path) } } +void AndroidMediaRecorder::setSurfaceTexture(AndroidSurfaceTexture *texture) +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("setPreviewDisplay", + "(Landroid/view/Surface;)V", + texture->surface()); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + +void AndroidMediaRecorder::setSurfaceHolder(AndroidSurfaceHolder *holder) +{ + QJNIEnvironmentPrivate env; + QJNIObjectPrivate surfaceHolder(holder->surfaceHolder()); + QJNIObjectPrivate surface = surfaceHolder.callObjectMethod("getSurface", + "()Landroid/view/Surface;"); + if (!surface.isValid()) + return; + + m_mediaRecorder.callMethod<void>("setPreviewDisplay", + "(Landroid/view/Surface;)V", + surface.object()); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + + bool AndroidMediaRecorder::initJNI(JNIEnv *env) { jclass clazz = QJNIEnvironmentPrivate::findClass(QtMediaRecorderListenerClassName, diff --git a/src/plugins/android/src/wrappers/jni/androidmediarecorder.h b/src/plugins/android/src/wrappers/jni/androidmediarecorder.h index 1aa83a201..95b48ed47 100644 --- a/src/plugins/android/src/wrappers/jni/androidmediarecorder.h +++ b/src/plugins/android/src/wrappers/jni/androidmediarecorder.h @@ -41,6 +41,8 @@ QT_BEGIN_NAMESPACE class AndroidCamera; +class AndroidSurfaceTexture; +class AndroidSurfaceHolder; class AndroidCamcorderProfile { @@ -149,6 +151,9 @@ public: void setOutputFormat(OutputFormat format); void setOutputFile(const QString &path); + void setSurfaceTexture(AndroidSurfaceTexture *texture); + void setSurfaceHolder(AndroidSurfaceHolder *holder); + static bool initJNI(JNIEnv *env); Q_SIGNALS: diff --git a/src/plugins/android/src/wrappers/jni/androidsurfacetexture.cpp b/src/plugins/android/src/wrappers/jni/androidsurfacetexture.cpp index ffa37d7d4..9a25b7e28 100644 --- a/src/plugins/android/src/wrappers/jni/androidsurfacetexture.cpp +++ b/src/plugins/android/src/wrappers/jni/androidsurfacetexture.cpp @@ -78,8 +78,8 @@ AndroidSurfaceTexture::AndroidSurfaceTexture(unsigned int texName) AndroidSurfaceTexture::~AndroidSurfaceTexture() { - if (QtAndroidPrivate::androidSdkVersion() > 13 && m_surfaceView.isValid()) - m_surfaceView.callMethod<void>("release"); + if (QtAndroidPrivate::androidSdkVersion() > 13 && m_surface.isValid()) + m_surface.callMethod<void>("release"); if (m_surfaceTexture.isValid()) { release(); @@ -124,21 +124,23 @@ jobject AndroidSurfaceTexture::surfaceTexture() return m_surfaceTexture.object(); } -jobject AndroidSurfaceTexture::surfaceView() +jobject AndroidSurfaceTexture::surface() { - return m_surfaceView.object(); + if (!m_surface.isValid()) { + m_surface = QJNIObjectPrivate("android/view/Surface", + "(Landroid/graphics/SurfaceTexture;)V", + m_surfaceTexture.object()); + } + + return m_surface.object(); } jobject AndroidSurfaceTexture::surfaceHolder() { if (!m_surfaceHolder.isValid()) { - m_surfaceView = QJNIObjectPrivate("android/view/Surface", - "(Landroid/graphics/SurfaceTexture;)V", - m_surfaceTexture.object()); - m_surfaceHolder = QJNIObjectPrivate("org/qtproject/qt5/android/multimedia/QtSurfaceTextureHolder", "(Landroid/view/Surface;)V", - m_surfaceView.object()); + surface()); } return m_surfaceHolder.object(); diff --git a/src/plugins/android/src/wrappers/jni/androidsurfacetexture.h b/src/plugins/android/src/wrappers/jni/androidsurfacetexture.h index 2618ed6c9..ac2af694e 100644 --- a/src/plugins/android/src/wrappers/jni/androidsurfacetexture.h +++ b/src/plugins/android/src/wrappers/jni/androidsurfacetexture.h @@ -50,7 +50,7 @@ public: int textureID() const { return m_texID; } jobject surfaceTexture(); - jobject surfaceView(); + jobject surface(); jobject surfaceHolder(); inline bool isValid() const { return m_surfaceTexture.isValid(); } @@ -66,7 +66,7 @@ Q_SIGNALS: private: int m_texID; QJNIObjectPrivate m_surfaceTexture; - QJNIObjectPrivate m_surfaceView; + QJNIObjectPrivate m_surface; QJNIObjectPrivate m_surfaceHolder; }; diff --git a/src/plugins/android/src/wrappers/jni/androidsurfaceview.cpp b/src/plugins/android/src/wrappers/jni/androidsurfaceview.cpp new file mode 100644 index 000000000..67560baf4 --- /dev/null +++ b/src/plugins/android/src/wrappers/jni/androidsurfaceview.cpp @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "androidsurfaceview.h" + +#include <QtCore/private/qjnihelpers_p.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qvector.h> +#include <QtCore/qdebug.h> +#include <QtCore/qmutex.h> +#include <QtGui/qwindow.h> + +QT_BEGIN_NAMESPACE + +static const char QtSurfaceHolderCallbackClassName[] = "org/qtproject/qt5/android/multimedia/QtSurfaceHolderCallback"; +typedef QVector<AndroidSurfaceHolder *> SurfaceHolders; +Q_GLOBAL_STATIC(SurfaceHolders, surfaceHolders) +Q_GLOBAL_STATIC(QMutex, shLock) + +AndroidSurfaceHolder::AndroidSurfaceHolder(QJNIObjectPrivate object) + : m_surfaceHolder(object) + , m_surfaceCreated(false) +{ + if (!m_surfaceHolder.isValid()) + return; + + { + QMutexLocker locker(shLock); + surfaceHolders->append(this); + } + + QJNIObjectPrivate callback(QtSurfaceHolderCallbackClassName, "(J)V", reinterpret_cast<jlong>(this)); + m_surfaceHolder.callMethod<void>("addCallback", + "(Landroid/view/SurfaceHolder$Callback;)V", + callback.object()); +} + +AndroidSurfaceHolder::~AndroidSurfaceHolder() +{ + QMutexLocker locker(shLock); + const int i = surfaceHolders->indexOf(this); + if (Q_UNLIKELY(i == -1)) + return; + + surfaceHolders->remove(i); +} + +jobject AndroidSurfaceHolder::surfaceHolder() const +{ + return m_surfaceHolder.object(); +} + +bool AndroidSurfaceHolder::isSurfaceCreated() const +{ + QMutexLocker locker(shLock); + return m_surfaceCreated; +} + +void AndroidSurfaceHolder::handleSurfaceCreated(JNIEnv*, jobject, jlong id) +{ + QMutexLocker locker(shLock); + const int i = surfaceHolders->indexOf(reinterpret_cast<AndroidSurfaceHolder *>(id)); + if (Q_UNLIKELY(i == -1)) + return; + + (*surfaceHolders)[i]->m_surfaceCreated = true; + Q_EMIT (*surfaceHolders)[i]->surfaceCreated(); +} + +void AndroidSurfaceHolder::handleSurfaceDestroyed(JNIEnv*, jobject, jlong id) +{ + QMutexLocker locker(shLock); + const int i = surfaceHolders->indexOf(reinterpret_cast<AndroidSurfaceHolder *>(id)); + if (Q_UNLIKELY(i == -1)) + return; + + (*surfaceHolders)[i]->m_surfaceCreated = false; +} + +bool AndroidSurfaceHolder::initJNI(JNIEnv *env) +{ + jclass clazz = QJNIEnvironmentPrivate::findClass(QtSurfaceHolderCallbackClassName, + env); + + static const JNINativeMethod methods[] = { + {"notifySurfaceCreated", "(J)V", (void *)AndroidSurfaceHolder::handleSurfaceCreated}, + {"notifySurfaceDestroyed", "(J)V", (void *)AndroidSurfaceHolder::handleSurfaceDestroyed} + }; + + if (clazz && env->RegisterNatives(clazz, + methods, + sizeof(methods) / sizeof(methods[0])) != JNI_OK) { + return false; + } + + return true; +} + +AndroidSurfaceView::AndroidSurfaceView() + : m_window(0) + , m_surfaceHolder(0) + , m_pendingVisible(-1) +{ + setAutoDelete(false); + QtAndroidPrivate::runOnUiThread(this, QJNIEnvironmentPrivate()); +} + +AndroidSurfaceView::~AndroidSurfaceView() +{ + delete m_surfaceHolder; + delete m_window; +} + +AndroidSurfaceHolder *AndroidSurfaceView::holder() const +{ + return m_surfaceHolder; +} + +void AndroidSurfaceView::setVisible(bool v) +{ + if (m_window) + m_window->setVisible(v); + else + m_pendingVisible = int(v); +} + +void AndroidSurfaceView::setGeometry(int x, int y, int width, int height) +{ + if (m_window) + m_window->setGeometry(x, y, width, height); + else + m_pendingGeometry = QRect(x, y, width, height); +} + +bool AndroidSurfaceView::event(QEvent *e) +{ + if (e->type() == QEvent::User) { + Q_ASSERT(m_surfaceView.isValid()); + + QJNIObjectPrivate holder = m_surfaceView.callObjectMethod("getHolder", + "()Landroid/view/SurfaceHolder;"); + if (!holder.isValid()) { + m_surfaceView = QJNIObjectPrivate(); + } else { + m_surfaceHolder = new AndroidSurfaceHolder(holder); + connect(m_surfaceHolder, &AndroidSurfaceHolder::surfaceCreated, + this, &AndroidSurfaceView::surfaceCreated); + { // Lock now to avoid a race with handleSurfaceCreated() + QMutexLocker locker(shLock); + m_window = QWindow::fromWinId(WId(m_surfaceView.object())); + + if (m_pendingVisible != -1) + m_window->setVisible(m_pendingVisible); + if (m_pendingGeometry.isValid()) + m_window->setGeometry(m_pendingGeometry); + } + } + + return true; + } + + return QObject::event(e); +} + +// Called on the Android UI thread. +void AndroidSurfaceView::run() +{ + m_surfaceView = QJNIObjectPrivate("android/view/SurfaceView", + "(Landroid/content/Context;)V", + QtAndroidPrivate::activity()); + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); +} + +QT_END_NAMESPACE diff --git a/src/plugins/android/src/wrappers/jni/androidsurfaceview.h b/src/plugins/android/src/wrappers/jni/androidsurfaceview.h new file mode 100644 index 000000000..661a5959f --- /dev/null +++ b/src/plugins/android/src/wrappers/jni/androidsurfaceview.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef ANDROIDSURFACEVIEW_H +#define ANDROIDSURFACEVIEW_H + +#include <QtCore/private/qjni_p.h> +#include <qrect.h> +#include <QtCore/qrunnable.h> + +QT_BEGIN_NAMESPACE + +class QWindow; + +class AndroidSurfaceHolder : public QObject +{ + Q_OBJECT +public: + ~AndroidSurfaceHolder(); + + jobject surfaceHolder() const; + bool isSurfaceCreated() const; + + static bool initJNI(JNIEnv *env); + +Q_SIGNALS: + void surfaceCreated(); + +private: + AndroidSurfaceHolder(QJNIObjectPrivate object); + + static void handleSurfaceCreated(JNIEnv*, jobject, jlong id); + static void handleSurfaceDestroyed(JNIEnv*, jobject, jlong id); + + QJNIObjectPrivate m_surfaceHolder; + bool m_surfaceCreated; + + friend class AndroidSurfaceView; +}; + +class AndroidSurfaceView : public QObject, public QRunnable +{ + Q_OBJECT +public: + AndroidSurfaceView(); + ~AndroidSurfaceView(); + + AndroidSurfaceHolder *holder() const; + + void setVisible(bool v); + void setGeometry(int x, int y, int width, int height); + + bool event(QEvent *); + +Q_SIGNALS: + void surfaceCreated(); + +protected: + void run() override; + +private: + QJNIObjectPrivate m_surfaceView; + QWindow *m_window; + AndroidSurfaceHolder *m_surfaceHolder; + int m_pendingVisible; + QRect m_pendingGeometry; +}; + +QT_END_NAMESPACE + +#endif // ANDROIDSURFACEVIEW_H diff --git a/src/plugins/android/src/wrappers/jni/jni.pri b/src/plugins/android/src/wrappers/jni/jni.pri index e96baff1c..930d7e922 100644 --- a/src/plugins/android/src/wrappers/jni/jni.pri +++ b/src/plugins/android/src/wrappers/jni/jni.pri @@ -8,7 +8,8 @@ HEADERS += \ $$PWD/androidmediametadataretriever.h \ $$PWD/androidcamera.h \ $$PWD/androidmultimediautils.h \ - $$PWD/androidmediarecorder.h + $$PWD/androidmediarecorder.h \ + $$PWD/androidsurfaceview.h SOURCES += \ $$PWD/androidmediaplayer.cpp \ @@ -16,4 +17,5 @@ SOURCES += \ $$PWD/androidmediametadataretriever.cpp \ $$PWD/androidcamera.cpp \ $$PWD/androidmultimediautils.cpp \ - $$PWD/androidmediarecorder.cpp + $$PWD/androidmediarecorder.cpp \ + $$PWD/androidsurfaceview.cpp |