EmulatedFakeCamera2: Add support for JPEG output, multiple streams
Required to support Camera.takePicture()
Bug: 6243944
Change-Id: I60d7a161a7037c25428eac5a6f9327aff47da584
diff --git a/tools/emulator/system/camera/Android.mk b/tools/emulator/system/camera/Android.mk
index dc2e624..ee08f94 100755
--- a/tools/emulator/system/camera/Android.mk
+++ b/tools/emulator/system/camera/Android.mk
@@ -60,7 +60,8 @@
EmulatedFakeCamera2.cpp \
EmulatedQemuCamera2.cpp \
fake-pipeline2/Scene.cpp \
- fake-pipeline2/Sensor.cpp
+ fake-pipeline2/Sensor.cpp \
+ fake-pipeline2/JpegCompressor.cpp
ifeq ($(TARGET_PRODUCT),vbox_x86)
diff --git a/tools/emulator/system/camera/EmulatedFakeCamera2.cpp b/tools/emulator/system/camera/EmulatedFakeCamera2.cpp
index ef273ba..31ac8ea 100644
--- a/tools/emulator/system/camera/EmulatedFakeCamera2.cpp
+++ b/tools/emulator/system/camera/EmulatedFakeCamera2.cpp
@@ -30,8 +30,10 @@
namespace android {
-const uint32_t EmulatedFakeCamera2::kAvailableFormats[3] = {
+const uint32_t EmulatedFakeCamera2::kAvailableFormats[5] = {
HAL_PIXEL_FORMAT_RAW_SENSOR,
+ HAL_PIXEL_FORMAT_BLOB,
+ HAL_PIXEL_FORMAT_RGBA_8888,
HAL_PIXEL_FORMAT_YV12,
HAL_PIXEL_FORMAT_YCrCb_420_SP
};
@@ -101,6 +103,11 @@
}
if (res != OK) return res;
+ mNextStreamId = 0;
+ mRawStreamCount = 0;
+ mProcessedStreamCount = 0;
+ mJpegStreamCount = 0;
+
return NO_ERROR;
}
@@ -114,10 +121,10 @@
mConfigureThread = new ConfigureThread(this);
mReadoutThread = new ReadoutThread(this);
- mSensor = new Sensor();
+ mSensor = new Sensor(this);
+ mJpegCompressor = new JpegCompressor(this);
mNextStreamId = 0;
- mRawStreamOps = NULL;
res = mSensor->startUp();
if (res != NO_ERROR) return res;
@@ -145,10 +152,12 @@
mConfigureThread->requestExit();
mReadoutThread->requestExit();
+ mJpegCompressor->cancel();
mConfigureThread->join();
mReadoutThread->join();
+
ALOGV("%s exit", __FUNCTION__);
return NO_ERROR;
}
@@ -174,12 +183,23 @@
ALOG_ASSERT(mFrameQueueDst != NULL,
"%s: Request queue src not set, but received queue notification!",
__FUNCTION__);
- ALOG_ASSERT(mRawStreamOps != NULL,
- "%s: No raw stream allocated, but received queue notification!",
+ ALOG_ASSERT(mStreams.size() != 0,
+ "%s: No streams allocated, but received queue notification!",
__FUNCTION__);
return mConfigureThread->newRequestAvailable();
}
+int EmulatedFakeCamera2::getInProgressCount() {
+ Mutex::Autolock l(mMutex);
+
+ int requestCount = 0;
+ requestCount += mConfigureThread->getInProgressCount();
+ requestCount += mReadoutThread->getInProgressCount();
+ requestCount += mJpegCompressor->isBusy() ? 1 : 0;
+
+ return requestCount;
+}
+
int EmulatedFakeCamera2::constructDefaultRequest(
int request_template,
camera_metadata_t **request) {
@@ -219,12 +239,6 @@
uint32_t *max_buffers) {
Mutex::Autolock l(mMutex);
- if (mNextStreamId > 0) {
- // TODO: Support more than one stream
- ALOGW("%s: Only one stream supported", __FUNCTION__);
- return BAD_VALUE;
- }
-
if (format != CAMERA2_HAL_PIXEL_FORMAT_OPAQUE) {
unsigned int numFormats = sizeof(kAvailableFormats) / sizeof(uint32_t);
unsigned int formatIdx = 0;
@@ -243,14 +257,25 @@
const uint32_t *availableSizes;
size_t availableSizeCount;
- if (format == HAL_PIXEL_FORMAT_RAW_SENSOR) {
- availableSizes = kAvailableRawSizes;
- availableSizeCount = sizeof(kAvailableRawSizes)/sizeof(uint32_t);
- } else {
- availableSizes = kAvailableProcessedSizes;
- availableSizeCount = sizeof(kAvailableProcessedSizes)/sizeof(uint32_t);
+ switch (format) {
+ case HAL_PIXEL_FORMAT_RAW_SENSOR:
+ availableSizes = kAvailableRawSizes;
+ availableSizeCount = sizeof(kAvailableRawSizes)/sizeof(uint32_t);
+ break;
+ case HAL_PIXEL_FORMAT_BLOB:
+ availableSizes = kAvailableJpegSizes;
+ availableSizeCount = sizeof(kAvailableJpegSizes)/sizeof(uint32_t);
+ break;
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_YV12:
+ case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ availableSizes = kAvailableProcessedSizes;
+ availableSizeCount = sizeof(kAvailableProcessedSizes)/sizeof(uint32_t);
+ break;
+ default:
+ ALOGE("%s: Unknown format 0x%x", __FUNCTION__, format);
+ return BAD_VALUE;
}
- // TODO: JPEG sizes
unsigned int resIdx = 0;
for (; resIdx < availableSizeCount; resIdx++) {
@@ -263,16 +288,41 @@
return BAD_VALUE;
}
- // TODO: Generalize below to work for variable types of streams, etc.
- // Currently only correct for raw sensor format, sensor resolution.
+ switch (format) {
+ case HAL_PIXEL_FORMAT_RAW_SENSOR:
+ if (mRawStreamCount >= kMaxRawStreamCount) {
+ ALOGE("%s: Cannot allocate another raw stream (%d already allocated)",
+ __FUNCTION__, mRawStreamCount);
+ return INVALID_OPERATION;
+ }
+ mRawStreamCount++;
+ break;
+ case HAL_PIXEL_FORMAT_BLOB:
+ if (mJpegStreamCount >= kMaxJpegStreamCount) {
+ ALOGE("%s: Cannot allocate another JPEG stream (%d already allocated)",
+ __FUNCTION__, mJpegStreamCount);
+ return INVALID_OPERATION;
+ }
+ mJpegStreamCount++;
+ break;
+ default:
+ if (mProcessedStreamCount >= kMaxProcessedStreamCount) {
+ ALOGE("%s: Cannot allocate another processed stream (%d already allocated)",
+ __FUNCTION__, mProcessedStreamCount);
+ return INVALID_OPERATION;
+ }
+ mProcessedStreamCount++;
+ }
- ALOG_ASSERT(width == Sensor::kResolution[0],
- "%s: TODO: Only supporting raw sensor size right now", __FUNCTION__);
- ALOG_ASSERT(height == Sensor::kResolution[1],
- "%s: TODO: Only supporting raw sensor size right now", __FUNCTION__);
+ Stream newStream;
+ newStream.ops = stream_ops;
+ newStream.width = width;
+ newStream.height = height;
+ newStream.format = format;
+ // TODO: Query stride from gralloc
+ newStream.stride = width;
- mStreamFormat = format;
- mRawStreamOps = stream_ops;
+ mStreams.add(mNextStreamId, newStream);
*stream_id = mNextStreamId;
if (format_actual) *format_actual = format;
@@ -298,11 +348,32 @@
int EmulatedFakeCamera2::releaseStream(uint32_t stream_id) {
Mutex::Autolock l(mMutex);
- ALOG_ASSERT(stream_id == 0,
- "%s: TODO: Only one stream supported", __FUNCTION__);
- // TODO: Need to clean up better than this - in-flight buffers likely
- mRawStreamOps = NULL;
+ ssize_t streamIndex = mStreams.indexOfKey(stream_id);
+ if (streamIndex < 0) {
+ ALOGE("%s: Unknown stream id %d!", __FUNCTION__, stream_id);
+ return BAD_VALUE;
+ }
+
+ if (isStreamInUse(stream_id)) {
+ ALOGE("%s: Cannot release stream %d; in use!", __FUNCTION__,
+ stream_id);
+ return BAD_VALUE;
+ }
+
+ switch(mStreams.valueAt(streamIndex).format) {
+ case HAL_PIXEL_FORMAT_RAW_SENSOR:
+ mRawStreamCount--;
+ break;
+ case HAL_PIXEL_FORMAT_BLOB:
+ mJpegStreamCount--;
+ break;
+ default:
+ mProcessedStreamCount--;
+ break;
+ }
+
+ mStreams.removeItemsAt(streamIndex);
return NO_ERROR;
}
@@ -435,6 +506,21 @@
return OK;
}
+bool EmulatedFakeCamera2::ConfigureThread::isStreamInUse(uint32_t id) {
+ Mutex::Autolock lock(mInternalsMutex);
+
+ if (mNextBuffers == NULL) return false;
+ for (size_t i=0; i < mNextBuffers->size(); i++) {
+ if ((*mNextBuffers)[i].streamId == (int)id) return true;
+ }
+ return false;
+}
+
+int EmulatedFakeCamera2::ConfigureThread::getInProgressCount() {
+ Mutex::Autolock lock(mInternalsMutex);
+ return mNextBuffers == NULL ? 0 : 1;
+}
+
bool EmulatedFakeCamera2::ConfigureThread::threadLoop() {
static const nsecs_t kWaitPerLoop = 10000000L; // 10 ms
status_t res;
@@ -457,6 +543,8 @@
// Active
}
if (mRequest == NULL) {
+ Mutex::Autolock il(mInternalsMutex);
+
ALOGV("Getting next request");
res = mParent->mRequestQueueSrc->dequeue_request(
mParent->mRequestQueueSrc,
@@ -486,11 +574,24 @@
mParent->signalError();
return false;
}
- // TODO: Only raw stream supported
- if (streams.count != 1 || streams.data.u8[0] != 0) {
- ALOGE("%s: TODO: Only raw stream supported", __FUNCTION__);
- mParent->signalError();
- return false;
+
+ mNextBuffers = new Buffers;
+ mNextNeedsJpeg = false;
+ ALOGV("Setting up buffers for capture");
+ for (size_t i = 0; i < streams.count; i++) {
+ const Stream &s = mParent->getStreamInfo(streams.data.u8[i]);
+ StreamBuffer b;
+ b.streamId = streams.data.u8[i];
+ b.width = s.width;
+ b.height = s.height;
+ b.format = s.format;
+ b.stride = s.stride;
+ mNextBuffers->push_back(b);
+ ALOGV(" Buffer %d: Stream %d, %d x %d, format 0x%x, stride %d",
+ i, b.streamId, b.width, b.height, b.format, b.stride);
+ if (b.format == HAL_PIXEL_FORMAT_BLOB) {
+ mNextNeedsJpeg = true;
+ }
}
camera_metadata_entry_t e;
@@ -548,56 +649,70 @@
mParent->mSensor->getScene().setHour(*e.data.i32);
}
- // TODO: Fetch stride from gralloc
- mNextBufferStride = Sensor::kResolution[0];
+ // Start waiting on sensor or JPEG block
+ if (mNextNeedsJpeg) {
+ ALOGV("Waiting for JPEG compressor");
+ } else {
+ ALOGV("Waiting for sensor");
+ }
+ }
- // Start waiting on sensor
+ if (mNextNeedsJpeg) {
+ bool jpegDone;
+ jpegDone = mParent->mJpegCompressor->waitForDone(kWaitPerLoop);
+ if (!jpegDone) return true;
+
ALOGV("Waiting for sensor");
+ mNextNeedsJpeg = false;
}
bool vsync = mParent->mSensor->waitForVSync(kWaitPerLoop);
- if (vsync) {
- ALOGV("Configuring sensor for frame %d", mNextFrameNumber);
- mParent->mSensor->setExposureTime(mNextExposureTime);
- mParent->mSensor->setFrameDuration(mNextFrameDuration);
- mParent->mSensor->setSensitivity(mNextSensitivity);
+ if (!vsync) return true;
- /** Get buffer to fill for this frame */
- // TODO: Only does raw stream
+ Mutex::Autolock il(mInternalsMutex);
+ ALOGV("Configuring sensor for frame %d", mNextFrameNumber);
+ mParent->mSensor->setExposureTime(mNextExposureTime);
+ mParent->mSensor->setFrameDuration(mNextFrameDuration);
+ mParent->mSensor->setSensitivity(mNextSensitivity);
- /* Get next buffer from raw stream */
- mNextBuffer = NULL;
- res = mParent->mRawStreamOps->dequeue_buffer(mParent->mRawStreamOps,
- &mNextBuffer);
- if (res != NO_ERROR || mNextBuffer == NULL) {
- ALOGE("%s: Unable to dequeue buffer from stream %d: %d",
- __FUNCTION__, 0, res);
+ /** Get buffers to fill for this frame */
+ for (size_t i = 0; i < mNextBuffers->size(); i++) {
+ StreamBuffer &b = mNextBuffers->editItemAt(i);
+
+ Stream s = mParent->getStreamInfo(b.streamId);
+
+ res = s.ops->dequeue_buffer(s.ops, &(b.buffer) );
+ if (res != NO_ERROR || b.buffer == NULL) {
+ ALOGE("%s: Unable to dequeue buffer from stream %d: %s (%d)",
+ __FUNCTION__, b.streamId, strerror(-res), res);
mParent->signalError();
return false;
}
/* Lock the buffer from the perspective of the graphics mapper */
uint8_t *img;
- const Rect rect(Sensor::kResolution[0], Sensor::kResolution[1]);
+ const Rect rect(s.width, s.height);
- res = GraphicBufferMapper::get().lock(*mNextBuffer,
+ res = GraphicBufferMapper::get().lock(*(b.buffer),
GRALLOC_USAGE_SW_WRITE_OFTEN,
- rect, (void**)&img);
+ rect, (void**)&(b.img) );
if (res != NO_ERROR) {
- ALOGE("%s: grbuffer_mapper.lock failure: %d", __FUNCTION__, res);
- mParent->mRawStreamOps->cancel_buffer(mParent->mRawStreamOps,
- mNextBuffer);
+ ALOGE("%s: grbuffer_mapper.lock failure: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ s.ops->cancel_buffer(s.ops,
+ b.buffer);
mParent->signalError();
return false;
}
- mParent->mSensor->setDestinationBuffer(img, mParent->mStreamFormat,
- mNextBufferStride);
- mParent->mReadoutThread->setNextCapture(mRequest, mNextBuffer);
-
- mRequest = NULL;
}
+ mParent->mReadoutThread->setNextCapture(mRequest, mNextBuffers);
+ mParent->mSensor->setDestinationBuffers(mNextBuffers);
+
+ mRequest = NULL;
+ mNextBuffers = NULL;
+
return true;
}
@@ -606,8 +721,7 @@
mParent(parent),
mRunning(false),
mActive(false),
- mRequest(NULL),
- mBuffer(NULL)
+ mRequest(NULL)
{
mInFlightQueue = new InFlightQueue[kInFlightQueueSize];
mInFlightHead = 0;
@@ -635,8 +749,9 @@
return OK;
}
-void EmulatedFakeCamera2::ReadoutThread::setNextCapture(camera_metadata_t *request,
- buffer_handle_t *buffer) {
+void EmulatedFakeCamera2::ReadoutThread::setNextCapture(
+ camera_metadata_t *request,
+ Buffers *buffers) {
Mutex::Autolock lock(mInputMutex);
if ( (mInFlightTail + 1) % kInFlightQueueSize == mInFlightHead) {
ALOGE("In flight queue full, dropping captures");
@@ -644,7 +759,7 @@
return;
}
mInFlightQueue[mInFlightTail].request = request;
- mInFlightQueue[mInFlightTail].buffer = buffer;
+ mInFlightQueue[mInFlightTail].buffers = buffers;
mInFlightTail = (mInFlightTail + 1) % kInFlightQueueSize;
if (!mActive) {
@@ -653,6 +768,41 @@
}
}
+bool EmulatedFakeCamera2::ReadoutThread::isStreamInUse(uint32_t id) {
+ Mutex::Autolock lock(mInputMutex);
+
+ size_t i = mInFlightHead;
+ while (i != mInFlightTail) {
+ for (size_t j = 0; j < mInFlightQueue[i].buffers->size(); j++) {
+ if ( (*(mInFlightQueue[i].buffers))[j].streamId == (int)id )
+ return true;
+ }
+ i = (i + 1) % kInFlightQueueSize;
+ }
+
+ Mutex::Autolock iLock(mInternalsMutex);
+
+ if (mBuffers != NULL) {
+ for (i = 0; i < mBuffers->size(); i++) {
+ if ( (*mBuffers)[i].streamId == (int)id) return true;
+ }
+ }
+
+ return false;
+}
+
+int EmulatedFakeCamera2::ReadoutThread::getInProgressCount() {
+ Mutex::Autolock lock(mInputMutex);
+ Mutex::Autolock iLock(mInternalsMutex);
+
+ int requestCount =
+ ((mInFlightTail + kInFlightQueueSize) - mInFlightHead)
+ % kInFlightQueueSize;
+ requestCount += (mBuffers == NULL) ? 0 : 1;
+
+ return requestCount;
+}
+
bool EmulatedFakeCamera2::ReadoutThread::threadLoop() {
static const nsecs_t kWaitPerLoop = 10000000L; // 10 ms
status_t res;
@@ -679,11 +829,14 @@
mActive = false;
return true;
} else {
+ Mutex::Autolock iLock(mInternalsMutex);
mRequest = mInFlightQueue[mInFlightHead].request;
- mBuffer = mInFlightQueue[mInFlightHead].buffer;
+ mBuffers = mInFlightQueue[mInFlightHead].buffers;
mInFlightQueue[mInFlightHead].request = NULL;
- mInFlightQueue[mInFlightHead].buffer = NULL;
+ mInFlightQueue[mInFlightHead].buffers = NULL;
mInFlightHead = (mInFlightHead + 1) % kInFlightQueueSize;
+ ALOGV("Ready to read out request %p, %d buffers",
+ mRequest, mBuffers->size());
}
}
}
@@ -700,6 +853,7 @@
// Got sensor data, construct frame and send it out
ALOGV("Readout: Constructing metadata and frames");
+ Mutex::Autolock iLock(mInternalsMutex);
camera_metadata_entry_t metadataMode;
res = find_camera_metadata_entry(mRequest,
@@ -771,16 +925,41 @@
}
mRequest = NULL;
- ALOGV("Sending image buffer to output stream.");
- GraphicBufferMapper::get().unlock(*mBuffer);
- res = mParent->mRawStreamOps->enqueue_buffer(mParent->mRawStreamOps,
- captureTime, mBuffer);
- if (res != OK) {
- ALOGE("Error enqueuing image buffer %p: %s (%d)", mBuffer,
- strerror(-res), res);
- // TODO: Should this cause a stop?
+ int compressedBufferIndex = -1;
+ ALOGV("Processing %d buffers", mBuffers->size());
+ for (size_t i = 0; i < mBuffers->size(); i++) {
+ const StreamBuffer &b = (*mBuffers)[i];
+ ALOGV(" Buffer %d: Stream %d, %d x %d, format 0x%x, stride %d",
+ i, b.streamId, b.width, b.height, b.format, b.stride);
+ if (b.streamId >= 0) {
+ if (b.format == HAL_PIXEL_FORMAT_BLOB) {
+ // Assumes only one BLOB buffer type per capture
+ compressedBufferIndex = i;
+ } else {
+ ALOGV("Sending image buffer %d to output stream %d",
+ i, b.streamId);
+ GraphicBufferMapper::get().unlock(*(b.buffer));
+ res = mParent->getStreamInfo(b.streamId).ops->enqueue_buffer(
+ mParent->getStreamInfo(b.streamId).ops,
+ captureTime, b.buffer);
+ if (res != OK) {
+ ALOGE("Error enqueuing image buffer %p: %s (%d)", b.buffer,
+ strerror(-res), res);
+ mParent->signalError();
+ }
+ }
+ }
}
- mBuffer = NULL;
+ if (compressedBufferIndex == -1) {
+ delete mBuffers;
+ mBuffers = NULL;
+ } else {
+ ALOGV("Starting JPEG compression for buffer %d, stream %d",
+ compressedBufferIndex,
+ (*mBuffers)[compressedBufferIndex].streamId);
+ mParent->mJpegCompressor->start(mBuffers, captureTime);
+ mBuffers = NULL;
+ }
return true;
}
@@ -789,7 +968,7 @@
status_t EmulatedFakeCamera2::constructStaticInfo(
camera_metadata_t **info,
- bool sizeRequest) {
+ bool sizeRequest) const {
size_t entryCount = 0;
size_t dataCount = 0;
@@ -958,6 +1137,9 @@
ADD_OR_SIZE(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
jpegThumbnailSizes, sizeof(jpegThumbnailSizes)/sizeof(int32_t));
+ static const int32_t jpegMaxSize = JpegCompressor::kMaxJpegSize;
+ ADD_OR_SIZE(ANDROID_JPEG_MAX_SIZE, &jpegMaxSize, 1);
+
// android.stats
static const uint8_t availableFaceDetectModes[] = {
@@ -1080,7 +1262,7 @@
status_t EmulatedFakeCamera2::constructDefaultRequest(
int request_template,
camera_metadata_t **request,
- bool sizeRequest) {
+ bool sizeRequest) const {
size_t entryCount = 0;
size_t dataCount = 0;
@@ -1433,5 +1615,25 @@
}
}
+bool EmulatedFakeCamera2::isStreamInUse(uint32_t id) {
+ // Assumes mMutex is locked; otherwise new requests could enter
+ // configureThread while readoutThread is being checked
+
+ // Order of isStreamInUse calls matters
+ if (mConfigureThread->isStreamInUse(id) ||
+ mReadoutThread->isStreamInUse(id) ||
+ mJpegCompressor->isStreamInUse(id) ) {
+ ALOGE("%s: Stream %d is in use in active requests!",
+ __FUNCTION__, id);
+ return true;
+ }
+ return false;
+ }
+
+const Stream& EmulatedFakeCamera2::getStreamInfo(uint32_t streamId) {
+ Mutex::Autolock lock(mMutex);
+
+ return mStreams.valueFor(streamId);
+}
}; /* namespace android */
diff --git a/tools/emulator/system/camera/EmulatedFakeCamera2.h b/tools/emulator/system/camera/EmulatedFakeCamera2.h
index eba1a8e..34d1b12 100644
--- a/tools/emulator/system/camera/EmulatedFakeCamera2.h
+++ b/tools/emulator/system/camera/EmulatedFakeCamera2.h
@@ -24,8 +24,11 @@
*/
#include "EmulatedCamera2.h"
+#include "fake-pipeline2/Base.h"
#include "fake-pipeline2/Sensor.h"
+#include "fake-pipeline2/JpegCompressor.h"
#include <utils/Condition.h>
+#include <utils/KeyedVector.h>
#include <utils/Thread.h>
namespace android {
@@ -69,7 +72,7 @@
virtual int requestQueueNotify();
/** Count of requests in flight */
- //virtual int getInProgressCount();
+ virtual int getInProgressCount();
/** Cancel all captures in flight */
//virtual int flushCapturesInProgress();
@@ -121,7 +124,13 @@
virtual int dump(int fd);
- /** Methods for worker threads to call */
+public:
+ /****************************************************************************
+ * Utility methods called by configure/readout threads and pipeline
+ ***************************************************************************/
+
+ // Get information about a given stream. Will lock mMutex
+ const Stream &getStreamInfo(uint32_t streamId);
// Notifies rest of camera subsystem of serious error
void signalError();
@@ -133,15 +142,15 @@
/** Construct static camera metadata, two-pass */
status_t constructStaticInfo(
camera_metadata_t **info,
- bool sizeRequest);
+ bool sizeRequest) const;
/** Two-pass implementation of constructDefaultRequest */
status_t constructDefaultRequest(
int request_template,
camera_metadata_t **request,
- bool sizeRequest);
+ bool sizeRequest) const;
/** Helper function for constructDefaultRequest */
- status_t addOrSize( camera_metadata_t *request,
+ static status_t addOrSize( camera_metadata_t *request,
bool sizeRequest,
size_t *entryCount,
size_t *dataCount,
@@ -149,6 +158,10 @@
const void *entry_data,
size_t entry_count);
+ /** Determine if the stream id is listed in any currently-in-flight
+ * requests. Assumes mMutex is locked */
+ bool isStreamInUse(uint32_t streamId);
+
/****************************************************************************
* Pipeline controller threads
***************************************************************************/
@@ -161,6 +174,9 @@
status_t waitUntilRunning();
status_t newRequestAvailable();
status_t readyToRun();
+
+ bool isStreamInUse(uint32_t id);
+ int getInProgressCount();
private:
EmulatedFakeCamera2 *mParent;
@@ -173,12 +189,14 @@
// working on them
camera_metadata_t *mRequest;
+
+ Mutex mInternalsMutex; // Lock before accessing below members.
+ bool mNextNeedsJpeg;
int32_t mNextFrameNumber;
int64_t mNextExposureTime;
int64_t mNextFrameDuration;
int32_t mNextSensitivity;
- buffer_handle_t *mNextBuffer;
- int mNextBufferStride;
+ Buffers *mNextBuffers;
};
class ReadoutThread: public Thread {
@@ -191,8 +209,10 @@
// Input
status_t waitUntilRunning();
void setNextCapture(camera_metadata_t *request,
- buffer_handle_t *buffer);
+ Buffers *buffers);
+ bool isStreamInUse(uint32_t id);
+ int getInProgressCount();
private:
EmulatedFakeCamera2 *mParent;
@@ -207,15 +227,16 @@
static const int kInFlightQueueSize = 4;
struct InFlightQueue {
camera_metadata_t *request;
- buffer_handle_t *buffer;
+ Buffers *buffers;
} *mInFlightQueue;
- int mInFlightHead;
- int mInFlightTail;
+ size_t mInFlightHead;
+ size_t mInFlightTail;
// Internals
+ Mutex mInternalsMutex;
camera_metadata_t *mRequest;
- buffer_handle_t *mBuffer;
+ Buffers *mBuffers;
};
@@ -223,6 +244,9 @@
* Static configuration information
***************************************************************************/
private:
+ static const uint32_t kMaxRawStreamCount = 1;
+ static const uint32_t kMaxProcessedStreamCount = 3;
+ static const uint32_t kMaxJpegStreamCount = 1;
static const uint32_t kAvailableFormats[];
static const uint32_t kAvailableRawSizes[];
static const uint64_t kAvailableRawMinDurations[];
@@ -245,11 +269,15 @@
/** Stream manipulation */
uint32_t mNextStreamId;
- const camera2_stream_ops_t *mRawStreamOps;
- uint32_t mStreamFormat;
+ uint32_t mRawStreamCount;
+ uint32_t mProcessedStreamCount;
+ uint32_t mJpegStreamCount;
+
+ KeyedVector<uint32_t, Stream> mStreams;
/** Simulated hardware interfaces */
sp<Sensor> mSensor;
+ sp<JpegCompressor> mJpegCompressor;
/** Pipeline control threads */
sp<ConfigureThread> mConfigureThread;
diff --git a/tools/emulator/system/camera/fake-pipeline2/Base.h b/tools/emulator/system/camera/fake-pipeline2/Base.h
new file mode 100644
index 0000000..f6ee5db
--- /dev/null
+++ b/tools/emulator/system/camera/fake-pipeline2/Base.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This file includes various basic structures that are needed by multiple parts
+ * of the fake camera 2 implementation.
+ */
+
+#ifndef HW_EMULATOR_CAMERA2_BASE_H
+#define HW_EMULATOR_CAMERA2_BASE_H
+
+#include <system/window.h>
+#include <hardware/camera2.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+
+/* Internal structure for passing buffers across threads */
+struct StreamBuffer {
+ int streamId;
+ uint32_t width, height;
+ uint32_t format;
+ uint32_t stride;
+ buffer_handle_t *buffer;
+ uint8_t *img;
+};
+typedef Vector<StreamBuffer> Buffers;
+
+struct Stream {
+ uint32_t id;
+ const camera2_stream_ops_t *ops;
+ uint32_t width, height;
+ uint32_t format;
+ uint32_t stride;
+};
+
+} // namespace android;
+
+#endif
diff --git a/tools/emulator/system/camera/fake-pipeline2/JpegCompressor.cpp b/tools/emulator/system/camera/fake-pipeline2/JpegCompressor.cpp
new file mode 100644
index 0000000..76fbb94
--- /dev/null
+++ b/tools/emulator/system/camera/fake-pipeline2/JpegCompressor.cpp
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera2_JpegCompressor"
+
+#include <utils/Log.h>
+#include <ui/GraphicBufferMapper.h>
+
+#include "JpegCompressor.h"
+#include "../EmulatedFakeCamera2.h"
+
+namespace android {
+
+JpegCompressor::JpegCompressor(EmulatedFakeCamera2 *parent):
+ Thread(false),
+ mIsBusy(false),
+ mParent(parent),
+ mBuffers(NULL),
+ mCaptureTime(0) {
+}
+
+JpegCompressor::~JpegCompressor() {
+ Mutex::Autolock lock(mMutex);
+}
+
+status_t JpegCompressor::start(Buffers *buffers,
+ nsecs_t captureTime) {
+ Mutex::Autolock lock(mMutex);
+ {
+ Mutex::Autolock busyLock(mBusyMutex);
+
+ if (mIsBusy) {
+ ALOGE("%s: Already processing a buffer!", __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+
+ mIsBusy = true;
+
+ mBuffers = buffers;
+ mCaptureTime = captureTime;
+ }
+
+ status_t res;
+ res = run("EmulatedFakeCamera2::JpegCompressor");
+ if (res != OK) {
+ ALOGE("%s: Unable to start up compression thread: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ delete mBuffers;
+ }
+ return res;
+}
+
+status_t JpegCompressor::cancel() {
+ requestExitAndWait();
+ return OK;
+}
+
+status_t JpegCompressor::readyToRun() {
+ return OK;
+}
+
+bool JpegCompressor::threadLoop() {
+ Mutex::Autolock lock(mMutex);
+ ALOGV("%s: Starting compression thread", __FUNCTION__);
+
+ // Find source and target buffers
+
+ bool foundJpeg = false, mFoundAux = false;
+ for (size_t i = 0; i < mBuffers->size(); i++) {
+ const StreamBuffer &b = (*mBuffers)[i];
+ if (b.format == HAL_PIXEL_FORMAT_BLOB) {
+ mJpegBuffer = b;
+ mFoundJpeg = true;
+ } else if (b.streamId == -1) {
+ mAuxBuffer = b;
+ mFoundAux = true;
+ }
+ if (mFoundJpeg && mFoundAux) break;
+ }
+ if (!mFoundJpeg || !mFoundAux) {
+ ALOGE("%s: Unable to find buffers for JPEG source/destination",
+ __FUNCTION__);
+ cleanUp();
+ return false;
+ }
+
+ // Set up error management
+
+ mJpegErrorInfo = NULL;
+ JpegError error;
+ error.parent = this;
+
+ mCInfo.err = jpeg_std_error(&error);
+ mCInfo.err->error_exit = jpegErrorHandler;
+
+ jpeg_create_compress(&mCInfo);
+ if (checkError("Error initializing compression")) return false;
+
+ // Route compressed data straight to output stream buffer
+
+ JpegDestination jpegDestMgr;
+ jpegDestMgr.parent = this;
+ jpegDestMgr.init_destination = jpegInitDestination;
+ jpegDestMgr.empty_output_buffer = jpegEmptyOutputBuffer;
+ jpegDestMgr.term_destination = jpegTermDestination;
+
+ mCInfo.dest = &jpegDestMgr;
+
+ // Set up compression parameters
+
+ mCInfo.image_width = mAuxBuffer.width;
+ mCInfo.image_height = mAuxBuffer.height;
+ mCInfo.input_components = 3;
+ mCInfo.in_color_space = JCS_RGB;
+
+ jpeg_set_defaults(&mCInfo);
+ if (checkError("Error configuring defaults")) return false;
+
+ // Do compression
+
+ jpeg_start_compress(&mCInfo, TRUE);
+ if (checkError("Error starting compression")) return false;
+
+ size_t rowStride = mAuxBuffer.stride * 3;
+ const size_t kChunkSize = 32;
+ while (mCInfo.next_scanline < mCInfo.image_height) {
+ JSAMPROW chunk[kChunkSize];
+ for (size_t i = 0 ; i < kChunkSize; i++) {
+ chunk[i] = (JSAMPROW)
+ (mAuxBuffer.img + (i + mCInfo.next_scanline) * rowStride);
+ }
+ jpeg_write_scanlines(&mCInfo, chunk, kChunkSize);
+ if (checkError("Error while compressing")) return false;
+ if (exitPending()) {
+ ALOGV("%s: Cancel called, exiting early", __FUNCTION__);
+ cleanUp();
+ return false;
+ }
+ }
+
+ jpeg_finish_compress(&mCInfo);
+ if (checkError("Error while finishing compression")) return false;
+
+ // Write to JPEG output stream
+
+ ALOGV("%s: Compression complete, pushing to stream %d", __FUNCTION__,
+ mJpegBuffer.streamId);
+
+ GraphicBufferMapper::get().unlock(*(mJpegBuffer.buffer));
+ status_t res;
+ const Stream &s = mParent->getStreamInfo(mJpegBuffer.streamId);
+ res = s.ops->enqueue_buffer(s.ops, mCaptureTime, mJpegBuffer.buffer);
+ if (res != OK) {
+ ALOGE("%s: Error queueing compressed image buffer %p: %s (%d)",
+ __FUNCTION__, mJpegBuffer.buffer, strerror(-res), res);
+ mParent->signalError();
+ }
+
+ // All done
+
+ cleanUp();
+
+ return false;
+}
+
+bool JpegCompressor::isBusy() {
+ Mutex::Autolock busyLock(mBusyMutex);
+ return mIsBusy;
+}
+
+bool JpegCompressor::isStreamInUse(uint32_t id) {
+ Mutex::Autolock lock(mBusyMutex);
+
+ if (mBuffers && mIsBusy) {
+ for (size_t i = 0; i < mBuffers->size(); i++) {
+ if ( (*mBuffers)[i].streamId == (int)id ) return true;
+ }
+ }
+ return false;
+}
+
+bool JpegCompressor::waitForDone(nsecs_t timeout) {
+ Mutex::Autolock lock(mBusyMutex);
+ status_t res = OK;
+ if (mIsBusy) {
+ res = mDone.waitRelative(mBusyMutex, timeout);
+ }
+ return (res == OK);
+}
+
+bool JpegCompressor::checkError(const char *msg) {
+ if (mJpegErrorInfo) {
+ char errBuffer[JMSG_LENGTH_MAX];
+ mJpegErrorInfo->err->format_message(mJpegErrorInfo, errBuffer);
+ ALOGE("%s: %s: %s",
+ __FUNCTION__, msg, errBuffer);
+ cleanUp();
+ mJpegErrorInfo = NULL;
+ return true;
+ }
+ return false;
+}
+
+void JpegCompressor::cleanUp() {
+ jpeg_destroy_compress(&mCInfo);
+ Mutex::Autolock lock(mBusyMutex);
+
+ if (mFoundAux) {
+ delete[] mAuxBuffer.img;
+ }
+ delete mBuffers;
+ mBuffers = NULL;
+
+ mIsBusy = false;
+ mDone.signal();
+}
+
+void JpegCompressor::jpegErrorHandler(j_common_ptr cinfo) {
+ JpegError *error = static_cast<JpegError*>(cinfo->err);
+ error->parent->mJpegErrorInfo = cinfo;
+}
+
+void JpegCompressor::jpegInitDestination(j_compress_ptr cinfo) {
+ JpegDestination *dest= static_cast<JpegDestination*>(cinfo->dest);
+ ALOGV("%s: Setting destination to %p, size %d",
+ __FUNCTION__, dest->parent->mJpegBuffer.img, kMaxJpegSize);
+ dest->next_output_byte = (JOCTET*)(dest->parent->mJpegBuffer.img);
+ dest->free_in_buffer = kMaxJpegSize;
+}
+
+boolean JpegCompressor::jpegEmptyOutputBuffer(j_compress_ptr cinfo) {
+ ALOGE("%s: JPEG destination buffer overflow!",
+ __FUNCTION__);
+ return true;
+}
+
+void JpegCompressor::jpegTermDestination(j_compress_ptr cinfo) {
+ ALOGV("%s: Done writing JPEG data. %d bytes left in buffer",
+ __FUNCTION__, cinfo->dest->free_in_buffer);
+}
+
+} // namespace android
diff --git a/tools/emulator/system/camera/fake-pipeline2/JpegCompressor.h b/tools/emulator/system/camera/fake-pipeline2/JpegCompressor.h
new file mode 100644
index 0000000..ea2a84f
--- /dev/null
+++ b/tools/emulator/system/camera/fake-pipeline2/JpegCompressor.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * This class simulates a hardware JPEG compressor. It receives image buffers
+ * in RGBA_8888 format, processes them in a worker thread, and then pushes them
+ * out to their destination stream.
+ */
+
+#ifndef HW_EMULATOR_CAMERA2_JPEG_H
+#define HW_EMULATOR_CAMERA2_JPEG_H
+
+#include "utils/Thread.h"
+#include "utils/Mutex.h"
+#include "utils/Timers.h"
+
+#include "Base.h"
+
+#include <stdio.h>
+
+extern "C" {
+#include <jpeglib.h>
+}
+
+namespace android {
+
+class EmulatedFakeCamera2;
+
+class JpegCompressor: private Thread, public virtual RefBase {
+ public:
+
+ JpegCompressor(EmulatedFakeCamera2 *parent);
+ ~JpegCompressor();
+
+ // Start compressing COMPRESSED format buffers; JpegCompressor takes
+ // ownership of the Buffers vector.
+ status_t start(Buffers *buffers,
+ nsecs_t captureTime);
+
+ status_t cancel();
+
+ bool isBusy();
+ bool isStreamInUse(uint32_t id);
+
+ bool waitForDone(nsecs_t timeout);
+
+ // TODO: Measure this
+ static const size_t kMaxJpegSize = 300000;
+
+ private:
+ Mutex mBusyMutex;
+ bool mIsBusy;
+ Condition mDone;
+
+ Mutex mMutex;
+
+ EmulatedFakeCamera2 *mParent;
+
+ Buffers *mBuffers;
+ nsecs_t mCaptureTime;
+
+ StreamBuffer mJpegBuffer, mAuxBuffer;
+ bool mFoundJpeg, mFoundAux;
+
+ jpeg_compress_struct mCInfo;
+
+ struct JpegError : public jpeg_error_mgr {
+ JpegCompressor *parent;
+ };
+ j_common_ptr mJpegErrorInfo;
+
+ struct JpegDestination : public jpeg_destination_mgr {
+ JpegCompressor *parent;
+ };
+
+ static void jpegErrorHandler(j_common_ptr cinfo);
+
+ static void jpegInitDestination(j_compress_ptr cinfo);
+ static boolean jpegEmptyOutputBuffer(j_compress_ptr cinfo);
+ static void jpegTermDestination(j_compress_ptr cinfo);
+
+ bool checkError(const char *msg);
+ void cleanUp();
+
+ /**
+ * Inherited Thread virtual overrides
+ */
+ private:
+ virtual status_t readyToRun();
+ virtual bool threadLoop();
+};
+
+} // namespace android
+
+#endif
diff --git a/tools/emulator/system/camera/fake-pipeline2/Sensor.cpp b/tools/emulator/system/camera/fake-pipeline2/Sensor.cpp
index bd4a656..29898b8 100644
--- a/tools/emulator/system/camera/fake-pipeline2/Sensor.cpp
+++ b/tools/emulator/system/camera/fake-pipeline2/Sensor.cpp
@@ -26,6 +26,7 @@
#include <utils/Log.h>
+#include "../EmulatedFakeCamera2.h"
#include "Sensor.h"
#include <cmath>
#include <cstdlib>
@@ -98,14 +99,15 @@
-Sensor::Sensor():
+Sensor::Sensor(EmulatedFakeCamera2 *parent):
Thread(false),
+ mParent(parent),
mGotVSync(false),
mExposureTime(kFrameDurationRange[0]-kMinVerticalBlank),
mFrameDuration(kFrameDurationRange[0]),
mGainFactor(kDefaultSensitivity),
- mNextBuffer(NULL),
- mCapturedBuffer(NULL),
+ mNextBuffers(NULL),
+ mCapturedBuffers(NULL),
mScene(kResolution[0], kResolution[1], kElectronsPerLuxSecond)
{
@@ -119,12 +121,7 @@
ALOGV("%s: E", __FUNCTION__);
int res;
- mCapturedBuffer = NULL;
- res = readyToRun();
- if (res != OK) {
- ALOGE("Unable to prepare sensor capture thread to run: %d", res);
- return res;
- }
+ mCapturedBuffers = NULL;
res = run("EmulatedFakeCamera2::Sensor",
ANDROID_PRIORITY_URGENT_DISPLAY);
@@ -167,12 +164,9 @@
mGainFactor = gain;
}
-void Sensor::setDestinationBuffer(uint8_t *buffer,
- uint32_t format, uint32_t stride) {
+void Sensor::setDestinationBuffers(Buffers *buffers) {
Mutex::Autolock lock(mControlMutex);
- mNextBuffer = buffer;
- mNextBufferFmt = format;
- mNextStride = stride;
+ mNextBuffers = buffers;
}
bool Sensor::waitForVSync(nsecs_t reltime) {
@@ -192,18 +186,18 @@
nsecs_t *captureTime) {
Mutex::Autolock lock(mReadoutMutex);
uint8_t *ret;
- if (mCapturedBuffer == NULL) {
+ if (mCapturedBuffers == NULL) {
int res;
res = mReadoutComplete.waitRelative(mReadoutMutex, reltime);
if (res == TIMED_OUT) {
return false;
- } else if (res != OK || mCapturedBuffer == NULL) {
+ } else if (res != OK || mCapturedBuffers == NULL) {
ALOGE("Error waiting for sensor readout signal: %d", res);
return false;
}
}
*captureTime = mCaptureTime;
- mCapturedBuffer = NULL;
+ mCapturedBuffers = NULL;
return true;
}
@@ -211,7 +205,7 @@
ALOGV("Starting up sensor thread");
mStartupTime = systemTime();
mNextCaptureTime = 0;
- mNextCapturedBuffer = NULL;
+ mNextCapturedBuffers = NULL;
return OK;
}
@@ -229,19 +223,15 @@
uint64_t exposureDuration;
uint64_t frameDuration;
uint32_t gain;
- uint8_t *nextBuffer;
- uint32_t nextBufferFmt;
- uint32_t stride;
+ Buffers *nextBuffers;
{
Mutex::Autolock lock(mControlMutex);
exposureDuration = mExposureTime;
frameDuration = mFrameDuration;
gain = mGainFactor;
- nextBuffer = mNextBuffer;
- nextBufferFmt = mNextBufferFmt;
- stride = mNextStride;
- // Don't reuse a buffer
- mNextBuffer = NULL;
+ nextBuffers = mNextBuffers;
+ // Don't reuse a buffer set
+ mNextBuffers = NULL;
// Signal VSync for start of readout
ALOGVV("Sensor VSync");
@@ -253,7 +243,7 @@
* Stage 3: Read out latest captured image
*/
- uint8_t *capturedBuffer = NULL;
+ Buffers *capturedBuffers = NULL;
nsecs_t captureTime = 0;
nsecs_t startRealTime = systemTime();
@@ -262,52 +252,78 @@
nsecs_t frameReadoutEndRealTime = startRealTime +
kRowReadoutTime * kResolution[1];
- if (mNextCapturedBuffer != NULL) {
+ if (mNextCapturedBuffers != NULL) {
ALOGVV("Sensor starting readout");
// Pretend we're doing readout now; will signal once enough time has elapsed
- capturedBuffer = mNextCapturedBuffer;
+ capturedBuffers = mNextCapturedBuffers;
captureTime = mNextCaptureTime;
}
simulatedTime += kRowReadoutTime + kMinVerticalBlank;
+ // TODO: Move this signal to another thread to simulate readout
+ // time properly
+ if (capturedBuffers != NULL) {
+ ALOGVV("Sensor readout complete");
+ Mutex::Autolock lock(mReadoutMutex);
+ mCapturedBuffers = capturedBuffers;
+ mCaptureTime = captureTime;
+ mReadoutComplete.signal();
+ capturedBuffers = NULL;
+ }
+
/**
* Stage 2: Capture new image
*/
mNextCaptureTime = simulatedTime;
- mNextCapturedBuffer = nextBuffer;
+ mNextCapturedBuffers = nextBuffers;
- if (mNextCapturedBuffer != NULL) {
- ALOGVV("Sensor capturing image (%d x %d) stride %d",
- kResolution[0], kResolution[1], stride);
- ALOGVV("Exposure: %f ms, gain: %d", (float)exposureDuration/1e6, gain);
+ if (mNextCapturedBuffers != NULL) {
+ ALOGVV("Starting next capture: Exposure: %f ms, gain: %d",
+ (float)exposureDuration/1e6, gain);
mScene.setExposureDuration((float)exposureDuration/1e9);
mScene.calculateScene(mNextCaptureTime);
-
- switch(nextBufferFmt) {
- case HAL_PIXEL_FORMAT_RAW_SENSOR:
- captureRaw(gain, stride, &capturedBuffer,
- captureTime, frameEndRealTime);
- break;
- case HAL_PIXEL_FORMAT_RGBA_8888:
- captureRGBA(gain, stride, &capturedBuffer,
- captureTime, frameEndRealTime);
- break;
- default:
- ALOGE("%s: Unknown format %x, no output", __FUNCTION__,
- nextBufferFmt);
- break;
+ for (size_t i = 0; i < mNextCapturedBuffers->size(); i++) {
+ const StreamBuffer &b = (*mNextCapturedBuffers)[i];
+ ALOGVV("Sensor capturing buffer %d: stream %d,"
+ " %d x %d, format %x, stride %d, buf %p, img %p",
+ i, b.streamId, b.width, b.height, b.format, b.stride,
+ b.buffer, b.img);
+ switch(b.format) {
+ case HAL_PIXEL_FORMAT_RAW_SENSOR:
+ captureRaw(b.img, gain, b.stride);
+ break;
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ captureRGBA(b.img, gain, b.stride);
+ break;
+ case HAL_PIXEL_FORMAT_BLOB:
+ // Add auxillary buffer of the right size
+ // Assumes only one BLOB (JPEG) buffer in
+ // mNextCapturedBuffers
+ StreamBuffer bAux;
+ bAux.streamId = -1;
+ bAux.width = b.width;
+ bAux.height = b.height;
+ bAux.format = HAL_PIXEL_FORMAT_RGB_888;
+ bAux.stride = b.width;
+ bAux.buffer = NULL;
+ // TODO: Reuse these
+ bAux.img = new uint8_t[b.width * b.height * 3];
+ captureRGB(bAux.img, gain, b.stride);
+ mNextCapturedBuffers->push_back(bAux);
+ break;
+ case HAL_PIXEL_FORMAT_YV12:
+ case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ // TODO:
+ ALOGE("%s: Format %x is TODO", __FUNCTION__, b.format);
+ break;
+ default:
+ ALOGE("%s: Unknown format %x, no output", __FUNCTION__,
+ b.format);
+ break;
+ }
}
}
- // No capture done, or finished image generation before readout was completed
- if (capturedBuffer != NULL) {
- ALOGVV("Sensor readout complete");
- Mutex::Autolock lock(mReadoutMutex);
- mCapturedBuffer = capturedBuffer;
- mCaptureTime = captureTime;
- mReadoutComplete.signal();
- capturedBuffer = NULL;
- }
ALOGVV("Sensor vertical blanking interval");
nsecs_t workDoneRealTime = systemTime();
@@ -329,18 +345,17 @@
return true;
};
-void Sensor::captureRaw(uint32_t gain, uint32_t stride,
- uint8_t **capturedBuffer, nsecs_t captureTime, nsecs_t frameReadoutTime) {
+void Sensor::captureRaw(uint8_t *img, uint32_t gain, uint32_t stride) {
float totalGain = gain/100.0 * kBaseGainFactor;
float noiseVarGain = totalGain * totalGain;
float readNoiseVar = kReadNoiseVarBeforeGain * noiseVarGain
+ kReadNoiseVarAfterGain;
int bayerSelect[4] = {Scene::R, Scene::Gr, Scene::Gb, Scene::B}; // RGGB
-
+ mScene.setReadoutPixel(0,0);
for (unsigned int y = 0; y < kResolution[1]; y++ ) {
int *bayerRow = bayerSelect + (y & 0x1) * 2;
- uint16_t *px = (uint16_t*)mNextCapturedBuffer + y * stride;
+ uint16_t *px = (uint16_t*)img + y * stride;
for (unsigned int x = 0; x < kResolution[0]; x++) {
uint32_t electronCount;
electronCount = mScene.getPixelElectrons()[bayerRow[x & 0x1]];
@@ -367,59 +382,61 @@
}
// TODO: Handle this better
//simulatedTime += kRowReadoutTime;
-
- // If enough time has elapsed to complete readout, signal done frame
- // Only check every so often, though
- if ((*capturedBuffer != NULL) &&
- ((y & 63) == 0) &&
- (systemTime() >= frameReadoutTime) ) {
- ALOGV("Sensor readout complete");
- Mutex::Autolock lock(mReadoutMutex);
- mCapturedBuffer = *capturedBuffer;
- mCaptureTime = captureTime;
- mReadoutComplete.signal();
- *capturedBuffer = NULL;
- }
}
ALOGVV("Raw sensor image captured");
}
-void Sensor::captureRGBA(uint32_t gain, uint32_t stride,
- uint8_t **capturedBuffer, nsecs_t captureTime, nsecs_t frameReadoutTime) {
- int totalGain = gain/100.0 * kBaseGainFactor;
+void Sensor::captureRGBA(uint8_t *img, uint32_t gain, uint32_t stride) {
+ float totalGain = gain/100.0 * kBaseGainFactor;
+ // In fixed-point math, calculate total scaling from electrons to 8bpp
+ int scale64x = 64 * totalGain * 255 / kMaxRawValue;
+ mScene.setReadoutPixel(0,0);
for (unsigned int y = 0; y < kResolution[1]; y++ ) {
- uint8_t *px = (uint8_t*)mNextCapturedBuffer + y * stride * 4;
+ uint8_t *px = img + y * stride * 4;
for (unsigned int x = 0; x < kResolution[0]; x++) {
uint32_t rCount, gCount, bCount;
// TODO: Perfect demosaicing is a cheat
const uint32_t *pixel = mScene.getPixelElectrons();
- rCount = pixel[Scene::R] * totalGain / (kMaxRawValue / 255);
- gCount = pixel[Scene::Gr] * totalGain / (kMaxRawValue / 255);
- bCount = pixel[Scene::B] * totalGain / (kMaxRawValue / 255);
+ rCount = pixel[Scene::R] * scale64x;
+ gCount = pixel[Scene::Gr] * scale64x;
+ bCount = pixel[Scene::B] * scale64x;
- *px++ = rCount < 255 ? rCount : 255;
- *px++ = gCount < 255 ? gCount : 255;
- *px++ = bCount < 255 ? bCount : 255;
+ *px++ = rCount < 255*64 ? rCount / 64 : 255;
+ *px++ = gCount < 255*64 ? gCount / 64 : 255;
+ *px++ = bCount < 255*64 ? bCount / 64 : 255;
*px++ = 255;
}
// TODO: Handle this better
//simulatedTime += kRowReadoutTime;
-
- // If enough time has elapsed to complete readout, signal done frame
- // Only check every so often, though
- if ((*capturedBuffer != NULL) &&
- ((y & 63) == 0) &&
- (systemTime() >= frameReadoutTime) ) {
- ALOGV("Sensor readout complete");
- Mutex::Autolock lock(mReadoutMutex);
- mCapturedBuffer = *capturedBuffer;
- mCaptureTime = captureTime;
- mReadoutComplete.signal();
- *capturedBuffer = NULL;
- }
}
ALOGVV("RGBA sensor image captured");
}
+void Sensor::captureRGB(uint8_t *img, uint32_t gain, uint32_t stride) {
+ float totalGain = gain/100.0 * kBaseGainFactor;
+ // In fixed-point math, calculate total scaling from electrons to 8bpp
+ int scale64x = 64 * totalGain * 255 / kMaxRawValue;
+ mScene.setReadoutPixel(0,0);
+
+ for (unsigned int y = 0; y < kResolution[1]; y++ ) {
+ uint8_t *px = img + y * stride * 3;
+ for (unsigned int x = 0; x < kResolution[0]; x++) {
+ uint32_t rCount, gCount, bCount;
+ // TODO: Perfect demosaicing is a cheat
+ const uint32_t *pixel = mScene.getPixelElectrons();
+ rCount = pixel[Scene::R] * scale64x;
+ gCount = pixel[Scene::Gr] * scale64x;
+ bCount = pixel[Scene::B] * scale64x;
+
+ *px++ = rCount < 255*64 ? rCount / 64 : 255;
+ *px++ = gCount < 255*64 ? gCount / 64 : 255;
+ *px++ = bCount < 255*64 ? bCount / 64 : 255;
+ }
+ // TODO: Handle this better
+ //simulatedTime += kRowReadoutTime;
+ }
+ ALOGVV("RGB sensor image captured");
+}
+
} // namespace android
diff --git a/tools/emulator/system/camera/fake-pipeline2/Sensor.h b/tools/emulator/system/camera/fake-pipeline2/Sensor.h
index 50ec2b7..a666cc3 100644
--- a/tools/emulator/system/camera/fake-pipeline2/Sensor.h
+++ b/tools/emulator/system/camera/fake-pipeline2/Sensor.h
@@ -78,13 +78,16 @@
#include "utils/Timers.h"
#include "Scene.h"
+#include "Base.h"
namespace android {
+class EmulatedFakeCamera2;
+
class Sensor: private Thread, public virtual RefBase {
public:
- Sensor();
+ Sensor(EmulatedFakeCamera2 *parent);
~Sensor();
/*
@@ -107,7 +110,7 @@
void setFrameDuration(uint64_t ns);
void setSensitivity(uint32_t gain);
// Buffer must be at least stride*height*2 bytes in size
- void setDestinationBuffer(uint8_t *buffer, uint32_t format, uint32_t stride);
+ void setDestinationBuffers(Buffers *buffers);
/*
* Controls that cause reconfiguration delay
@@ -169,6 +172,7 @@
static const uint32_t kDefaultSensitivity;
private:
+ EmulatedFakeCamera2 *mParent;
Mutex mControlMutex; // Lock before accessing control parameters
// Start of control parameters
@@ -177,16 +181,14 @@
uint64_t mExposureTime;
uint64_t mFrameDuration;
uint32_t mGainFactor;
- uint8_t *mNextBuffer;
- uint32_t mNextBufferFmt;
- uint32_t mNextStride;
+ Buffers *mNextBuffers;
// End of control parameters
Mutex mReadoutMutex; // Lock before accessing readout variables
// Start of readout variables
Condition mReadoutComplete;
- uint8_t *mCapturedBuffer;
+ Buffers *mCapturedBuffers;
nsecs_t mCaptureTime;
// End of readout variables
@@ -203,17 +205,13 @@
virtual bool threadLoop();
nsecs_t mNextCaptureTime;
- uint8_t *mNextCapturedBuffer;
+ Buffers *mNextCapturedBuffers;
Scene mScene;
- void captureRaw(uint32_t gain, uint32_t stride,
- uint8_t **capturedBuffer, nsecs_t captureTime,
- nsecs_t frameReadoutTime);
-
- void captureRGBA(uint32_t gain, uint32_t stride,
- uint8_t **capturedBuffer, nsecs_t captureTime,
- nsecs_t frameReadoutTime);
+ void captureRaw(uint8_t *img, uint32_t gain, uint32_t stride);
+ void captureRGBA(uint8_t *img, uint32_t gain, uint32_t stride);
+ void captureRGB(uint8_t *img, uint32_t gain, uint32_t stride);
};