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);
bool isCaptureModeSupported(QCamera::CaptureModes mode) const;
QAndroidVideoOutput *videoOutput() const { return m_videoOutput; }
void setVideoOutput(QAndroidVideoOutput *output);
void adjustViewfinderSize(const QSize &captureSize, bool restartPreview = true);

View File

@@ -37,6 +37,7 @@
#include "qandroidcamerasession.h"
#include "androidmultimediautils.h"
#include "qandroidmultimediautils.h"
#include "qandroidvideooutput.h"
QT_BEGIN_NAMESPACE
@@ -217,6 +218,20 @@ void QAndroidCaptureSession::start()
m_usedOutputLocation = QUrl::fromLocalFile(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()) {
emit error(QMediaRecorder::FormatError, QLatin1String("Unable to prepare the media recorder."));
restartViewfinder();
@@ -412,13 +427,23 @@ void QAndroidCaptureSession::applySettings()
void QAndroidCaptureSession::updateViewfinder()
{
m_cameraSession->camera()->stopPreview();
m_cameraSession->camera()->stopPreviewSynchronous();
m_cameraSession->adjustViewfinderSize(m_videoSettings.resolution(), false);
}
void QAndroidCaptureSession::restartViewfinder()
{
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->setReadyForCapture(true);
}

View File

@@ -744,6 +744,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)

View File

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

View File

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

View File

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

View File

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

View File

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