From b3a95a56f0adaf2b60ff164275487d1870442734 Mon Sep 17 00:00:00 2001 From: Igor Murashkin Date: Mon, 1 Apr 2013 17:29:07 -0700 Subject: [PATCH] Camera: implement takePicture for HAL3-using clients * Implements Camera2Device-style triggers by mutating the next request * Implements Camera3Device::waitUntilRequestReceived Change-Id: Ie0b5591158872513a0bffbfab33123cf18dacf8a --- services/camera/libcameraservice/Camera3Device.cpp | 350 +++++++++++++++++++-- services/camera/libcameraservice/Camera3Device.h | 55 +++- .../libcameraservice/camera2/CaptureSequencer.cpp | 29 +- .../libcameraservice/camera2/JpegProcessor.h | 1 + 4 files changed, 414 insertions(+), 21 deletions(-) diff --git a/services/camera/libcameraservice/Camera3Device.cpp b/services/camera/libcameraservice/Camera3Device.cpp index f2c8c04ad8..e53dbb5a34 100644 --- a/services/camera/libcameraservice/Camera3Device.cpp +++ b/services/camera/libcameraservice/Camera3Device.cpp @@ -262,6 +262,8 @@ status_t Camera3Device::capture(CameraMetadata &request) { ATRACE_CALL(); Mutex::Autolock l(mLock); + // TODO: take ownership of the request + switch (mStatus) { case STATUS_ERROR: ALOGE("%s: Device has encountered a serious error", __FUNCTION__); @@ -363,10 +365,8 @@ status_t Camera3Device::clearStreamingRequest() { status_t Camera3Device::waitUntilRequestReceived(int32_t requestId, nsecs_t timeout) { ATRACE_CALL(); - (void)requestId; (void)timeout; - ALOGE("%s: Unimplemented", __FUNCTION__); - return INVALID_OPERATION; + return mRequestThread->waitUntilRequestProcessed(requestId, timeout); } status_t Camera3Device::createStream(sp consumer, @@ -698,28 +698,62 @@ status_t Camera3Device::getNextFrame(CameraMetadata *frame) { status_t Camera3Device::triggerAutofocus(uint32_t id) { ATRACE_CALL(); - (void)id; - ALOGE("%s: Unimplemented", __FUNCTION__); - return INVALID_OPERATION; + ALOGV("%s: Triggering autofocus, id %d", __FUNCTION__, id); + // Mix-in this trigger into the next request and only the next request. + RequestTrigger trigger[] = { + { + ANDROID_CONTROL_AF_TRIGGER, + ANDROID_CONTROL_AF_TRIGGER_START + }, + { + ANDROID_CONTROL_AF_TRIGGER_ID, + static_cast(id) + }, + }; + + return mRequestThread->queueTrigger(trigger, + sizeof(trigger)/sizeof(trigger[0])); } status_t Camera3Device::triggerCancelAutofocus(uint32_t id) { ATRACE_CALL(); - (void)id; - - ALOGE("%s: Unimplemented", __FUNCTION__); - return INVALID_OPERATION; + ALOGV("%s: Triggering cancel autofocus, id %d", __FUNCTION__, id); + // Mix-in this trigger into the next request and only the next request. + RequestTrigger trigger[] = { + { + ANDROID_CONTROL_AF_TRIGGER, + ANDROID_CONTROL_AF_TRIGGER_CANCEL + }, + { + ANDROID_CONTROL_AF_TRIGGER_ID, + static_cast(id) + }, + }; + + return mRequestThread->queueTrigger(trigger, + sizeof(trigger)/sizeof(trigger[0])); } status_t Camera3Device::triggerPrecaptureMetering(uint32_t id) { ATRACE_CALL(); - (void)id; - - ALOGE("%s: Unimplemented", __FUNCTION__); - return INVALID_OPERATION; + ALOGV("%s: Triggering precapture metering, id %d", __FUNCTION__, id); + // Mix-in this trigger into the next request and only the next request. + RequestTrigger trigger[] = { + { + ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER, + ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_START + }, + { + ANDROID_CONTROL_AE_PRECAPTURE_ID, + static_cast(id) + }, + }; + + return mRequestThread->queueTrigger(trigger, + sizeof(trigger)/sizeof(trigger[0])); } status_t Camera3Device::pushReprocessBuffer(int reprocessStreamId, @@ -997,9 +1031,13 @@ void Camera3Device::processCaptureResult(const camera3_capture_result *result) { // Dispatch any 3A change events to listeners if (listener != NULL) { if (new3aState.aeState != cur3aState.aeState) { + ALOGVV("%s: AE state changed from 0x%x to 0x%x", + __FUNCTION__, cur3aState.aeState, new3aState.aeState); listener->notifyAutoExposure(new3aState.aeState, aeTriggerId); } if (new3aState.afState != cur3aState.afState) { + ALOGVV("%s: AF state changed from 0x%x to 0x%x", + __FUNCTION__, cur3aState.afState, new3aState.afState); listener->notifyAutoFocus(new3aState.afState, afTriggerId); } if (new3aState.awbState != cur3aState.awbState) { @@ -1059,7 +1097,8 @@ Camera3Device::RequestThread::RequestThread(wp parent, mReconfigured(false), mDoPause(false), mPaused(true), - mFrameNumber(0) { + mFrameNumber(0), + mLatestRequestId(NAME_NOT_FOUND) { } void Camera3Device::RequestThread::configurationComplete() { @@ -1075,6 +1114,57 @@ status_t Camera3Device::RequestThread::queueRequest( return OK; } + +status_t Camera3Device::RequestThread::queueTrigger( + RequestTrigger trigger[], + size_t count) { + + Mutex::Autolock l(mTriggerMutex); + status_t ret; + + for (size_t i = 0; i < count; ++i) { + ret = queueTriggerLocked(trigger[i]); + + if (ret != OK) { + return ret; + } + } + + return OK; +} + +status_t Camera3Device::RequestThread::queueTriggerLocked( + RequestTrigger trigger) { + + uint32_t tag = trigger.metadataTag; + ssize_t index = mTriggerMap.indexOfKey(tag); + + switch (trigger.getTagType()) { + case TYPE_BYTE: + // fall-through + case TYPE_INT32: + break; + default: + ALOGE("%s: Type not supported: 0x%x", + __FUNCTION__, + trigger.getTagType()); + return INVALID_OPERATION; + } + + /** + * Collect only the latest trigger, since we only have 1 field + * in the request settings per trigger tag, and can't send more than 1 + * trigger per request. + */ + if (index != NAME_NOT_FOUND) { + mTriggerMap.editValueAt(index) = trigger; + } else { + mTriggerMap.add(tag, trigger); + } + + return OK; +} + status_t Camera3Device::RequestThread::setRepeatingRequests( const RequestList &requests) { Mutex::Autolock l(mRequestLock); @@ -1108,6 +1198,24 @@ status_t Camera3Device::RequestThread::waitUntilPaused(nsecs_t timeout) { return OK; } +status_t Camera3Device::RequestThread::waitUntilRequestProcessed( + int32_t requestId, nsecs_t timeout) { + Mutex::Autolock l(mLatestRequestMutex); + status_t res; + while (mLatestRequestId != requestId) { + nsecs_t startTime = systemTime(); + + res = mLatestRequestSignal.waitRelative(mLatestRequestMutex, timeout); + if (res != OK) return res; + + timeout -= (systemTime() - startTime); + } + + return OK; +} + + + bool Camera3Device::RequestThread::threadLoop() { status_t res; @@ -1125,16 +1233,55 @@ bool Camera3Device::RequestThread::threadLoop() { } // Create request to HAL - camera3_capture_request_t request = camera3_capture_request_t(); + Vector outputBuffers; - if (mPrevRequest != nextRequest) { + // Insert any queued triggers (before metadata is locked) + int32_t triggerCount; + res = insertTriggers(nextRequest); + if (res < 0) { + ALOGE("RequestThread: Unable to insert triggers " + "(capture request %d, HAL device: %s (%d)", + (mFrameNumber+1), strerror(-res), res); + cleanUpFailedRequest(request, nextRequest, outputBuffers); + return false; + } + triggerCount = res; + + bool triggersMixedIn = (triggerCount > 0 || mPrevTriggers > 0); + + // If the request is the same as last, or we had triggers last time + if (mPrevRequest != nextRequest || triggersMixedIn) { + /** + * The request should be presorted so accesses in HAL + * are O(logn). Sidenote, sorting a sorted metadata is nop. + */ + nextRequest->mSettings.sort(); request.settings = nextRequest->mSettings.getAndLock(); mPrevRequest = nextRequest; - } // else leave request.settings NULL to indicate 'reuse latest given' + ALOGVV("%s: Request settings are NEW", __FUNCTION__); + + IF_ALOGV() { + camera_metadata_ro_entry_t e = camera_metadata_ro_entry_t(); + find_camera_metadata_ro_entry( + request.settings, + ANDROID_CONTROL_AF_TRIGGER, + &e + ); + if (e.count > 0) { + ALOGV("%s: Request (frame num %d) had AF trigger 0x%x", + __FUNCTION__, + mFrameNumber+1, + e.data.u8[0]); + } + } + } else { + // leave request.settings NULL to indicate 'reuse latest given' + ALOGVV("%s: Request settings are REUSED", + __FUNCTION__); + } camera3_stream_buffer_t inputBuffer; - Vector outputBuffers; // Fill in buffers @@ -1168,6 +1315,7 @@ bool Camera3Device::RequestThread::threadLoop() { request.frame_number = mFrameNumber++; + // Submit request and block until ready for next one res = mHal3Device->ops->process_capture_request(mHal3Device, &request); @@ -1181,6 +1329,35 @@ bool Camera3Device::RequestThread::threadLoop() { if (request.settings != NULL) { nextRequest->mSettings.unlock(request.settings); } + + // Remove any previously queued triggers (after unlock) + res = removeTriggers(mPrevRequest); + if (res != OK) { + ALOGE("RequestThread: Unable to remove triggers " + "(capture request %d, HAL device: %s (%d)", + request.frame_number, strerror(-res), res); + return false; + } + mPrevTriggers = triggerCount; + + // Read android.request.id from the request settings metadata + // - inform waitUntilRequestProcessed thread of a new request ID + { + Mutex::Autolock al(mLatestRequestMutex); + + camera_metadata_entry_t requestIdEntry = + nextRequest->mSettings.find(ANDROID_REQUEST_ID); + if (requestIdEntry.count > 0) { + mLatestRequestId = requestIdEntry.data.i32[0]; + } else { + ALOGW("%s: Did not have android.request.id set in the request", + __FUNCTION__); + mLatestRequestId = NAME_NOT_FOUND; + } + + mLatestRequestSignal.signal(); + } + return true; } @@ -1285,6 +1462,141 @@ bool Camera3Device::RequestThread::waitIfPaused() { return false; } +status_t Camera3Device::RequestThread::insertTriggers( + const sp &request) { + + Mutex::Autolock al(mTriggerMutex); + + CameraMetadata &metadata = request->mSettings; + size_t count = mTriggerMap.size(); + + for (size_t i = 0; i < count; ++i) { + RequestTrigger trigger = mTriggerMap.valueAt(i); + + uint32_t tag = trigger.metadataTag; + camera_metadata_entry entry = metadata.find(tag); + + if (entry.count > 0) { + /** + * Already has an entry for this trigger in the request. + * Rewrite it with our requested trigger value. + */ + RequestTrigger oldTrigger = trigger; + + oldTrigger.entryValue = entry.data.u8[0]; + + mTriggerReplacedMap.add(tag, oldTrigger); + } else { + /** + * More typical, no trigger entry, so we just add it + */ + mTriggerRemovedMap.add(tag, trigger); + } + + status_t res; + + switch (trigger.getTagType()) { + case TYPE_BYTE: { + uint8_t entryValue = static_cast(trigger.entryValue); + res = metadata.update(tag, + &entryValue, + /*count*/1); + break; + } + case TYPE_INT32: + res = metadata.update(tag, + &trigger.entryValue, + /*count*/1); + break; + default: + ALOGE("%s: Type not supported: 0x%x", + __FUNCTION__, + trigger.getTagType()); + return INVALID_OPERATION; + } + + if (res != OK) { + ALOGE("%s: Failed to update request metadata with trigger tag %s" + ", value %d", __FUNCTION__, trigger.getTagName(), + trigger.entryValue); + return res; + } + + ALOGV("%s: Mixed in trigger %s, value %d", __FUNCTION__, + trigger.getTagName(), + trigger.entryValue); + } + + mTriggerMap.clear(); + + return count; +} + +status_t Camera3Device::RequestThread::removeTriggers( + const sp &request) { + Mutex::Autolock al(mTriggerMutex); + + CameraMetadata &metadata = request->mSettings; + + /** + * Replace all old entries with their old values. + */ + for (size_t i = 0; i < mTriggerReplacedMap.size(); ++i) { + RequestTrigger trigger = mTriggerReplacedMap.valueAt(i); + + status_t res; + + uint32_t tag = trigger.metadataTag; + switch (trigger.getTagType()) { + case TYPE_BYTE: { + uint8_t entryValue = static_cast(trigger.entryValue); + res = metadata.update(tag, + &entryValue, + /*count*/1); + break; + } + case TYPE_INT32: + res = metadata.update(tag, + &trigger.entryValue, + /*count*/1); + break; + default: + ALOGE("%s: Type not supported: 0x%x", + __FUNCTION__, + trigger.getTagType()); + return INVALID_OPERATION; + } + + if (res != OK) { + ALOGE("%s: Failed to restore request metadata with trigger tag %s" + ", trigger value %d", __FUNCTION__, + trigger.getTagName(), trigger.entryValue); + return res; + } + } + mTriggerReplacedMap.clear(); + + /** + * Remove all new entries. + */ + for (size_t i = 0; i < mTriggerRemovedMap.size(); ++i) { + RequestTrigger trigger = mTriggerRemovedMap.valueAt(i); + status_t res = metadata.erase(trigger.metadataTag); + + if (res != OK) { + ALOGE("%s: Failed to erase metadata with trigger tag %s" + ", trigger value %d", __FUNCTION__, + trigger.getTagName(), trigger.entryValue); + return res; + } + } + mTriggerRemovedMap.clear(); + + return OK; +} + + + /** * Static callback forwarding methods from HAL to instance */ diff --git a/services/camera/libcameraservice/Camera3Device.h b/services/camera/libcameraservice/Camera3Device.h index 8600c6c723..7f294e6fd5 100644 --- a/services/camera/libcameraservice/Camera3Device.h +++ b/services/camera/libcameraservice/Camera3Device.h @@ -109,7 +109,7 @@ class Camera3Device : private: static const nsecs_t kShutdownTimeout = 5000000000; // 5 sec - + struct RequestTrigger; Mutex mLock; @@ -172,6 +172,23 @@ class Camera3Device : */ status_t configureStreamsLocked(); + struct RequestTrigger { + // Metadata tag number, e.g. android.control.aePrecaptureTrigger + uint32_t metadataTag; + // Metadata value, e.g. 'START' or the trigger ID + int32_t entryValue; + + // The last part of the fully qualified path, e.g. afTrigger + const char *getTagName() const { + return get_camera_metadata_tag_name(metadataTag) ?: "NULL"; + } + + // e.g. TYPE_BYTE, TYPE_INT32, etc. + int getTagType() const { + return get_camera_metadata_tag_type(metadataTag); + } + }; + /** * Thread for managing capture request submission to HAL device. */ @@ -198,6 +215,14 @@ class Camera3Device : status_t queueRequest(sp request); /** + * Queue a trigger to be dispatched with the next outgoing + * process_capture_request. The settings for that request only + * will be temporarily rewritten to add the trigger tag/value. + * Subsequent requests will not be rewritten (for this tag). + */ + status_t queueTrigger(RequestTrigger trigger[], size_t count); + + /** * Pause/unpause the capture thread. Doesn't block, so use * waitUntilPaused to wait until the thread is paused. */ @@ -210,11 +235,27 @@ class Camera3Device : */ status_t waitUntilPaused(nsecs_t timeout); + /** + * Wait until thread processes the capture request with settings' + * android.request.id == requestId. + * + * Returns TIMED_OUT in case the thread does not process the request + * within the timeout. + */ + status_t waitUntilRequestProcessed(int32_t requestId, nsecs_t timeout); + protected: virtual bool threadLoop(); private: + status_t queueTriggerLocked(RequestTrigger trigger); + // Mix-in queued triggers into this request + int32_t insertTriggers(const sp &request); + // Purge the queued triggers from this request, + // restoring the old field values for those tags. + status_t removeTriggers(const sp &request); + static const nsecs_t kRequestTimeout = 50e6; // 50 ms // Waits for a request, or returns NULL if times out. @@ -249,8 +290,20 @@ class Camera3Device : Condition mPausedSignal; sp mPrevRequest; + int32_t mPrevTriggers; int32_t mFrameNumber; + + Mutex mLatestRequestMutex; + Condition mLatestRequestSignal; + // android.request.id for latest process_capture_request + int32_t mLatestRequestId; + + typedef KeyedVector TriggerMap; + Mutex mTriggerMutex; + TriggerMap mTriggerMap; + TriggerMap mTriggerRemovedMap; + TriggerMap mTriggerReplacedMap; }; sp mRequestThread; diff --git a/services/camera/libcameraservice/camera2/CaptureSequencer.cpp b/services/camera/libcameraservice/camera2/CaptureSequencer.cpp index 18809125b5..ee03329953 100644 --- a/services/camera/libcameraservice/camera2/CaptureSequencer.cpp +++ b/services/camera/libcameraservice/camera2/CaptureSequencer.cpp @@ -270,6 +270,9 @@ CaptureSequencer::CaptureState CaptureSequencer::manageDone(sp &c processor->clearZslQueue(); } + /** + * Fire the jpegCallback in Camera#takePicture(..., jpegCallback) + */ if (mCaptureBuffer != 0 && res == OK) { Camera2Client::SharedCameraCallbacks::Lock l(client->mSharedCameraCallbacks); @@ -367,6 +370,8 @@ CaptureSequencer::CaptureState CaptureSequencer::manageZslReprocessing( CaptureSequencer::CaptureState CaptureSequencer::manageStandardStart( sp &client) { ATRACE_CALL(); + + // Get the onFrameAvailable callback when the requestID == mCaptureId client->registerFrameListener(mCaptureId, mCaptureId + 1, this); { @@ -426,6 +431,13 @@ CaptureSequencer::CaptureState CaptureSequencer::manageStandardCapture( SharedParameters::Lock l(client->getParameters()); Vector outputStreams; + /** + * Set up output streams in the request + * - preview + * - capture/jpeg + * - callback (if preview callbacks enabled) + * - recording (if recording enabled) + */ outputStreams.push(client->getPreviewStreamId()); outputStreams.push(client->getCaptureStreamId()); @@ -454,6 +466,7 @@ CaptureSequencer::CaptureState CaptureSequencer::manageStandardCapture( return DONE; } + // Create a capture copy since CameraDeviceBase#capture takes ownership CameraMetadata captureCopy = mCaptureRequest; if (captureCopy.entryCount() == 0) { ALOGE("%s: Camera %d: Unable to copy capture request for HAL device", @@ -461,7 +474,12 @@ CaptureSequencer::CaptureState CaptureSequencer::manageStandardCapture( return DONE; } + /** + * Clear the streaming request for still-capture pictures + * (as opposed to i.e. video snapshots) + */ if (l.mParameters.state == Parameters::STILL_CAPTURE) { + // API definition of takePicture() - stop preview before taking pic res = client->stopStream(); if (res != OK) { ALOGE("%s: Camera %d: Unable to stop preview for still capture: " @@ -488,6 +506,8 @@ CaptureSequencer::CaptureState CaptureSequencer::manageStandardCaptureWait( status_t res; ATRACE_CALL(); Mutex::Autolock l(mInputMutex); + + // Wait for new metadata result (mNewFrame) while (!mNewFrameReceived) { res = mNewFrameSignal.waitRelative(mInputMutex, kWaitDuration); if (res == TIMED_OUT) { @@ -495,12 +515,17 @@ CaptureSequencer::CaptureState CaptureSequencer::manageStandardCaptureWait( break; } } + + // Approximation of the shutter being closed + // - TODO: use the hal3 exposure callback in Camera3Device instead if (mNewFrameReceived && !mShutterNotified) { SharedParameters::Lock l(client->getParameters()); /* warning: this also locks a SharedCameraCallbacks */ shutterNotifyLocked(l.mParameters, client, mMsgType); mShutterNotified = true; } + + // Wait until jpeg was captured by JpegProcessor while (mNewFrameReceived && !mNewCaptureReceived) { res = mNewCaptureSignal.waitRelative(mInputMutex, kWaitDuration); if (res == TIMED_OUT) { @@ -524,7 +549,9 @@ CaptureSequencer::CaptureState CaptureSequencer::manageStandardCaptureWait( } if (entry.data.i64[0] != mCaptureTimestamp) { ALOGW("Mismatched capture timestamps: Metadata frame %lld," - " captured buffer %lld", entry.data.i64[0], mCaptureTimestamp); + " captured buffer %lld", + entry.data.i64[0], + mCaptureTimestamp); } client->removeFrameListener(mCaptureId, mCaptureId + 1, this); diff --git a/services/camera/libcameraservice/camera2/JpegProcessor.h b/services/camera/libcameraservice/camera2/JpegProcessor.h index 2283f28dec..74f4738d36 100644 --- a/services/camera/libcameraservice/camera2/JpegProcessor.h +++ b/services/camera/libcameraservice/camera2/JpegProcessor.h @@ -44,6 +44,7 @@ class JpegProcessor: JpegProcessor(wp client, wp sequencer); ~JpegProcessor(); + // CpuConsumer listener implementation void onFrameAvailable(); status_t updateStream(const Parameters ¶ms); -- 2.11.0