Camera: batching dequeueBuffer call for batched requests
Add plumbing to call into batched version of getBuffer call.
Also add some logic so batched version can be used even
when camera HAL is request buffer one at a time.
Camera3OutputStream will batch dequeue buffers on a batch unit.
This way we can save extra IPCs even when the client (camera
framework or camera HAL) does not call into batch dequeue API
(dequeueBuffers) directly.
As a safety measure, right before client is about to return
all outstanding buffers, all prefetched buffers will also
be returned.
TODO:
- Consider also batch queueBuffer path
- switch to batch API in requestStreamBuffer API
Test: GCA high speed recording
Bug: 113788435
Change-Id: I3cc0b62e8a6891d6ff6cad0e3c78e1d7abff6317
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index db6d3cf..95a9ad0 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -913,6 +913,7 @@
for (auto& outputStream : (*firstRequest)->mOutputStreams) {
if (outputStream->isVideoStream()) {
(*firstRequest)->mBatchSize = requestList->size();
+ outputStream->setBatchSize(requestList->size());
break;
}
}
diff --git a/services/camera/libcameraservice/device3/Camera3FakeStream.cpp b/services/camera/libcameraservice/device3/Camera3FakeStream.cpp
index ab5084e..2196c7d 100644
--- a/services/camera/libcameraservice/device3/Camera3FakeStream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3FakeStream.cpp
@@ -134,6 +134,11 @@
return INVALID_OPERATION;
}
+status_t Camera3FakeStream::setBatchSize(size_t /*batchSize*/) {
+ ALOGE("%s: this method is not supported!", __FUNCTION__);
+ return INVALID_OPERATION;
+}
+
}; // namespace camera3
}; // namespace android
diff --git a/services/camera/libcameraservice/device3/Camera3FakeStream.h b/services/camera/libcameraservice/device3/Camera3FakeStream.h
index 4a99079..914ccbf 100644
--- a/services/camera/libcameraservice/device3/Camera3FakeStream.h
+++ b/services/camera/libcameraservice/device3/Camera3FakeStream.h
@@ -98,6 +98,8 @@
const std::vector<size_t> &removedSurfaceIds,
KeyedVector<sp<Surface>, size_t> *outputMap/*out*/);
+ virtual status_t setBatchSize(size_t batchSize) override;
+
protected:
/**
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStream.cpp b/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
index 4b5889c..c835f51 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
@@ -189,6 +189,65 @@
return OK;
}
+status_t Camera3OutputStream::getBuffersLocked(std::vector<OutstandingBuffer>* outBuffers) {
+ status_t res;
+
+ if ((res = getBufferPreconditionCheckLocked()) != OK) {
+ return res;
+ }
+
+ if (mUseBufferManager) {
+ ALOGE("%s: stream %d is managed by buffer manager and does not support batch operation",
+ __FUNCTION__, mId);
+ return INVALID_OPERATION;
+ }
+
+ sp<Surface> consumer = mConsumer;
+ /**
+ * Release the lock briefly to avoid deadlock for below scenario:
+ * Thread 1: StreamingProcessor::startStream -> Camera3Stream::isConfiguring().
+ * This thread acquired StreamingProcessor lock and try to lock Camera3Stream lock.
+ * Thread 2: Camera3Stream::returnBuffer->StreamingProcessor::onFrameAvailable().
+ * This thread acquired Camera3Stream lock and bufferQueue lock, and try to lock
+ * StreamingProcessor lock.
+ * Thread 3: Camera3Stream::getBuffer(). This thread acquired Camera3Stream lock
+ * and try to lock bufferQueue lock.
+ * Then there is circular locking dependency.
+ */
+ mLock.unlock();
+
+ size_t numBuffersRequested = outBuffers->size();
+ std::vector<Surface::BatchBuffer> buffers(numBuffersRequested);
+
+ nsecs_t dequeueStart = systemTime(SYSTEM_TIME_MONOTONIC);
+ res = consumer->dequeueBuffers(&buffers);
+ nsecs_t dequeueEnd = systemTime(SYSTEM_TIME_MONOTONIC);
+ mDequeueBufferLatency.add(dequeueStart, dequeueEnd);
+
+ mLock.lock();
+
+ if (res != OK) {
+ if (shouldLogError(res, mState)) {
+ ALOGE("%s: Stream %d: Can't dequeue %zu output buffers: %s (%d)",
+ __FUNCTION__, mId, numBuffersRequested, strerror(-res), res);
+ }
+ checkRetAndSetAbandonedLocked(res);
+ return res;
+ }
+ checkRemovedBuffersLocked();
+
+ /**
+ * FenceFD now owned by HAL except in case of error,
+ * in which case we reassign it to acquire_fence
+ */
+ for (size_t i = 0; i < numBuffersRequested; i++) {
+ handoutBufferLocked(*(outBuffers->at(i).outBuffer),
+ &(buffers[i].buffer->handle), /*acquireFence*/buffers[i].fenceFd,
+ /*releaseFence*/-1, CAMERA_BUFFER_STATUS_OK, /*output*/true);
+ }
+ return OK;
+}
+
status_t Camera3OutputStream::queueBufferToConsumer(sp<ANativeWindow>& consumer,
ANativeWindowBuffer* buffer, int anwReleaseFence,
const std::vector<size_t>&) {
@@ -200,6 +259,10 @@
nsecs_t timestamp, const std::vector<size_t>& surface_ids) {
ATRACE_HFR_CALL();
+ if (mHandoutTotalBufferCount == 1) {
+ returnPrefetchedBuffersLocked();
+ }
+
status_t res = returnAnyBufferLocked(buffer, timestamp, /*output*/true, surface_ids);
if (res != OK) {
@@ -580,11 +643,46 @@
* and try to lock bufferQueue lock.
* Then there is circular locking dependency.
*/
- sp<ANativeWindow> currentConsumer = mConsumer;
+ sp<Surface> consumer = mConsumer;
+ size_t remainingBuffers = camera_stream::max_buffers - mHandoutTotalBufferCount;
mLock.unlock();
+ std::unique_lock<std::mutex> batchLock(mBatchLock);
nsecs_t dequeueStart = systemTime(SYSTEM_TIME_MONOTONIC);
- res = currentConsumer->dequeueBuffer(currentConsumer.get(), anb, fenceFd);
+
+ if (mBatchSize == 1) {
+ sp<ANativeWindow> anw = consumer;
+ res = anw->dequeueBuffer(anw.get(), anb, fenceFd);
+ } else {
+ res = OK;
+ if (mBatchedBuffers.size() == 0) {
+ size_t batchSize = mBatchSize;
+ if (remainingBuffers == 0) {
+ ALOGE("%s: cannot get buffer while all buffers are handed out", __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+ if (batchSize > remainingBuffers) {
+ batchSize = remainingBuffers;
+ }
+ // Refill batched buffers
+ mBatchedBuffers.resize(batchSize);
+ res = consumer->dequeueBuffers(&mBatchedBuffers);
+ if (res != OK) {
+ ALOGE("%s: batch dequeueBuffers call failed! %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ mBatchedBuffers.clear();
+ }
+ }
+
+ if (res == OK) {
+ // Dispatch batch buffers
+ *anb = mBatchedBuffers.back().buffer;
+ *fenceFd = mBatchedBuffers.back().fenceFd;
+ mBatchedBuffers.pop_back();
+ }
+ }
+ batchLock.unlock();
+
nsecs_t dequeueEnd = systemTime(SYSTEM_TIME_MONOTONIC);
mDequeueBufferLatency.add(dequeueStart, dequeueEnd);
@@ -678,6 +776,8 @@
return OK;
}
+ returnPrefetchedBuffersLocked();
+
ALOGV("%s: disconnecting stream %d from native window", __FUNCTION__, getId());
res = native_window_api_disconnect(mConsumer.get(),
@@ -1013,6 +1113,52 @@
graphicBuffer->unlock();
}
+status_t Camera3OutputStream::setBatchSize(size_t batchSize) {
+ Mutex::Autolock l(mLock);
+ std::lock_guard<std::mutex> lock(mBatchLock);
+ if (batchSize == 0) {
+ ALOGE("%s: invalid batch size 0", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ if (mUseBufferManager) {
+ ALOGE("%s: batch operation is not supported with buffer manager", __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+
+ if (!isVideoStream()) {
+ ALOGE("%s: batch operation is not supported with non-video stream", __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+
+ if (batchSize != mBatchSize) {
+ if (mBatchedBuffers.size() != 0) {
+ ALOGE("%s: change batch size from %zu to %zu dynamically is not supported",
+ __FUNCTION__, mBatchSize, batchSize);
+ return INVALID_OPERATION;
+ }
+
+ if (camera_stream::max_buffers < batchSize) {
+ ALOGW("%s: batch size is capped by max_buffers %d", __FUNCTION__,
+ camera_stream::max_buffers);
+ batchSize = camera_stream::max_buffers;
+ }
+ mBatchSize = batchSize;
+ }
+ return OK;
+}
+
+void Camera3OutputStream::returnPrefetchedBuffersLocked() {
+ std::lock_guard<std::mutex> batchLock(mBatchLock);
+ if (mBatchedBuffers.size() != 0) {
+ ALOGW("%s: %zu extra prefetched buffers detected. Returning",
+ __FUNCTION__, mBatchedBuffers.size());
+
+ mConsumer->cancelBuffers(mBatchedBuffers);
+ mBatchedBuffers.clear();
+ }
+}
+
}; // namespace camera3
}; // namespace android
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStream.h b/services/camera/libcameraservice/device3/Camera3OutputStream.h
index f504171..366d22a 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStream.h
+++ b/services/camera/libcameraservice/device3/Camera3OutputStream.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_SERVERS_CAMERA3_OUTPUT_STREAM_H
#define ANDROID_SERVERS_CAMERA3_OUTPUT_STREAM_H
+#include <mutex>
#include <utils/RefBase.h>
#include <gui/IProducerListener.h>
#include <gui/Surface.h>
@@ -206,6 +207,19 @@
KeyedVector<sp<Surface>, size_t> *outputMap/*out*/);
/**
+ * Set the batch size for buffer operations. The output stream will request
+ * buffers from buffer queue on a batch basis. Currently only video streams
+ * are allowed to set the batch size. Also if the stream is managed by
+ * buffer manager (Surface group in Java API) then batching is also not
+ * supported. Changing batch size on the fly while there is already batched
+ * buffers in the stream is also not supported.
+ * If the batch size is larger than the max dequeue count set
+ * by the camera HAL, the batch size will be set to the max dequeue count
+ * instead.
+ */
+ virtual status_t setBatchSize(size_t batchSize = 1) override;
+
+ /**
* Apply ZSL related consumer usage quirk.
*/
static void applyZSLUsageQuirk(int format, uint64_t *consumerUsage /*inout*/);
@@ -292,12 +306,26 @@
// Whether to drop valid buffers.
bool mDropBuffers;
+
+ // Protecting batch states below, must be acquired after mLock
+ std::mutex mBatchLock;
+
+ // The batch size for buffer operation
+ size_t mBatchSize = 1;
+
+ // Prefetched buffers (ready to be handed to client)
+ std::vector<Surface::BatchBuffer> mBatchedBuffers;
+
+ // ---- End of mBatchLock protected scope ----
+
/**
* Internal Camera3Stream interface
*/
virtual status_t getBufferLocked(camera_stream_buffer *buffer,
const std::vector<size_t>& surface_ids);
+ virtual status_t getBuffersLocked(/*out*/std::vector<OutstandingBuffer>* buffers) override;
+
virtual status_t returnBufferLocked(
const camera_stream_buffer &buffer,
nsecs_t timestamp, const std::vector<size_t>& surface_ids);
@@ -330,6 +358,8 @@
// Dump images to disk before returning to consumer
void dumpImageToDisk(nsecs_t timestamp, ANativeWindowBuffer* anwBuffer, int fence);
+ void returnPrefetchedBuffersLocked();
+
static const int32_t kDequeueLatencyBinSize = 5; // in ms
CameraLatencyHistogram mDequeueBufferLatency;
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h b/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h
index 7f5c87a..49f9f62 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h
+++ b/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h
@@ -95,6 +95,19 @@
* Query the physical camera id for the output stream.
*/
virtual const String8& getPhysicalCameraId() const = 0;
+
+ /**
+ * Set the batch size for buffer operations. The output stream will request
+ * buffers from buffer queue on a batch basis. Currently only video streams
+ * are allowed to set the batch size. Also if the stream is managed by
+ * buffer manager (Surface group in Java API) then batching is also not
+ * supported. Changing batch size on the fly while there is already batched
+ * buffers in the stream is also not supported.
+ * If the batch size is larger than the max dequeue count set
+ * by the camera HAL, the batch size will be set to the max dequeue count
+ * instead.
+ */
+ virtual status_t setBatchSize(size_t batchSize = 1) = 0;
};
// Helper class to organize a synchronized mapping of stream IDs to stream instances
diff --git a/services/camera/libcameraservice/device3/Camera3Stream.cpp b/services/camera/libcameraservice/device3/Camera3Stream.cpp
index ffdb90b..4cb954e 100644
--- a/services/camera/libcameraservice/device3/Camera3Stream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Stream.cpp
@@ -629,7 +629,8 @@
}
// Wait for new buffer returned back if we are running into the limit.
- if (getHandoutOutputBufferCountLocked() == camera_stream::max_buffers) {
+ size_t numOutstandingBuffers = getHandoutOutputBufferCountLocked();
+ if (numOutstandingBuffers == camera_stream::max_buffers) {
ALOGV("%s: Already dequeued max output buffers (%d), wait for next returned one.",
__FUNCTION__, camera_stream::max_buffers);
nsecs_t waitStart = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -647,6 +648,14 @@
}
return res;
}
+
+ size_t updatedNumOutstandingBuffers = getHandoutOutputBufferCountLocked();
+ if (updatedNumOutstandingBuffers >= numOutstandingBuffers) {
+ ALOGE("%s: outsanding buffer count goes from %zu to %zu, "
+ "getBuffer(s) call must not run in parallel!", __FUNCTION__,
+ numOutstandingBuffers, updatedNumOutstandingBuffers);
+ return INVALID_OPERATION;
+ }
}
res = getBufferLocked(buffer, surface_ids);
@@ -898,6 +907,12 @@
ALOGE("%s: This type of stream does not support output", __FUNCTION__);
return INVALID_OPERATION;
}
+
+status_t Camera3Stream::getBuffersLocked(std::vector<OutstandingBuffer>*) {
+ ALOGE("%s: This type of stream does not support output", __FUNCTION__);
+ return INVALID_OPERATION;
+}
+
status_t Camera3Stream::returnBufferLocked(const camera_stream_buffer &,
nsecs_t, const std::vector<size_t>&) {
ALOGE("%s: This type of stream does not support output", __FUNCTION__);
@@ -971,6 +986,80 @@
mBufferFreedListener = listener;
}
+status_t Camera3Stream::getBuffers(std::vector<OutstandingBuffer>* buffers,
+ nsecs_t waitBufferTimeout) {
+ ATRACE_CALL();
+ Mutex::Autolock l(mLock);
+ status_t res = OK;
+
+ if (buffers == nullptr) {
+ ALOGI("%s: buffers must not be null!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ size_t numBuffersRequested = buffers->size();
+ if (numBuffersRequested == 0) {
+ ALOGE("%s: 0 buffers are requested!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ // This function should be only called when the stream is configured already.
+ if (mState != STATE_CONFIGURED) {
+ ALOGE("%s: Stream %d: Can't get buffers if stream is not in CONFIGURED state %d",
+ __FUNCTION__, mId, mState);
+ if (mState == STATE_ABANDONED) {
+ return DEAD_OBJECT;
+ } else {
+ return INVALID_OPERATION;
+ }
+ }
+
+ size_t numOutstandingBuffers = getHandoutOutputBufferCountLocked();
+ // Wait for new buffer returned back if we are running into the limit.
+ while (numOutstandingBuffers + numBuffersRequested > camera_stream::max_buffers) {
+ ALOGV("%s: Already dequeued %zu output buffers and requesting %zu (max is %d), waiting.",
+ __FUNCTION__, numOutstandingBuffers, numBuffersRequested,
+ camera_stream::max_buffers);
+ nsecs_t waitStart = systemTime(SYSTEM_TIME_MONOTONIC);
+ if (waitBufferTimeout < kWaitForBufferDuration) {
+ waitBufferTimeout = kWaitForBufferDuration;
+ }
+ res = mOutputBufferReturnedSignal.waitRelative(mLock, waitBufferTimeout);
+ nsecs_t waitEnd = systemTime(SYSTEM_TIME_MONOTONIC);
+ mBufferLimitLatency.add(waitStart, waitEnd);
+ if (res != OK) {
+ if (res == TIMED_OUT) {
+ ALOGE("%s: wait for output buffer return timed out after %lldms (max_buffers %d)",
+ __FUNCTION__, waitBufferTimeout / 1000000LL,
+ camera_stream::max_buffers);
+ }
+ return res;
+ }
+ size_t updatedNumOutstandingBuffers = getHandoutOutputBufferCountLocked();
+ if (updatedNumOutstandingBuffers >= numOutstandingBuffers) {
+ ALOGE("%s: outsanding buffer count goes from %zu to %zu, "
+ "getBuffer(s) call must not run in parallel!", __FUNCTION__,
+ numOutstandingBuffers, updatedNumOutstandingBuffers);
+ return INVALID_OPERATION;
+ }
+ numOutstandingBuffers = updatedNumOutstandingBuffers;
+ }
+
+ res = getBuffersLocked(buffers);
+ if (res == OK) {
+ for (auto& outstandingBuffer : *buffers) {
+ camera_stream_buffer* buffer = outstandingBuffer.outBuffer;
+ fireBufferListenersLocked(*buffer, /*acquired*/true, /*output*/true);
+ if (buffer->buffer) {
+ Mutex::Autolock l(mOutstandingBuffersLock);
+ mOutstandingBuffers.push_back(*buffer->buffer);
+ }
+ }
+ }
+
+ return res;
+}
+
}; // namespace camera3
}; // namespace android
diff --git a/services/camera/libcameraservice/device3/Camera3Stream.h b/services/camera/libcameraservice/device3/Camera3Stream.h
index 72914f8..55ed2f2 100644
--- a/services/camera/libcameraservice/device3/Camera3Stream.h
+++ b/services/camera/libcameraservice/device3/Camera3Stream.h
@@ -327,6 +327,12 @@
const std::vector<size_t>& surface_ids = std::vector<size_t>());
/**
+ * Similar to getBuffer() except this method fills multiple buffers.
+ */
+ status_t getBuffers(std::vector<OutstandingBuffer>* buffers,
+ nsecs_t waitBufferTimeout);
+
+ /**
* Return a buffer to the stream after use by the HAL.
*
* Multiple surfaces could share the same HAL stream, but a request may
@@ -500,7 +506,11 @@
virtual status_t returnBufferLocked(const camera_stream_buffer &buffer,
nsecs_t timestamp,
const std::vector<size_t>& surface_ids = std::vector<size_t>());
+
+ virtual status_t getBuffersLocked(std::vector<OutstandingBuffer>*);
+
virtual status_t getInputBufferLocked(camera_stream_buffer *buffer);
+
virtual status_t returnInputBufferLocked(
const camera_stream_buffer &buffer);
virtual bool hasOutstandingBuffersLocked() const = 0;
diff --git a/services/camera/libcameraservice/device3/Camera3StreamInterface.h b/services/camera/libcameraservice/device3/Camera3StreamInterface.h
index d417252..c558b07 100644
--- a/services/camera/libcameraservice/device3/Camera3StreamInterface.h
+++ b/services/camera/libcameraservice/device3/Camera3StreamInterface.h
@@ -306,6 +306,24 @@
nsecs_t waitBufferTimeout,
const std::vector<size_t>& surface_ids = std::vector<size_t>()) = 0;
+ struct OutstandingBuffer {
+ camera_stream_buffer* outBuffer;
+
+ /**
+ * Multiple surfaces could share the same HAL stream, but a request may
+ * be only for a subset of surfaces. In this case, the
+ * Camera3StreamInterface object needs the surface ID information to acquire
+ * buffers for those surfaces. For the case of single surface for a HAL
+ * stream, surface_ids parameter has no effect.
+ */
+ std::vector<size_t> surface_ids;
+ };
+ /**
+ * Similar to getBuffer() except this method fills multiple buffers.
+ */
+ virtual status_t getBuffers(std::vector<OutstandingBuffer>* buffers,
+ nsecs_t waitBufferTimeout) = 0;
+
/**
* Return a buffer to the stream after use by the HAL.
*