Android: fix QMediaRecorder crashing the camera server on some devices.
Some devices require MediaRecorder.setPreviewDisplay() to always be called, even though the Android doc says otherwise. Task-number: QTBUG-37837 Change-Id: I1e9b56f06e7c41bdf684f93b5ec7635f8ae9f379 Reviewed-by: Christian Stromme <christian.stromme@theqtcompany.com>
This commit is contained in:
@@ -68,6 +68,7 @@ public:
|
|||||||
void setCaptureMode(QCamera::CaptureModes mode);
|
void setCaptureMode(QCamera::CaptureModes mode);
|
||||||
bool isCaptureModeSupported(QCamera::CaptureModes mode) const;
|
bool isCaptureModeSupported(QCamera::CaptureModes mode) const;
|
||||||
|
|
||||||
|
QAndroidVideoOutput *videoOutput() const { return m_videoOutput; }
|
||||||
void setVideoOutput(QAndroidVideoOutput *output);
|
void setVideoOutput(QAndroidVideoOutput *output);
|
||||||
void adjustViewfinderSize(const QSize &captureSize, bool restartPreview = true);
|
void adjustViewfinderSize(const QSize &captureSize, bool restartPreview = true);
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
#include "qandroidcamerasession.h"
|
#include "qandroidcamerasession.h"
|
||||||
#include "androidmultimediautils.h"
|
#include "androidmultimediautils.h"
|
||||||
#include "qandroidmultimediautils.h"
|
#include "qandroidmultimediautils.h"
|
||||||
|
#include "qandroidvideooutput.h"
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
@@ -217,6 +218,20 @@ void QAndroidCaptureSession::start()
|
|||||||
m_usedOutputLocation = QUrl::fromLocalFile(filePath);
|
m_usedOutputLocation = QUrl::fromLocalFile(filePath);
|
||||||
m_mediaRecorder->setOutputFile(filePath);
|
m_mediaRecorder->setOutputFile(filePath);
|
||||||
|
|
||||||
|
// Even though the Android doc explicitly says that calling MediaRecorder.setPreviewDisplay()
|
||||||
|
// is not necessary when the Camera already has a Surface, it doesn't actually work on some
|
||||||
|
// devices. For example on the Samsung Galaxy Tab 2, the camera server dies after prepare()
|
||||||
|
// and start() if MediaRecorder.setPreviewDispaly() is not called.
|
||||||
|
if (m_cameraSession) {
|
||||||
|
// When using a SurfaceTexture, we need to pass a new one to the MediaRecorder, not the same
|
||||||
|
// one that is set on the Camera or it will crash, hence the reset().
|
||||||
|
m_cameraSession->videoOutput()->reset();
|
||||||
|
if (m_cameraSession->videoOutput()->surfaceTexture())
|
||||||
|
m_mediaRecorder->setSurfaceTexture(m_cameraSession->videoOutput()->surfaceTexture());
|
||||||
|
else if (m_cameraSession->videoOutput()->surfaceHolder())
|
||||||
|
m_mediaRecorder->setSurfaceHolder(m_cameraSession->videoOutput()->surfaceHolder());
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_mediaRecorder->prepare()) {
|
if (!m_mediaRecorder->prepare()) {
|
||||||
emit error(QMediaRecorder::FormatError, QLatin1String("Unable to prepare the media recorder."));
|
emit error(QMediaRecorder::FormatError, QLatin1String("Unable to prepare the media recorder."));
|
||||||
restartViewfinder();
|
restartViewfinder();
|
||||||
@@ -412,13 +427,23 @@ void QAndroidCaptureSession::applySettings()
|
|||||||
|
|
||||||
void QAndroidCaptureSession::updateViewfinder()
|
void QAndroidCaptureSession::updateViewfinder()
|
||||||
{
|
{
|
||||||
m_cameraSession->camera()->stopPreview();
|
m_cameraSession->camera()->stopPreviewSynchronous();
|
||||||
m_cameraSession->adjustViewfinderSize(m_videoSettings.resolution(), false);
|
m_cameraSession->adjustViewfinderSize(m_videoSettings.resolution(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QAndroidCaptureSession::restartViewfinder()
|
void QAndroidCaptureSession::restartViewfinder()
|
||||||
{
|
{
|
||||||
m_cameraSession->camera()->reconnect();
|
m_cameraSession->camera()->reconnect();
|
||||||
|
|
||||||
|
// This is not necessary on most devices, but it crashes on some if we don't stop the
|
||||||
|
// preview and reset the preview display on the camera when recording is over.
|
||||||
|
m_cameraSession->camera()->stopPreviewSynchronous();
|
||||||
|
m_cameraSession->videoOutput()->reset();
|
||||||
|
if (m_cameraSession->videoOutput()->surfaceTexture())
|
||||||
|
m_cameraSession->camera()->setPreviewTexture(m_cameraSession->videoOutput()->surfaceTexture());
|
||||||
|
else if (m_cameraSession->videoOutput()->surfaceHolder())
|
||||||
|
m_cameraSession->camera()->setPreviewDisplay(m_cameraSession->videoOutput()->surfaceHolder());
|
||||||
|
|
||||||
m_cameraSession->camera()->startPreview();
|
m_cameraSession->camera()->startPreview();
|
||||||
m_cameraSession->setReadyForCapture(true);
|
m_cameraSession->setReadyForCapture(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -744,6 +744,12 @@ void AndroidCamera::stopPreview()
|
|||||||
QMetaObject::invokeMethod(d, "stopPreview");
|
QMetaObject::invokeMethod(d, "stopPreview");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AndroidCamera::stopPreviewSynchronous()
|
||||||
|
{
|
||||||
|
Q_D(AndroidCamera);
|
||||||
|
QMetaObject::invokeMethod(d, "stopPreview", Qt::BlockingQueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
AndroidCameraPrivate::AndroidCameraPrivate()
|
AndroidCameraPrivate::AndroidCameraPrivate()
|
||||||
: QObject(),
|
: QObject(),
|
||||||
m_parametersMutex(QMutex::Recursive)
|
m_parametersMutex(QMutex::Recursive)
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ public:
|
|||||||
|
|
||||||
void startPreview();
|
void startPreview();
|
||||||
void stopPreview();
|
void stopPreview();
|
||||||
|
void stopPreviewSynchronous();
|
||||||
|
|
||||||
void takePicture();
|
void takePicture();
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
#include "androidmediarecorder.h"
|
#include "androidmediarecorder.h"
|
||||||
|
|
||||||
#include "androidcamera.h"
|
#include "androidcamera.h"
|
||||||
|
#include "androidsurfacetexture.h"
|
||||||
|
#include "androidsurfaceview.h"
|
||||||
#include <QtCore/private/qjni_p.h>
|
#include <QtCore/private/qjni_p.h>
|
||||||
#include <qmap.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)
|
bool AndroidMediaRecorder::initJNI(JNIEnv *env)
|
||||||
{
|
{
|
||||||
jclass clazz = QJNIEnvironmentPrivate::findClass(QtMediaRecorderListenerClassName,
|
jclass clazz = QJNIEnvironmentPrivate::findClass(QtMediaRecorderListenerClassName,
|
||||||
|
|||||||
@@ -41,6 +41,8 @@
|
|||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
class AndroidCamera;
|
class AndroidCamera;
|
||||||
|
class AndroidSurfaceTexture;
|
||||||
|
class AndroidSurfaceHolder;
|
||||||
|
|
||||||
class AndroidCamcorderProfile
|
class AndroidCamcorderProfile
|
||||||
{
|
{
|
||||||
@@ -149,6 +151,9 @@ public:
|
|||||||
void setOutputFormat(OutputFormat format);
|
void setOutputFormat(OutputFormat format);
|
||||||
void setOutputFile(const QString &path);
|
void setOutputFile(const QString &path);
|
||||||
|
|
||||||
|
void setSurfaceTexture(AndroidSurfaceTexture *texture);
|
||||||
|
void setSurfaceHolder(AndroidSurfaceHolder *holder);
|
||||||
|
|
||||||
static bool initJNI(JNIEnv *env);
|
static bool initJNI(JNIEnv *env);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ AndroidSurfaceTexture::AndroidSurfaceTexture(unsigned int texName)
|
|||||||
|
|
||||||
AndroidSurfaceTexture::~AndroidSurfaceTexture()
|
AndroidSurfaceTexture::~AndroidSurfaceTexture()
|
||||||
{
|
{
|
||||||
if (QtAndroidPrivate::androidSdkVersion() > 13 && m_surfaceView.isValid())
|
if (QtAndroidPrivate::androidSdkVersion() > 13 && m_surface.isValid())
|
||||||
m_surfaceView.callMethod<void>("release");
|
m_surface.callMethod<void>("release");
|
||||||
|
|
||||||
if (m_surfaceTexture.isValid()) {
|
if (m_surfaceTexture.isValid()) {
|
||||||
release();
|
release();
|
||||||
@@ -124,21 +124,23 @@ jobject AndroidSurfaceTexture::surfaceTexture()
|
|||||||
return m_surfaceTexture.object();
|
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()
|
jobject AndroidSurfaceTexture::surfaceHolder()
|
||||||
{
|
{
|
||||||
if (!m_surfaceHolder.isValid()) {
|
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",
|
m_surfaceHolder = QJNIObjectPrivate("org/qtproject/qt5/android/multimedia/QtSurfaceTextureHolder",
|
||||||
"(Landroid/view/Surface;)V",
|
"(Landroid/view/Surface;)V",
|
||||||
m_surfaceView.object());
|
surface());
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_surfaceHolder.object();
|
return m_surfaceHolder.object();
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public:
|
|||||||
|
|
||||||
int textureID() const { return m_texID; }
|
int textureID() const { return m_texID; }
|
||||||
jobject surfaceTexture();
|
jobject surfaceTexture();
|
||||||
jobject surfaceView();
|
jobject surface();
|
||||||
jobject surfaceHolder();
|
jobject surfaceHolder();
|
||||||
inline bool isValid() const { return m_surfaceTexture.isValid(); }
|
inline bool isValid() const { return m_surfaceTexture.isValid(); }
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ Q_SIGNALS:
|
|||||||
private:
|
private:
|
||||||
int m_texID;
|
int m_texID;
|
||||||
QJNIObjectPrivate m_surfaceTexture;
|
QJNIObjectPrivate m_surfaceTexture;
|
||||||
QJNIObjectPrivate m_surfaceView;
|
QJNIObjectPrivate m_surface;
|
||||||
QJNIObjectPrivate m_surfaceHolder;
|
QJNIObjectPrivate m_surfaceHolder;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user