Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5e271159 authored by Dave Sparks's avatar Dave Sparks
Browse files

Use a ref-counted callback interface for Camera.

This allows the camera service to hang onto the callback interface
until all callbacks have been processed. This prevents problems
where pending callbacks in binder worker threads are processed
after the Java camera object and its associated native resources
have been released.
Bug 1884362
parent 5f80605a
Loading
Loading
Loading
Loading
+156 −182
Original line number Diff line number Diff line
@@ -53,19 +53,33 @@ struct fields_t {
static fields_t fields;
static Mutex sLock;

struct camera_context_t {
// provides persistent context for calls from native code to Java
class JNICameraContext: public CameraListener
{
public:
    JNICameraContext(JNIEnv* env, jobject weak_this, jclass clazz, const sp<Camera>& camera);
    ~JNICameraContext() { release(); }
    virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2);
    virtual void postData(int32_t msgType, const sp<IMemory>& dataPtr);
    sp<Camera> getCamera() { Mutex::Autolock _l(mLock); return mCamera; }
    void release();

private:
    void copyAndPost(JNIEnv* env, const sp<IMemory>& dataPtr, int msgType);

    jobject     mCameraJObjectWeak;     // weak reference to java object
    jclass      mCameraJClass;          // strong reference to java class
    sp<Camera>  mCamera;                // strong reference to native object 
    Mutex       mLock;
};

sp<Camera> get_native_camera(JNIEnv *env, jobject thiz, camera_context_t** pContext)
sp<Camera> get_native_camera(JNIEnv *env, jobject thiz, JNICameraContext** pContext)
{
    sp<Camera> camera;
    Mutex::Autolock _l(sLock);
    camera_context_t* context = reinterpret_cast<camera_context_t*>(env->GetIntField(thiz, fields.context));
    JNICameraContext* context = reinterpret_cast<JNICameraContext*>(env->GetIntField(thiz, fields.context));
    if (context != NULL) {
        camera = context->mCamera;
        camera = context->getCamera();
    }
    LOGV("get_native_camera: context=%p, camera=%p", context, camera.get());
    if (camera == 0) {
@@ -76,15 +90,48 @@ sp<Camera> get_native_camera(JNIEnv *env, jobject thiz, camera_context_t** pCont
    return camera;
}

static void err_callback(status_t err, void *cookie)
JNICameraContext::JNICameraContext(JNIEnv* env, jobject weak_this, jclass clazz, const sp<Camera>& camera)
{
    camera_context_t* context = reinterpret_cast<camera_context_t*>(cookie);
    if ((context == NULL) || (context->mCamera == 0)) return;
    mCameraJObjectWeak = env->NewGlobalRef(weak_this);
    mCameraJClass = (jclass)env->NewGlobalRef(clazz);
    mCamera = camera;
}

    LOGV("err_callback: context=%p, camera=%p", context, context->mCamera.get());
void JNICameraContext::release()
{
    LOGV("release");
    Mutex::Autolock _l(mLock);
    JNIEnv *env = AndroidRuntime::getJNIEnv();

    if (mCameraJObjectWeak != NULL) {
        env->DeleteGlobalRef(mCameraJObjectWeak);
        mCameraJObjectWeak = NULL;
    }
    if (mCameraJClass != NULL) {
        env->DeleteGlobalRef(mCameraJClass);
        mCameraJClass = NULL;
    }
    mCamera.clear();
}

void JNICameraContext::notify(int32_t msgType, int32_t ext1, int32_t ext2)
{
    LOGV("notify");

    // VM pointer will be NULL if object is released
    Mutex::Autolock _l(mLock);
    if (mCameraJObjectWeak == NULL) {
        LOGW("callback on dead camera object");
        return;
    }
    JNIEnv *env = AndroidRuntime::getJNIEnv();

    // parse message
    switch (msgType) {
    case CAMERA_MSG_ERROR:
        LOGV("errorCallback");
        int error;
    switch (err) {
        switch (ext1) {
            case DEAD_OBJECT:
                error = kCameraErrorMediaServer;
                break;
@@ -92,14 +139,91 @@ static void err_callback(status_t err, void *cookie)
                error = kCameraErrorUnknown;
                break;
        }
        env->CallStaticVoidMethod(mCameraJClass, fields.post_event,
                mCameraJObjectWeak, kErrorCallback, error, 0, NULL);
        break;
    case CAMERA_MSG_FOCUS:
        LOGV("autoFocusCallback");
        env->CallStaticVoidMethod(mCameraJClass, fields.post_event,
                mCameraJObjectWeak, kAutoFocusCallback, ext1, 0, NULL);
        break;
    case CAMERA_MSG_SHUTTER:
        LOGV("shutterCallback");
        env->CallStaticVoidMethod(mCameraJClass, fields.post_event,
                mCameraJObjectWeak, kShutterCallback, 0, 0, NULL);
        break;
    default:
        LOGV("notifyCallback(%d, %d, %d)", msgType, ext1, ext2);
        break;
    }
}

void JNICameraContext::copyAndPost(JNIEnv* env, const sp<IMemory>& dataPtr, int msgType)
{
    jbyteArray obj = NULL;

    // allocate Java byte array and copy data
    if (dataPtr != NULL) {
        ssize_t offset;
        size_t size;
        sp<IMemoryHeap> heap = dataPtr->getMemory(&offset, &size);
        LOGV("postData: off=%d, size=%d", offset, size);
        uint8_t *heapBase = (uint8_t*)heap->base();

        if (heapBase != NULL) {
            uint8_t *data = heapBase + offset;
            obj = env->NewByteArray(size);
            if (obj == NULL) {
                LOGE("Couldn't allocate byte array for JPEG data");
                env->ExceptionClear();
            } else {
                jbyte *bytes = env->GetByteArrayElements(obj, NULL);
                memcpy(bytes, data, size);
                env->ReleaseByteArrayElements(obj, bytes, 0);

            }
        } else {
            LOGE("image heap is NULL");
        }
    }

    // post image data to Java
    env->CallStaticVoidMethod(mCameraJClass, fields.post_event,
            mCameraJObjectWeak, msgType, 0, 0, obj);
    if (obj) {
        env->DeleteLocalRef(obj);
    }
}

void JNICameraContext::postData(int32_t msgType, const sp<IMemory>& dataPtr)
{
    // VM pointer will be NULL if object is released
    Mutex::Autolock _l(mLock);
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    if (env == NULL) {
        LOGE("err_callback on dead VM");
        return;

    // return data based on callback type
    switch(msgType) {
    case CAMERA_MSG_PREVIEW_FRAME:
        LOGV("previewCallback");
        copyAndPost(env, dataPtr, kPreviewCallback);
        break;
    case CAMERA_MSG_VIDEO_FRAME:
        LOGV("recordingCallback");
        break;
    case CAMERA_MSG_RAW_IMAGE:
        LOGV("rawCallback");
        env->CallStaticVoidMethod(mCameraJClass, fields.post_event,
                mCameraJObjectWeak, kRawCallback, 0, 0, NULL);
        break;
    case CAMERA_MSG_COMPRESSED_IMAGE:
        LOGV("jpegCallback");
        copyAndPost(env, dataPtr, kJpegCallback);
        break;
    default:
        LOGV("dataCallback(%d, %p)", msgType, dataPtr.get());
        break;
    }
    env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
            context->mCameraJObjectWeak, kErrorCallback, error, 0, NULL);

}

// connect to camera service
@@ -127,19 +251,12 @@ static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobj

    // We use a weak reference so the Camera object can be garbage collected.
    // The reference is only used as a proxy for callbacks.
    camera_context_t* context = new camera_context_t;
    context->mCameraJObjectWeak = env->NewGlobalRef(weak_this);
    context->mCameraJClass = (jclass)env->NewGlobalRef(clazz);
    context->mCamera = camera;
    sp<JNICameraContext> context = new JNICameraContext(env, weak_this, clazz, camera);
    context->incStrong(thiz);
    camera->setListener(context);

    // save context in opaque field
    env->SetIntField(thiz, fields.context, (int)context);

    LOGV("native_setup: mCameraJObjectWeak=%x, camera_obj=%x, context=%p",
            (int)context->mCameraJObjectWeak, (int)thiz, context);

    // set error callback
    camera->setErrorCallback(err_callback, context);
    env->SetIntField(thiz, fields.context, (int)context.get());
}

// disconnect from camera service
@@ -148,11 +265,11 @@ static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobj
// finalizer is invoked later.
static void android_hardware_Camera_release(JNIEnv *env, jobject thiz)
{
    camera_context_t* context = NULL;
    JNICameraContext* context = NULL;
    sp<Camera> camera;
    {
        Mutex::Autolock _l(sLock);
        context = reinterpret_cast<camera_context_t*>(env->GetIntField(thiz, fields.context));
        context = reinterpret_cast<JNICameraContext*>(env->GetIntField(thiz, fields.context));

        // Make sure we do not attempt to callback on a deleted Java object.
        env->SetIntField(thiz, fields.context, 0);
@@ -160,21 +277,18 @@ static void android_hardware_Camera_release(JNIEnv *env, jobject thiz)

    // clean up if release has not been called before
    if (context != NULL) {
        camera = context->mCamera;
        context->mCamera.clear();
        camera = context->getCamera();
        context->release();
        LOGV("native_release: context=%p camera=%p", context, camera.get());

        // clear callbacks
        if (camera != NULL) {
            camera->setPreviewCallback(NULL, NULL, FRAME_CALLBACK_FLAG_NOOP);
            camera->setErrorCallback(NULL, NULL);
            camera->setPreviewCallbackFlags(FRAME_CALLBACK_FLAG_NOOP);
            camera->disconnect();
            env->DeleteGlobalRef(context->mCameraJObjectWeak);
            env->DeleteGlobalRef(context->mCameraJClass);
        }

        // remove context to prevent further Java access
        delete context;
        context->decStrong(thiz);
    }
}

@@ -190,48 +304,6 @@ static void android_hardware_Camera_setPreviewDisplay(JNIEnv *env, jobject thiz,
    }
}

static void preview_callback(const sp<IMemory>& mem, void *cookie)
{
    LOGV("preview_callback");
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    if (env == NULL) {
        LOGE("preview_callback on dead VM");
        return;
    }
    camera_context_t* context = reinterpret_cast<camera_context_t*>(cookie);
    if ((context == NULL) || (context->mCamera == 0)) {
        LOGW("context or camera is NULL in preview_callback");
        return;
    }
    LOGV("native_release: context=%p camera=%p", context, context->mCamera.get());

    int arg1 = 0, arg2 = 0;
    jobject obj = NULL;

    ssize_t offset;
    size_t size;
    sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);

    uint8_t *data = ((uint8_t *)heap->base()) + offset;

    jbyteArray array = env->NewByteArray(size);
    if (array == NULL) {
        LOGE("Couldn't allocate byte array for YUV data");
        env->ExceptionClear();
        return;
    }

    jbyte *bytes = env->GetByteArrayElements(array, NULL);
    memcpy(bytes, data, size);
    env->ReleaseByteArrayElements(array, bytes, 0);

    obj = array;

    env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
            context->mCameraJObjectWeak, kPreviewCallback, arg1, arg2, obj);
    env->DeleteLocalRef(array);
}

static void android_hardware_Camera_startPreview(JNIEnv *env, jobject thiz)
{
    LOGV("startPreview");
@@ -267,7 +339,7 @@ static void android_hardware_Camera_setHasPreviewCallback(JNIEnv *env, jobject t
    // Important: Only install preview_callback if the Java code has called
    // setPreviewCallback() with a non-null value, otherwise we'd pay to memcpy
    // each preview frame for nothing.
    camera_context_t* context;
    JNICameraContext* context;
    sp<Camera> camera = get_native_camera(env, thiz, &context);
    if (camera == 0) return;

@@ -277,130 +349,32 @@ static void android_hardware_Camera_setHasPreviewCallback(JNIEnv *env, jobject t
    } else {
        callback_flag = FRAME_CALLBACK_FLAG_NOOP;
    }
    camera->setPreviewCallback(installed ? preview_callback : NULL, context, callback_flag);
}

static void autofocus_callback_impl(bool success, void *cookie)
{
    LOGV("autoFocusCallback");
    camera_context_t* context = reinterpret_cast<camera_context_t*>(cookie);

    JNIEnv *env = AndroidRuntime::getJNIEnv();
    if (env == NULL) {
        LOGE("autofocus_callback on dead VM");
        return;
    }
    env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
            context->mCameraJObjectWeak, kAutoFocusCallback, success, 0, NULL);
    camera->setPreviewCallbackFlags(callback_flag);
}

static void android_hardware_Camera_autoFocus(JNIEnv *env, jobject thiz)
{
    LOGV("autoFocus");
    camera_context_t* context;
    JNICameraContext* context;
    sp<Camera> c = get_native_camera(env, thiz, &context);
    if (c == 0) return;

    c->setAutoFocusCallback(autofocus_callback_impl, context);
    if (c->autoFocus() != NO_ERROR) {
        jniThrowException(env, "java/lang/RuntimeException", "autoFocus failed");
    }
}

static void jpeg_callback(const sp<IMemory>& mem, void *cookie)
{
    LOGV("jpegCallback");
    camera_context_t* context = reinterpret_cast<camera_context_t*>(cookie);

    JNIEnv *env = AndroidRuntime::getJNIEnv();
    if (env == NULL) {
        LOGE("jpeg`_callback on dead VM");
        return;
    }
    int arg1 = 0, arg2 = 0;
    jobject obj = NULL;

    if (mem == NULL) {
        env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
                                  context->mCameraJObjectWeak, kJpegCallback, arg1, arg2, NULL);
        return;
    }
    ssize_t offset;
    size_t size;
    sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
    LOGV("jpeg_callback: mem off=%d, size=%d", offset, size);

    uint8_t *heap_base = (uint8_t *)heap->base();
    if (heap_base == NULL) {
        LOGE("YUV heap is NULL");
        return;
    }

    uint8_t *data = heap_base + offset;

    jbyteArray array = env->NewByteArray(size);
    if (array == NULL) {
        LOGE("Couldn't allocate byte array for JPEG data");
        env->ExceptionClear();
        return;
    }

    jbyte *bytes = env->GetByteArrayElements(array, NULL);
    memcpy(bytes, data, size);
    env->ReleaseByteArrayElements(array, bytes, 0);

    obj = array;

    env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
                              context->mCameraJObjectWeak, kJpegCallback, arg1, arg2, obj);
    env->DeleteLocalRef(array);
}

static void shutter_callback_impl(void *cookie)
{
    LOGV("shutterCallback");
    camera_context_t* context = reinterpret_cast<camera_context_t*>(cookie);

    JNIEnv *env = AndroidRuntime::getJNIEnv();
    if (env == NULL) {
        LOGE("shutter_callback on dead VM");
        return;
    }
    env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
                              context->mCameraJObjectWeak, kShutterCallback, 0, 0, NULL);
}

static void raw_callback(const sp<IMemory>& mem __attribute__((unused)),
                         void *cookie)
{
    LOGV("rawCallback");
    camera_context_t* context = reinterpret_cast<camera_context_t*>(cookie);

    JNIEnv *env = AndroidRuntime::getJNIEnv();
    if (env == NULL) {
        LOGE("raw_callback on dead VM");
        return;
    }
    env->CallStaticVoidMethod(context->mCameraJClass, fields.post_event,
                              context->mCameraJObjectWeak, kRawCallback, 0, 0, NULL);
}

static void android_hardware_Camera_takePicture(JNIEnv *env, jobject thiz)
{
    LOGV("takePicture");
    camera_context_t* context;
    JNICameraContext* context;
    sp<Camera> camera = get_native_camera(env, thiz, &context);
    if (camera == 0) return;

    camera->setShutterCallback(shutter_callback_impl, context);
    camera->setRawCallback(raw_callback, context);
    camera->setJpegCallback(jpeg_callback, context);
    if (camera->takePicture() != NO_ERROR) {
        jniThrowException(env, "java/lang/RuntimeException", "takePicture failed");
        return;
    }

    return;
}

static void android_hardware_Camera_setParameters(JNIEnv *env, jobject thiz, jstring params)
+13 −0
Original line number Diff line number Diff line
@@ -86,6 +86,14 @@ class Surface;
class Mutex;
class String8;

// ref-counted object for callbacks
class CameraListener: virtual public RefBase
{
public:
    virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2) = 0;
    virtual void postData(int32_t msgType, const sp<IMemory>& dataPtr) = 0;
};

typedef void (*shutter_callback)(void *cookie);
typedef void (*frame_callback)(const sp<IMemory>& mem, void *cookie);
typedef void (*autofocus_callback)(bool focused, void *cookie);
@@ -152,6 +160,9 @@ public:
            void        setErrorCallback(error_callback cb, void *cookie);
            void        setAutoFocusCallback(autofocus_callback cb, void *cookie);

            void        setListener(const sp<CameraListener>& listener);
            void        setPreviewCallbackFlags(int preview_callback_flag);

    // ICameraClient interface
    virtual void        notifyCallback(int32_t msgType, int32_t ext, int32_t ext2);
    virtual void        dataCallback(int32_t msgType, const sp<IMemory>& dataPtr);
@@ -194,6 +205,8 @@ private:
            autofocus_callback  mAutoFocusCallback;
            void                *mAutoFocusCallbackCookie;

            sp<CameraListener>  mListener;

            friend class DeathNotifier;

            static  Mutex               mLock;
+33 −0
Original line number Diff line number Diff line
@@ -337,9 +337,32 @@ void Camera::setErrorCallback(error_callback cb, void *cookie)
    mErrorCallbackCookie = cookie;
}

void Camera::setListener(const sp<CameraListener>& listener)
{
    Mutex::Autolock _l(mLock);
    mListener = listener;
}

void Camera::setPreviewCallbackFlags(int flag)
{
    LOGV("setPreviewCallbackFlags");
    sp <ICamera> c = mCamera;
    if (c == 0) return;
    mCamera->setPreviewCallbackFlag(flag);
}

// callback from camera service
void Camera::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2)
{
    sp<CameraListener> listener;
    {
        Mutex::Autolock _l(mLock);
        listener = mListener;
    }
    if (listener != NULL) {
        listener->notify(msgType, ext1, ext2);
    }

    switch(msgType) {
    case CAMERA_MSG_ERROR:
        LOGV("errorCallback");
@@ -368,6 +391,15 @@ void Camera::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2)
// callback from camera service when frame or image is ready
void Camera::dataCallback(int32_t msgType, const sp<IMemory>& dataPtr)
{
    sp<CameraListener> listener;
    {
        Mutex::Autolock _l(mLock);
        listener = mListener;
    }
    if (listener != NULL) {
        listener->postData(msgType, dataPtr);
    }

    switch(msgType) {
    case CAMERA_MSG_PREVIEW_FRAME:
        LOGV("previewCallback");
@@ -401,6 +433,7 @@ void Camera::dataCallback(int32_t msgType, const sp<IMemory>& dataPtr)

void Camera::binderDied(const wp<IBinder>& who) {
    LOGW("ICamera died");
    notifyCallback(CAMERA_MSG_ERROR, DEAD_OBJECT, 0);
    if (mErrorCallback) {
        mErrorCallback(DEAD_OBJECT, mErrorCallbackCookie);
    }
+1 −1
Original line number Diff line number Diff line
@@ -41,7 +41,7 @@ using namespace android;
// ----------------------------------------------------------------------------

// helper function to extract a native Camera object from a Camera Java object
extern sp<Camera> get_native_camera(JNIEnv *env, jobject thiz, struct camera_context_t** context);
extern sp<Camera> get_native_camera(JNIEnv *env, jobject thiz, struct JNICameraContext** context);

struct fields_t {
    jfieldID    context;