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:
Yoann Lopes
2015-05-13 17:04:12 +02:00
parent 8debbfbc9b
commit 43f3d14a99
8 changed files with 89 additions and 12 deletions

View File

@@ -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);

View File

@@ -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);
} }

View File

@@ -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)

View File

@@ -155,6 +155,7 @@ public:
void startPreview(); void startPreview();
void stopPreview(); void stopPreview();
void stopPreviewSynchronous();
void takePicture(); void takePicture();

View File

@@ -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,

View File

@@ -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:

View File

@@ -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();

View File

@@ -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;
}; };