Camera2: Add recording support

- Support startRecording/stopRecording
- Support lock/unlock/connect
- Some rearrangement of class definitions for clarity/consistency

Bug: 6243944

Change-Id: I00c600a798572d2f69bb3f2bab3d79e4bd9a91e5
diff --git a/services/camera/libcameraservice/Camera2Client.cpp b/services/camera/libcameraservice/Camera2Client.cpp
index c90f81a..8d4add4 100644
--- a/services/camera/libcameraservice/Camera2Client.cpp
+++ b/services/camera/libcameraservice/Camera2Client.cpp
@@ -55,7 +55,9 @@
         mPreviewStreamId(NO_STREAM),
         mPreviewRequest(NULL),
         mCaptureStreamId(NO_STREAM),
-        mCaptureRequest(NULL)
+        mCaptureRequest(NULL),
+        mRecordingStreamId(NO_STREAM),
+        mRecordingRequest(NULL)
 {
     ATRACE_CALL();
 
@@ -341,23 +343,59 @@
 
 status_t Camera2Client::connect(const sp<ICameraClient>& client) {
     ATRACE_CALL();
+
     Mutex::Autolock icl(mICameraLock);
 
-    return BAD_VALUE;
+    if (mClientPid != 0 && getCallingPid() != mClientPid) {
+        ALOGE("%s: Camera %d: Connection attempt from pid %d; "
+                "current locked to pid %d", __FUNCTION__,
+                mCameraId, getCallingPid(), mClientPid);
+        return BAD_VALUE;
+    }
+
+    mClientPid = getCallingPid();
+    mCameraClient = client;
+
+    return OK;
 }
 
 status_t Camera2Client::lock() {
     ATRACE_CALL();
     Mutex::Autolock icl(mICameraLock);
+    ALOGV("%s: Camera %d: Lock call from pid %d; current client pid %d",
+            __FUNCTION__, mCameraId, getCallingPid(), mClientPid);
 
-    return BAD_VALUE;
+    if (mClientPid == 0) {
+        mClientPid = getCallingPid();
+        return OK;
+    }
+
+    if (mClientPid != getCallingPid()) {
+        ALOGE("%s: Camera %d: Lock call from pid %d; currently locked to pid %d",
+                __FUNCTION__, mCameraId, getCallingPid(), mClientPid);
+        return EBUSY;
+    }
+
+    return OK;
 }
 
 status_t Camera2Client::unlock() {
     ATRACE_CALL();
     Mutex::Autolock icl(mICameraLock);
+    ALOGV("%s: Camera %d: Unlock call from pid %d; current client pid %d",
+            __FUNCTION__, mCameraId, getCallingPid(), mClientPid);
 
-    return BAD_VALUE;
+    // TODO: Check for uninterruptable conditions
+
+    if (mClientPid == getCallingPid()) {
+        mClientPid = 0;
+        mCameraClient.clear();
+        return OK;
+    }
+
+    ALOGE("%s: Camera %d: Unlock call from pid %d; currently locked to pid %d",
+            __FUNCTION__, mCameraId, getCallingPid(), mClientPid);
+    return EBUSY;
 }
 
 status_t Camera2Client::setPreviewDisplay(
@@ -365,8 +403,6 @@
     ATRACE_CALL();
     Mutex::Autolock icl(mICameraLock);
 
-    if (mState >= PREVIEW) return INVALID_OPERATION;
-
     sp<IBinder> binder;
     sp<ANativeWindow> window;
     if (surface != 0) {
@@ -382,8 +418,6 @@
     ATRACE_CALL();
     Mutex::Autolock icl(mICameraLock);
 
-    if (mState >= PREVIEW) return INVALID_OPERATION;
-
     sp<IBinder> binder;
     sp<ANativeWindow> window;
     if (surfaceTexture != 0) {
@@ -402,6 +436,27 @@
         return NO_ERROR;
     }
 
+    switch (mState) {
+        case NOT_INITIALIZED:
+        case RECORD:
+        case STILL_CAPTURE:
+        case VIDEO_SNAPSHOT:
+            ALOGE("%s: Camera %d: Cannot set preview display while in state %s",
+                    __FUNCTION__, mCameraId, getStateName(mState));
+            return INVALID_OPERATION;
+        case STOPPED:
+        case WAITING_FOR_PREVIEW_WINDOW:
+            // OK
+            break;
+        case PREVIEW:
+            // Already running preview - need to stop and create a new stream
+            // TODO: Optimize this so that we don't wait for old stream to drain
+            // before spinning up new stream
+            mDevice->setStreamingRequest(NULL);
+            mState = WAITING_FOR_PREVIEW_WINDOW;
+            break;
+    }
+
     if (mPreviewStreamId != NO_STREAM) {
         res = mDevice->waitUntilDrained();
         if (res != OK) {
@@ -454,6 +509,8 @@
     }
     mState = STOPPED;
 
+    Mutex::Autolock pl(mParamsLock);
+
     res = updatePreviewStream();
     if (res != OK) {
         ALOGE("%s: Camera %d: Unable to update preview stream: %s (%d)",
@@ -544,23 +601,125 @@
 status_t Camera2Client::startRecording() {
     ATRACE_CALL();
     Mutex::Autolock icl(mICameraLock);
-    return BAD_VALUE;
+    status_t res;
+    switch (mState) {
+        case STOPPED:
+            res = startPreviewLocked();
+            if (res != OK) return res;
+            break;
+        case PREVIEW:
+            // Ready to go
+            break;
+        case RECORD:
+        case VIDEO_SNAPSHOT:
+            // OK to call this when recording is already on
+            return OK;
+            break;
+        default:
+            ALOGE("%s: Camera %d: Can't start recording in state %s",
+                    __FUNCTION__, mCameraId, getStateName(mState));
+            return INVALID_OPERATION;
+    };
+
+    Mutex::Autolock pl(mParamsLock);
+
+    res = updateRecordingStream();
+    if (res != OK) {
+        ALOGE("%s: Camera %d: Unable to update recording stream: %s (%d)",
+                __FUNCTION__, mCameraId, strerror(-res), res);
+        return res;
+    }
+
+    if (mRecordingRequest == NULL) {
+        res = updateRecordingRequest();
+        if (res != OK) {
+            ALOGE("%s: Camera %d: Unable to create recording request: %s (%d)",
+                    __FUNCTION__, mCameraId, strerror(-res), res);
+            return res;
+        }
+    }
+
+    uint8_t outputStreams[2] = { mPreviewStreamId, mRecordingStreamId };
+    res = updateEntry(mRecordingRequest,
+            ANDROID_REQUEST_OUTPUT_STREAMS,
+            outputStreams, 2);
+    if (res != OK) {
+        ALOGE("%s: Camera %d: Unable to set up recording request: %s (%d)",
+                __FUNCTION__, mCameraId, strerror(-res), res);
+        return res;
+    }
+    res = sort_camera_metadata(mRecordingRequest);
+    if (res != OK) {
+        ALOGE("%s: Camera %d: Error sorting recording request: %s (%d)",
+                __FUNCTION__, mCameraId, strerror(-res), res);
+        return res;
+    }
+
+    res = mDevice->setStreamingRequest(mRecordingRequest);
+    if (res != OK) {
+        ALOGE("%s: Camera %d: Unable to set recording request to start "
+                "recording: %s (%d)", __FUNCTION__, mCameraId,
+                strerror(-res), res);
+        return res;
+    }
+    mState = RECORD;
+
+    return OK;
 }
 
 void Camera2Client::stopRecording() {
     ATRACE_CALL();
     Mutex::Autolock icl(mICameraLock);
+    status_t res;
+    switch (mState) {
+        case RECORD:
+            // OK to stop
+            break;
+        case STOPPED:
+        case PREVIEW:
+        case STILL_CAPTURE:
+        case VIDEO_SNAPSHOT:
+        default:
+            ALOGE("%s: Camera %d: Can't stop recording in state %s",
+                    __FUNCTION__, mCameraId, getStateName(mState));
+            return;
+    };
+
+    // Back to preview. Since record can only be reached through preview,
+    // all preview stream setup should be up to date.
+    res = mDevice->setStreamingRequest(mPreviewRequest);
+    if (res != OK) {
+        ALOGE("%s: Camera %d: Unable to switch back to preview request: "
+                "%s (%d)", __FUNCTION__, mCameraId, strerror(-res), res);
+        return;
+    }
+
+    // TODO: Should recording heap be freed? Can't do it yet since requests
+    // could still be in flight.
+
+    mState = PREVIEW;
 }
 
 bool Camera2Client::recordingEnabled() {
     ATRACE_CALL();
     Mutex::Autolock icl(mICameraLock);
-    return BAD_VALUE;
+    return (mState == RECORD || mState == VIDEO_SNAPSHOT);
 }
 
 void Camera2Client::releaseRecordingFrame(const sp<IMemory>& mem) {
     ATRACE_CALL();
     Mutex::Autolock icl(mICameraLock);
+    // Make sure this is for the current heap
+    ssize_t offset;
+    size_t size;
+    sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
+    if (heap->getHeapID() != mRecordingHeap->mHeap->getHeapID()) {
+        ALOGW("%s: Camera %d: Mismatched heap ID, ignoring release "
+                "(got %x, expected %x)", __FUNCTION__, mCameraId,
+                heap->getHeapID(), mRecordingHeap->mHeap->getHeapID());
+        return;
+    }
+    mRecordingHeapFree++;
 }
 
 status_t Camera2Client::autoFocus() {
@@ -616,11 +775,18 @@
         }
     }
 
-    // TODO: For video snapshot, will need 3 streams here
     camera_metadata_entry_t outputStreams;
-    uint8_t streamIds[2] = { mPreviewStreamId, mCaptureStreamId };
-    res = updateEntry(mCaptureRequest, ANDROID_REQUEST_OUTPUT_STREAMS,
-            &streamIds, 2);
+    if (mState == PREVIEW) {
+        uint8_t streamIds[2] = { mPreviewStreamId, mCaptureStreamId };
+        res = updateEntry(mCaptureRequest, ANDROID_REQUEST_OUTPUT_STREAMS,
+                &streamIds, 2);
+    } else if (mState == RECORD) {
+        uint8_t streamIds[3] = { mPreviewStreamId, mRecordingStreamId,
+                                 mCaptureStreamId };
+        res = updateEntry(mCaptureRequest, ANDROID_REQUEST_OUTPUT_STREAMS,
+                &streamIds, 3);
+    }
+
     if (res != OK) {
         ALOGE("%s: Camera %d: Unable to set up still image capture request: "
                 "%s (%d)",
@@ -650,7 +816,7 @@
             return res;
         }
     }
-
+    // TODO: Capture should be atomic with setStreamingRequest here
     res = mDevice->capture(captureCopy);
     if (res != OK) {
         ALOGE("%s: Camera %d: Unable to submit still image capture request: "
@@ -699,7 +865,10 @@
             previewHeight != mParameters.previewHeight) {
         if (mState >= PREVIEW) {
             ALOGE("%s: Preview size cannot be updated when preview "
-                    "is active!", __FUNCTION__);
+                    "is active! (Currently %d x %d, requested %d x %d",
+                    __FUNCTION__,
+                    mParameters.previewWidth, mParameters.previewHeight,
+                    previewWidth, previewHeight);
             return BAD_VALUE;
         }
         camera_metadata_entry_t availablePreviewSizes =
@@ -1127,6 +1296,7 @@
     }
 
     /** Update internal parameters */
+
     mParameters.previewWidth = previewWidth;
     mParameters.previewHeight = previewHeight;
     mParameters.previewFpsRange[0] = previewFpsRange[0];
@@ -1183,6 +1353,13 @@
         return res;
     }
 
+    res = updateRecordingRequest();
+    if (res != OK) {
+        ALOGE("%s: Camera %d: Unable to update recording request: %s (%d)",
+                __FUNCTION__, mCameraId, strerror(-res), res);
+        return res;
+    }
+
     if (mState == PREVIEW) {
         res = mDevice->setStreamingRequest(mPreviewRequest);
         if (res != OK) {
@@ -1190,8 +1367,17 @@
                     __FUNCTION__, mCameraId, strerror(-res), res);
             return res;
         }
+    } else if (mState == RECORD || mState == VIDEO_SNAPSHOT) {
+        res = mDevice->setStreamingRequest(mRecordingRequest);
+        if (res != OK) {
+            ALOGE("%s: Camera %d: Error streaming new record request: %s (%d)",
+                    __FUNCTION__, mCameraId, strerror(-res), res);
+            return res;
+        }
     }
 
+    mParamsFlattened = params;
+
     return OK;
 }
 
@@ -1240,6 +1426,8 @@
     ATRACE_CALL();
     status_t res;
     sp<ICameraClient> currentClient;
+    ALOGV("%s: Camera %d: Still capture available", __FUNCTION__, mCameraId);
+
     CpuConsumer::LockedBuffer imgBuffer;
     {
         Mutex::Autolock icl(mICameraLock);
@@ -1268,8 +1456,8 @@
         }
 
         // TODO: Optimize this to avoid memcopy
-        void* captureMemory = mCaptureHeap->getBase();
-        size_t size = mCaptureHeap->getSize();
+        void* captureMemory = mCaptureHeap->mHeap->getBase();
+        size_t size = mCaptureHeap->mHeap->getSize();
         memcpy(captureMemory, imgBuffer.data, size);
 
         mCaptureConsumer->unlockBuffer(imgBuffer);
@@ -1291,7 +1479,100 @@
     // Call outside mICameraLock to allow re-entrancy from notification
     if (currentClient != 0) {
         currentClient->dataCallback(CAMERA_MSG_COMPRESSED_IMAGE,
-                mCaptureMemory, NULL);
+                mCaptureHeap->mBuffers[0], NULL);
+    }
+}
+
+void Camera2Client::onRecordingFrameAvailable() {
+    ATRACE_CALL();
+    status_t res;
+    sp<ICameraClient> currentClient;
+    size_t heapIdx = 0;
+    nsecs_t timestamp;
+    {
+        Mutex::Autolock icl(mICameraLock);
+        // TODO: Signal errors here upstream
+        if (mState != RECORD && mState != VIDEO_SNAPSHOT) {
+            ALOGE("%s: Camera %d: Recording image buffer produced unexpectedly!",
+                    __FUNCTION__, mCameraId);
+            return;
+        }
+
+        CpuConsumer::LockedBuffer imgBuffer;
+        res = mRecordingConsumer->lockNextBuffer(&imgBuffer);
+        if (res != OK) {
+            ALOGE("%s: Camera %d: Error receiving recording buffer: %s (%d)",
+                    __FUNCTION__, mCameraId, strerror(-res), res);
+            return;
+        }
+
+        if (imgBuffer.format != (int)kRecordingFormat) {
+            ALOGE("%s: Camera %d: Unexpected recording format: %x",
+                    __FUNCTION__, mCameraId, imgBuffer.format);
+            mRecordingConsumer->unlockBuffer(imgBuffer);
+            return;
+        }
+        size_t bufferSize = imgBuffer.width * imgBuffer.height * 3 / 2;
+
+        if (mRecordingHeap == 0 ||
+                bufferSize >
+                mRecordingHeap->mHeap->getSize() / kRecordingHeapCount) {
+            ALOGV("%s: Camera %d: Creating recording heap with %d buffers of "
+                    "size %d bytes", __FUNCTION__, mCameraId,
+                    kRecordingHeapCount, bufferSize);
+            if (mRecordingHeap != 0) {
+                ALOGV("%s: Camera %d: Previous heap has size %d "
+                        "(new will be %d) bytes", __FUNCTION__, mCameraId,
+                        mRecordingHeap->mHeap->getSize(),
+                        bufferSize * kRecordingHeapCount);
+            }
+            // Need to allocate memory for heap
+            mRecordingHeap.clear();
+
+            mRecordingHeap = new Camera2Heap(bufferSize, kRecordingHeapCount,
+                    "Camera2Client::RecordingHeap");
+            if (mRecordingHeap->mHeap->getSize() == 0) {
+                ALOGE("%s: Camera %d: Unable to allocate memory for recording",
+                        __FUNCTION__, mCameraId);
+                mRecordingConsumer->unlockBuffer(imgBuffer);
+                return;
+            }
+            mRecordingHeapHead = 0;
+            mRecordingHeapFree = kRecordingHeapCount;
+        }
+
+        // TODO: Optimize this to avoid memcopy
+        if ( mRecordingHeapFree == 0) {
+            ALOGE("%s: Camera %d: No free recording buffers, dropping frame",
+                    __FUNCTION__, mCameraId);
+            mRecordingConsumer->unlockBuffer(imgBuffer);
+            return;
+        }
+        heapIdx = mRecordingHeapHead;
+        timestamp = imgBuffer.timestamp;
+        mRecordingHeapHead = (mRecordingHeapHead + 1) % kRecordingHeapCount;
+        mRecordingHeapFree--;
+
+        ALOGV("%s: Camera %d: Timestamp %lld",
+                __FUNCTION__, mCameraId, timestamp);
+
+        ssize_t offset;
+        size_t size;
+        sp<IMemoryHeap> heap =
+                mRecordingHeap->mBuffers[heapIdx]->getMemory(&offset,
+                        &size);
+
+        memcpy((uint8_t*)heap->getBase() + offset, imgBuffer.data, size);
+
+        mRecordingConsumer->unlockBuffer(imgBuffer);
+
+        currentClient = mCameraClient;
+    }
+    // Call outside mICameraLock to allow re-entrancy from notification
+    if (currentClient != 0) {
+        currentClient->dataCallbackTimestamp(timestamp,
+                CAMERA_MSG_VIDEO_FRAME,
+                mRecordingHeap->mBuffers[heapIdx]);
     }
 }
 
@@ -1999,7 +2280,7 @@
             0);
 
     params.set(CameraParameters::KEY_VIDEO_FRAME_FORMAT,
-            formatEnumToString(HAL_PIXEL_FORMAT_YCrCb_420_SP));
+            formatEnumToString(kRecordingFormat));
 
     params.set(CameraParameters::KEY_RECORDING_HINT,
             CameraParameters::FALSE);
@@ -2126,15 +2407,13 @@
         mCaptureWindow = new SurfaceTextureClient(
             mCaptureConsumer->getProducerInterface());
         // Create memory for API consumption
-        mCaptureHeap = new MemoryHeapBase(maxJpegSize.data.i32[0], 0,
-                "Camera2Client::CaptureHeap");
-        if (mCaptureHeap->getSize() == 0) {
+        mCaptureHeap = new Camera2Heap(maxJpegSize.data.i32[0], 1,
+                                       "Camera2Client::CaptureHeap");
+        if (mCaptureHeap->mHeap->getSize() == 0) {
             ALOGE("%s: Camera %d: Unable to allocate memory for capture",
                     __FUNCTION__, mCameraId);
             return NO_MEMORY;
         }
-        mCaptureMemory = new MemoryBase(mCaptureHeap,
-                0, maxJpegSize.data.i32[0]);
     }
 
     if (mCaptureStreamId != NO_STREAM) {
@@ -2243,6 +2522,81 @@
     return OK;
 }
 
+status_t Camera2Client::updateRecordingRequest() {
+    ATRACE_CALL();
+    status_t res;
+    if (mRecordingRequest == NULL) {
+        res = mDevice->createDefaultRequest(CAMERA2_TEMPLATE_VIDEO_RECORD,
+                &mRecordingRequest);
+        if (res != OK) {
+            ALOGE("%s: Camera %d: Unable to create default recording request:"
+                    " %s (%d)", __FUNCTION__, mCameraId, strerror(-res), res);
+            return res;
+        }
+    }
+
+    res = updateRequestCommon(mRecordingRequest);
+    if (res != OK) {
+        ALOGE("%s: Camera %d: Unable to update common entries of recording "
+                "request: %s (%d)", __FUNCTION__, mCameraId,
+                strerror(-res), res);
+        return res;
+    }
+
+    return OK;
+}
+
+status_t Camera2Client::updateRecordingStream() {
+    status_t res;
+
+    if (mRecordingConsumer == 0) {
+        // Create CPU buffer queue endpoint
+        mRecordingConsumer = new CpuConsumer(1);
+        mRecordingConsumer->setFrameAvailableListener(new RecordingWaiter(this));
+        mRecordingConsumer->setName(String8("Camera2Client::RecordingConsumer"));
+        mRecordingWindow = new SurfaceTextureClient(
+            mRecordingConsumer->getProducerInterface());
+        // Allocate memory later, since we don't know buffer size until receipt
+    }
+
+    if (mRecordingStreamId != NO_STREAM) {
+        // Check if stream parameters have to change
+        uint32_t currentWidth, currentHeight;
+        res = mDevice->getStreamInfo(mRecordingStreamId,
+                &currentWidth, &currentHeight, 0);
+        if (res != OK) {
+            ALOGE("%s: Camera %d: Error querying recording output stream info: "
+                    "%s (%d)", __FUNCTION__, mCameraId, strerror(-res), res);
+            return res;
+        }
+        if (currentWidth != (uint32_t)mParameters.videoWidth ||
+                currentHeight != (uint32_t)mParameters.videoHeight) {
+            // TODO: Should wait to be sure previous recording has finished
+            res = mDevice->deleteStream(mRecordingStreamId);
+            if (res != OK) {
+                ALOGE("%s: Camera %d: Unable to delete old output stream "
+                        "for recording: %s (%d)", __FUNCTION__, mCameraId,
+                        strerror(-res), res);
+                return res;
+            }
+            mRecordingStreamId = NO_STREAM;
+        }
+    }
+
+    if (mRecordingStreamId == NO_STREAM) {
+        res = mDevice->createStream(mRecordingWindow,
+                mParameters.videoWidth, mParameters.videoHeight,
+                kRecordingFormat, 0, &mRecordingStreamId);
+        if (res != OK) {
+            ALOGE("%s: Camera %d: Can't create output stream for recording: "
+                    "%s (%d)", __FUNCTION__, mCameraId, strerror(-res), res);
+            return res;
+        }
+    }
+
+    return OK;
+}
+
 status_t Camera2Client::updateRequestCommon(camera_metadata_t *request) {
     ATRACE_CALL();
     status_t res;
diff --git a/services/camera/libcameraservice/Camera2Client.h b/services/camera/libcameraservice/Camera2Client.h
index 2dc02bc..8d410f1 100644
--- a/services/camera/libcameraservice/Camera2Client.h
+++ b/services/camera/libcameraservice/Camera2Client.h
@@ -174,6 +174,29 @@
 
     /** Camera device-related private members */
 
+    class Camera2Heap;
+
+    // Number of zoom steps to simulate
+    static const unsigned int NUM_ZOOM_STEPS = 10;
+    // Used with stream IDs
+    static const int NO_STREAM = -1;
+
+    /* Preview related members */
+
+    int mPreviewStreamId;
+    camera_metadata_t *mPreviewRequest;
+    sp<IBinder> mPreviewSurface;
+    sp<ANativeWindow> mPreviewWindow;
+    // Update preview request based on mParameters
+    status_t updatePreviewRequest();
+    // Update preview stream based on mParameters
+    status_t updatePreviewStream();
+
+    /* Still image capture related members */
+
+    int mCaptureStreamId;
+    sp<CpuConsumer>    mCaptureConsumer;
+    sp<ANativeWindow>  mCaptureWindow;
     // Simple listener that forwards frame available notifications from
     // a CPU consumer to the capture notification
     class CaptureWaiter: public CpuConsumer::FrameAvailableListener {
@@ -183,30 +206,77 @@
       private:
         Camera2Client *mParent;
     };
-
-    void onCaptureAvailable();
-
-    // Number of zoom steps to simulate
-    static const unsigned int NUM_ZOOM_STEPS = 10;
-    // Used with mPreviewStreamId, mCaptureStreamId
-    static const int NO_STREAM = -1;
-
-    sp<IBinder> mPreviewSurface;
-    sp<ANativeWindow> mPreviewWindow;
-
-    int mPreviewStreamId;
-    camera_metadata_t *mPreviewRequest;
-
-    int mCaptureStreamId;
-    sp<CpuConsumer>    mCaptureConsumer;
-    sp<ANativeWindow>  mCaptureWindow;
     sp<CaptureWaiter>  mCaptureWaiter;
     camera_metadata_t *mCaptureRequest;
-    sp<MemoryHeapBase> mCaptureHeap;
-    sp<MemoryBase>     mCaptureMemory;
+    sp<Camera2Heap>    mCaptureHeap;
+    // Handle captured image buffers
+    void onCaptureAvailable();
+    // Update capture request based on mParameters
+    status_t updateCaptureRequest();
+    // Update capture stream based on mParameters
+    status_t updateCaptureStream();
+
+    /* Recording related members */
+
+    int mRecordingStreamId;
+    sp<CpuConsumer>    mRecordingConsumer;
+    sp<ANativeWindow>  mRecordingWindow;
+    // Simple listener that forwards frame available notifications from
+    // a CPU consumer to the recording notification
+    class RecordingWaiter: public CpuConsumer::FrameAvailableListener {
+      public:
+        RecordingWaiter(Camera2Client *parent) : mParent(parent) {}
+        void onFrameAvailable() { mParent->onRecordingFrameAvailable(); }
+      private:
+        Camera2Client *mParent;
+    };
+    sp<RecordingWaiter>  mRecordingWaiter;
+    camera_metadata_t *mRecordingRequest;
+    sp<Camera2Heap> mRecordingHeap;
+
+    // TODO: This needs to be queried from somewhere, or the BufferQueue needs
+    // to be passed all the way to stagefright
+    static const size_t kRecordingHeapCount = 4;
+    static const uint32_t kRecordingFormat = HAL_PIXEL_FORMAT_YCrCb_420_SP;
+    size_t mRecordingHeapHead, mRecordingHeapFree;
+    // Handle new recording image buffers
+    void onRecordingFrameAvailable();
+    // Update recording request based on mParameters
+    status_t updateRecordingRequest();
+    // Update recording stream based on mParameters
+    status_t updateRecordingStream();
+
+    /** Camera2Device instance wrapping HAL2 entry */
 
     sp<Camera2Device> mDevice;
 
+    /** Utility members */
+
+    // Utility class for managing a set of IMemory blocks
+    class Camera2Heap : public RefBase {
+    public:
+        Camera2Heap(size_t buf_size, uint_t num_buffers = 1,
+                const char *name = NULL) :
+                         mBufSize(buf_size),
+                         mNumBufs(num_buffers) {
+            mHeap = new MemoryHeapBase(buf_size * num_buffers, 0, name);
+            mBuffers = new sp<MemoryBase>[mNumBufs];
+            for (uint_t i = 0; i < mNumBufs; i++)
+                mBuffers[i] = new MemoryBase(mHeap,
+                                             i * mBufSize,
+                                             mBufSize);
+        }
+
+        virtual ~Camera2Heap()
+        {
+            delete [] mBuffers;
+        }
+
+        size_t mBufSize;
+        uint_t mNumBufs;
+        sp<MemoryHeapBase> mHeap;
+        sp<MemoryBase> *mBuffers;
+    };
 
     // Get values for static camera info entry. min/maxCount are used for error
     // checking the number of values in the entry. 0 for max/minCount means to
@@ -215,22 +285,10 @@
     camera_metadata_entry_t staticInfo(uint32_t tag,
             size_t minCount=0, size_t maxCount=0);
 
-    /** Utility methods */
-
     // Convert static camera info from a camera2 device to the
     // old API parameter map.
     status_t buildDefaultParameters();
 
-    // Update preview request based on mParameters
-    status_t updatePreviewRequest();
-    // Update preview stream based on mParameters
-    status_t updatePreviewStream();
-
-    // Update capture request based on mParameters
-    status_t updateCaptureRequest();
-    // Update capture stream based on mParameters
-    status_t updateCaptureStream();
-
     // Update parameters all requests use, based on mParameters
     status_t updateRequestCommon(camera_metadata_t *request);
 
diff --git a/services/camera/libcameraservice/Camera2Device.cpp b/services/camera/libcameraservice/Camera2Device.cpp
index 8d07eee..54dde80 100644
--- a/services/camera/libcameraservice/Camera2Device.cpp
+++ b/services/camera/libcameraservice/Camera2Device.cpp
@@ -239,9 +239,9 @@
     for (StreamList::iterator streamI = mStreams.begin();
          streamI != mStreams.end(); streamI++) {
         if ((*streamI)->getId() == id) {
-            status_t res = (*streamI)->disconnect();
+            status_t res = (*streamI)->release();
             if (res != OK) {
-                ALOGE("%s: Unable to disconnect stream %d from HAL device: "
+                ALOGE("%s: Unable to release stream %d from HAL device: "
                         "%s (%d)", __FUNCTION__, id, strerror(-res), res);
                 return res;
             }
@@ -615,7 +615,7 @@
 #endif
 
 Camera2Device::StreamAdapter::StreamAdapter(camera2_device_t *d):
-        mState(DISCONNECTED),
+        mState(RELEASED),
         mDevice(d),
         mId(-1),
         mWidth(0), mHeight(0), mFormat(0), mSize(0), mUsage(0),
@@ -633,7 +633,9 @@
 }
 
 Camera2Device::StreamAdapter::~StreamAdapter() {
-    disconnect();
+    if (mState != RELEASED) {
+        release();
+    }
 }
 
 status_t Camera2Device::StreamAdapter::connectToDevice(
@@ -641,12 +643,15 @@
         uint32_t width, uint32_t height, int format, size_t size) {
     status_t res;
 
-    if (mState != DISCONNECTED) return INVALID_OPERATION;
+    if (mState != RELEASED) return INVALID_OPERATION;
     if (consumer == NULL) {
         ALOGE("%s: Null consumer passed to stream adapter", __FUNCTION__);
         return BAD_VALUE;
     }
 
+    ALOGV("%s: New stream parameters %d x %d, format 0x%x, size %d",
+            __FUNCTION__, width, height, format, size);
+
     mConsumerInterface = consumer;
     mWidth = width;
     mHeight = height;
@@ -668,6 +673,10 @@
         return res;
     }
 
+    ALOGV("%s: Allocated stream id %d, actual format 0x%x, "
+            "usage 0x%x, producer wants %d buffers", __FUNCTION__,
+            id, formatActual, usage, maxBuffers);
+
     mId = id;
     mFormat = formatActual;
     mUsage = usage;
@@ -737,8 +746,8 @@
     }
     mMaxConsumerBuffers = maxConsumerBuffers;
 
-    ALOGV("%s: Producer wants %d buffers, consumer wants %d", __FUNCTION__,
-            mMaxProducerBuffers, mMaxConsumerBuffers);
+    ALOGV("%s: Consumer wants %d buffers", __FUNCTION__,
+            mMaxConsumerBuffers);
 
     mTotalBuffers = mMaxConsumerBuffers + mMaxProducerBuffers;
     mActiveBuffers = 0;
@@ -761,7 +770,7 @@
         res = native_window_dequeue_buffer_and_wait(mConsumerInterface.get(),
                 &anwBuffers[bufferIdx]);
         if (res != OK) {
-            ALOGE("%s: Unable to dequeue buffer %d for initial registration for"
+            ALOGE("%s: Unable to dequeue buffer %d for initial registration for "
                     "stream %d", __FUNCTION__, bufferIdx, mId);
             goto cleanUpBuffers;
         }
@@ -795,8 +804,9 @@
     return res;
 }
 
-status_t Camera2Device::StreamAdapter::disconnect() {
+status_t Camera2Device::StreamAdapter::release() {
     status_t res;
+    ALOGV("%s: Releasing stream %d", __FUNCTION__, mId);
     if (mState >= ALLOCATED) {
         res = mDevice->ops->release_stream(mDevice, mId);
         if (res != OK) {
@@ -815,7 +825,7 @@
         }
     }
     mId = -1;
-    mState = DISCONNECTED;
+    mState = RELEASED;
     return OK;
 }
 
diff --git a/services/camera/libcameraservice/Camera2Device.h b/services/camera/libcameraservice/Camera2Device.h
index 1116be0..4ac63df 100644
--- a/services/camera/libcameraservice/Camera2Device.h
+++ b/services/camera/libcameraservice/Camera2Device.h
@@ -219,7 +219,7 @@
         status_t connectToDevice(sp<ANativeWindow> consumer,
                 uint32_t width, uint32_t height, int format, size_t size);
 
-        status_t disconnect();
+        status_t release();
 
         status_t setTransform(int transform);
 
@@ -236,7 +236,7 @@
       private:
         enum {
             ERROR = -1,
-            DISCONNECTED = 0,
+            RELEASED = 0,
             ALLOCATED,
             CONNECTED,
             ACTIVE