OSDN Git Service

Add a BufferQueue CPU consumer.
authorEino-Ville Talvala <etalvala@google.com>
Tue, 17 Apr 2012 00:54:33 +0000 (17:54 -0700)
committerEino-Ville Talvala <etalvala@google.com>
Tue, 15 May 2012 01:04:01 +0000 (18:04 -0700)
Aimed for use cases where gralloc buffers need to be consumed by CPU
users, such as camera image data streams.

The CpuConsumer is a synchronous queue, which exposes raw pointers to
the underlying graphics buffers to applications. Multiple buffers may
be acquired at once, up to the limit set at time of construction.

Change-Id: If1d99f12471438e95a69696e40685948778055fd

include/gui/BufferQueue.h
include/gui/CpuConsumer.h [new file with mode: 0644]
libs/gui/Android.mk
libs/gui/CpuConsumer.cpp [new file with mode: 0644]
libs/gui/tests/Android.mk
libs/gui/tests/CpuConsumer_test.cpp [new file with mode: 0644]

index 1c80d0c..0539a1b 100644 (file)
@@ -209,6 +209,11 @@ public:
     // releaseBuffer releases a buffer slot from the consumer back to the
     // BufferQueue pending a fence sync.
     //
+    // If releaseBuffer returns STALE_BUFFER_SLOT, then the consumer must free
+    // any references to the just-released buffer that it might have, as if it
+    // had received a onBuffersReleased() call with a mask set for the released
+    // buffer.
+    //
     // Note that the dependencies on EGL will be removed once we switch to using
     // the Android HW Sync HAL.
     status_t releaseBuffer(int buf, EGLDisplay display, EGLSyncKHR fence);
diff --git a/include/gui/CpuConsumer.h b/include/gui/CpuConsumer.h
new file mode 100644 (file)
index 0000000..a50a1de
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_GUI_CPUCONSUMER_H
+#define ANDROID_GUI_CPUCONSUMER_H
+
+#include <gui/BufferQueue.h>
+
+#include <ui/GraphicBuffer.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#define ANDROID_GRAPHICS_CPUCONSUMER_JNI_ID "mCpuConsumer"
+
+namespace android {
+
+/**
+ * CpuConsumer is a BufferQueue consumer endpoint that allows direct CPU
+ * access to the underlying gralloc buffers provided by BufferQueue. Multiple
+ * buffers may be acquired by it at once, to be used concurrently by the
+ * CpuConsumer owner. Sets gralloc usage flags to be software-read-only.
+ * This queue is synchronous by default.
+ */
+
+class CpuConsumer: public virtual RefBase,
+                     protected BufferQueue::ConsumerListener
+{
+  public:
+    struct FrameAvailableListener : public virtual RefBase {
+        // onFrameAvailable() is called each time an additional frame becomes
+        // available for consumption. A new frame queued will always trigger the
+        // callback, whether the queue is empty or not.
+        //
+        // This is called without any lock held and can be called concurrently
+        // by multiple threads.
+        virtual void onFrameAvailable() = 0;
+    };
+
+    struct LockedBuffer {
+        uint8_t    *data;
+        uint32_t    width;
+        uint32_t    height;
+        PixelFormat format;
+        uint32_t    stride;
+        Rect        crop;
+        uint32_t    transform;
+        uint32_t    scalingMode;
+        int64_t     timestamp;
+        uint64_t    frameNumber;
+    };
+
+    // Create a new CPU consumer. The maxLockedBuffers parameter specifies
+    // how many buffers can be locked for user access at the same time.
+    CpuConsumer(uint32_t maxLockedBuffers);
+
+    virtual ~CpuConsumer();
+
+    // set the name of the CpuConsumer that will be used to identify it in
+    // log messages.
+    void setName(const String8& name);
+
+    // Gets the next graphics buffer from the producer and locks it for CPU use,
+    // filling out the passed-in locked buffer structure with the native pointer
+    // and metadata. Returns BAD_VALUE if no new buffer is available, and
+    // INVALID_OPERATION if the maximum number of buffers is already locked.
+    //
+    // Only a fixed number of buffers can be locked at a time, determined by the
+    // construction-time maxLockedBuffers parameter. If INVALID_OPERATION is
+    // returned by lockNextBuffer, then old buffers must be returned to the queue
+    // by calling unlockBuffer before more buffers can be acquired.
+    status_t lockNextBuffer(LockedBuffer *nativeBuffer);
+
+    // Returns a locked buffer to the queue, allowing it to be reused. Since
+    // only a fixed number of buffers may be locked at a time, old buffers must
+    // be released by calling unlockBuffer to ensure new buffers can be acquired by
+    // lockNextBuffer.
+    status_t unlockBuffer(const LockedBuffer &nativeBuffer);
+
+    // setFrameAvailableListener sets the listener object that will be notified
+    // when a new frame becomes available.
+    void setFrameAvailableListener(const sp<FrameAvailableListener>& listener);
+
+    sp<ISurfaceTexture> getProducerInterface() const { return mBufferQueue; }
+  protected:
+
+    // Implementation of the BufferQueue::ConsumerListener interface.  These
+    // calls are used to notify the CpuConsumer of asynchronous events in the
+    // BufferQueue.
+    virtual void onFrameAvailable();
+    virtual void onBuffersReleased();
+
+  private:
+    // Free local buffer state
+    status_t freeBufferLocked(int buf);
+
+    // Maximum number of buffers that can be locked at a time
+    uint32_t mMaxLockedBuffers;
+
+    // mName is a string used to identify the SurfaceTexture in log messages.
+    // It can be set by the setName method.
+    String8 mName;
+
+    // mFrameAvailableListener is the listener object that will be called when a
+    // new frame becomes available. If it is not NULL it will be called from
+    // queueBuffer.
+    sp<FrameAvailableListener> mFrameAvailableListener;
+
+    // Underlying buffer queue
+    sp<BufferQueue> mBufferQueue;
+
+    // Array for caching buffers from the buffer queue
+    sp<GraphicBuffer> mBufferSlot[BufferQueue::NUM_BUFFER_SLOTS];
+    // Array for tracking pointers passed to the consumer, matching the
+    // mBufferSlot indexing
+    void *mBufferPointers[BufferQueue::NUM_BUFFER_SLOTS];
+    // Count of currently locked buffers
+    uint32_t mCurrentLockedBuffers;
+
+    // mMutex is the mutex used to prevent concurrent access to the member
+    // variables of CpuConsumer objects. It must be locked whenever the
+    // member variables are accessed.
+    mutable Mutex mMutex;
+};
+
+} // namespace android
+
+#endif // ANDROID_GUI_CPUCONSUMER_H
index 8bda3aa..a3a715a 100644 (file)
@@ -21,7 +21,8 @@ LOCAL_SRC_FILES:= \
        LayerState.cpp \
        Surface.cpp \
        SurfaceComposerClient.cpp \
-       DummyConsumer.cpp
+       DummyConsumer.cpp \
+       CpuConsumer.cpp
 
 LOCAL_SHARED_LIBRARIES := \
        libcutils \
diff --git a/libs/gui/CpuConsumer.cpp b/libs/gui/CpuConsumer.cpp
new file mode 100644 (file)
index 0000000..48a54c7
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * 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 "CpuConsumer"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <utils/Log.h>
+
+#include <gui/CpuConsumer.h>
+
+#define CC_LOGV(x, ...) ALOGV("[%s] "x, mName.string(), ##__VA_ARGS__)
+#define CC_LOGD(x, ...) ALOGD("[%s] "x, mName.string(), ##__VA_ARGS__)
+#define CC_LOGI(x, ...) ALOGI("[%s] "x, mName.string(), ##__VA_ARGS__)
+#define CC_LOGW(x, ...) ALOGW("[%s] "x, mName.string(), ##__VA_ARGS__)
+#define CC_LOGE(x, ...) ALOGE("[%s] "x, mName.string(), ##__VA_ARGS__)
+
+namespace android {
+
+// Get an ID that's unique within this process.
+static int32_t createProcessUniqueId() {
+    static volatile int32_t globalCounter = 0;
+    return android_atomic_inc(&globalCounter);
+}
+
+CpuConsumer::CpuConsumer(uint32_t maxLockedBuffers) :
+    mMaxLockedBuffers(maxLockedBuffers),
+    mCurrentLockedBuffers(0)
+{
+    mName = String8::format("cc-unnamed-%d-%d", getpid(),
+            createProcessUniqueId());
+
+    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+        mBufferPointers[i] = NULL;
+    }
+
+    mBufferQueue = new BufferQueue(true);
+
+    wp<BufferQueue::ConsumerListener> listener;
+    sp<BufferQueue::ConsumerListener> proxy;
+    listener = static_cast<BufferQueue::ConsumerListener*>(this);
+    proxy = new BufferQueue::ProxyConsumerListener(listener);
+
+    status_t err = mBufferQueue->consumerConnect(proxy);
+    if (err != NO_ERROR) {
+        ALOGE("CpuConsumer: error connecting to BufferQueue: %s (%d)",
+                strerror(-err), err);
+    } else {
+        mBufferQueue->setSynchronousMode(true);
+        mBufferQueue->setConsumerUsageBits(GRALLOC_USAGE_SW_READ_OFTEN);
+        mBufferQueue->setConsumerName(mName);
+    }
+}
+
+CpuConsumer::~CpuConsumer()
+{
+    Mutex::Autolock _l(mMutex);
+    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+        freeBufferLocked(i);
+    }
+    mBufferQueue->consumerDisconnect();
+    mBufferQueue.clear();
+}
+
+void CpuConsumer::setName(const String8& name) {
+    Mutex::Autolock _l(mMutex);
+    mName = name;
+    mBufferQueue->setConsumerName(name);
+}
+
+status_t CpuConsumer::lockNextBuffer(LockedBuffer *nativeBuffer) {
+    status_t err;
+
+    if (!nativeBuffer) return BAD_VALUE;
+    if (mCurrentLockedBuffers == mMaxLockedBuffers) {
+        return INVALID_OPERATION;
+    }
+
+    BufferQueue::BufferItem b;
+
+    Mutex::Autolock _l(mMutex);
+
+    err = mBufferQueue->acquireBuffer(&b);
+    if (err != OK) {
+        if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
+            return BAD_VALUE;
+        } else {
+            CC_LOGE("Error acquiring buffer: %s (%d)", strerror(err), err);
+            return err;
+        }
+    }
+
+    int buf = b.mBuf;
+
+    if (b.mGraphicBuffer != NULL) {
+        if (mBufferPointers[buf] != NULL) {
+            CC_LOGE("Reallocation of buffer %d while in consumer use!", buf);
+            mBufferQueue->releaseBuffer(buf, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR);
+            return BAD_VALUE;
+        }
+        mBufferSlot[buf] = b.mGraphicBuffer;
+    }
+
+    err = mBufferSlot[buf]->lock(
+        GraphicBuffer::USAGE_SW_READ_OFTEN,
+        b.mCrop,
+        &mBufferPointers[buf]);
+
+    if (mBufferPointers[buf] != NULL && err != OK) {
+        CC_LOGE("Unable to lock buffer for CPU reading: %s (%d)", strerror(-err),
+                err);
+        return err;
+    }
+
+    nativeBuffer->data   = reinterpret_cast<uint8_t*>(mBufferPointers[buf]);
+    nativeBuffer->width  = mBufferSlot[buf]->getWidth();
+    nativeBuffer->height = mBufferSlot[buf]->getHeight();
+    nativeBuffer->format = mBufferSlot[buf]->getPixelFormat();
+    nativeBuffer->stride = mBufferSlot[buf]->getStride();
+
+    nativeBuffer->crop        = b.mCrop;
+    nativeBuffer->transform   = b.mTransform;
+    nativeBuffer->scalingMode = b.mScalingMode;
+    nativeBuffer->timestamp   = b.mTimestamp;
+    nativeBuffer->frameNumber = b.mFrameNumber;
+
+    mCurrentLockedBuffers++;
+
+    return OK;
+}
+
+status_t CpuConsumer::unlockBuffer(const LockedBuffer &nativeBuffer) {
+    Mutex::Autolock _l(mMutex);
+    int buf = 0;
+    status_t err;
+
+    void *bufPtr = reinterpret_cast<void *>(nativeBuffer.data);
+    for (; buf < BufferQueue::NUM_BUFFER_SLOTS; buf++) {
+        if (bufPtr == mBufferPointers[buf]) break;
+    }
+    if (buf == BufferQueue::NUM_BUFFER_SLOTS) {
+        CC_LOGE("%s: Can't find buffer to free", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    mBufferPointers[buf] = NULL;
+    err = mBufferSlot[buf]->unlock();
+    if (err != OK) {
+        CC_LOGE("%s: Unable to unlock graphic buffer %d", __FUNCTION__, buf);
+        return err;
+    }
+    err = mBufferQueue->releaseBuffer(buf, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR);
+    if (err == BufferQueue::STALE_BUFFER_SLOT) {
+        freeBufferLocked(buf);
+    } else if (err != OK) {
+        CC_LOGE("%s: Unable to release graphic buffer %d to queue", __FUNCTION__,
+                buf);
+        return err;
+    }
+
+    mCurrentLockedBuffers--;
+
+    return OK;
+}
+
+void CpuConsumer::setFrameAvailableListener(
+        const sp<FrameAvailableListener>& listener) {
+    CC_LOGV("setFrameAvailableListener");
+    Mutex::Autolock lock(mMutex);
+    mFrameAvailableListener = listener;
+}
+
+
+void CpuConsumer::onFrameAvailable() {
+    CC_LOGV("onFrameAvailable");
+    sp<FrameAvailableListener> listener;
+    { // scope for the lock
+        Mutex::Autolock _l(mMutex);
+        listener = mFrameAvailableListener;
+    }
+
+    if (listener != NULL) {
+        CC_LOGV("actually calling onFrameAvailable");
+        listener->onFrameAvailable();
+    }
+}
+
+void CpuConsumer::onBuffersReleased() {
+    CC_LOGV("onBuffersReleased");
+
+    Mutex::Autolock lock(mMutex);
+
+    uint32_t mask = 0;
+    mBufferQueue->getReleasedBuffers(&mask);
+    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+        if (mask & (1 << i)) {
+            freeBufferLocked(i);
+        }
+    }
+
+}
+
+status_t CpuConsumer::freeBufferLocked(int buf) {
+    status_t err = OK;
+
+    if (mBufferPointers[buf] != NULL) {
+        CC_LOGW("Buffer %d freed while locked by consumer", buf);
+        mBufferPointers[buf] = NULL;
+        err = mBufferSlot[buf]->unlock();
+        if (err != OK) {
+            CC_LOGE("%s: Unable to unlock graphic buffer %d", __FUNCTION__, buf);
+        }
+        mCurrentLockedBuffers--;
+    }
+    mBufferSlot[buf] = NULL;
+    return err;
+}
+
+} // namespace android
index 741534b..3a8e356 100644 (file)
@@ -31,6 +31,35 @@ LOCAL_C_INCLUDES := \
 # to integrate with auto-test framework.
 include $(BUILD_NATIVE_TEST)
 
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := CpuConsumer_test
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+    CpuConsumer_test.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+       libEGL \
+       libGLESv2 \
+       libbinder \
+       libcutils \
+       libgui \
+       libstlport \
+       libui \
+       libutils \
+
+LOCAL_C_INCLUDES := \
+    bionic \
+    bionic/libstdc++/include \
+    external/gtest/include \
+    external/stlport/stlport \
+
+# Build the binary to $(TARGET_OUT_DATA_NATIVE_TESTS)/$(LOCAL_MODULE)
+# to integrate with auto-test framework.
+include $(BUILD_NATIVE_TEST)
+
 # Include subdirectory makefiles
 # ============================================================
 
diff --git a/libs/gui/tests/CpuConsumer_test.cpp b/libs/gui/tests/CpuConsumer_test.cpp
new file mode 100644 (file)
index 0000000..7ad60e8
--- /dev/null
@@ -0,0 +1,579 @@
+/*
+ * 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_TAG "CpuConsumer_test"
+//#define LOG_NDEBUG 0
+//#define LOG_NNDEBUG 0
+
+#ifdef LOG_NNDEBUG
+#define ALOGVV(...) ALOGV(__VA_ARGS__)
+#else
+#define ALOGVV(...) ((void)0)
+#endif
+
+#include <gtest/gtest.h>
+#include <gui/CpuConsumer.h>
+#include <gui/SurfaceTextureClient.h>
+#include <ui/GraphicBuffer.h>
+#include <utils/String8.h>
+#include <utils/Thread.h>
+#include <utils/Mutex.h>
+#include <utils/Condition.h>
+
+#include <ui/FramebufferNativeWindow.h>
+
+namespace android {
+
+struct CpuConsumerTestParams {
+    uint32_t width;
+    uint32_t height;
+    int maxLockedBuffers;
+    PixelFormat format;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const CpuConsumerTestParams& p) {
+    return os << "[ (" << p.width << ", " << p.height << "), B:"
+              << p.maxLockedBuffers << ", F:0x"
+              << ::std::hex << p.format << "]";
+}
+
+class CpuConsumerTest : public ::testing::TestWithParam<CpuConsumerTestParams> {
+protected:
+
+    virtual void SetUp() {
+        const ::testing::TestInfo* const test_info =
+                ::testing::UnitTest::GetInstance()->current_test_info();
+        CpuConsumerTestParams params = GetParam();
+        ALOGV("** Starting test %s (%d x %d, %d, 0x%x)",
+                test_info->name(),
+                params.width, params.height,
+                params.maxLockedBuffers, params.format);
+        mCC = new CpuConsumer(params.maxLockedBuffers);
+        String8 name("CpuConsumer_Under_Test");
+        mCC->setName(name);
+        mSTC = new SurfaceTextureClient(mCC->getProducerInterface());
+        mANW = mSTC;
+    }
+
+    virtual void TearDown() {
+        mANW.clear();
+        mSTC.clear();
+        mCC.clear();
+    }
+
+    class FrameWaiter : public CpuConsumer::FrameAvailableListener {
+    public:
+        FrameWaiter():
+                mPendingFrames(0) {
+        }
+
+        void waitForFrame() {
+            Mutex::Autolock lock(mMutex);
+            while (mPendingFrames == 0) {
+                mCondition.wait(mMutex);
+            }
+            mPendingFrames--;
+        }
+
+        virtual void onFrameAvailable() {
+            Mutex::Autolock lock(mMutex);
+            mPendingFrames++;
+            mCondition.signal();
+        }
+
+        int mPendingFrames;
+        Mutex mMutex;
+        Condition mCondition;
+    };
+
+    // Note that SurfaceTexture will lose the notifications
+    // onBuffersReleased and onFrameAvailable as there is currently
+    // no way to forward the events.  This DisconnectWaiter will not let the
+    // disconnect finish until finishDisconnect() is called.  It will
+    // also block until a disconnect is called
+    class DisconnectWaiter : public BufferQueue::ConsumerListener {
+    public:
+        DisconnectWaiter () :
+            mWaitForDisconnect(false),
+            mPendingFrames(0) {
+        }
+
+        void waitForFrame() {
+            Mutex::Autolock lock(mMutex);
+            while (mPendingFrames == 0) {
+                mFrameCondition.wait(mMutex);
+            }
+            mPendingFrames--;
+        }
+
+        virtual void onFrameAvailable() {
+            Mutex::Autolock lock(mMutex);
+            mPendingFrames++;
+            mFrameCondition.signal();
+        }
+
+        virtual void onBuffersReleased() {
+            Mutex::Autolock lock(mMutex);
+            while (!mWaitForDisconnect) {
+                mDisconnectCondition.wait(mMutex);
+            }
+        }
+
+        void finishDisconnect() {
+            Mutex::Autolock lock(mMutex);
+            mWaitForDisconnect = true;
+            mDisconnectCondition.signal();
+        }
+
+    private:
+        Mutex mMutex;
+
+        bool mWaitForDisconnect;
+        Condition mDisconnectCondition;
+
+        int mPendingFrames;
+        Condition mFrameCondition;
+    };
+
+    sp<CpuConsumer> mCC;
+    sp<SurfaceTextureClient> mSTC;
+    sp<ANativeWindow> mANW;
+};
+
+#define ASSERT_NO_ERROR(err, msg) \
+    ASSERT_EQ(NO_ERROR, err) << msg << strerror(-err)
+
+void checkPixel(const CpuConsumer::LockedBuffer &buf,
+        uint32_t x, uint32_t y, uint32_t r, uint32_t g, uint32_t b) {
+    // Ignores components that don't exist for given pixel
+    switch(buf.format) {
+        case HAL_PIXEL_FORMAT_RAW_SENSOR: {
+            String8 msg;
+            uint16_t *bPtr = (uint16_t*)buf.data;
+            bPtr += y * buf.stride + x;
+            // GRBG Bayer mosaic; only check the matching channel
+            switch( ((y & 1) << 1) | (x & 1) ) {
+                case 0: // G
+                case 3: // G
+                    EXPECT_EQ(g, *bPtr);
+                    break;
+                case 1: // R
+                    EXPECT_EQ(r, *bPtr);
+                    break;
+                case 2: // B
+                    EXPECT_EQ(b, *bPtr);
+                    break;
+            }
+            break;
+        }
+        default: {
+            ADD_FAILURE() << "Unknown format for check:" << buf.format;
+            break;
+        }
+    }
+}
+
+// Fill a YV12 buffer with a multi-colored checkerboard pattern
+void fillYV12Buffer(uint8_t* buf, int w, int h, int stride) {
+    const int blockWidth = w > 16 ? w / 16 : 1;
+    const int blockHeight = h > 16 ? h / 16 : 1;
+    const int yuvTexOffsetY = 0;
+    int yuvTexStrideY = stride;
+    int yuvTexOffsetV = yuvTexStrideY * h;
+    int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf;
+    int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2;
+    int yuvTexStrideU = yuvTexStrideV;
+    for (int x = 0; x < w; x++) {
+        for (int y = 0; y < h; y++) {
+            int parityX = (x / blockWidth) & 1;
+            int parityY = (y / blockHeight) & 1;
+            unsigned char intensity = (parityX ^ parityY) ? 63 : 191;
+            buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = intensity;
+            if (x < w / 2 && y < h / 2) {
+                buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = intensity;
+                if (x * 2 < w / 2 && y * 2 < h / 2) {
+                    buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 0] =
+                    buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 1] =
+                    buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 0] =
+                    buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 1] =
+                        intensity;
+                }
+            }
+        }
+    }
+}
+
+// Fill a RAW sensor buffer with a multi-colored checkerboard pattern.
+// Assumes GRBG mosaic ordering. Result should be a grid in a 2x2 pattern
+// of [ R, B; G, W]
+void fillBayerRawBuffer(uint8_t* buf, int w, int h, int stride) {
+    ALOGVV("fillBayerRawBuffer: %p with %d x %d, stride %d", buf, w, h ,stride);
+    // Blocks need to be even-width/height, aim for 8-wide otherwise
+    const int blockWidth = (w > 16 ? w / 8 : 2) & ~0x1;
+    const int blockHeight = (h > 16 ? h / 8 : 2) & ~0x1;
+    for (int y = 0; y < h; y+=2) {
+        uint16_t *bPtr1 = ((uint16_t*)buf) + stride*y;
+        uint16_t *bPtr2 = bPtr1 + stride;
+        for (int x = 0; x < w; x+=2) {
+            int blockX = (x / blockWidth ) & 1;
+            int blockY = (y / blockHeight) & 1;
+            unsigned short r = (blockX == blockY) ? 1000 : 200;
+            unsigned short g = blockY ? 1000: 200;
+            unsigned short b = blockX ? 1000: 200;
+            // GR row
+            *bPtr1++ = g;
+            *bPtr1++ = r;
+            // BG row
+            *bPtr2++ = b;
+            *bPtr2++ = g;
+        }
+    }
+
+}
+
+void checkBayerRawBuffer(const CpuConsumer::LockedBuffer &buf) {
+    uint32_t w = buf.width;
+    uint32_t h = buf.height;
+    const int blockWidth = (w > 16 ? w / 8 : 2) & ~0x1;
+    const int blockHeight = (h > 16 ? h / 8 : 2) & ~0x1;
+    const int blockRows = h / blockHeight;
+    const int blockCols = w / blockWidth;
+
+    // Top-left square is red
+    checkPixel(buf, 0, 0, 1000, 200, 200);
+    checkPixel(buf, 1, 0, 1000, 200, 200);
+    checkPixel(buf, 0, 1, 1000, 200, 200);
+    checkPixel(buf, 1, 1, 1000, 200, 200);
+
+    // One-right square is blue
+    checkPixel(buf, blockWidth,     0, 200, 200, 1000);
+    checkPixel(buf, blockWidth + 1, 0, 200, 200, 1000);
+    checkPixel(buf, blockWidth,     1, 200, 200, 1000);
+    checkPixel(buf, blockWidth + 1, 1, 200, 200, 1000);
+
+    // One-down square is green
+    checkPixel(buf, 0, blockHeight, 200, 1000, 200);
+    checkPixel(buf, 1, blockHeight, 200, 1000, 200);
+    checkPixel(buf, 0, blockHeight + 1, 200, 1000, 200);
+    checkPixel(buf, 1, blockHeight + 1, 200, 1000, 200);
+
+    // One-diag square is white
+    checkPixel(buf, blockWidth,     blockHeight, 1000, 1000, 1000);
+    checkPixel(buf, blockWidth + 1, blockHeight, 1000, 1000, 1000);
+    checkPixel(buf, blockWidth,     blockHeight + 1, 1000, 1000, 1000);
+    checkPixel(buf, blockWidth + 1, blockHeight + 1, 1000, 1000, 1000);
+
+    // Test bottom-right pixel
+    const int maxBlockX = ((w-1) / blockWidth) & 0x1;
+    const int maxBlockY = ((w-1) / blockHeight) & 0x1;
+    unsigned short maxR = (maxBlockX == maxBlockY) ? 1000 : 200;
+    unsigned short maxG = maxBlockY ? 1000: 200;
+    unsigned short maxB = maxBlockX ? 1000: 200;
+    checkPixel(buf, w-1, h-1, maxR, maxG, maxB);
+}
+
+// Fill a YV12 buffer with red outside a given rectangle and green inside it.
+void fillYV12BufferRect(uint8_t* buf, int w, int h, int stride,
+        const android_native_rect_t& rect) {
+    const int yuvTexOffsetY = 0;
+    int yuvTexStrideY = stride;
+    int yuvTexOffsetV = yuvTexStrideY * h;
+    int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf;
+    int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2;
+    int yuvTexStrideU = yuvTexStrideV;
+    for (int x = 0; x < w; x++) {
+        for (int y = 0; y < h; y++) {
+            bool inside = rect.left <= x && x < rect.right &&
+                    rect.top <= y && y < rect.bottom;
+            buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = inside ? 240 : 64;
+            if (x < w / 2 && y < h / 2) {
+                bool inside = rect.left <= 2*x && 2*x < rect.right &&
+                        rect.top <= 2*y && 2*y < rect.bottom;
+                buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = 16;
+                buf[yuvTexOffsetV + (y * yuvTexStrideV) + x] =
+                        inside ? 16 : 255;
+            }
+        }
+    }
+}
+
+void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride) {
+    const size_t PIXEL_SIZE = 4;
+    for (int x = 0; x < w; x++) {
+        for (int y = 0; y < h; y++) {
+            off_t offset = (y * stride + x) * PIXEL_SIZE;
+            for (int c = 0; c < 4; c++) {
+                int parityX = (x / (1 << (c+2))) & 1;
+                int parityY = (y / (1 << (c+2))) & 1;
+                buf[offset + c] = (parityX ^ parityY) ? 231 : 35;
+            }
+        }
+    }
+}
+
+void fillRGBA8BufferSolid(uint8_t* buf, int w, int h, int stride, uint8_t r,
+        uint8_t g, uint8_t b, uint8_t a) {
+    const size_t PIXEL_SIZE = 4;
+    for (int y = 0; y < h; y++) {
+        for (int x = 0; x < h; x++) {
+            off_t offset = (y * stride + x) * PIXEL_SIZE;
+            buf[offset + 0] = r;
+            buf[offset + 1] = g;
+            buf[offset + 2] = b;
+            buf[offset + 3] = a;
+        }
+    }
+}
+
+// Configures the ANativeWindow producer-side interface based on test parameters
+void configureANW(const sp<ANativeWindow>& anw,
+        const CpuConsumerTestParams& params,
+        int maxBufferSlack) {
+    status_t err;
+    err = native_window_set_buffers_geometry(anw.get(),
+            params.width, params.height, params.format);
+    ASSERT_NO_ERROR(err, "set_buffers_geometry error: ");
+
+    err = native_window_set_usage(anw.get(),
+            GRALLOC_USAGE_SW_WRITE_OFTEN);
+    ASSERT_NO_ERROR(err, "set_usage error: ");
+
+    int minUndequeuedBuffers;
+    err = anw.get()->query(anw.get(),
+            NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+            &minUndequeuedBuffers);
+    ASSERT_NO_ERROR(err, "query error: ");
+
+    ALOGVV("Setting buffer count to %d",
+            maxBufferSlack + 1 + minUndequeuedBuffers);
+    err = native_window_set_buffer_count(anw.get(),
+            maxBufferSlack + 1 + minUndequeuedBuffers);
+    ASSERT_NO_ERROR(err, "set_buffer_count error: ");
+
+}
+
+// Produce one frame of image data; assumes format and resolution configuration
+// is already done.
+void produceOneFrame(const sp<ANativeWindow>& anw,
+        const CpuConsumerTestParams& params,
+        int64_t timestamp, uint32_t *stride) {
+    status_t err;
+    ANativeWindowBuffer* anb;
+    ALOGVV("Dequeue buffer from %p", anw.get());
+    err = anw->dequeueBuffer(anw.get(), &anb);
+    ASSERT_NO_ERROR(err, "dequeueBuffer error: ");
+
+    ASSERT_TRUE(anb != NULL);
+
+    sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
+
+    ALOGVV("Lock buffer from %p", anw.get());
+    err = anw->lockBuffer(anw.get(), buf->getNativeBuffer());
+    ASSERT_NO_ERROR(err, "lockBuffer error: ");
+
+    *stride = buf->getStride();
+    uint8_t* img = NULL;
+
+    ALOGVV("Lock buffer from %p for write", anw.get());
+    err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
+    ASSERT_NO_ERROR(err, "lock error: ");
+
+    switch (params.format) {
+        case HAL_PIXEL_FORMAT_YV12:
+            fillYV12Buffer(img, params.width, params.height, *stride);
+            break;
+        case HAL_PIXEL_FORMAT_RAW_SENSOR:
+            fillBayerRawBuffer(img, params.width, params.height, buf->getStride());
+            break;
+        default:
+            FAIL() << "Unknown pixel format under test!";
+            break;
+    }
+    ALOGVV("Unlock buffer from %p", anw.get());
+    err = buf->unlock();
+    ASSERT_NO_ERROR(err, "unlock error: ");
+
+    ALOGVV("Set timestamp to %p", anw.get());
+    err = native_window_set_buffers_timestamp(anw.get(), timestamp);
+    ASSERT_NO_ERROR(err, "set_buffers_timestamp error: ");
+
+    ALOGVV("Queue buffer to %p", anw.get());
+    err = anw->queueBuffer(anw.get(), buf->getNativeBuffer());
+    ASSERT_NO_ERROR(err, "queueBuffer error:");
+};
+
+TEST_P(CpuConsumerTest, FromCpuSingle) {
+    status_t err;
+    CpuConsumerTestParams params = GetParam();
+
+    // Set up
+
+    ASSERT_NO_FATAL_FAILURE(configureANW(mANW, params, 1));
+
+    // Produce
+
+    const int64_t time = 12345678L;
+    uint32_t stride;
+    ASSERT_NO_FATAL_FAILURE(produceOneFrame(mANW, params, time,
+                    &stride));
+
+    // Consume
+
+    CpuConsumer::LockedBuffer b;
+    err = mCC->lockNextBuffer(&b);
+    ASSERT_NO_ERROR(err, "getNextBuffer error: ");
+
+    ASSERT_TRUE(b.data != NULL);
+    EXPECT_EQ(params.width,  b.width);
+    EXPECT_EQ(params.height, b.height);
+    EXPECT_EQ(params.format, b.format);
+    EXPECT_EQ(stride, b.stride);
+    EXPECT_EQ(time, b.timestamp);
+
+    checkBayerRawBuffer(b);
+    mCC->unlockBuffer(b);
+}
+
+TEST_P(CpuConsumerTest, FromCpuManyInQueue) {
+    status_t err;
+    CpuConsumerTestParams params = GetParam();
+
+    const int numInQueue = 5;
+    // Set up
+
+    ASSERT_NO_FATAL_FAILURE(configureANW(mANW, params, numInQueue));
+
+    // Produce
+
+    const int64_t time[numInQueue] = { 1L, 2L, 3L, 4L, 5L};
+    uint32_t stride[numInQueue];
+
+    for (int i = 0; i < numInQueue; i++) {
+        ALOGV("Producing frame %d", i);
+        ASSERT_NO_FATAL_FAILURE(produceOneFrame(mANW, params, time[i],
+                        &stride[i]));
+    }
+
+    // Consume
+
+    for (int i = 0; i < numInQueue; i++) {
+        ALOGV("Consuming frame %d", i);
+        CpuConsumer::LockedBuffer b;
+        err = mCC->lockNextBuffer(&b);
+        ASSERT_NO_ERROR(err, "getNextBuffer error: ");
+
+        ASSERT_TRUE(b.data != NULL);
+        EXPECT_EQ(params.width,  b.width);
+        EXPECT_EQ(params.height, b.height);
+        EXPECT_EQ(params.format, b.format);
+        EXPECT_EQ(stride[i], b.stride);
+        EXPECT_EQ(time[i], b.timestamp);
+
+        checkBayerRawBuffer(b);
+
+        mCC->unlockBuffer(b);
+    }
+}
+
+TEST_P(CpuConsumerTest, FromCpuLockMax) {
+    status_t err;
+    CpuConsumerTestParams params = GetParam();
+
+    // Set up
+
+    ASSERT_NO_FATAL_FAILURE(configureANW(mANW, params, params.maxLockedBuffers + 1));
+
+    // Produce
+
+    const int64_t time = 1234L;
+    uint32_t stride;
+
+    for (int i = 0; i < params.maxLockedBuffers + 1; i++) {
+        ALOGV("Producing frame %d", i);
+        ASSERT_NO_FATAL_FAILURE(produceOneFrame(mANW, params, time,
+                        &stride));
+    }
+
+    // Consume
+
+    CpuConsumer::LockedBuffer *b = new CpuConsumer::LockedBuffer[params.maxLockedBuffers];
+    for (int i = 0; i < params.maxLockedBuffers; i++) {
+        ALOGV("Locking frame %d", i);
+        err = mCC->lockNextBuffer(&b[i]);
+        ASSERT_NO_ERROR(err, "getNextBuffer error: ");
+
+        ASSERT_TRUE(b[i].data != NULL);
+        EXPECT_EQ(params.width,  b[i].width);
+        EXPECT_EQ(params.height, b[i].height);
+        EXPECT_EQ(params.format, b[i].format);
+        EXPECT_EQ(stride, b[i].stride);
+        EXPECT_EQ(time, b[i].timestamp);
+
+        checkBayerRawBuffer(b[i]);
+    }
+
+    ALOGV("Locking frame %d (too many)", params.maxLockedBuffers);
+    CpuConsumer::LockedBuffer bTooMuch;
+    err = mCC->lockNextBuffer(&bTooMuch);
+    ASSERT_TRUE(err == INVALID_OPERATION) << "Allowing too many locks";
+
+    ALOGV("Unlocking frame 0");
+    err = mCC->unlockBuffer(b[0]);
+    ASSERT_NO_ERROR(err, "Could not unlock buffer 0: ");
+
+    ALOGV("Locking frame %d (should work now)", params.maxLockedBuffers);
+    err = mCC->lockNextBuffer(&bTooMuch);
+    ASSERT_NO_ERROR(err, "Did not allow new lock after unlock");
+
+    ASSERT_TRUE(bTooMuch.data != NULL);
+    EXPECT_EQ(params.width,  bTooMuch.width);
+    EXPECT_EQ(params.height, bTooMuch.height);
+    EXPECT_EQ(params.format, bTooMuch.format);
+    EXPECT_EQ(stride, bTooMuch.stride);
+    EXPECT_EQ(time, bTooMuch.timestamp);
+
+    checkBayerRawBuffer(bTooMuch);
+
+    ALOGV("Unlocking extra buffer");
+    err = mCC->unlockBuffer(bTooMuch);
+    ASSERT_NO_ERROR(err, "Could not unlock extra buffer: ");
+
+    ALOGV("Locking frame %d (no more available)", params.maxLockedBuffers + 1);
+    err = mCC->lockNextBuffer(&b[0]);
+    ASSERT_EQ(BAD_VALUE, err) << "Not out of buffers somehow";
+
+    for (int i = 1; i < params.maxLockedBuffers; i++) {
+        mCC->unlockBuffer(b[i]);
+    }
+
+    delete[] b;
+
+}
+
+CpuConsumerTestParams rawTestSets[] = {
+    { 512,   512, 1, HAL_PIXEL_FORMAT_RAW_SENSOR},
+    { 512,   512, 3, HAL_PIXEL_FORMAT_RAW_SENSOR},
+    { 2608, 1960, 1, HAL_PIXEL_FORMAT_RAW_SENSOR},
+    { 2608, 1960, 3, HAL_PIXEL_FORMAT_RAW_SENSOR},
+    { 100,   100, 1, HAL_PIXEL_FORMAT_RAW_SENSOR},
+    { 100,   100, 3, HAL_PIXEL_FORMAT_RAW_SENSOR}
+};
+
+INSTANTIATE_TEST_CASE_P(RawTests,
+        CpuConsumerTest,
+        ::testing::ValuesIn(rawTestSets));
+
+} // namespace android