Android: fix camera preview
At some(?) devices we can't rely on one shot preview callback because it receives data only after we start new previewing session. And this cause situation when imageCaptured signal is never emitted. This fix applies preview callback with already allocated buffers and collects all preview images in these buffers. When we capture image - we can simply fetch last preview image (if there was any) from c++ part. Task-number: QTBUG-34993 Change-Id: I608750c344ca3c089f4673df4907e0f47e57e2ba Reviewed-by: Yoann Lopes <yoann.lopes@digia.com>
This commit is contained in:
committed by
The Qt Project
parent
29ded57cef
commit
ecce937a05
@@ -42,8 +42,11 @@
|
|||||||
package org.qtproject.qt5.android.multimedia;
|
package org.qtproject.qt5.android.multimedia;
|
||||||
|
|
||||||
import android.hardware.Camera;
|
import android.hardware.Camera;
|
||||||
|
import android.graphics.ImageFormat;
|
||||||
import android.graphics.SurfaceTexture;
|
import android.graphics.SurfaceTexture;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import java.lang.Math;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
public class QtCamera implements Camera.ShutterCallback,
|
public class QtCamera implements Camera.ShutterCallback,
|
||||||
Camera.PictureCallback,
|
Camera.PictureCallback,
|
||||||
@@ -52,6 +55,11 @@ public class QtCamera implements Camera.ShutterCallback,
|
|||||||
{
|
{
|
||||||
private int m_cameraId = -1;
|
private int m_cameraId = -1;
|
||||||
private Camera m_camera = null;
|
private Camera m_camera = null;
|
||||||
|
private byte[] m_cameraPreviewFirstBuffer = null;
|
||||||
|
private byte[] m_cameraPreviewSecondBuffer = null;
|
||||||
|
private int m_actualPreviewBuffer = 0;
|
||||||
|
private final ReentrantLock m_buffersLock = new ReentrantLock();
|
||||||
|
private boolean m_isReleased = false;
|
||||||
|
|
||||||
private static final String TAG = "Qt Camera";
|
private static final String TAG = "Qt Camera";
|
||||||
|
|
||||||
@@ -97,6 +105,7 @@ public class QtCamera implements Camera.ShutterCallback,
|
|||||||
|
|
||||||
public void release()
|
public void release()
|
||||||
{
|
{
|
||||||
|
m_isReleased = true;
|
||||||
m_camera.release();
|
m_camera.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +143,22 @@ public class QtCamera implements Camera.ShutterCallback,
|
|||||||
|
|
||||||
public void startPreview()
|
public void startPreview()
|
||||||
{
|
{
|
||||||
|
Camera.Size previewSize = m_camera.getParameters().getPreviewSize();
|
||||||
|
double bytesPerPixel = ImageFormat.getBitsPerPixel(m_camera.getParameters().getPreviewFormat()) / 8.0;
|
||||||
|
int bufferSizeNeeded = (int)Math.ceil(bytesPerPixel*previewSize.width*previewSize.height);
|
||||||
|
|
||||||
|
//We need to clear preview buffers queue here, but there is no method to do it
|
||||||
|
//Though just resetting preview callback do the trick
|
||||||
|
m_camera.setPreviewCallback(null);
|
||||||
|
m_buffersLock.lock();
|
||||||
|
if (m_cameraPreviewFirstBuffer == null || m_cameraPreviewFirstBuffer.length < bufferSizeNeeded)
|
||||||
|
m_cameraPreviewFirstBuffer = new byte[bufferSizeNeeded];
|
||||||
|
if (m_cameraPreviewSecondBuffer == null || m_cameraPreviewSecondBuffer.length < bufferSizeNeeded)
|
||||||
|
m_cameraPreviewSecondBuffer = new byte[bufferSizeNeeded];
|
||||||
|
addCallbackBuffer();
|
||||||
|
m_buffersLock.unlock();
|
||||||
|
m_camera.setPreviewCallbackWithBuffer(this);
|
||||||
|
|
||||||
m_camera.startPreview();
|
m_camera.startPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,11 +177,6 @@ public class QtCamera implements Camera.ShutterCallback,
|
|||||||
m_camera.cancelAutoFocus();
|
m_camera.cancelAutoFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestPreviewFrame()
|
|
||||||
{
|
|
||||||
m_camera.setOneShotPreviewCallback(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void takePicture()
|
public void takePicture()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -166,6 +186,37 @@ public class QtCamera implements Camera.ShutterCallback,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] lockAndFetchPreviewBuffer()
|
||||||
|
{
|
||||||
|
//This method should always be followed by unlockPreviewBuffer()
|
||||||
|
//This method is not just a getter. It also marks last preview as already seen one.
|
||||||
|
//We should reset actualBuffer flag here to make sure we will not use old preview with future captures
|
||||||
|
byte[] result = null;
|
||||||
|
m_buffersLock.lock();
|
||||||
|
if (m_actualPreviewBuffer == 1)
|
||||||
|
result = m_cameraPreviewFirstBuffer;
|
||||||
|
else if (m_actualPreviewBuffer == 2)
|
||||||
|
result = m_cameraPreviewSecondBuffer;
|
||||||
|
m_actualPreviewBuffer = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlockPreviewBuffer()
|
||||||
|
{
|
||||||
|
if (m_buffersLock.isHeldByCurrentThread())
|
||||||
|
m_buffersLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCallbackBuffer()
|
||||||
|
{
|
||||||
|
if (m_isReleased)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_camera.addCallbackBuffer((m_actualPreviewBuffer == 1)
|
||||||
|
? m_cameraPreviewSecondBuffer
|
||||||
|
: m_cameraPreviewFirstBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onShutter()
|
public void onShutter()
|
||||||
{
|
{
|
||||||
@@ -181,7 +232,15 @@ public class QtCamera implements Camera.ShutterCallback,
|
|||||||
@Override
|
@Override
|
||||||
public void onPreviewFrame(byte[] data, Camera camera)
|
public void onPreviewFrame(byte[] data, Camera camera)
|
||||||
{
|
{
|
||||||
notifyPreviewFrame(m_cameraId, data);
|
m_buffersLock.lock();
|
||||||
|
if (data == m_cameraPreviewFirstBuffer)
|
||||||
|
m_actualPreviewBuffer = 1;
|
||||||
|
else if (data == m_cameraPreviewSecondBuffer)
|
||||||
|
m_actualPreviewBuffer = 2;
|
||||||
|
else
|
||||||
|
m_actualPreviewBuffer = 0;
|
||||||
|
addCallbackBuffer();
|
||||||
|
m_buffersLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -193,5 +252,4 @@ public class QtCamera implements Camera.ShutterCallback,
|
|||||||
private static native void notifyAutoFocusComplete(int id, boolean success);
|
private static native void notifyAutoFocusComplete(int id, boolean success);
|
||||||
private static native void notifyPictureExposed(int id);
|
private static native void notifyPictureExposed(int id);
|
||||||
private static native void notifyPictureCaptured(int id, byte[] data);
|
private static native void notifyPictureCaptured(int id, byte[] data);
|
||||||
private static native void notifyPreviewFrame(int id, byte[] data);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,7 +183,6 @@ bool QAndroidCameraSession::open()
|
|||||||
if (m_camera) {
|
if (m_camera) {
|
||||||
connect(m_camera, SIGNAL(pictureExposed()), this, SLOT(onCameraPictureExposed()));
|
connect(m_camera, SIGNAL(pictureExposed()), this, SLOT(onCameraPictureExposed()));
|
||||||
connect(m_camera, SIGNAL(pictureCaptured(QByteArray)), this, SLOT(onCameraPictureCaptured(QByteArray)));
|
connect(m_camera, SIGNAL(pictureCaptured(QByteArray)), this, SLOT(onCameraPictureCaptured(QByteArray)));
|
||||||
connect(m_camera, SIGNAL(previewFrameAvailable(QByteArray)), this, SLOT(onCameraPreviewFrameAvailable(QByteArray)));
|
|
||||||
|
|
||||||
m_nativeOrientation = m_camera->getNativeOrientation();
|
m_nativeOrientation = m_camera->getNativeOrientation();
|
||||||
|
|
||||||
@@ -484,7 +483,6 @@ int QAndroidCameraSession::capture(const QString &fileName)
|
|||||||
// adjust picture rotation depending on the device orientation
|
// adjust picture rotation depending on the device orientation
|
||||||
m_camera->setRotation(currentCameraRotation());
|
m_camera->setRotation(currentCameraRotation());
|
||||||
|
|
||||||
m_camera->requestPreviewFrame();
|
|
||||||
m_camera->takePicture();
|
m_camera->takePicture();
|
||||||
} else {
|
} else {
|
||||||
//: Drive mode is the camera's shutter mode, for example single shot, continuos exposure, etc.
|
//: Drive mode is the camera's shutter mode, for example single shot, continuos exposure, etc.
|
||||||
@@ -509,6 +507,13 @@ void QAndroidCameraSession::onCameraPictureExposed()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
emit imageExposed(m_currentImageCaptureId);
|
emit imageExposed(m_currentImageCaptureId);
|
||||||
|
QByteArray lastFrame = m_camera->fetchLastPreviewFrame();
|
||||||
|
if (lastFrame.size()) {
|
||||||
|
QtConcurrent::run(this, &QAndroidCameraSession::processPreviewImage,
|
||||||
|
m_currentImageCaptureId,
|
||||||
|
lastFrame,
|
||||||
|
m_camera->getRotation());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QAndroidCameraSession::onCameraPictureCaptured(const QByteArray &data)
|
void QAndroidCameraSession::onCameraPictureCaptured(const QByteArray &data)
|
||||||
@@ -571,17 +576,6 @@ void QAndroidCameraSession::processCapturedImage(int id,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QAndroidCameraSession::onCameraPreviewFrameAvailable(const QByteArray &data)
|
|
||||||
{
|
|
||||||
if (m_captureCanceled || m_readyForCapture)
|
|
||||||
return;
|
|
||||||
|
|
||||||
QtConcurrent::run(this, &QAndroidCameraSession::processPreviewImage,
|
|
||||||
m_currentImageCaptureId,
|
|
||||||
data,
|
|
||||||
m_camera->getRotation());
|
|
||||||
}
|
|
||||||
|
|
||||||
void QAndroidCameraSession::processPreviewImage(int id, const QByteArray &data, int rotation)
|
void QAndroidCameraSession::processPreviewImage(int id, const QByteArray &data, int rotation)
|
||||||
{
|
{
|
||||||
QSize frameSize = m_camera->previewSize();
|
QSize frameSize = m_camera->previewSize();
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ private Q_SLOTS:
|
|||||||
|
|
||||||
void onCameraPictureExposed();
|
void onCameraPictureExposed();
|
||||||
void onCameraPictureCaptured(const QByteArray &data);
|
void onCameraPictureCaptured(const QByteArray &data);
|
||||||
void onCameraPreviewFrameAvailable(const QByteArray &data);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool open();
|
bool open();
|
||||||
|
|||||||
@@ -102,18 +102,6 @@ static void notifyPictureCaptured(JNIEnv *env, jobject, int id, jbyteArray data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void notifyPreviewFrame(JNIEnv *env, jobject, int id, jbyteArray data)
|
|
||||||
{
|
|
||||||
JCamera *obj = g_objectMap.value(id, 0);
|
|
||||||
if (obj) {
|
|
||||||
QByteArray bytes;
|
|
||||||
int arrayLength = env->GetArrayLength(data);
|
|
||||||
bytes.resize(arrayLength);
|
|
||||||
env->GetByteArrayRegion(data, 0, arrayLength, (jbyte*)bytes.data());
|
|
||||||
Q_EMIT obj->previewFrameAvailable(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JCamera::JCamera(int cameraId, jobject cam)
|
JCamera::JCamera(int cameraId, jobject cam)
|
||||||
: QObject()
|
: QObject()
|
||||||
, QJNIObjectPrivate(cam)
|
, QJNIObjectPrivate(cam)
|
||||||
@@ -667,16 +655,28 @@ void JCamera::setJpegQuality(int quality)
|
|||||||
applyParameters();
|
applyParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
void JCamera::requestPreviewFrame()
|
|
||||||
{
|
|
||||||
callMethod<void>("requestPreviewFrame");
|
|
||||||
}
|
|
||||||
|
|
||||||
void JCamera::takePicture()
|
void JCamera::takePicture()
|
||||||
{
|
{
|
||||||
callMethod<void>("takePicture");
|
callMethod<void>("takePicture");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray JCamera::fetchLastPreviewFrame()
|
||||||
|
{
|
||||||
|
QJNIEnvironmentPrivate env;
|
||||||
|
QJNIObjectPrivate dataObj = callObjectMethod("lockAndFetchPreviewBuffer", "()[B");
|
||||||
|
if (!dataObj.object()) {
|
||||||
|
callMethod<void>("unlockPreviewBuffer");
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
jbyteArray data = static_cast<jbyteArray>(dataObj.object());
|
||||||
|
QByteArray bytes;
|
||||||
|
int arrayLength = env->GetArrayLength(data);
|
||||||
|
bytes.resize(arrayLength);
|
||||||
|
env->GetByteArrayRegion(data, 0, arrayLength, (jbyte*)bytes.data());
|
||||||
|
callMethod<void>("unlockPreviewBuffer");
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
void JCamera::startPreview()
|
void JCamera::startPreview()
|
||||||
{
|
{
|
||||||
callMethod<void>("startPreview");
|
callMethod<void>("startPreview");
|
||||||
@@ -720,8 +720,7 @@ QStringList JCamera::callStringListMethod(const char *methodName)
|
|||||||
static JNINativeMethod methods[] = {
|
static JNINativeMethod methods[] = {
|
||||||
{"notifyAutoFocusComplete", "(IZ)V", (void *)notifyAutoFocusComplete},
|
{"notifyAutoFocusComplete", "(IZ)V", (void *)notifyAutoFocusComplete},
|
||||||
{"notifyPictureExposed", "(I)V", (void *)notifyPictureExposed},
|
{"notifyPictureExposed", "(I)V", (void *)notifyPictureExposed},
|
||||||
{"notifyPictureCaptured", "(I[B)V", (void *)notifyPictureCaptured},
|
{"notifyPictureCaptured", "(I[B)V", (void *)notifyPictureCaptured}
|
||||||
{"notifyPreviewFrame", "(I[B)V", (void *)notifyPreviewFrame}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool JCamera::initJNI(JNIEnv *env)
|
bool JCamera::initJNI(JNIEnv *env)
|
||||||
|
|||||||
@@ -147,10 +147,10 @@ public:
|
|||||||
void startPreview();
|
void startPreview();
|
||||||
void stopPreview();
|
void stopPreview();
|
||||||
|
|
||||||
void requestPreviewFrame();
|
|
||||||
|
|
||||||
void takePicture();
|
void takePicture();
|
||||||
|
|
||||||
|
QByteArray fetchLastPreviewFrame();
|
||||||
|
|
||||||
static bool initJNI(JNIEnv *env);
|
static bool initJNI(JNIEnv *env);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
@@ -161,8 +161,6 @@ Q_SIGNALS:
|
|||||||
|
|
||||||
void whiteBalanceChanged();
|
void whiteBalanceChanged();
|
||||||
|
|
||||||
void previewFrameAvailable(const QByteArray &data);
|
|
||||||
|
|
||||||
void pictureExposed();
|
void pictureExposed();
|
||||||
void pictureCaptured(const QByteArray &data);
|
void pictureCaptured(const QByteArray &data);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user