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);
 
 };