From 443860a7c4adec0135121047f06acd62da9fb3cc Mon Sep 17 00:00:00 2001 From: Danny Wood Date: Fri, 21 Jun 2019 10:46:36 +0100 Subject: [PATCH] universal7580: camera: create a worker thread to handle all callbacks to avoid hard locking the camera hal with single threaded clients Change-Id: I31e5b4e874d32ffdceb04aaf5cdd4b805a34f26e --- camera/Android.mk | 3 +- camera/CallbackWorkerThread.cpp | 219 ++++++++++++++++++++++++++++++++ camera/CallbackWorkerThread.h | 77 +++++++++++ camera/Camera2Wrapper.cpp | 106 +++++++++++++--- 4 files changed, 389 insertions(+), 16 deletions(-) create mode 100644 camera/CallbackWorkerThread.cpp create mode 100644 camera/CallbackWorkerThread.h diff --git a/camera/Android.mk b/camera/Android.mk index 95d1ee2..4d9cf0b 100644 --- a/camera/Android.mk +++ b/camera/Android.mk @@ -3,7 +3,8 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ CameraWrapper.cpp \ - Camera2Wrapper.cpp + Camera2Wrapper.cpp \ + CallbackWorkerThread.cpp LOCAL_STATIC_LIBRARIES := libbase libarect LOCAL_SHARED_LIBRARIES := \ diff --git a/camera/CallbackWorkerThread.cpp b/camera/CallbackWorkerThread.cpp new file mode 100644 index 0000000..676e6bd --- /dev/null +++ b/camera/CallbackWorkerThread.cpp @@ -0,0 +1,219 @@ + +#define LOG_NDEBUG 0 +#define LOG_TAG "Camera2WrapperCbThread" + +#include "CallbackWorkerThread.h" +#include +#include + +using namespace std; + +#define MSG_EXIT_THREAD 1 +#define MSG_EXECUTE_CALLBACK 2 +#define MSG_UPDATE_CALLBACKS 3 + +struct ThreadMsg +{ + ThreadMsg(int i, const void* m, long long ts) { id = i; msg = m; CallerTS = ts; } + int id; + const void* msg; + long long CallerTS; +}; + +CallbackWorkerThread::CallbackWorkerThread() : m_thread(0) { +} + +CallbackWorkerThread::~CallbackWorkerThread() { + ExitThread(); +} + +bool CallbackWorkerThread::CreateThread() { + if (!m_thread) + m_thread = new thread(&CallbackWorkerThread::Process, this); + return true; +} + +void CallbackWorkerThread::ExitThread() { + if (!m_thread) + return; + + /* Create the exit thread worker message */ + ThreadMsg* threadMsg = new ThreadMsg(MSG_EXIT_THREAD, 0, GetTimestamp()); + + /* Add it to the message queue */ + { + lock_guard lock(m_mutex); + m_queue.push(threadMsg); + m_cv.notify_one(); + } + + /* Join the thread and then cleanup */ + m_thread->join(); + delete m_thread; + m_thread = 0; +} + +void CallbackWorkerThread::AddCallback(const WorkerMessage* data) { + /* Assert that the thread exists */ + assert(!m_thread); + + /* Create a new worker thread message from the data */ + ThreadMsg* threadMsg = new ThreadMsg(MSG_EXECUTE_CALLBACK, data, GetTimestamp()); + + /* Add it to our worker queue and notify the worker */ + std::unique_lock lk(m_mutex); + m_queue.push(threadMsg); + m_cv.notify_one(); +} + +void CallbackWorkerThread::SetCallbacks(const CallbackData* data) { + /* Assert that the thread exists */ + assert(!m_thread); + + /* Create a new worker thread message from the callback data */ + ThreadMsg* threadMsg = new ThreadMsg(MSG_UPDATE_CALLBACKS, data, GetTimestamp()); + + /* Add it to our worker queue and notify the worker */ + std::unique_lock lk(m_mutex); + m_queue.push(threadMsg); + m_cv.notify_one(); +} + +void CallbackWorkerThread::ClearCallbacks() { + /* Assert that the thread exists */ + assert(!m_thread); + + /* Lock the mutex and clear the message queue */ + std::unique_lock lk(m_mutex); + + ALOGV("%s: Clearing %i messages", __FUNCTION__, m_queue.size()); + + /* Whilst the queue is not empty */ + while (!m_queue.empty()) { + /* Pop the message from the queue and delete the allocated data */ + ThreadMsg* msg = m_queue.front(); + m_queue.pop(); + delete msg; + } + + m_cv.notify_one(); +} + +void CallbackWorkerThread::Process() { + camera_notify_callback UserNotifyCb = NULL; + camera_data_callback UserDataCb = NULL; + + while (1) { + ThreadMsg* msg = 0; + { + /* Wait for a message to be added to the queue */ + std::unique_lock lk(m_mutex); + while (m_queue.empty()) + m_cv.wait(lk); + + if (m_queue.empty()) + continue; + + msg = m_queue.front(); + m_queue.pop(); + } + + switch (msg->id) { + case MSG_EXECUTE_CALLBACK: + { + /* Assert that we have a valid message */ + assert(msg->msg != NULL); + + /* Cast the the ThreadMsg void* data back to a WorkerMessage* */ + const WorkerMessage* userData = static_cast(msg->msg); + + /* If the callback is not stale (newer than 5mS) */ + if(GetTimestamp() - msg->CallerTS < 5) { + /* If the callback type is set to notifycb */ + if(userData->CbType == CB_TYPE_NOTIFY) { + /* Execute the users notify callback if it is valid */ + if(UserNotifyCb != NULL) { + ALOGV("%s: UserNotifyCb: %i %i %i %p", __FUNCTION__, userData->msg_type, userData->ext1, userData->ext2, userData->user); + UserNotifyCb(userData->msg_type, userData->ext1, userData->ext2, userData->user); + } + } /* If the callback type is set to notifycb */ + else if(userData->CbType == CB_TYPE_DATA) { + /* Execute the users data callback if it is valid */ + if(UserDataCb != NULL) { + ALOGV("%s: UserDataCb: %i %p %i %p %p", __FUNCTION__, userData->msg_type, userData->data, userData->index, userData->metadata, userData->user); + UserDataCb(userData->msg_type, userData->data, userData->index, userData->metadata, userData->user); + } + } + } else { + /* If the callback type is set to notifycb */ + if(userData->CbType == CB_TYPE_NOTIFY) { + ALOGV("%s: UserNotifyCb Stale: %llimS old", __FUNCTION__, GetTimestamp() - msg->CallerTS); + } /* If the callback type is set to notifycb */ + else if(userData->CbType == CB_TYPE_DATA) { + ALOGV("%s: UserDataCb Stale: %llimS old", __FUNCTION__, GetTimestamp() - msg->CallerTS); + } + } + + /* Cleanup allocated data */ + delete userData; + delete msg; + break; + } + + case MSG_UPDATE_CALLBACKS: + { + /* Assert that we have a valid message */ + assert(msg->msg != NULL); + + /* Cast the the ThreadMsg void* data back to a CallbackData* */ + const CallbackData* callbackData = static_cast(msg->msg); + + ALOGV("%s: UpdateCallbacks", __FUNCTION__); + + /* Copy the new callback pointers */ + UserNotifyCb = callbackData->NewUserNotifyCb; + UserDataCb = callbackData->NewUserDataCb; + + /* Cleanup allocated data */ + delete callbackData; + delete msg; + break; + } + + case MSG_EXIT_THREAD: + { + /* Delete current message */ + delete msg; + /* Then delete all pending messages in the queue */ + std::unique_lock lk(m_mutex); + /* Whilst the queue is not empty */ + while (!m_queue.empty()) { + /* Pop the message from the queue and delete the allocated data */ + msg = m_queue.front(); + m_queue.pop(); + delete msg; + } + + ALOGV("%s: Exit Thread", __FUNCTION__); + return; + } + + default: + /* Error if we get here */ + assert(0); + } + } +} + + +/* based on current_timestamp() function from stack overflow: + * https://stackoverflow.com/questions/3756323/how-to-get-the-current-time-in-milliseconds-from-c-in-linux/17083824 + */ + +long long CallbackWorkerThread::GetTimestamp() { + struct timeval te; + gettimeofday(&te, NULL); // get current time + long long milliseconds = te.tv_sec*1000LL + te.tv_usec/1000; // calculate milliseconds + return milliseconds; +} + diff --git a/camera/CallbackWorkerThread.h b/camera/CallbackWorkerThread.h new file mode 100644 index 0000000..a304692 --- /dev/null +++ b/camera/CallbackWorkerThread.h @@ -0,0 +1,77 @@ +#ifndef _THREAD_STD_H +#define _THREAD_STD_H + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define CB_TYPE_NONE 0 +#define CB_TYPE_NOTIFY 1 +#define CB_TYPE_DATA 2 + +struct WorkerMessage { + /* Worker callback type */ + int32_t CbType; + + /* Callback data */ + int32_t msg_type; + const camera_memory_t *data; + unsigned int index; + camera_frame_metadata_t *metadata; + void *user; + int32_t ext1; + int32_t ext2; +}; + +struct CallbackData { + camera_notify_callback NewUserNotifyCb; + camera_data_callback NewUserDataCb; +}; + +struct ThreadMsg; + +class CallbackWorkerThread { +public: + CallbackWorkerThread(); + ~CallbackWorkerThread(); + + /* Creates our worker, returns true on success */ + bool CreateThread(); + + /* Exits the worker thread */ + void ExitThread(); + + /* Sends a new callback to our worker thread */ + void AddCallback(const WorkerMessage* data); + + /* Sets the callback function pointers for our worker to call */ + void SetCallbacks(const CallbackData* data); + + /* Clears the worker message queue */ + void ClearCallbacks(void); + +private: + CallbackWorkerThread(const CallbackWorkerThread&); + CallbackWorkerThread& operator=(const CallbackWorkerThread&); + + long long GetTimestamp(); + + /* Entry point for the worker thread */ + void Process(); + + std::thread* m_thread; + std::queue m_queue; + std::mutex m_mutex; + std::condition_variable m_cv; + const char* m_name; +}; + +#endif + diff --git a/camera/Camera2Wrapper.cpp b/camera/Camera2Wrapper.cpp index 1eead7c..d4d8573 100644 --- a/camera/Camera2Wrapper.cpp +++ b/camera/Camera2Wrapper.cpp @@ -21,9 +21,13 @@ #include #include +#include #include "CameraWrapper.h" #include "Camera2Wrapper.h" +#include "CallbackWorkerThread.h" + +CallbackWorkerThread cbThread; #include @@ -142,17 +146,62 @@ static int camera2_set_preview_window(struct camera_device * device, return rc; } -uint8_t BlockNotifyCb = 0; -camera_notify_callback UserNotifyCb = NULL; +atomic_int BlockCbs; void WrappedNotifyCb (int32_t msg_type, int32_t ext1, int32_t ext2, void *user) { ALOGV("%s->In", __FUNCTION__); - /* If the notify callback is valid and we are not blocking callbacks */ - if((UserNotifyCb != NULL) && (BlockNotifyCb == 0)) { - ALOGV("%s->Calling UserNotifyCb", __FUNCTION__); - UserNotifyCb(msg_type, ext1, ext2, user); + /* Print a log message and return if we currently blocking adding callbacks */ + if(BlockCbs == 1) { + ALOGV("%s->BlockCbs == 1", __FUNCTION__); + return; } + + /* Create message to send to the callback worker */ + WorkerMessage* newWorkerMessage = new WorkerMessage(); + newWorkerMessage->CbType = CB_TYPE_NOTIFY; + + /* Copy the callback data to our worker message */ + newWorkerMessage->msg_type = msg_type; + newWorkerMessage->ext1 = ext1; + newWorkerMessage->ext2 = ext2; + newWorkerMessage->user = user; + + /* Post the message to the callback worker */ + cbThread.AddCallback(newWorkerMessage); + + /* 5mS delay to slow down the camera hal thread */ + usleep(5000); + ALOGV("%s->Out", __FUNCTION__); +} + +void WrappedDataCb (int32_t msg_type, const camera_memory_t *data, unsigned int index, + camera_frame_metadata_t *metadata, void *user) { + ALOGV("%s->In, %i, %u", __FUNCTION__, msg_type, index); + + /* Print a log message and return if we currently blocking adding callbacks */ + if(BlockCbs == 1) { + ALOGV("%s->BlockCbs == 1", __FUNCTION__); + return; + } + + /* Create message to send to the callback worker */ + WorkerMessage* newWorkerMessage = new WorkerMessage(); + newWorkerMessage->CbType = CB_TYPE_DATA; + + /* Copy the callback data to our worker message */ + newWorkerMessage->msg_type = msg_type; + newWorkerMessage->data = data; + newWorkerMessage->index= index; + newWorkerMessage->metadata = metadata; + newWorkerMessage->user = user; + + /* Post the message to the callback worker */ + cbThread.AddCallback(newWorkerMessage); + + /* 20mS delay to slow down the camera hal thread */ + usleep(20000); + ALOGV("%s->Out", __FUNCTION__); } static void camera2_set_callbacks(struct camera_device * device, @@ -167,11 +216,16 @@ static void camera2_set_callbacks(struct camera_device * device, if(!device) return; - /* Copy the notify_cb to our user pointer */ - UserNotifyCb = notify_cb; + /* Create and populate a new callback data structure */ + CallbackData* newCallbackData = new CallbackData(); + newCallbackData->NewUserNotifyCb = notify_cb; + newCallbackData->NewUserDataCb = data_cb; + + /* Send it to our worker thread */ + cbThread.SetCallbacks(newCallbackData); /* Call the set_callbacks function substituting the notify callback with our wrapper */ - VENDOR_CALL(device, set_callbacks, WrappedNotifyCb, data_cb, data_cb_timestamp, get_memory, user); + VENDOR_CALL(device, set_callbacks, WrappedNotifyCb, WrappedDataCb, data_cb_timestamp, get_memory, user); } static void camera2_enable_msg_type(struct camera_device * device, int32_t msg_type) @@ -229,7 +283,16 @@ static void camera2_stop_preview(struct camera_device * device) if(!device) return; + /* Block queueing more callbacks */ + BlockCbs = 1; + + /* Clear the callback queue */ + cbThread.ClearCallbacks(); + /* Execute stop_preview */ VENDOR_CALL(device, stop_preview); + + /* Unblock queueing more callbacks */ + BlockCbs = 0; } static int camera2_preview_enabled(struct camera_device * device) @@ -309,6 +372,9 @@ static int camera2_auto_focus(struct camera_device * device) if(!device) return -EINVAL; + /* Clear the callback queue */ + cbThread.ClearCallbacks(); + /* Call the auto_focus function */ Ret = VENDOR_CALL(device, auto_focus); @@ -326,6 +392,12 @@ static int camera2_cancel_auto_focus(struct camera_device * device) if(!device) return -EINVAL; + /* Block queueing more callbacks */ + BlockCbs = 1; + + /* Clear the callback queue */ + cbThread.ClearCallbacks(); + /* Calculate the difference between our guard time and now */ long long TimeDiff = CancelAFTimeGuard - current_timestamp(); /* Post a log message and return success (skipping the call) if the diff is greater than 0 */ @@ -334,14 +406,11 @@ static int camera2_cancel_auto_focus(struct camera_device * device) return 0; } - /* Block notify callbacks whilst we are in cancel_auto_focus */ - BlockNotifyCb = 1; - /* No active time guard so call the vendor function */ Ret = VENDOR_CALL(device, cancel_auto_focus); - /* Clear the block flag */ - BlockNotifyCb = 0; + /* Unblock queueing more callbacks */ + BlockCbs = 0; return Ret; } @@ -459,7 +528,10 @@ static int camera2_device_close(hw_device_t* device) done: gPreviewWindow = 0; gPreviewStartDeferred = false; - UserNotifyCb = NULL; + + /* Exit our callback dispatch thread */ + cbThread.ExitThread(); + return ret; } @@ -484,6 +556,10 @@ int camera2_device_open(const hw_module_t* module, const char* name, android::Mutex::Autolock lock(gCameraWrapperLock); + /* Create our callback dispatch thread */ + cbThread.CreateThread(); + BlockCbs = 0; + ALOGV("%s", __FUNCTION__); if (name != NULL) { -- 2.20.1