From 98fbbf3af12def9ad0fb1daba9728761859a943b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20R=C3=B8dal?= Date: Wed, 13 Mar 2013 15:13:17 +0100 Subject: Added nativemedia for video playback. See the test.qml in src/imports/nativemedia for usage. --- src/imports/imports.pro | 2 +- src/imports/nativemedia/BufferQueue.cpp | 1061 ++++++++++++++++++++ src/imports/nativemedia/BufferQueue.h | 490 ++++++++++ src/imports/nativemedia/SurfaceTexture.cpp | 5 + src/imports/nativemedia/SurfaceTexture.h | 5 + src/imports/nativemedia/SurfaceTexture_4_0.cpp | 1236 ++++++++++++++++++++++++ src/imports/nativemedia/SurfaceTexture_4_0.h | 517 ++++++++++ src/imports/nativemedia/SurfaceTexture_4_1.cpp | 866 +++++++++++++++++ src/imports/nativemedia/SurfaceTexture_4_1.h | 420 ++++++++ src/imports/nativemedia/main.cpp | 55 ++ src/imports/nativemedia/nativemedia.pro | 21 + src/imports/nativemedia/omx.cpp | 648 +++++++++++++ src/imports/nativemedia/omxmodule.cpp | 19 + src/imports/nativemedia/omxnode.cpp | 166 ++++ src/imports/nativemedia/omxnode.h | 110 +++ src/imports/nativemedia/omxplayer.h | 24 + src/imports/nativemedia/qmldir | 2 + src/imports/nativemedia/test.qml | 35 + 18 files changed, 5681 insertions(+), 1 deletion(-) create mode 100644 src/imports/nativemedia/BufferQueue.cpp create mode 100644 src/imports/nativemedia/BufferQueue.h create mode 100644 src/imports/nativemedia/SurfaceTexture.cpp create mode 100644 src/imports/nativemedia/SurfaceTexture.h create mode 100644 src/imports/nativemedia/SurfaceTexture_4_0.cpp create mode 100644 src/imports/nativemedia/SurfaceTexture_4_0.h create mode 100644 src/imports/nativemedia/SurfaceTexture_4_1.cpp create mode 100644 src/imports/nativemedia/SurfaceTexture_4_1.h create mode 100644 src/imports/nativemedia/main.cpp create mode 100644 src/imports/nativemedia/nativemedia.pro create mode 100644 src/imports/nativemedia/omx.cpp create mode 100644 src/imports/nativemedia/omxmodule.cpp create mode 100644 src/imports/nativemedia/omxnode.cpp create mode 100644 src/imports/nativemedia/omxnode.h create mode 100644 src/imports/nativemedia/omxplayer.h create mode 100644 src/imports/nativemedia/qmldir create mode 100644 src/imports/nativemedia/test.qml diff --git a/src/imports/imports.pro b/src/imports/imports.pro index 92eddcd..8a13bdd 100644 --- a/src/imports/imports.pro +++ b/src/imports/imports.pro @@ -1,2 +1,2 @@ TEMPLATE = subdirs -SUBDIRS = utils +SUBDIRS = utils nativemedia diff --git a/src/imports/nativemedia/BufferQueue.cpp b/src/imports/nativemedia/BufferQueue.cpp new file mode 100644 index 0000000..1dbd498 --- /dev/null +++ b/src/imports/nativemedia/BufferQueue.cpp @@ -0,0 +1,1061 @@ +/* + * 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. + */ + +#if Q_ANDROID_VERSION_MAJOR > 4 || (Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR >= 1) + +#define LOG_TAG "BufferQueue" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS +//#define LOG_NDEBUG 0 + +#define GL_GLEXT_PROTOTYPES +#define EGL_EGLEXT_PROTOTYPES + +#include +#include + +#include +#include +#include + +#include +#include +#include + +// This compile option causes SurfaceTexture to return the buffer that is currently +// attached to the GL texture from dequeueBuffer when no other buffers are +// available. It requires the drivers (Gralloc, GL, OMX IL, and Camera) to do +// implicit cross-process synchronization to prevent the buffer from being +// written to before the buffer has (a) been detached from the GL texture and +// (b) all GL reads from the buffer have completed. + +// During refactoring, do not support dequeuing the current buffer +#undef ALLOW_DEQUEUE_CURRENT_BUFFER + +#ifdef ALLOW_DEQUEUE_CURRENT_BUFFER +#define FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER true +#warning "ALLOW_DEQUEUE_CURRENT_BUFFER enabled" +#else +#define FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER false +#endif + +// Macros for including the BufferQueue name in log messages +#define ST_LOGV(x, ...) ALOGV("[%s] "x, mConsumerName.string(), ##__VA_ARGS__) +#define ST_LOGD(x, ...) ALOGD("[%s] "x, mConsumerName.string(), ##__VA_ARGS__) +#define ST_LOGI(x, ...) ALOGI("[%s] "x, mConsumerName.string(), ##__VA_ARGS__) +#define ST_LOGW(x, ...) ALOGW("[%s] "x, mConsumerName.string(), ##__VA_ARGS__) +#define ST_LOGE(x, ...) ALOGE("[%s] "x, mConsumerName.string(), ##__VA_ARGS__) + +#define ATRACE_BUFFER_INDEX(index) \ + if (ATRACE_ENABLED()) { \ + char ___traceBuf[1024]; \ + snprintf(___traceBuf, 1024, "%s: %d", mConsumerName.string(), \ + (index)); \ + android::ScopedTrace ___bufTracer(ATRACE_TAG, ___traceBuf); \ + } + +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); +} + +static const char* scalingModeName(int scalingMode) { + switch (scalingMode) { + case NATIVE_WINDOW_SCALING_MODE_FREEZE: return "FREEZE"; + case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: return "SCALE_TO_WINDOW"; + case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: return "SCALE_CROP"; + default: return "Unknown"; + } +} + +BufferQueue::BufferQueue( bool allowSynchronousMode, int bufferCount ) : + mDefaultWidth(1), + mDefaultHeight(1), + mPixelFormat(PIXEL_FORMAT_RGBA_8888), + mMinUndequeuedBuffers(bufferCount), + mMinAsyncBufferSlots(bufferCount + 1), + mMinSyncBufferSlots(bufferCount), + mBufferCount(mMinAsyncBufferSlots), + mClientBufferCount(0), + mServerBufferCount(mMinAsyncBufferSlots), + mSynchronousMode(false), + mAllowSynchronousMode(allowSynchronousMode), + mConnectedApi(NO_CONNECTED_API), + mAbandoned(false), + mFrameCounter(0), + mBufferHasBeenQueued(false), + mDefaultBufferFormat(0), + mConsumerUsageBits(0), + mTransformHint(0) +{ + // Choose a name using the PID and a process-unique ID. + mConsumerName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId()); + + ST_LOGV("BufferQueue"); +#if 0 + sp composer(ComposerService::getComposerService()); + mGraphicBufferAlloc = composer->createGraphicBufferAlloc(); + if (mGraphicBufferAlloc == 0) { + ST_LOGE("createGraphicBufferAlloc() failed in BufferQueue()"); + } +#endif +} + +BufferQueue::~BufferQueue() { + ST_LOGV("~BufferQueue"); +} + +status_t BufferQueue::setBufferCountServerLocked(int bufferCount) { + if (bufferCount > NUM_BUFFER_SLOTS) + return BAD_VALUE; + + // special-case, nothing to do + if (bufferCount == mBufferCount) + return OK; + + if (!mClientBufferCount && + bufferCount >= mBufferCount) { + // easy, we just have more buffers + mBufferCount = bufferCount; + mServerBufferCount = bufferCount; + mDequeueCondition.broadcast(); + } else { + // we're here because we're either + // - reducing the number of available buffers + // - or there is a client-buffer-count in effect + + // less than 2 buffers is never allowed + if (bufferCount < 2) + return BAD_VALUE; + + // when there is non client-buffer-count in effect, the client is not + // allowed to dequeue more than one buffer at a time, + // so the next time they dequeue a buffer, we know that they don't + // own one. the actual resizing will happen during the next + // dequeueBuffer. + + mServerBufferCount = bufferCount; + mDequeueCondition.broadcast(); + } + return OK; +} + +bool BufferQueue::isSynchronousMode() const { + Mutex::Autolock lock(mMutex); + return mSynchronousMode; +} + +void BufferQueue::setConsumerName(const String8& name) { + Mutex::Autolock lock(mMutex); + mConsumerName = name; +} + +status_t BufferQueue::setDefaultBufferFormat(uint32_t defaultFormat) { + Mutex::Autolock lock(mMutex); + mDefaultBufferFormat = defaultFormat; + return OK; +} + +status_t BufferQueue::setConsumerUsageBits(uint32_t usage) { + Mutex::Autolock lock(mMutex); + mConsumerUsageBits = usage; + return OK; +} + +status_t BufferQueue::setTransformHint(uint32_t hint) { + Mutex::Autolock lock(mMutex); + mTransformHint = hint; + return OK; +} + +status_t BufferQueue::setBufferCount(int bufferCount) { + ST_LOGV("setBufferCount: count=%d", bufferCount); + + sp listener; + { + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("setBufferCount: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + if (bufferCount > NUM_BUFFER_SLOTS) { + ST_LOGE("setBufferCount: bufferCount larger than slots available"); + return BAD_VALUE; + } + + // Error out if the user has dequeued buffers + for (int i=0 ; i= minBufferSlots) ? + mServerBufferCount : minBufferSlots; + return setBufferCountServerLocked(bufferCount); + } + + if (bufferCount < minBufferSlots) { + ST_LOGE("setBufferCount: requested buffer count (%d) is less than " + "minimum (%d)", bufferCount, minBufferSlots); + return BAD_VALUE; + } + + // here we're guaranteed that the client doesn't have dequeued buffers + // and will release all of its buffer references. + freeAllBuffersLocked(); + mBufferCount = bufferCount; + mClientBufferCount = bufferCount; + mBufferHasBeenQueued = false; + mQueue.clear(); + mDequeueCondition.broadcast(); + listener = mConsumerListener; + } // scope for lock + + if (listener != NULL) { + listener->onBuffersReleased(); + } + + return OK; +} + +int BufferQueue::query(int what, int* outValue) +{ + ATRACE_CALL(); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("query: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + + int value; + switch (what) { + case NATIVE_WINDOW_WIDTH: + value = mDefaultWidth; + break; + case NATIVE_WINDOW_HEIGHT: + value = mDefaultHeight; + break; + case NATIVE_WINDOW_FORMAT: + value = mPixelFormat; + break; + case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS: + value = mSynchronousMode ? + (mMinUndequeuedBuffers-1) : mMinUndequeuedBuffers; + break; + case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND: + value = (mQueue.size() >= 2); + break; + default: + return BAD_VALUE; + } + outValue[0] = value; + return NO_ERROR; +} + +status_t BufferQueue::requestBuffer(int slot, sp* buf) { + ATRACE_CALL(); + ST_LOGV("requestBuffer: slot=%d", slot); + Mutex::Autolock lock(mMutex); + if (mAbandoned) { + ST_LOGE("requestBuffer: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + if (slot < 0 || mBufferCount <= slot) { + ST_LOGE("requestBuffer: slot index out of range [0, %d]: %d", + mBufferCount, slot); + return BAD_VALUE; + } + mSlots[slot].mRequestBufferCalled = true; + *buf = mSlots[slot].mGraphicBuffer; + return NO_ERROR; +} + +status_t BufferQueue::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h, + uint32_t format, uint32_t usage) { + ATRACE_CALL(); + ST_LOGV("dequeueBuffer: w=%d h=%d fmt=%#x usage=%#x", w, h, format, usage); + + if ((w && !h) || (!w && h)) { + ST_LOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h); + return BAD_VALUE; + } + + status_t returnFlags(OK); + EGLDisplay dpy = EGL_NO_DISPLAY; + EGLSyncKHR fence = EGL_NO_SYNC_KHR; + + { // Scope for the lock + Mutex::Autolock lock(mMutex); + + if (format == 0) { + format = mDefaultBufferFormat; + } + // turn on usage bits the consumer requested + usage |= mConsumerUsageBits; + + int found = -1; + int foundSync = -1; + int dequeuedCount = 0; + bool tryAgain = true; + while (tryAgain) { + if (mAbandoned) { + ST_LOGE("dequeueBuffer: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + + // We need to wait for the FIFO to drain if the number of buffer + // needs to change. + // + // The condition "number of buffers needs to change" is true if + // - the client doesn't care about how many buffers there are + // - AND the actual number of buffer is different from what was + // set in the last setBufferCountServer() + // - OR - + // setBufferCountServer() was set to a value incompatible with + // the synchronization mode (for instance because the sync mode + // changed since) + // + // As long as this condition is true AND the FIFO is not empty, we + // wait on mDequeueCondition. + + const int minBufferCountNeeded = mSynchronousMode ? + mMinSyncBufferSlots : mMinAsyncBufferSlots; + + const bool numberOfBuffersNeedsToChange = !mClientBufferCount && + ((mServerBufferCount != mBufferCount) || + (mServerBufferCount < minBufferCountNeeded)); + + if (!mQueue.isEmpty() && numberOfBuffersNeedsToChange) { + // wait for the FIFO to drain + mDequeueCondition.wait(mMutex); + // NOTE: we continue here because we need to reevaluate our + // whole state (eg: we could be abandoned or disconnected) + continue; + } + + if (numberOfBuffersNeedsToChange) { + // here we're guaranteed that mQueue is empty + freeAllBuffersLocked(); + mBufferCount = mServerBufferCount; + if (mBufferCount < minBufferCountNeeded) + mBufferCount = minBufferCountNeeded; + mBufferHasBeenQueued = false; + returnFlags |= ISurfaceTexture::RELEASE_ALL_BUFFERS; + } + + // look for a free buffer to give to the client + found = INVALID_BUFFER_SLOT; + foundSync = INVALID_BUFFER_SLOT; + dequeuedCount = 0; + for (int i = 0; i < mBufferCount; i++) { + const int state = mSlots[i].mBufferState; + if (state == BufferSlot::DEQUEUED) { + dequeuedCount++; + } + + // this logic used to be if (FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER) + // but dequeuing the current buffer is disabled. + if (false) { + // This functionality has been temporarily removed so + // BufferQueue and SurfaceTexture can be refactored into + // separate objects + } else { + if (state == BufferSlot::FREE) { + /* We return the oldest of the free buffers to avoid + * stalling the producer if possible. This is because + * the consumer may still have pending reads of the + * buffers in flight. + */ + bool isOlder = mSlots[i].mFrameNumber < + mSlots[found].mFrameNumber; + if (found < 0 || isOlder) { + foundSync = i; + found = i; + } + } + } + } + + // clients are not allowed to dequeue more than one buffer + // if they didn't set a buffer count. + if (!mClientBufferCount && dequeuedCount) { + ST_LOGE("dequeueBuffer: can't dequeue multiple buffers without " + "setting the buffer count"); + return -EINVAL; + } + + // See whether a buffer has been queued since the last + // setBufferCount so we know whether to perform the + // mMinUndequeuedBuffers check below. + if (mBufferHasBeenQueued) { + // make sure the client is not trying to dequeue more buffers + // than allowed. + const int avail = mBufferCount - (dequeuedCount+1); + if (avail < (mMinUndequeuedBuffers-int(mSynchronousMode))) { + ST_LOGE("dequeueBuffer: mMinUndequeuedBuffers=%d exceeded " + "(dequeued=%d)", + mMinUndequeuedBuffers-int(mSynchronousMode), + dequeuedCount); + return -EBUSY; + } + } + + // if no buffer is found, wait for a buffer to be released + tryAgain = found == INVALID_BUFFER_SLOT; + if (tryAgain) { + mDequeueCondition.wait(mMutex); + } + } + + + if (found == INVALID_BUFFER_SLOT) { + // This should not happen. + ST_LOGE("dequeueBuffer: no available buffer slots"); + return -EBUSY; + } + + const int buf = found; + *outBuf = found; + + ATRACE_BUFFER_INDEX(buf); + + const bool useDefaultSize = !w && !h; + if (useDefaultSize) { + // use the default size + w = mDefaultWidth; + h = mDefaultHeight; + } + + const bool updateFormat = (format != 0); + if (!updateFormat) { + // keep the current (or default) format + format = mPixelFormat; + } + + // buffer is now in DEQUEUED (but can also be current at the same time, + // if we're in synchronous mode) + mSlots[buf].mBufferState = BufferSlot::DEQUEUED; + + const sp& buffer(mSlots[buf].mGraphicBuffer); + if ((buffer == NULL) || + (uint32_t(buffer->width) != w) || + (uint32_t(buffer->height) != h) || + (uint32_t(buffer->format) != format) || + ((uint32_t(buffer->usage) & usage) != usage)) + { + status_t error; + sp graphicBuffer = new GraphicBuffer(w, h, format, usage); + if (graphicBuffer == 0) { + ST_LOGE("dequeueBuffer: SurfaceComposer::createGraphicBuffer " + "failed"); + return error; + } + if (updateFormat) { + mPixelFormat = format; + } + + mSlots[buf].mAcquireCalled = false; + mSlots[buf].mGraphicBuffer = graphicBuffer; + mSlots[buf].mRequestBufferCalled = false; + mSlots[buf].mFence = EGL_NO_SYNC_KHR; + mSlots[buf].mEglDisplay = EGL_NO_DISPLAY; + + returnFlags |= ISurfaceTexture::BUFFER_NEEDS_REALLOCATION; + } + + dpy = mSlots[buf].mEglDisplay; + fence = mSlots[buf].mFence; + mSlots[buf].mFence = EGL_NO_SYNC_KHR; + } // end lock scope + + if (fence != EGL_NO_SYNC_KHR) { + EGLint result = eglClientWaitSyncKHR(dpy, fence, 0, 1000000000); + // If something goes wrong, log the error, but return the buffer without + // synchronizing access to it. It's too late at this point to abort the + // dequeue operation. + if (result == EGL_FALSE) { + ST_LOGE("dequeueBuffer: error waiting for fence: %#x", eglGetError()); + } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { + ST_LOGE("dequeueBuffer: timeout waiting for fence"); + } + eglDestroySyncKHR(dpy, fence); + } + + ST_LOGV("dequeueBuffer: returning slot=%d buf=%p flags=%#x", *outBuf, + mSlots[*outBuf].mGraphicBuffer->handle, returnFlags); + + return returnFlags; +} + +status_t BufferQueue::setSynchronousMode(bool enabled) { + ATRACE_CALL(); + ST_LOGV("setSynchronousMode: enabled=%d", enabled); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("setSynchronousMode: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + + status_t err = OK; + if (!mAllowSynchronousMode && enabled) + return err; + + if (!enabled) { + // going to asynchronous mode, drain the queue + err = drainQueueLocked(); + if (err != NO_ERROR) + return err; + } + + if (mSynchronousMode != enabled) { + // - if we're going to asynchronous mode, the queue is guaranteed to be + // empty here + // - if the client set the number of buffers, we're guaranteed that + // we have at least 3 (because we don't allow less) + mSynchronousMode = enabled; + mDequeueCondition.broadcast(); + } + return err; +} + +status_t BufferQueue::queueBuffer(int buf, + const QueueBufferInput& input, QueueBufferOutput* output) { + ATRACE_CALL(); + ATRACE_BUFFER_INDEX(buf); + + Rect crop; + uint32_t transform; + int scalingMode; + int64_t timestamp; + + input.deflate(×tamp, &crop, &scalingMode, &transform); + + ST_LOGV("queueBuffer: slot=%d time=%#llx crop=[%d,%d,%d,%d] tr=%#x " + "scale=%s", + buf, timestamp, crop.left, crop.top, crop.right, crop.bottom, + transform, scalingModeName(scalingMode)); + + sp listener; + + { // scope for the lock + Mutex::Autolock lock(mMutex); + if (mAbandoned) { + ST_LOGE("queueBuffer: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + if (buf < 0 || buf >= mBufferCount) { + ST_LOGE("queueBuffer: slot index out of range [0, %d]: %d", + mBufferCount, buf); + return -EINVAL; + } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) { + ST_LOGE("queueBuffer: slot %d is not owned by the client " + "(state=%d)", buf, mSlots[buf].mBufferState); + return -EINVAL; + } else if (!mSlots[buf].mRequestBufferCalled) { + ST_LOGE("queueBuffer: slot %d was enqueued without requesting a " + "buffer", buf); + return -EINVAL; + } + + const sp& graphicBuffer(mSlots[buf].mGraphicBuffer); + Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight()); + Rect croppedCrop; + crop.intersect(bufferRect, &croppedCrop); + if (croppedCrop != crop) { + ST_LOGE("queueBuffer: crop rect is not contained within the " + "buffer in slot %d", buf); + return -EINVAL; + } + + if (mSynchronousMode) { + // In synchronous mode we queue all buffers in a FIFO. + mQueue.push_back(buf); + + // Synchronous mode always signals that an additional frame should + // be consumed. + listener = mConsumerListener; + } else { + // In asynchronous mode we only keep the most recent buffer. + if (mQueue.empty()) { + mQueue.push_back(buf); + + // Asynchronous mode only signals that a frame should be + // consumed if no previous frame was pending. If a frame were + // pending then the consumer would have already been notified. + listener = mConsumerListener; + } else { + Fifo::iterator front(mQueue.begin()); + // buffer currently queued is freed + mSlots[*front].mBufferState = BufferSlot::FREE; + // and we record the new buffer index in the queued list + *front = buf; + } + } + + mSlots[buf].mTimestamp = timestamp; + mSlots[buf].mCrop = crop; + mSlots[buf].mTransform = transform; + + switch (scalingMode) { + case NATIVE_WINDOW_SCALING_MODE_FREEZE: + case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: + case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: + break; + default: + ST_LOGE("unknown scaling mode: %d (ignoring)", scalingMode); + scalingMode = mSlots[buf].mScalingMode; + break; + } + + mSlots[buf].mBufferState = BufferSlot::QUEUED; + mSlots[buf].mScalingMode = scalingMode; + mFrameCounter++; + mSlots[buf].mFrameNumber = mFrameCounter; + + mBufferHasBeenQueued = true; + mDequeueCondition.broadcast(); + + output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint, + mQueue.size()); + + ATRACE_INT(mConsumerName.string(), mQueue.size()); + } // scope for the lock + + // call back without lock held + if (listener != 0) { + listener->onFrameAvailable(); + } + return OK; +} + +void BufferQueue::cancelBuffer(int buf) { + ATRACE_CALL(); + ST_LOGV("cancelBuffer: slot=%d", buf); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGW("cancelBuffer: BufferQueue has been abandoned!"); + return; + } + + if (buf < 0 || buf >= mBufferCount) { + ST_LOGE("cancelBuffer: slot index out of range [0, %d]: %d", + mBufferCount, buf); + return; + } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) { + ST_LOGE("cancelBuffer: slot %d is not owned by the client (state=%d)", + buf, mSlots[buf].mBufferState); + return; + } + mSlots[buf].mBufferState = BufferSlot::FREE; + mSlots[buf].mFrameNumber = 0; + mDequeueCondition.broadcast(); +} + +status_t BufferQueue::connect(int api, QueueBufferOutput* output) { + ATRACE_CALL(); + ST_LOGV("connect: api=%d", api); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("connect: BufferQueue has been abandoned!"); + return NO_INIT; + } + + if (mConsumerListener == NULL) { + ST_LOGE("connect: BufferQueue has no consumer!"); + return NO_INIT; + } + + int err = NO_ERROR; + switch (api) { + case NATIVE_WINDOW_API_EGL: + case NATIVE_WINDOW_API_CPU: + case NATIVE_WINDOW_API_MEDIA: + case NATIVE_WINDOW_API_CAMERA: + if (mConnectedApi != NO_CONNECTED_API) { + ST_LOGE("connect: already connected (cur=%d, req=%d)", + mConnectedApi, api); + err = -EINVAL; + } else { + mConnectedApi = api; + output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint, + mQueue.size()); + } + break; + default: + err = -EINVAL; + break; + } + + mBufferHasBeenQueued = false; + + return err; +} + +status_t BufferQueue::disconnect(int api) { + ATRACE_CALL(); + ST_LOGV("disconnect: api=%d", api); + + int err = NO_ERROR; + sp listener; + + { // Scope for the lock + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + // it is not really an error to disconnect after the surface + // has been abandoned, it should just be a no-op. + return NO_ERROR; + } + + switch (api) { + case NATIVE_WINDOW_API_EGL: + case NATIVE_WINDOW_API_CPU: + case NATIVE_WINDOW_API_MEDIA: + case NATIVE_WINDOW_API_CAMERA: + if (mConnectedApi == api) { + drainQueueAndFreeBuffersLocked(); + mConnectedApi = NO_CONNECTED_API; + mDequeueCondition.broadcast(); + listener = mConsumerListener; + } else { + ST_LOGE("disconnect: connected to another api (cur=%d, req=%d)", + mConnectedApi, api); + err = -EINVAL; + } + break; + default: + ST_LOGE("disconnect: unknown API %d", api); + err = -EINVAL; + break; + } + } + + if (listener != NULL) { + listener->onBuffersReleased(); + } + + return err; +} + +void BufferQueue::dump(String8& result) const +{ + char buffer[1024]; + BufferQueue::dump(result, "", buffer, 1024); +} + +void BufferQueue::dump(String8& result, const char* prefix, + char* buffer, size_t SIZE) const +{ + Mutex::Autolock _l(mMutex); + + String8 fifo; + int fifoSize = 0; + Fifo::const_iterator i(mQueue.begin()); + while (i != mQueue.end()) { + snprintf(buffer, SIZE, "%02d ", *i++); + fifoSize++; + fifo.append(buffer); + } + + snprintf(buffer, SIZE, + "%s-BufferQueue mBufferCount=%d, mSynchronousMode=%d, default-size=[%dx%d], " + "mPixelFormat=%d, FIFO(%d)={%s}\n", + prefix, mBufferCount, mSynchronousMode, mDefaultWidth, + mDefaultHeight, mPixelFormat, fifoSize, fifo.string()); + result.append(buffer); + + + struct { + const char * operator()(int state) const { + switch (state) { + case BufferSlot::DEQUEUED: return "DEQUEUED"; + case BufferSlot::QUEUED: return "QUEUED"; + case BufferSlot::FREE: return "FREE"; + case BufferSlot::ACQUIRED: return "ACQUIRED"; + default: return "Unknown"; + } + } + } stateName; + + for (int i=0 ; i":" ", i, + stateName(slot.mBufferState), + slot.mCrop.left, slot.mCrop.top, slot.mCrop.right, + slot.mCrop.bottom, slot.mTransform, slot.mTimestamp, + scalingModeName(slot.mScalingMode) + ); + result.append(buffer); + + const sp& buf(slot.mGraphicBuffer); + if (buf != NULL) { + snprintf(buffer, SIZE, + ", %p [%4ux%4u:%4u,%3X]", + buf->handle, buf->width, buf->height, buf->stride, + buf->format); + result.append(buffer); + } + result.append("\n"); + } +} + +void BufferQueue::freeBufferLocked(int i) { + mSlots[i].mGraphicBuffer = 0; + if (mSlots[i].mBufferState == BufferSlot::ACQUIRED) { + mSlots[i].mNeedsCleanupOnRelease = true; + } + mSlots[i].mBufferState = BufferSlot::FREE; + mSlots[i].mFrameNumber = 0; + mSlots[i].mAcquireCalled = false; + + // destroy fence as BufferQueue now takes ownership + if (mSlots[i].mFence != EGL_NO_SYNC_KHR) { + eglDestroySyncKHR(mSlots[i].mEglDisplay, mSlots[i].mFence); + mSlots[i].mFence = EGL_NO_SYNC_KHR; + } +} + +void BufferQueue::freeAllBuffersLocked() { + ALOGW_IF(!mQueue.isEmpty(), + "freeAllBuffersLocked called but mQueue is not empty"); + mQueue.clear(); + mBufferHasBeenQueued = false; + for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { + freeBufferLocked(i); + } +} + +status_t BufferQueue::acquireBuffer(BufferItem *buffer) { + ATRACE_CALL(); + Mutex::Autolock _l(mMutex); + // check if queue is empty + // In asynchronous mode the list is guaranteed to be one buffer + // deep, while in synchronous mode we use the oldest buffer. + if (!mQueue.empty()) { + Fifo::iterator front(mQueue.begin()); + int buf = *front; + + ATRACE_BUFFER_INDEX(buf); + + if (mSlots[buf].mAcquireCalled) { + buffer->mGraphicBuffer = NULL; + } else { + buffer->mGraphicBuffer = mSlots[buf].mGraphicBuffer; + } + buffer->mCrop = mSlots[buf].mCrop; + buffer->mTransform = mSlots[buf].mTransform; + buffer->mScalingMode = mSlots[buf].mScalingMode; + buffer->mFrameNumber = mSlots[buf].mFrameNumber; + buffer->mTimestamp = mSlots[buf].mTimestamp; + buffer->mBuf = buf; + mSlots[buf].mAcquireCalled = true; + + mSlots[buf].mBufferState = BufferSlot::ACQUIRED; + mQueue.erase(front); + mDequeueCondition.broadcast(); + + ATRACE_INT(mConsumerName.string(), mQueue.size()); + } else { + return NO_BUFFER_AVAILABLE; + } + + return OK; +} + +status_t BufferQueue::releaseBuffer(int buf, EGLDisplay display, + EGLSyncKHR fence) { + ATRACE_CALL(); + ATRACE_BUFFER_INDEX(buf); + + Mutex::Autolock _l(mMutex); + + if (buf == INVALID_BUFFER_SLOT) { + return -EINVAL; + } + + mSlots[buf].mEglDisplay = display; + mSlots[buf].mFence = fence; + + // The buffer can now only be released if its in the acquired state + if (mSlots[buf].mBufferState == BufferSlot::ACQUIRED) { + mSlots[buf].mBufferState = BufferSlot::FREE; + } else if (mSlots[buf].mNeedsCleanupOnRelease) { + ST_LOGV("releasing a stale buf %d its state was %d", buf, mSlots[buf].mBufferState); + mSlots[buf].mNeedsCleanupOnRelease = false; + return STALE_BUFFER_SLOT; + } else { + ST_LOGE("attempted to release buf %d but its state was %d", buf, mSlots[buf].mBufferState); + return -EINVAL; + } + + mDequeueCondition.broadcast(); + return OK; +} + +status_t BufferQueue::consumerConnect(const sp& consumerListener) { + ST_LOGV("consumerConnect"); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("consumerConnect: BufferQueue has been abandoned!"); + return NO_INIT; + } + + mConsumerListener = consumerListener; + + return OK; +} + +status_t BufferQueue::consumerDisconnect() { + ST_LOGV("consumerDisconnect"); + Mutex::Autolock lock(mMutex); + + if (mConsumerListener == NULL) { + ST_LOGE("consumerDisconnect: No consumer is connected!"); + return -EINVAL; + } + + mAbandoned = true; + mConsumerListener = NULL; + mQueue.clear(); + freeAllBuffersLocked(); + mDequeueCondition.broadcast(); + return OK; +} + +status_t BufferQueue::getReleasedBuffers(uint32_t* slotMask) { + ST_LOGV("getReleasedBuffers"); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("getReleasedBuffers: BufferQueue has been abandoned!"); + return NO_INIT; + } + + uint32_t mask = 0; + for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { + if (!mSlots[i].mAcquireCalled) { + mask |= 1 << i; + } + } + *slotMask = mask; + + ST_LOGV("getReleasedBuffers: returning mask %#x", mask); + return NO_ERROR; +} + +status_t BufferQueue::setDefaultBufferSize(uint32_t w, uint32_t h) +{ + ST_LOGV("setDefaultBufferSize: w=%d, h=%d", w, h); + if (!w || !h) { + ST_LOGE("setDefaultBufferSize: dimensions cannot be 0 (w=%d, h=%d)", + w, h); + return BAD_VALUE; + } + + Mutex::Autolock lock(mMutex); + mDefaultWidth = w; + mDefaultHeight = h; + return OK; +} + +status_t BufferQueue::setBufferCountServer(int bufferCount) { + ATRACE_CALL(); + Mutex::Autolock lock(mMutex); + return setBufferCountServerLocked(bufferCount); +} + +void BufferQueue::freeAllBuffersExceptHeadLocked() { + int head = -1; + if (!mQueue.empty()) { + Fifo::iterator front(mQueue.begin()); + head = *front; + } + mBufferHasBeenQueued = false; + for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { + if (i != head) { + freeBufferLocked(i); + } + } +} + +status_t BufferQueue::drainQueueLocked() { + while (mSynchronousMode && !mQueue.isEmpty()) { + mDequeueCondition.wait(mMutex); + if (mAbandoned) { + ST_LOGE("drainQueueLocked: BufferQueue has been abandoned!"); + return NO_INIT; + } + if (mConnectedApi == NO_CONNECTED_API) { + ST_LOGE("drainQueueLocked: BufferQueue is not connected!"); + return NO_INIT; + } + } + return NO_ERROR; +} + +status_t BufferQueue::drainQueueAndFreeBuffersLocked() { + status_t err = drainQueueLocked(); + if (err == NO_ERROR) { + if (mSynchronousMode) { + freeAllBuffersLocked(); + } else { + freeAllBuffersExceptHeadLocked(); + } + } + return err; +} + +BufferQueue::ProxyConsumerListener::ProxyConsumerListener( + const wp& consumerListener): + mConsumerListener(consumerListener) {} + +BufferQueue::ProxyConsumerListener::~ProxyConsumerListener() {} + +void BufferQueue::ProxyConsumerListener::onFrameAvailable() { + sp listener(mConsumerListener.promote()); + if (listener != NULL) { + listener->onFrameAvailable(); + } +} + +void BufferQueue::ProxyConsumerListener::onBuffersReleased() { + sp listener(mConsumerListener.promote()); + if (listener != NULL) { + listener->onBuffersReleased(); + } +} + +}; // namespace android + +#endif diff --git a/src/imports/nativemedia/BufferQueue.h b/src/imports/nativemedia/BufferQueue.h new file mode 100644 index 0000000..1c80d0c --- /dev/null +++ b/src/imports/nativemedia/BufferQueue.h @@ -0,0 +1,490 @@ +/* + * 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_BUFFERQUEUE_H +#define ANDROID_GUI_BUFFERQUEUE_H + +#include +#include + +#include +#include + +#include + +#include +#include +#include + +namespace android { +// ---------------------------------------------------------------------------- + +class BufferQueue : public BnSurfaceTexture { +public: + enum { MIN_UNDEQUEUED_BUFFERS = 2 }; + enum { NUM_BUFFER_SLOTS = 32 }; + enum { NO_CONNECTED_API = 0 }; + enum { INVALID_BUFFER_SLOT = -1 }; + enum { STALE_BUFFER_SLOT = 1, NO_BUFFER_AVAILABLE }; + + // ConsumerListener is the interface through which the BufferQueue notifies + // the consumer of events that the consumer may wish to react to. Because + // the consumer will generally have a mutex that is locked during calls from + // teh consumer to the BufferQueue, these calls from the BufferQueue to the + // consumer *MUST* be called only when the BufferQueue mutex is NOT locked. + struct ConsumerListener : public virtual RefBase { + // onFrameAvailable is called from queueBuffer each time an additional + // frame becomes available for consumption. This means that frames that + // are queued while in asynchronous mode only trigger the callback if no + // previous frames are pending. Frames queued while in synchronous mode + // always trigger the callback. + // + // This is called without any lock held and can be called concurrently + // by multiple threads. + virtual void onFrameAvailable() = 0; + + // onBuffersReleased is called to notify the buffer consumer that the + // BufferQueue has released its references to one or more GraphicBuffers + // contained in its slots. The buffer consumer should then call + // BufferQueue::getReleasedBuffers to retrieve the list of buffers + // + // This is called without any lock held and can be called concurrently + // by multiple threads. + virtual void onBuffersReleased() = 0; + }; + + // ProxyConsumerListener is a ConsumerListener implementation that keeps a weak + // reference to the actual consumer object. It forwards all calls to that + // consumer object so long as it exists. + // + // This class exists to avoid having a circular reference between the + // BufferQueue object and the consumer object. The reason this can't be a weak + // reference in the BufferQueue class is because we're planning to expose the + // consumer side of a BufferQueue as a binder interface, which doesn't support + // weak references. + class ProxyConsumerListener : public BufferQueue::ConsumerListener { + public: + + ProxyConsumerListener(const wp& consumerListener); + virtual ~ProxyConsumerListener(); + virtual void onFrameAvailable(); + virtual void onBuffersReleased(); + + private: + + // mConsumerListener is a weak reference to the ConsumerListener. This is + // the raison d'etre of ProxyConsumerListener. + wp mConsumerListener; + }; + + + // BufferQueue manages a pool of gralloc memory slots to be used + // by producers and consumers. + // allowSynchronousMode specifies whether or not synchronous mode can be + // enabled. + // bufferCount sets the minimum number of undequeued buffers for this queue + BufferQueue( bool allowSynchronousMode = true, int bufferCount = MIN_UNDEQUEUED_BUFFERS); + virtual ~BufferQueue(); + + virtual int query(int what, int* value); + + // setBufferCount updates the number of available buffer slots. After + // calling this all buffer slots are both unallocated and owned by the + // BufferQueue object (i.e. they are not owned by the client). + virtual status_t setBufferCount(int bufferCount); + + virtual status_t requestBuffer(int slot, sp* buf); + + // dequeueBuffer gets the next buffer slot index for the client to use. If a + // buffer slot is available then that slot index is written to the location + // pointed to by the buf argument and a status of OK is returned. If no + // slot is available then a status of -EBUSY is returned and buf is + // unmodified. + // The width and height parameters must be no greater than the minimum of + // GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv). + // An error due to invalid dimensions might not be reported until + // updateTexImage() is called. + virtual status_t dequeueBuffer(int *buf, uint32_t width, uint32_t height, + uint32_t format, uint32_t usage); + + // queueBuffer returns a filled buffer to the BufferQueue. In addition, a + // timestamp must be provided for the buffer. The timestamp is in + // nanoseconds, and must be monotonically increasing. Its other semantics + // (zero point, etc) are client-dependent and should be documented by the + // client. + virtual status_t queueBuffer(int buf, + const QueueBufferInput& input, QueueBufferOutput* output); + + virtual void cancelBuffer(int buf); + + // setSynchronousMode set whether dequeueBuffer is synchronous or + // asynchronous. In synchronous mode, dequeueBuffer blocks until + // a buffer is available, the currently bound buffer can be dequeued and + // queued buffers will be retired in order. + // The default mode is asynchronous. + virtual status_t setSynchronousMode(bool enabled); + + // connect attempts to connect a producer client API to the BufferQueue. + // This must be called before any other ISurfaceTexture methods are called + // except for getAllocator. + // + // This method will fail if the connect was previously called on the + // BufferQueue and no corresponding disconnect call was made. + virtual status_t connect(int api, QueueBufferOutput* output); + + // disconnect attempts to disconnect a producer client API from the + // BufferQueue. Calling this method will cause any subsequent calls to other + // ISurfaceTexture methods to fail except for getAllocator and connect. + // Successfully calling connect after this will allow the other methods to + // succeed again. + // + // This method will fail if the the BufferQueue is not currently + // connected to the specified client API. + virtual status_t disconnect(int api); + + // dump our state in a String + virtual void dump(String8& result) const; + virtual void dump(String8& result, const char* prefix, char* buffer, size_t SIZE) const; + + // public facing structure for BufferSlot + struct BufferItem { + + BufferItem() + : + mTransform(0), + mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), + mTimestamp(0), + mFrameNumber(0), + mBuf(INVALID_BUFFER_SLOT) { + mCrop.makeInvalid(); + } + // mGraphicBuffer points to the buffer allocated for this slot or is NULL + // if no buffer has been allocated. + sp mGraphicBuffer; + + // mCrop is the current crop rectangle for this buffer slot. + Rect mCrop; + + // mTransform is the current transform flags for this buffer slot. + uint32_t mTransform; + + // mScalingMode is the current scaling mode for this buffer slot. + uint32_t mScalingMode; + + // mTimestamp is the current timestamp for this buffer slot. This gets + // to set by queueBuffer each time this slot is queued. + int64_t mTimestamp; + + // mFrameNumber is the number of the queued frame for this slot. + uint64_t mFrameNumber; + + // mBuf is the slot index of this buffer + int mBuf; + }; + + // The following public functions is the consumer facing interface + + // acquireBuffer attempts to acquire ownership of the next pending buffer in + // the BufferQueue. If no buffer is pending then it returns -EINVAL. If a + // buffer is successfully acquired, the information about the buffer is + // returned in BufferItem. If the buffer returned had previously been + // acquired then the BufferItem::mGraphicBuffer field of buffer is set to + // NULL and it is assumed that the consumer still holds a reference to the + // buffer. + status_t acquireBuffer(BufferItem *buffer); + + // releaseBuffer releases a buffer slot from the consumer back to the + // BufferQueue pending a fence sync. + // + // 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); + + // consumerConnect connects a consumer to the BufferQueue. Only one + // consumer may be connected, and when that consumer disconnects the + // BufferQueue is placed into the "abandoned" state, causing most + // interactions with the BufferQueue by the producer to fail. + status_t consumerConnect(const sp& consumer); + + // consumerDisconnect disconnects a consumer from the BufferQueue. All + // buffers will be freed and the BufferQueue is placed in the "abandoned" + // state, causing most interactions with the BufferQueue by the producer to + // fail. + status_t consumerDisconnect(); + + // getReleasedBuffers sets the value pointed to by slotMask to a bit mask + // indicating which buffer slots the have been released by the BufferQueue + // but have not yet been released by the consumer. + status_t getReleasedBuffers(uint32_t* slotMask); + + // setDefaultBufferSize is used to set the size of buffers returned by + // requestBuffers when a with and height of zero is requested. + status_t setDefaultBufferSize(uint32_t w, uint32_t h); + + // setBufferCountServer set the buffer count. If the client has requested + // a buffer count using setBufferCount, the server-buffer count will + // take effect once the client sets the count back to zero. + status_t setBufferCountServer(int bufferCount); + + // isSynchronousMode returns whether the SurfaceTexture is currently in + // synchronous mode. + bool isSynchronousMode() const; + + // setConsumerName sets the name used in logging + void setConsumerName(const String8& name); + + // setDefaultBufferFormat allows the BufferQueue to create + // GraphicBuffers of a defaultFormat if no format is specified + // in dequeueBuffer + status_t setDefaultBufferFormat(uint32_t defaultFormat); + + // setConsumerUsageBits will turn on additional usage bits for dequeueBuffer + status_t setConsumerUsageBits(uint32_t usage); + + // setTransformHint bakes in rotation to buffers so overlays can be used + status_t setTransformHint(uint32_t hint); + +private: + // freeBufferLocked frees the resources (both GraphicBuffer and EGLImage) + // for the given slot. + void freeBufferLocked(int index); + + // freeAllBuffersLocked frees the resources (both GraphicBuffer and + // EGLImage) for all slots. + void freeAllBuffersLocked(); + + // freeAllBuffersExceptHeadLocked frees the resources (both GraphicBuffer + // and EGLImage) for all slots except the head of mQueue + void freeAllBuffersExceptHeadLocked(); + + // drainQueueLocked drains the buffer queue if we're in synchronous mode + // returns immediately otherwise. It returns NO_INIT if the BufferQueue + // became abandoned or disconnected during this call. + status_t drainQueueLocked(); + + // drainQueueAndFreeBuffersLocked drains the buffer queue if we're in + // synchronous mode and free all buffers. In asynchronous mode, all buffers + // are freed except the current buffer. + status_t drainQueueAndFreeBuffersLocked(); + + status_t setBufferCountServerLocked(int bufferCount); + + struct BufferSlot { + + BufferSlot() + : mEglDisplay(EGL_NO_DISPLAY), + mBufferState(BufferSlot::FREE), + mRequestBufferCalled(false), + mTransform(0), + mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), + mTimestamp(0), + mFrameNumber(0), + mFence(EGL_NO_SYNC_KHR), + mAcquireCalled(false), + mNeedsCleanupOnRelease(false) { + mCrop.makeInvalid(); + } + + // mGraphicBuffer points to the buffer allocated for this slot or is NULL + // if no buffer has been allocated. + sp mGraphicBuffer; + + // mEglDisplay is the EGLDisplay used to create mEglImage. + EGLDisplay mEglDisplay; + + // BufferState represents the different states in which a buffer slot + // can be. + enum BufferState { + // FREE indicates that the buffer is not currently being used and + // will not be used in the future until it gets dequeued and + // subsequently queued by the client. + // aka "owned by BufferQueue, ready to be dequeued" + FREE = 0, + + // DEQUEUED indicates that the buffer has been dequeued by the + // client, but has not yet been queued or canceled. The buffer is + // considered 'owned' by the client, and the server should not use + // it for anything. + // + // Note that when in synchronous-mode (mSynchronousMode == true), + // the buffer that's currently attached to the texture may be + // dequeued by the client. That means that the current buffer can + // be in either the DEQUEUED or QUEUED state. In asynchronous mode, + // however, the current buffer is always in the QUEUED state. + // aka "owned by producer, ready to be queued" + DEQUEUED = 1, + + // QUEUED indicates that the buffer has been queued by the client, + // and has not since been made available for the client to dequeue. + // Attaching the buffer to the texture does NOT transition the + // buffer away from the QUEUED state. However, in Synchronous mode + // the current buffer may be dequeued by the client under some + // circumstances. See the note about the current buffer in the + // documentation for DEQUEUED. + // aka "owned by BufferQueue, ready to be acquired" + QUEUED = 2, + + // aka "owned by consumer, ready to be released" + ACQUIRED = 3 + }; + + // mBufferState is the current state of this buffer slot. + BufferState mBufferState; + + // mRequestBufferCalled is used for validating that the client did + // call requestBuffer() when told to do so. Technically this is not + // needed but useful for debugging and catching client bugs. + bool mRequestBufferCalled; + + // mCrop is the current crop rectangle for this buffer slot. + Rect mCrop; + + // mTransform is the current transform flags for this buffer slot. + uint32_t mTransform; + + // mScalingMode is the current scaling mode for this buffer slot. + uint32_t mScalingMode; + + // mTimestamp is the current timestamp for this buffer slot. This gets + // to set by queueBuffer each time this slot is queued. + int64_t mTimestamp; + + // mFrameNumber is the number of the queued frame for this slot. + uint64_t mFrameNumber; + + // mFence is the EGL sync object that must signal before the buffer + // associated with this buffer slot may be dequeued. It is initialized + // to EGL_NO_SYNC_KHR when the buffer is created and (optionally, based + // on a compile-time option) set to a new sync object in updateTexImage. + EGLSyncKHR mFence; + + // Indicates whether this buffer has been seen by a consumer yet + bool mAcquireCalled; + + // Indicates whether this buffer needs to be cleaned up by consumer + bool mNeedsCleanupOnRelease; + }; + + // mSlots is the array of buffer slots that must be mirrored on the client + // side. This allows buffer ownership to be transferred between the client + // and server without sending a GraphicBuffer over binder. The entire array + // is initialized to NULL at construction time, and buffers are allocated + // for a slot when requestBuffer is called with that slot's index. + BufferSlot mSlots[NUM_BUFFER_SLOTS]; + + // mDefaultWidth holds the default width of allocated buffers. It is used + // in requestBuffers() if a width and height of zero is specified. + uint32_t mDefaultWidth; + + // mDefaultHeight holds the default height of allocated buffers. It is used + // in requestBuffers() if a width and height of zero is specified. + uint32_t mDefaultHeight; + + // mPixelFormat holds the pixel format of allocated buffers. It is used + // in requestBuffers() if a format of zero is specified. + uint32_t mPixelFormat; + + // mMinUndequeuedBuffers is a constraint on the number of buffers + // not dequeued at any time + int mMinUndequeuedBuffers; + + // mMinAsyncBufferSlots is a constraint on the minimum mBufferCount + // when this BufferQueue is in asynchronous mode + int mMinAsyncBufferSlots; + + // mMinSyncBufferSlots is a constraint on the minimum mBufferCount + // when this BufferQueue is in synchronous mode + int mMinSyncBufferSlots; + + // mBufferCount is the number of buffer slots that the client and server + // must maintain. It defaults to MIN_ASYNC_BUFFER_SLOTS and can be changed + // by calling setBufferCount or setBufferCountServer + int mBufferCount; + + // mClientBufferCount is the number of buffer slots requested by the client. + // The default is zero, which means the client doesn't care how many buffers + // there is. + int mClientBufferCount; + + // mServerBufferCount buffer count requested by the server-side + int mServerBufferCount; + + // mGraphicBufferAlloc is the connection to SurfaceFlinger that is used to + // allocate new GraphicBuffer objects. + sp mGraphicBufferAlloc; + + // mConsumerListener is used to notify the connected consumer of + // asynchronous events that it may wish to react to. It is initially set + // to NULL and is written by consumerConnect and consumerDisconnect. + sp mConsumerListener; + + // mSynchronousMode whether we're in synchronous mode or not + bool mSynchronousMode; + + // mAllowSynchronousMode whether we allow synchronous mode or not + const bool mAllowSynchronousMode; + + // mConnectedApi indicates the API that is currently connected to this + // BufferQueue. It defaults to NO_CONNECTED_API (= 0), and gets updated + // by the connect and disconnect methods. + int mConnectedApi; + + // mDequeueCondition condition used for dequeueBuffer in synchronous mode + mutable Condition mDequeueCondition; + + // mQueue is a FIFO of queued buffers used in synchronous mode + typedef Vector Fifo; + Fifo mQueue; + + // mAbandoned indicates that the BufferQueue will no longer be used to + // consume images buffers pushed to it using the ISurfaceTexture interface. + // It is initialized to false, and set to true in the abandon method. A + // BufferQueue that has been abandoned will return the NO_INIT error from + // all ISurfaceTexture methods capable of returning an error. + bool mAbandoned; + + // mName is a string used to identify the BufferQueue in log messages. + // It is set by the setName method. + String8 mConsumerName; + + // mMutex is the mutex used to prevent concurrent access to the member + // variables of BufferQueue objects. It must be locked whenever the + // member variables are accessed. + mutable Mutex mMutex; + + // mFrameCounter is the free running counter, incremented for every buffer queued + // with the surface Texture. + uint64_t mFrameCounter; + + // mBufferHasBeenQueued is true once a buffer has been queued. It is reset + // by changing the buffer count. + bool mBufferHasBeenQueued; + + // mDefaultBufferFormat can be set so it will override + // the buffer format when it isn't specified in dequeueBuffer + uint32_t mDefaultBufferFormat; + + // mConsumerUsageBits contains flags the consumer wants for GraphicBuffers + uint32_t mConsumerUsageBits; + + // mTransformHint is used to optimize for screen rotations + uint32_t mTransformHint; +}; + +// ---------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_GUI_BUFFERQUEUE_H diff --git a/src/imports/nativemedia/SurfaceTexture.cpp b/src/imports/nativemedia/SurfaceTexture.cpp new file mode 100644 index 0000000..c973079 --- /dev/null +++ b/src/imports/nativemedia/SurfaceTexture.cpp @@ -0,0 +1,5 @@ +#if Q_ANDROID_VERSION_MAJOR > 4 || (Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR >= 1) +#include "SurfaceTexture_4_1.cpp" +#else +#include "SurfaceTexture_4_0.cpp" +#endif diff --git a/src/imports/nativemedia/SurfaceTexture.h b/src/imports/nativemedia/SurfaceTexture.h new file mode 100644 index 0000000..efc49c8 --- /dev/null +++ b/src/imports/nativemedia/SurfaceTexture.h @@ -0,0 +1,5 @@ +#if Q_ANDROID_VERSION_MAJOR > 4 || (Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR >= 1) +#include "SurfaceTexture_4_1.h" +#else +#include "SurfaceTexture_4_0.h" +#endif diff --git a/src/imports/nativemedia/SurfaceTexture_4_0.cpp b/src/imports/nativemedia/SurfaceTexture_4_0.cpp new file mode 100644 index 0000000..1ec08a7 --- /dev/null +++ b/src/imports/nativemedia/SurfaceTexture_4_0.cpp @@ -0,0 +1,1236 @@ +/* + * Copyright (C) 2010 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 "SurfaceTexture" +//#define LOG_NDEBUG 0 + +#define GL_GLEXT_PROTOTYPES +#define EGL_EGLEXT_PROTOTYPES + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include +#include + +#include + +// This compile option causes SurfaceTexture to return the buffer that is currently +// attached to the GL texture from dequeueBuffer when no other buffers are +// available. It requires the drivers (Gralloc, GL, OMX IL, and Camera) to do +// implicit cross-process synchronization to prevent the buffer from being +// written to before the buffer has (a) been detached from the GL texture and +// (b) all GL reads from the buffer have completed. +#ifdef ALLOW_DEQUEUE_CURRENT_BUFFER +#define FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER true +#warning "ALLOW_DEQUEUE_CURRENT_BUFFER enabled" +#else +#define FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER false +#endif + +// This compile option makes SurfaceTexture use the EGL_KHR_fence_sync extension +// to synchronize access to the buffers. It will cause dequeueBuffer to stall, +// waiting for the GL reads for the buffer being dequeued to complete before +// allowing the buffer to be dequeued. +#ifdef USE_FENCE_SYNC +#ifdef ALLOW_DEQUEUE_CURRENT_BUFFER +#error "USE_FENCE_SYNC and ALLOW_DEQUEUE_CURRENT_BUFFER are incompatible" +#endif +#endif + +// Macros for including the SurfaceTexture name in log messages +#if 0 +#define ST_LOGV(x, ...) LOGV("[%s] "x, mName.string(), ##__VA_ARGS__) +#define ST_LOGD(x, ...) LOGD("[%s] "x, mName.string(), ##__VA_ARGS__) +#define ST_LOGI(x, ...) LOGI("[%s] "x, mName.string(), ##__VA_ARGS__) +#define ST_LOGW(x, ...) LOGW("[%s] "x, mName.string(), ##__VA_ARGS__) +#define ST_LOGE(x, ...) LOGE("[%s] "x, mName.string(), ##__VA_ARGS__) +#else +#define ST_LOGV(x, ...) LOGV("[V/%s] "x, mName.string(), ##__VA_ARGS__) +#define ST_LOGD(x, ...) LOGD("[D/%s] "x, mName.string(), ##__VA_ARGS__) +#define ST_LOGI(x, ...) qDebug("[I/%s] "x, mName.string(), ##__VA_ARGS__) +#define ST_LOGW(x, ...) qDebug("[W/%s] "x, mName.string(), ##__VA_ARGS__) +#define ST_LOGE(x, ...) qDebug("[E/%s] "x, mName.string(), ##__VA_ARGS__) +#endif + +namespace android { + +// Transform matrices +static float mtxIdentity[16] = { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, +}; +static float mtxFlipH[16] = { + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 1, 0, 0, 1, +}; +static float mtxFlipV[16] = { + 1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, 1, 0, + 0, 1, 0, 1, +}; +static float mtxRot90[16] = { + 0, 1, 0, 0, + -1, 0, 0, 0, + 0, 0, 1, 0, + 1, 0, 0, 1, +}; +static float mtxRot180[16] = { + -1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, 1, 0, + 1, 1, 0, 1, +}; +static float mtxRot270[16] = { + 0, -1, 0, 0, + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, 1, 0, 1, +}; + +static void mtxMul(float out[16], const float a[16], const float b[16]); + +// Get an ID that's unique within this process. +static int32_t createProcessUniqueId() { + static volatile int32_t globalCounter = 0; + return android_atomic_inc(&globalCounter); +} + +SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode, + GLenum texTarget, bool useFenceSync) : + mDefaultWidth(1), + mDefaultHeight(1), + mPixelFormat(PIXEL_FORMAT_RGBA_8888), + mBufferCount(MIN_ASYNC_BUFFER_SLOTS), + mClientBufferCount(0), + mServerBufferCount(MIN_ASYNC_BUFFER_SLOTS), + mCurrentTexture(INVALID_BUFFER_SLOT), + mCurrentTransform(0), + mCurrentTimestamp(0), + mNextTransform(0), + mNextScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), + mTexName(tex), + mSynchronousMode(false), + mAllowSynchronousMode(allowSynchronousMode), + mConnectedApi(NO_CONNECTED_API), + mAbandoned(false), +#ifdef USE_FENCE_SYNC + mUseFenceSync(useFenceSync), +#else + mUseFenceSync(false), +#endif + mTexTarget(texTarget), + mFrameCounter(0) { + // Choose a name using the PID and a process-unique ID. + mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId()); + + ST_LOGV("SurfaceTexture"); +// sp composer(ComposerService::getComposerService()); +// mGraphicBufferAlloc = composer->createGraphicBufferAlloc(); + mNextCrop.makeInvalid(); + memcpy(mCurrentTransformMatrix, mtxIdentity, + sizeof(mCurrentTransformMatrix)); +} + +SurfaceTexture::~SurfaceTexture() { + ST_LOGV("~SurfaceTexture"); + freeAllBuffersLocked(); +} + +status_t SurfaceTexture::setBufferCountServerLocked(int bufferCount) { + if (bufferCount > NUM_BUFFER_SLOTS) + return BAD_VALUE; + + // special-case, nothing to do + if (bufferCount == mBufferCount) + return OK; + + if (!mClientBufferCount && + bufferCount >= mBufferCount) { + // easy, we just have more buffers + mBufferCount = bufferCount; + mServerBufferCount = bufferCount; + mDequeueCondition.signal(); + } else { + // we're here because we're either + // - reducing the number of available buffers + // - or there is a client-buffer-count in effect + + // less than 2 buffers is never allowed + if (bufferCount < 2) + return BAD_VALUE; + + // when there is non client-buffer-count in effect, the client is not + // allowed to dequeue more than one buffer at a time, + // so the next time they dequeue a buffer, we know that they don't + // own one. the actual resizing will happen during the next + // dequeueBuffer. + + mServerBufferCount = bufferCount; + } + return OK; +} + +status_t SurfaceTexture::setBufferCountServer(int bufferCount) { + Mutex::Autolock lock(mMutex); + return setBufferCountServerLocked(bufferCount); +} + +status_t SurfaceTexture::setBufferCount(int bufferCount) { + ST_LOGV("setBufferCount: count=%d", bufferCount); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("setBufferCount: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + if (bufferCount > NUM_BUFFER_SLOTS) { + ST_LOGE("setBufferCount: bufferCount larger than slots available"); + return BAD_VALUE; + } + + // Error out if the user has dequeued buffers + for (int i=0 ; i= minBufferSlots) ? + mServerBufferCount : minBufferSlots; + return setBufferCountServerLocked(bufferCount); + } + + if (bufferCount < minBufferSlots) { + ST_LOGE("setBufferCount: requested buffer count (%d) is less than " + "minimum (%d)", bufferCount, minBufferSlots); + return BAD_VALUE; + } + + // here we're guaranteed that the client doesn't have dequeued buffers + // and will release all of its buffer references. + freeAllBuffersLocked(); + mBufferCount = bufferCount; + mClientBufferCount = bufferCount; + mCurrentTexture = INVALID_BUFFER_SLOT; + mQueue.clear(); + mDequeueCondition.signal(); + return OK; +} + +status_t SurfaceTexture::setDefaultBufferSize(uint32_t w, uint32_t h) +{ + ST_LOGV("setDefaultBufferSize: w=%d, h=%d", w, h); + if (!w || !h) { + ST_LOGE("setDefaultBufferSize: dimensions cannot be 0 (w=%d, h=%d)", + w, h); + return BAD_VALUE; + } + + Mutex::Autolock lock(mMutex); + mDefaultWidth = w; + mDefaultHeight = h; + return OK; +} + +status_t SurfaceTexture::requestBuffer(int slot, sp* buf) { + ST_LOGV("requestBuffer: slot=%d", slot); + Mutex::Autolock lock(mMutex); + if (mAbandoned) { + ST_LOGE("requestBuffer: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + if (slot < 0 || mBufferCount <= slot) { + ST_LOGE("requestBuffer: slot index out of range [0, %d]: %d", + mBufferCount, slot); + return BAD_VALUE; + } + mSlots[slot].mRequestBufferCalled = true; + *buf = mSlots[slot].mGraphicBuffer; + return NO_ERROR; +} + +status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h, + uint32_t format, uint32_t usage) { + ST_LOGV("dequeueBuffer: w=%d h=%d fmt=%#x usage=%#x", w, h, format, usage); + + if ((w && !h) || (!w && h)) { + ST_LOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h); + return BAD_VALUE; + } + + status_t returnFlags(OK); + EGLDisplay dpy = EGL_NO_DISPLAY; + EGLSyncKHR fence = EGL_NO_SYNC_KHR; + + { // Scope for the lock + Mutex::Autolock lock(mMutex); + + int found = -1; + int foundSync = -1; + int dequeuedCount = 0; + bool tryAgain = true; + while (tryAgain) { + if (mAbandoned) { + ST_LOGE("dequeueBuffer: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + + // We need to wait for the FIFO to drain if the number of buffer + // needs to change. + // + // The condition "number of buffers needs to change" is true if + // - the client doesn't care about how many buffers there are + // - AND the actual number of buffer is different from what was + // set in the last setBufferCountServer() + // - OR - + // setBufferCountServer() was set to a value incompatible with + // the synchronization mode (for instance because the sync mode + // changed since) + // + // As long as this condition is true AND the FIFO is not empty, we + // wait on mDequeueCondition. + + const int minBufferCountNeeded = mSynchronousMode ? + MIN_SYNC_BUFFER_SLOTS : MIN_ASYNC_BUFFER_SLOTS; + + const bool numberOfBuffersNeedsToChange = !mClientBufferCount && + ((mServerBufferCount != mBufferCount) || + (mServerBufferCount < minBufferCountNeeded)); + + if (!mQueue.isEmpty() && numberOfBuffersNeedsToChange) { + // wait for the FIFO to drain + mDequeueCondition.wait(mMutex); + // NOTE: we continue here because we need to reevaluate our + // whole state (eg: we could be abandoned or disconnected) + continue; + } + + if (numberOfBuffersNeedsToChange) { + // here we're guaranteed that mQueue is empty + freeAllBuffersLocked(); + mBufferCount = mServerBufferCount; + if (mBufferCount < minBufferCountNeeded) + mBufferCount = minBufferCountNeeded; + mCurrentTexture = INVALID_BUFFER_SLOT; + returnFlags |= ISurfaceTexture::RELEASE_ALL_BUFFERS; + } + + // look for a free buffer to give to the client + found = INVALID_BUFFER_SLOT; + foundSync = INVALID_BUFFER_SLOT; + dequeuedCount = 0; + for (int i = 0; i < mBufferCount; i++) { + const int state = mSlots[i].mBufferState; + if (state == BufferSlot::DEQUEUED) { + dequeuedCount++; + } + + // if buffer is FREE it CANNOT be current + LOGW_IF((state == BufferSlot::FREE) && (mCurrentTexture==i), + "dequeueBuffer: buffer %d is both FREE and current!", + i); + + if (FLAG_ALLOW_DEQUEUE_CURRENT_BUFFER) { + if (state == BufferSlot::FREE || i == mCurrentTexture) { + foundSync = i; + if (i != mCurrentTexture) { + found = i; + break; + } + } + } else { + if (state == BufferSlot::FREE) { + /* We return the oldest of the free buffers to avoid + * stalling the producer if possible. This is because + * the consumer may still have pending reads of the + * buffers in flight. + */ + bool isOlder = mSlots[i].mFrameNumber < + mSlots[found].mFrameNumber; + if (found < 0 || isOlder) { + foundSync = i; + found = i; + } + } + } + } + + // clients are not allowed to dequeue more than one buffer + // if they didn't set a buffer count. + if (!mClientBufferCount && dequeuedCount) { + ST_LOGE("dequeueBuffer: can't dequeue multiple buffers without " + "setting the buffer count"); + return -EINVAL; + } + + // See whether a buffer has been queued since the last + // setBufferCount so we know whether to perform the + // MIN_UNDEQUEUED_BUFFERS check below. + bool bufferHasBeenQueued = mCurrentTexture != INVALID_BUFFER_SLOT; + if (bufferHasBeenQueued) { + // make sure the client is not trying to dequeue more buffers + // than allowed. + const int avail = mBufferCount - (dequeuedCount+1); + if (avail < (MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode))) { + ST_LOGE("dequeueBuffer: MIN_UNDEQUEUED_BUFFERS=%d exceeded " + "(dequeued=%d)", + MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode), + dequeuedCount); + return -EBUSY; + } + } + + // we're in synchronous mode and didn't find a buffer, we need to + // wait for some buffers to be consumed + tryAgain = mSynchronousMode && (foundSync == INVALID_BUFFER_SLOT); + if (tryAgain) { + mDequeueCondition.wait(mMutex); + } + } + + if (mSynchronousMode && found == INVALID_BUFFER_SLOT) { + // foundSync guaranteed to be != INVALID_BUFFER_SLOT + found = foundSync; + } + + if (found == INVALID_BUFFER_SLOT) { + // This should not happen. + ST_LOGE("dequeueBuffer: no available buffer slots"); + return -EBUSY; + } + + const int buf = found; + *outBuf = found; + + const bool useDefaultSize = !w && !h; + if (useDefaultSize) { + // use the default size + w = mDefaultWidth; + h = mDefaultHeight; + } + + const bool updateFormat = (format != 0); + if (!updateFormat) { + // keep the current (or default) format + format = mPixelFormat; + } + + // buffer is now in DEQUEUED (but can also be current at the same time, + // if we're in synchronous mode) + mSlots[buf].mBufferState = BufferSlot::DEQUEUED; + + const sp& buffer(mSlots[buf].mGraphicBuffer); + if ((buffer == NULL) || + (uint32_t(buffer->width) != w) || + (uint32_t(buffer->height) != h) || + (uint32_t(buffer->format) != format) || + ((uint32_t(buffer->usage) & usage) != usage)) + { + usage |= GraphicBuffer::USAGE_HW_TEXTURE; + status_t error; + sp graphicBuffer( + new GraphicBuffer(w, h, format, usage)); +// mGraphicBufferAlloc->createGraphicBuffer( +// w, h, format, usage, &error)); + if (graphicBuffer == 0) { + ST_LOGE("dequeueBuffer: SurfaceComposer::createGraphicBuffer " + "failed"); + return error; + } + if (updateFormat) { + mPixelFormat = format; + } + mSlots[buf].mGraphicBuffer = graphicBuffer; + mSlots[buf].mRequestBufferCalled = false; + mSlots[buf].mFence = EGL_NO_SYNC_KHR; + if (mSlots[buf].mEglImage != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(mSlots[buf].mEglDisplay, + mSlots[buf].mEglImage); + mSlots[buf].mEglImage = EGL_NO_IMAGE_KHR; + mSlots[buf].mEglDisplay = EGL_NO_DISPLAY; + } + if (mCurrentTexture == buf) { + // The current texture no longer references the buffer in this slot + // since we just allocated a new buffer. + mCurrentTexture = INVALID_BUFFER_SLOT; + } + returnFlags |= ISurfaceTexture::BUFFER_NEEDS_REALLOCATION; + } + + dpy = mSlots[buf].mEglDisplay; + fence = mSlots[buf].mFence; + mSlots[buf].mFence = EGL_NO_SYNC_KHR; + } + + if (fence != EGL_NO_SYNC_KHR) { + EGLint result = eglClientWaitSyncKHR(dpy, fence, 0, 1000000000); + // If something goes wrong, log the error, but return the buffer without + // synchronizing access to it. It's too late at this point to abort the + // dequeue operation. + if (result == EGL_FALSE) { + LOGE("dequeueBuffer: error waiting for fence: %#x", eglGetError()); + } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { + LOGE("dequeueBuffer: timeout waiting for fence"); + } + eglDestroySyncKHR(dpy, fence); + } + + ST_LOGV("dequeueBuffer: returning slot=%d buf=%p flags=%#x", *outBuf, + mSlots[*outBuf].mGraphicBuffer->handle, returnFlags); + + return returnFlags; +} + +status_t SurfaceTexture::setSynchronousMode(bool enabled) { + ST_LOGV("setSynchronousMode: enabled=%d", enabled); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("setSynchronousMode: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + + status_t err = OK; + if (!mAllowSynchronousMode && enabled) + return err; + + if (!enabled) { + // going to asynchronous mode, drain the queue + err = drainQueueLocked(); + if (err != NO_ERROR) + return err; + } + + if (mSynchronousMode != enabled) { + // - if we're going to asynchronous mode, the queue is guaranteed to be + // empty here + // - if the client set the number of buffers, we're guaranteed that + // we have at least 3 (because we don't allow less) + mSynchronousMode = enabled; + mDequeueCondition.signal(); + } + return err; +} + +status_t SurfaceTexture::queueBuffer(int buf, int64_t timestamp, + uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform) { + ST_LOGV("queueBuffer: slot=%d time=%lld", buf, timestamp); + + sp listener; + + { // scope for the lock + Mutex::Autolock lock(mMutex); + if (mAbandoned) { + ST_LOGE("queueBuffer: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + if (buf < 0 || buf >= mBufferCount) { + ST_LOGE("queueBuffer: slot index out of range [0, %d]: %d", + mBufferCount, buf); + return -EINVAL; + } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) { + ST_LOGE("queueBuffer: slot %d is not owned by the client " + "(state=%d)", buf, mSlots[buf].mBufferState); + return -EINVAL; + } else if (buf == mCurrentTexture) { + ST_LOGE("queueBuffer: slot %d is current!", buf); + return -EINVAL; + } else if (!mSlots[buf].mRequestBufferCalled) { + ST_LOGE("queueBuffer: slot %d was enqueued without requesting a " + "buffer", buf); + return -EINVAL; + } + + if (mSynchronousMode) { + // In synchronous mode we queue all buffers in a FIFO. + mQueue.push_back(buf); + + // Synchronous mode always signals that an additional frame should + // be consumed. + listener = mFrameAvailableListener; + } else { + // In asynchronous mode we only keep the most recent buffer. + if (mQueue.empty()) { + mQueue.push_back(buf); + + // Asynchronous mode only signals that a frame should be + // consumed if no previous frame was pending. If a frame were + // pending then the consumer would have already been notified. + listener = mFrameAvailableListener; + } else { + Fifo::iterator front(mQueue.begin()); + // buffer currently queued is freed + mSlots[*front].mBufferState = BufferSlot::FREE; + // and we record the new buffer index in the queued list + *front = buf; + } + } + + mSlots[buf].mBufferState = BufferSlot::QUEUED; + mSlots[buf].mCrop = mNextCrop; + mSlots[buf].mTransform = mNextTransform; + mSlots[buf].mScalingMode = mNextScalingMode; + mSlots[buf].mTimestamp = timestamp; + mFrameCounter++; + mSlots[buf].mFrameNumber = mFrameCounter; + + mDequeueCondition.signal(); + + *outWidth = mDefaultWidth; + *outHeight = mDefaultHeight; + *outTransform = 0; + } // scope for the lock + + // call back without lock held + if (listener != 0) { + listener->onFrameAvailable(); + } + return OK; +} + +void SurfaceTexture::cancelBuffer(int buf) { + ST_LOGV("cancelBuffer: slot=%d", buf); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGW("cancelBuffer: SurfaceTexture has been abandoned!"); + return; + } + + if (buf < 0 || buf >= mBufferCount) { + ST_LOGE("cancelBuffer: slot index out of range [0, %d]: %d", + mBufferCount, buf); + return; + } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) { + ST_LOGE("cancelBuffer: slot %d is not owned by the client (state=%d)", + buf, mSlots[buf].mBufferState); + return; + } + mSlots[buf].mBufferState = BufferSlot::FREE; + mSlots[buf].mFrameNumber = 0; + mDequeueCondition.signal(); +} + +status_t SurfaceTexture::setCrop(const Rect& crop) { + ST_LOGV("setCrop: crop=[%d,%d,%d,%d]", crop.left, crop.top, crop.right, + crop.bottom); + + Mutex::Autolock lock(mMutex); + if (mAbandoned) { + ST_LOGE("setCrop: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + mNextCrop = crop; + return OK; +} + +status_t SurfaceTexture::setTransform(uint32_t transform) { + ST_LOGV("setTransform: xform=%#x", transform); + Mutex::Autolock lock(mMutex); + if (mAbandoned) { + ST_LOGE("setTransform: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + mNextTransform = transform; + return OK; +} + +status_t SurfaceTexture::connect(int api, + uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform) { + ST_LOGV("connect: api=%d", api); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("connect: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + + int err = NO_ERROR; + switch (api) { + case NATIVE_WINDOW_API_EGL: + case NATIVE_WINDOW_API_CPU: + case NATIVE_WINDOW_API_MEDIA: + case NATIVE_WINDOW_API_CAMERA: + if (mConnectedApi != NO_CONNECTED_API) { + ST_LOGE("connect: already connected (cur=%d, req=%d)", + mConnectedApi, api); + err = -EINVAL; + } else { + mConnectedApi = api; + *outWidth = mDefaultWidth; + *outHeight = mDefaultHeight; + *outTransform = 0; + } + break; + default: + err = -EINVAL; + break; + } + return err; +} + +status_t SurfaceTexture::disconnect(int api) { + ST_LOGV("disconnect: api=%d", api); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + // it is not really an error to disconnect after the surface + // has been abandoned, it should just be a no-op. + return NO_ERROR; + } + + int err = NO_ERROR; + switch (api) { + case NATIVE_WINDOW_API_EGL: + case NATIVE_WINDOW_API_CPU: + case NATIVE_WINDOW_API_MEDIA: + case NATIVE_WINDOW_API_CAMERA: + if (mConnectedApi == api) { + drainQueueAndFreeBuffersLocked(); + mConnectedApi = NO_CONNECTED_API; + mNextCrop.makeInvalid(); + mNextScalingMode = NATIVE_WINDOW_SCALING_MODE_FREEZE; + mNextTransform = 0; + mDequeueCondition.signal(); + } else { + ST_LOGE("disconnect: connected to another api (cur=%d, req=%d)", + mConnectedApi, api); + err = -EINVAL; + } + break; + default: + ST_LOGE("disconnect: unknown API %d", api); + err = -EINVAL; + break; + } + return err; +} + +status_t SurfaceTexture::setScalingMode(int mode) { + ST_LOGV("setScalingMode: mode=%d", mode); + + switch (mode) { + case NATIVE_WINDOW_SCALING_MODE_FREEZE: + case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: + break; + default: + ST_LOGE("unknown scaling mode: %d", mode); + return BAD_VALUE; + } + + Mutex::Autolock lock(mMutex); + mNextScalingMode = mode; + return OK; +} + +status_t SurfaceTexture::updateTexImage() { + ST_LOGV("updateTexImage"); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + qDebug("calling updateTexImage() on an abandoned SurfaceTexture"); + return NO_INIT; + } + + // In asynchronous mode the list is guaranteed to be one buffer + // deep, while in synchronous mode we use the oldest buffer. + if (!mQueue.empty()) { + Fifo::iterator front(mQueue.begin()); + int buf = *front; + + // Update the GL texture object. + EGLImageKHR image = mSlots[buf].mEglImage; + EGLDisplay dpy = eglGetCurrentDisplay(); + if (image == EGL_NO_IMAGE_KHR) { + if (mSlots[buf].mGraphicBuffer == 0) { + qDebug("buffer at slot %d is null", buf); + return BAD_VALUE; + } + image = createImage(dpy, mSlots[buf].mGraphicBuffer); + mSlots[buf].mEglImage = image; + mSlots[buf].mEglDisplay = dpy; + if (image == EGL_NO_IMAGE_KHR) { + // NOTE: if dpy was invalid, createImage() is guaranteed to + // fail. so we'd end up here. + qDebug("EGL_NO_IMAGE_KHR: %p", dpy); + return -EINVAL; + } + } + + GLint error; + while ((error = glGetError()) != GL_NO_ERROR) { + ST_LOGW("updateTexImage: clearing GL error: %#04x", error); + } + + glBindTexture(mTexTarget, mTexName); + glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image); + + bool failed = false; + while ((error = glGetError()) != GL_NO_ERROR) { + ST_LOGE("error binding external texture image %p (slot %d): %#04x", + image, buf, error); + failed = true; + } + if (failed) { + return -EINVAL; + } + + if (mCurrentTexture != INVALID_BUFFER_SLOT) { + if (mUseFenceSync) { + EGLSyncKHR fence = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, + NULL); + if (fence == EGL_NO_SYNC_KHR) { + ST_LOGE("updateTexImage: error creating fence: %#x", + eglGetError()); + return -EINVAL; + } + glFlush(); + mSlots[mCurrentTexture].mFence = fence; + } + } + + ST_LOGV("updateTexImage: (slot=%d buf=%p) -> (slot=%d buf=%p)", + mCurrentTexture, + mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0, + buf, mSlots[buf].mGraphicBuffer->handle); + + if (mCurrentTexture != INVALID_BUFFER_SLOT) { + // The current buffer becomes FREE if it was still in the queued + // state. If it has already been given to the client + // (synchronous mode), then it stays in DEQUEUED state. + if (mSlots[mCurrentTexture].mBufferState == BufferSlot::QUEUED) { + mSlots[mCurrentTexture].mBufferState = BufferSlot::FREE; + } + } + + // Update the SurfaceTexture state. + mCurrentTexture = buf; + mCurrentTextureBuf = mSlots[buf].mGraphicBuffer; + mCurrentCrop = mSlots[buf].mCrop; + mCurrentTransform = mSlots[buf].mTransform; + mCurrentScalingMode = mSlots[buf].mScalingMode; + mCurrentTimestamp = mSlots[buf].mTimestamp; + computeCurrentTransformMatrix(); + + // Now that we've passed the point at which failures can happen, + // it's safe to remove the buffer from the front of the queue. + mQueue.erase(front); + mDequeueCondition.signal(); + } else { + // We always bind the texture even if we don't update its contents. + glBindTexture(mTexTarget, mTexName); + } + + return OK; +} + +bool SurfaceTexture::isExternalFormat(uint32_t format) +{ + switch (format) { + // supported YUV formats + case HAL_PIXEL_FORMAT_YV12: + // Legacy/deprecated YUV formats + case HAL_PIXEL_FORMAT_YCbCr_422_SP: + case HAL_PIXEL_FORMAT_YCrCb_420_SP: + case HAL_PIXEL_FORMAT_YCbCr_422_I: + return true; + } + + // Any OEM format needs to be considered + if (format>=0x100 && format<=0x1FF) + return true; + + return false; +} + +GLenum SurfaceTexture::getCurrentTextureTarget() const { + return mTexTarget; +} + +void SurfaceTexture::getTransformMatrix(float mtx[16]) { + Mutex::Autolock lock(mMutex); + memcpy(mtx, mCurrentTransformMatrix, sizeof(mCurrentTransformMatrix)); +} + +void SurfaceTexture::computeCurrentTransformMatrix() { + ST_LOGV("computeCurrentTransformMatrix"); + + float xform[16]; + for (int i = 0; i < 16; i++) { + xform[i] = mtxIdentity[i]; + } + if (mCurrentTransform & NATIVE_WINDOW_TRANSFORM_FLIP_H) { + float result[16]; + mtxMul(result, xform, mtxFlipH); + for (int i = 0; i < 16; i++) { + xform[i] = result[i]; + } + } + if (mCurrentTransform & NATIVE_WINDOW_TRANSFORM_FLIP_V) { + float result[16]; + mtxMul(result, xform, mtxFlipV); + for (int i = 0; i < 16; i++) { + xform[i] = result[i]; + } + } + if (mCurrentTransform & NATIVE_WINDOW_TRANSFORM_ROT_90) { + float result[16]; + mtxMul(result, xform, mtxRot90); + for (int i = 0; i < 16; i++) { + xform[i] = result[i]; + } + } + + sp& buf(mSlots[mCurrentTexture].mGraphicBuffer); + float tx, ty, sx, sy; + if (!mCurrentCrop.isEmpty()) { + // In order to prevent bilinear sampling at the of the crop rectangle we + // may need to shrink it by 2 texels in each direction. Normally this + // would just need to take 1/2 a texel off each end, but because the + // chroma channels will likely be subsampled we need to chop off a whole + // texel. This will cause artifacts if someone does nearest sampling + // with 1:1 pixel:texel ratio, but it's impossible to simultaneously + // accomodate the bilinear and nearest sampling uses. + // + // If nearest sampling turns out to be a desirable usage of these + // textures then we could add the ability to switch a SurfaceTexture to + // nearest-mode. Preferably, however, the image producers (video + // decoder, camera, etc.) would simply not use a crop rectangle (or at + // least not tell the framework about it) so that the GPU can do the + // correct edge behavior. + int xshrink = 0, yshrink = 0; + if (mCurrentCrop.left > 0) { + tx = float(mCurrentCrop.left + 1) / float(buf->getWidth()); + xshrink++; + } else { + tx = 0.0f; + } + if (mCurrentCrop.right < int32_t(buf->getWidth())) { + xshrink++; + } + if (mCurrentCrop.bottom < int32_t(buf->getHeight())) { + ty = (float(buf->getHeight() - mCurrentCrop.bottom) + 1.0f) / + float(buf->getHeight()); + yshrink++; + } else { + ty = 0.0f; + } + if (mCurrentCrop.top > 0) { + yshrink++; + } + sx = float(mCurrentCrop.width() - xshrink) / float(buf->getWidth()); + sy = float(mCurrentCrop.height() - yshrink) / float(buf->getHeight()); + } else { + tx = 0.0f; + ty = 0.0f; + sx = 1.0f; + sy = 1.0f; + } + float crop[16] = { + sx, 0, 0, 0, + 0, sy, 0, 0, + 0, 0, 1, 0, + tx, ty, 0, 1, + }; + + float mtxBeforeFlipV[16]; + mtxMul(mtxBeforeFlipV, crop, xform); + + // SurfaceFlinger expects the top of its window textures to be at a Y + // coordinate of 0, so SurfaceTexture must behave the same way. We don't + // want to expose this to applications, however, so we must add an + // additional vertical flip to the transform after all the other transforms. + mtxMul(mCurrentTransformMatrix, mtxFlipV, mtxBeforeFlipV); +} + +nsecs_t SurfaceTexture::getTimestamp() { + ST_LOGV("getTimestamp"); + Mutex::Autolock lock(mMutex); + return mCurrentTimestamp; +} + +void SurfaceTexture::setFrameAvailableListener( + const sp& listener) { + ST_LOGV("setFrameAvailableListener"); + Mutex::Autolock lock(mMutex); + mFrameAvailableListener = listener; +} + +void SurfaceTexture::freeBufferLocked(int i) { + mSlots[i].mGraphicBuffer = 0; + mSlots[i].mBufferState = BufferSlot::FREE; + mSlots[i].mFrameNumber = 0; + if (mSlots[i].mEglImage != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(mSlots[i].mEglDisplay, mSlots[i].mEglImage); + mSlots[i].mEglImage = EGL_NO_IMAGE_KHR; + mSlots[i].mEglDisplay = EGL_NO_DISPLAY; + } +} + +void SurfaceTexture::freeAllBuffersLocked() { + LOGW_IF(!mQueue.isEmpty(), + "freeAllBuffersLocked called but mQueue is not empty"); + mCurrentTexture = INVALID_BUFFER_SLOT; + for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { + freeBufferLocked(i); + } +} + +void SurfaceTexture::freeAllBuffersExceptHeadLocked() { + LOGW_IF(!mQueue.isEmpty(), + "freeAllBuffersExceptCurrentLocked called but mQueue is not empty"); + int head = -1; + if (!mQueue.empty()) { + Fifo::iterator front(mQueue.begin()); + head = *front; + } + mCurrentTexture = INVALID_BUFFER_SLOT; + for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { + if (i != head) { + freeBufferLocked(i); + } + } +} + +status_t SurfaceTexture::drainQueueLocked() { + while (mSynchronousMode && !mQueue.isEmpty()) { + mDequeueCondition.wait(mMutex); + if (mAbandoned) { + ST_LOGE("drainQueueLocked: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + if (mConnectedApi == NO_CONNECTED_API) { + ST_LOGE("drainQueueLocked: SurfaceTexture is not connected!"); + return NO_INIT; + } + } + return NO_ERROR; +} + +status_t SurfaceTexture::drainQueueAndFreeBuffersLocked() { + status_t err = drainQueueLocked(); + if (err == NO_ERROR) { + if (mSynchronousMode) { + freeAllBuffersLocked(); + } else { + freeAllBuffersExceptHeadLocked(); + } + } + return err; +} + +EGLImageKHR SurfaceTexture::createImage(EGLDisplay dpy, + const sp& graphicBuffer) { + EGLClientBuffer cbuf = (EGLClientBuffer)graphicBuffer->getNativeBuffer(); + EGLint attrs[] = { + EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, + EGL_NONE, + }; + EGLImageKHR image = eglCreateImageKHR(dpy, EGL_NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, cbuf, attrs); + if (image == EGL_NO_IMAGE_KHR) { + EGLint error = eglGetError(); + ST_LOGE("error creating EGLImage: %#x", error); + } + return image; +} + +sp SurfaceTexture::getCurrentBuffer() const { + Mutex::Autolock lock(mMutex); + return mCurrentTextureBuf; +} + +Rect SurfaceTexture::getCurrentCrop() const { + Mutex::Autolock lock(mMutex); + return mCurrentCrop; +} + +uint32_t SurfaceTexture::getCurrentTransform() const { + Mutex::Autolock lock(mMutex); + return mCurrentTransform; +} + +uint32_t SurfaceTexture::getCurrentScalingMode() const { + Mutex::Autolock lock(mMutex); + return mCurrentScalingMode; +} + +bool SurfaceTexture::isSynchronousMode() const { + Mutex::Autolock lock(mMutex); + return mSynchronousMode; +} + +int SurfaceTexture::query(int what, int* outValue) +{ + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("query: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + + int value; + switch (what) { + case NATIVE_WINDOW_WIDTH: + value = mDefaultWidth; + break; + case NATIVE_WINDOW_HEIGHT: + value = mDefaultHeight; + break; + case NATIVE_WINDOW_FORMAT: + value = mPixelFormat; + break; + case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS: + value = mSynchronousMode ? + (MIN_UNDEQUEUED_BUFFERS-1) : MIN_UNDEQUEUED_BUFFERS; + break; + default: + return BAD_VALUE; + } + outValue[0] = value; + return NO_ERROR; +} + +void SurfaceTexture::abandon() { + Mutex::Autolock lock(mMutex); + mQueue.clear(); + mAbandoned = true; + mCurrentTextureBuf.clear(); + freeAllBuffersLocked(); + mDequeueCondition.signal(); +} + +void SurfaceTexture::setName(const String8& name) { + mName = name; +} + +void SurfaceTexture::dump(String8& result) const +{ + char buffer[1024]; + dump(result, "", buffer, 1024); +} + +void SurfaceTexture::dump(String8& result, const char* prefix, + char* buffer, size_t SIZE) const +{ + Mutex::Autolock _l(mMutex); + snprintf(buffer, SIZE, + "%smBufferCount=%d, mSynchronousMode=%d, default-size=[%dx%d], " + "mPixelFormat=%d, mTexName=%d\n", + prefix, mBufferCount, mSynchronousMode, mDefaultWidth, + mDefaultHeight, mPixelFormat, mTexName); + result.append(buffer); + + String8 fifo; + int fifoSize = 0; + Fifo::const_iterator i(mQueue.begin()); + while (i != mQueue.end()) { + snprintf(buffer, SIZE, "%02d ", *i++); + fifoSize++; + fifo.append(buffer); + } + + snprintf(buffer, SIZE, + "%scurrent: {crop=[%d,%d,%d,%d], transform=0x%02x, current=%d}\n" + "%snext : {crop=[%d,%d,%d,%d], transform=0x%02x, FIFO(%d)={%s}}\n" + , + prefix, mCurrentCrop.left, + mCurrentCrop.top, mCurrentCrop.right, mCurrentCrop.bottom, + mCurrentTransform, mCurrentTexture, + prefix, mNextCrop.left, mNextCrop.top, mNextCrop.right, + mNextCrop.bottom, mNextTransform, fifoSize, fifo.string() + ); + result.append(buffer); + + struct { + const char * operator()(int state) const { + switch (state) { + case BufferSlot::DEQUEUED: return "DEQUEUED"; + case BufferSlot::QUEUED: return "QUEUED"; + case BufferSlot::FREE: return "FREE"; + default: return "Unknown"; + } + } + } stateName; + + for (int i=0 ; i":" ", i, + stateName(slot.mBufferState), + slot.mCrop.left, slot.mCrop.top, slot.mCrop.right, + slot.mCrop.bottom, slot.mTransform, slot.mTimestamp + ); + result.append(buffer); + + const sp& buf(slot.mGraphicBuffer); + if (buf != NULL) { + snprintf(buffer, SIZE, + ", %p [%4ux%4u:%4u,%3X]", + buf->handle, buf->width, buf->height, buf->stride, + buf->format); + result.append(buffer); + } + result.append("\n"); + } +} + +static void mtxMul(float out[16], const float a[16], const float b[16]) { + out[0] = a[0]*b[0] + a[4]*b[1] + a[8]*b[2] + a[12]*b[3]; + out[1] = a[1]*b[0] + a[5]*b[1] + a[9]*b[2] + a[13]*b[3]; + out[2] = a[2]*b[0] + a[6]*b[1] + a[10]*b[2] + a[14]*b[3]; + out[3] = a[3]*b[0] + a[7]*b[1] + a[11]*b[2] + a[15]*b[3]; + + out[4] = a[0]*b[4] + a[4]*b[5] + a[8]*b[6] + a[12]*b[7]; + out[5] = a[1]*b[4] + a[5]*b[5] + a[9]*b[6] + a[13]*b[7]; + out[6] = a[2]*b[4] + a[6]*b[5] + a[10]*b[6] + a[14]*b[7]; + out[7] = a[3]*b[4] + a[7]*b[5] + a[11]*b[6] + a[15]*b[7]; + + out[8] = a[0]*b[8] + a[4]*b[9] + a[8]*b[10] + a[12]*b[11]; + out[9] = a[1]*b[8] + a[5]*b[9] + a[9]*b[10] + a[13]*b[11]; + out[10] = a[2]*b[8] + a[6]*b[9] + a[10]*b[10] + a[14]*b[11]; + out[11] = a[3]*b[8] + a[7]*b[9] + a[11]*b[10] + a[15]*b[11]; + + out[12] = a[0]*b[12] + a[4]*b[13] + a[8]*b[14] + a[12]*b[15]; + out[13] = a[1]*b[12] + a[5]*b[13] + a[9]*b[14] + a[13]*b[15]; + out[14] = a[2]*b[12] + a[6]*b[13] + a[10]*b[14] + a[14]*b[15]; + out[15] = a[3]*b[12] + a[7]*b[13] + a[11]*b[14] + a[15]*b[15]; +} + +}; // namespace android diff --git a/src/imports/nativemedia/SurfaceTexture_4_0.h b/src/imports/nativemedia/SurfaceTexture_4_0.h new file mode 100644 index 0000000..53cc852 --- /dev/null +++ b/src/imports/nativemedia/SurfaceTexture_4_0.h @@ -0,0 +1,517 @@ +/* + * Copyright (C) 2010 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_SURFACETEXTURE_H +#define ANDROID_GUI_SURFACETEXTURE_H + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#define ANDROID_GRAPHICS_SURFACETEXTURE_JNI_ID "mSurfaceTexture" + +namespace android { +// ---------------------------------------------------------------------------- + +class IGraphicBufferAlloc; +class String8; + +class SurfaceTexture : public BnSurfaceTexture { +public: + enum { MIN_UNDEQUEUED_BUFFERS = 2 }; + enum { + MIN_ASYNC_BUFFER_SLOTS = MIN_UNDEQUEUED_BUFFERS + 1, + MIN_SYNC_BUFFER_SLOTS = MIN_UNDEQUEUED_BUFFERS + }; + enum { NUM_BUFFER_SLOTS = 32 }; + enum { NO_CONNECTED_API = 0 }; + + struct FrameAvailableListener : public virtual RefBase { + // onFrameAvailable() is called from queueBuffer() each time an + // additional frame becomes available for consumption. This means that + // frames that are queued while in asynchronous mode only trigger the + // callback if no previous frames are pending. Frames queued while in + // synchronous mode always trigger the callback. + // + // This is called without any lock held and can be called concurrently + // by multiple threads. + virtual void onFrameAvailable() = 0; + }; + + // SurfaceTexture constructs a new SurfaceTexture object. tex indicates the + // name of the OpenGL ES texture to which images are to be streamed. This + // texture name cannot be changed once the SurfaceTexture is created. + // allowSynchronousMode specifies whether or not synchronous mode can be + // enabled. texTarget specifies the OpenGL ES texture target to which the + // texture will be bound in updateTexImage. useFenceSync specifies whether + // fences should be used to synchronize access to buffers if that behavior + // is enabled at compile-time. + SurfaceTexture(GLuint tex, bool allowSynchronousMode = true, + GLenum texTarget = GL_TEXTURE_EXTERNAL_OES, bool useFenceSync = true); + + virtual ~SurfaceTexture(); + + // setBufferCount updates the number of available buffer slots. After + // calling this all buffer slots are both unallocated and owned by the + // SurfaceTexture object (i.e. they are not owned by the client). + virtual status_t setBufferCount(int bufferCount); + + virtual status_t requestBuffer(int slot, sp* buf); + + // dequeueBuffer gets the next buffer slot index for the client to use. If a + // buffer slot is available then that slot index is written to the location + // pointed to by the buf argument and a status of OK is returned. If no + // slot is available then a status of -EBUSY is returned and buf is + // unmodified. + // The width and height parameters must be no greater than the minimum of + // GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv). + // An error due to invalid dimensions might not be reported until + // updateTexImage() is called. + virtual status_t dequeueBuffer(int *buf, uint32_t width, uint32_t height, + uint32_t format, uint32_t usage); + + // queueBuffer returns a filled buffer to the SurfaceTexture. In addition, a + // timestamp must be provided for the buffer. The timestamp is in + // nanoseconds, and must be monotonically increasing. Its other semantics + // (zero point, etc) are client-dependent and should be documented by the + // client. + virtual status_t queueBuffer(int buf, int64_t timestamp, + uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform); + virtual void cancelBuffer(int buf); + virtual status_t setCrop(const Rect& reg); + virtual status_t setTransform(uint32_t transform); + virtual status_t setScalingMode(int mode); + + virtual int query(int what, int* value); + + // setSynchronousMode set whether dequeueBuffer is synchronous or + // asynchronous. In synchronous mode, dequeueBuffer blocks until + // a buffer is available, the currently bound buffer can be dequeued and + // queued buffers will be retired in order. + // The default mode is asynchronous. + virtual status_t setSynchronousMode(bool enabled); + + // connect attempts to connect a client API to the SurfaceTexture. This + // must be called before any other ISurfaceTexture methods are called except + // for getAllocator. + // + // This method will fail if the connect was previously called on the + // SurfaceTexture and no corresponding disconnect call was made. + virtual status_t connect(int api, + uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform); + + // disconnect attempts to disconnect a client API from the SurfaceTexture. + // Calling this method will cause any subsequent calls to other + // ISurfaceTexture methods to fail except for getAllocator and connect. + // Successfully calling connect after this will allow the other methods to + // succeed again. + // + // This method will fail if the the SurfaceTexture is not currently + // connected to the specified client API. + virtual status_t disconnect(int api); + + // updateTexImage sets the image contents of the target texture to that of + // the most recently queued buffer. + // + // This call may only be made while the OpenGL ES context to which the + // target texture belongs is bound to the calling thread. + status_t updateTexImage(); + + // setBufferCountServer set the buffer count. If the client has requested + // a buffer count using setBufferCount, the server-buffer count will + // take effect once the client sets the count back to zero. + status_t setBufferCountServer(int bufferCount); + + // getTransformMatrix retrieves the 4x4 texture coordinate transform matrix + // associated with the texture image set by the most recent call to + // updateTexImage. + // + // This transform matrix maps 2D homogeneous texture coordinates of the form + // (s, t, 0, 1) with s and t in the inclusive range [0, 1] to the texture + // coordinate that should be used to sample that location from the texture. + // Sampling the texture outside of the range of this transform is undefined. + // + // This transform is necessary to compensate for transforms that the stream + // content producer may implicitly apply to the content. By forcing users of + // a SurfaceTexture to apply this transform we avoid performing an extra + // copy of the data that would be needed to hide the transform from the + // user. + // + // The matrix is stored in column-major order so that it may be passed + // directly to OpenGL ES via the glLoadMatrixf or glUniformMatrix4fv + // functions. + void getTransformMatrix(float mtx[16]); + + // getTimestamp retrieves the timestamp associated with the texture image + // set by the most recent call to updateTexImage. + // + // The timestamp is in nanoseconds, and is monotonically increasing. Its + // other semantics (zero point, etc) are source-dependent and should be + // documented by the source. + int64_t getTimestamp(); + + // setFrameAvailableListener sets the listener object that will be notified + // when a new frame becomes available. + void setFrameAvailableListener(const sp& listener); + + // getAllocator retrieves the binder object that must be referenced as long + // as the GraphicBuffers dequeued from this SurfaceTexture are referenced. + // Holding this binder reference prevents SurfaceFlinger from freeing the + // buffers before the client is done with them. + sp getAllocator(); + + // setDefaultBufferSize is used to set the size of buffers returned by + // requestBuffers when a with and height of zero is requested. + // A call to setDefaultBufferSize() may trigger requestBuffers() to + // be called from the client. + // The width and height parameters must be no greater than the minimum of + // GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv). + // An error due to invalid dimensions might not be reported until + // updateTexImage() is called. + status_t setDefaultBufferSize(uint32_t width, uint32_t height); + + // getCurrentBuffer returns the buffer associated with the current image. + sp getCurrentBuffer() const; + + // getCurrentTextureTarget returns the texture target of the current + // texture as returned by updateTexImage(). + GLenum getCurrentTextureTarget() const; + + // getCurrentCrop returns the cropping rectangle of the current buffer + Rect getCurrentCrop() const; + + // getCurrentTransform returns the transform of the current buffer + uint32_t getCurrentTransform() const; + + // getCurrentScalingMode returns the scaling mode of the current buffer + uint32_t getCurrentScalingMode() const; + + // isSynchronousMode returns whether the SurfaceTexture is currently in + // synchronous mode. + bool isSynchronousMode() const; + + // abandon frees all the buffers and puts the SurfaceTexture into the + // 'abandoned' state. Once put in this state the SurfaceTexture can never + // leave it. When in the 'abandoned' state, all methods of the + // ISurfaceTexture interface will fail with the NO_INIT error. + // + // Note that while calling this method causes all the buffers to be freed + // from the perspective of the the SurfaceTexture, if there are additional + // references on the buffers (e.g. if a buffer is referenced by a client or + // by OpenGL ES as a texture) then those buffer will remain allocated. + void abandon(); + + // set the name of the SurfaceTexture that will be used to identify it in + // log messages. + void setName(const String8& name); + + // dump our state in a String + void dump(String8& result) const; + void dump(String8& result, const char* prefix, char* buffer, size_t SIZE) const; + +protected: + + // freeBufferLocked frees the resources (both GraphicBuffer and EGLImage) + // for the given slot. + void freeBufferLocked(int index); + + // freeAllBuffersLocked frees the resources (both GraphicBuffer and + // EGLImage) for all slots. + void freeAllBuffersLocked(); + + // freeAllBuffersExceptHeadLocked frees the resources (both GraphicBuffer + // and EGLImage) for all slots except the head of mQueue + void freeAllBuffersExceptHeadLocked(); + + // drainQueueLocked drains the buffer queue if we're in synchronous mode + // returns immediately otherwise. return NO_INIT if SurfaceTexture + // became abandoned or disconnected during this call. + status_t drainQueueLocked(); + + // drainQueueAndFreeBuffersLocked drains the buffer queue if we're in + // synchronous mode and free all buffers. In asynchronous mode, all buffers + // are freed except the current buffer. + status_t drainQueueAndFreeBuffersLocked(); + + static bool isExternalFormat(uint32_t format); + +private: + + // createImage creates a new EGLImage from a GraphicBuffer. + EGLImageKHR createImage(EGLDisplay dpy, + const sp& graphicBuffer); + + status_t setBufferCountServerLocked(int bufferCount); + + // computeCurrentTransformMatrix computes the transform matrix for the + // current texture. It uses mCurrentTransform and the current GraphicBuffer + // to compute this matrix and stores it in mCurrentTransformMatrix. + void computeCurrentTransformMatrix(); + + enum { INVALID_BUFFER_SLOT = -1 }; + + struct BufferSlot { + + BufferSlot() + : mEglImage(EGL_NO_IMAGE_KHR), + mEglDisplay(EGL_NO_DISPLAY), + mBufferState(BufferSlot::FREE), + mRequestBufferCalled(false), + mTransform(0), + mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), + mTimestamp(0), + mFrameNumber(0), + mFence(EGL_NO_SYNC_KHR) { + mCrop.makeInvalid(); + } + + // mGraphicBuffer points to the buffer allocated for this slot or is NULL + // if no buffer has been allocated. + sp mGraphicBuffer; + + // mEglImage is the EGLImage created from mGraphicBuffer. + EGLImageKHR mEglImage; + + // mEglDisplay is the EGLDisplay used to create mEglImage. + EGLDisplay mEglDisplay; + + // BufferState represents the different states in which a buffer slot + // can be. + enum BufferState { + // FREE indicates that the buffer is not currently being used and + // will not be used in the future until it gets dequeued and + // subsequently queued by the client. + FREE = 0, + + // DEQUEUED indicates that the buffer has been dequeued by the + // client, but has not yet been queued or canceled. The buffer is + // considered 'owned' by the client, and the server should not use + // it for anything. + // + // Note that when in synchronous-mode (mSynchronousMode == true), + // the buffer that's currently attached to the texture may be + // dequeued by the client. That means that the current buffer can + // be in either the DEQUEUED or QUEUED state. In asynchronous mode, + // however, the current buffer is always in the QUEUED state. + DEQUEUED = 1, + + // QUEUED indicates that the buffer has been queued by the client, + // and has not since been made available for the client to dequeue. + // Attaching the buffer to the texture does NOT transition the + // buffer away from the QUEUED state. However, in Synchronous mode + // the current buffer may be dequeued by the client under some + // circumstances. See the note about the current buffer in the + // documentation for DEQUEUED. + QUEUED = 2, + }; + + // mBufferState is the current state of this buffer slot. + BufferState mBufferState; + + // mRequestBufferCalled is used for validating that the client did + // call requestBuffer() when told to do so. Technically this is not + // needed but useful for debugging and catching client bugs. + bool mRequestBufferCalled; + + // mCrop is the current crop rectangle for this buffer slot. This gets + // set to mNextCrop each time queueBuffer gets called for this buffer. + Rect mCrop; + + // mTransform is the current transform flags for this buffer slot. This + // gets set to mNextTransform each time queueBuffer gets called for this + // slot. + uint32_t mTransform; + + // mScalingMode is the current scaling mode for this buffer slot. This + // gets set to mNextScalingMode each time queueBuffer gets called for + // this slot. + uint32_t mScalingMode; + + // mTimestamp is the current timestamp for this buffer slot. This gets + // to set by queueBuffer each time this slot is queued. + int64_t mTimestamp; + + // mFrameNumber is the number of the queued frame for this slot. + uint64_t mFrameNumber; + + // mFence is the EGL sync object that must signal before the buffer + // associated with this buffer slot may be dequeued. It is initialized + // to EGL_NO_SYNC_KHR when the buffer is created and (optionally, based + // on a compile-time option) set to a new sync object in updateTexImage. + EGLSyncKHR mFence; + }; + + // mSlots is the array of buffer slots that must be mirrored on the client + // side. This allows buffer ownership to be transferred between the client + // and server without sending a GraphicBuffer over binder. The entire array + // is initialized to NULL at construction time, and buffers are allocated + // for a slot when requestBuffer is called with that slot's index. + BufferSlot mSlots[NUM_BUFFER_SLOTS]; + + // mDefaultWidth holds the default width of allocated buffers. It is used + // in requestBuffers() if a width and height of zero is specified. + uint32_t mDefaultWidth; + + // mDefaultHeight holds the default height of allocated buffers. It is used + // in requestBuffers() if a width and height of zero is specified. + uint32_t mDefaultHeight; + + // mPixelFormat holds the pixel format of allocated buffers. It is used + // in requestBuffers() if a format of zero is specified. + uint32_t mPixelFormat; + + // mBufferCount is the number of buffer slots that the client and server + // must maintain. It defaults to MIN_ASYNC_BUFFER_SLOTS and can be changed + // by calling setBufferCount or setBufferCountServer + int mBufferCount; + + // mClientBufferCount is the number of buffer slots requested by the client. + // The default is zero, which means the client doesn't care how many buffers + // there is. + int mClientBufferCount; + + // mServerBufferCount buffer count requested by the server-side + int mServerBufferCount; + + // mCurrentTexture is the buffer slot index of the buffer that is currently + // bound to the OpenGL texture. It is initialized to INVALID_BUFFER_SLOT, + // indicating that no buffer slot is currently bound to the texture. Note, + // however, that a value of INVALID_BUFFER_SLOT does not necessarily mean + // that no buffer is bound to the texture. A call to setBufferCount will + // reset mCurrentTexture to INVALID_BUFFER_SLOT. + int mCurrentTexture; + + // mCurrentTextureBuf is the graphic buffer of the current texture. It's + // possible that this buffer is not associated with any buffer slot, so we + // must track it separately in order to support the getCurrentBuffer method. + sp mCurrentTextureBuf; + + // mCurrentCrop is the crop rectangle that applies to the current texture. + // It gets set each time updateTexImage is called. + Rect mCurrentCrop; + + // mCurrentTransform is the transform identifier for the current texture. It + // gets set each time updateTexImage is called. + uint32_t mCurrentTransform; + + // mCurrentScalingMode is the scaling mode for the current texture. It gets + // set to each time updateTexImage is called. + uint32_t mCurrentScalingMode; + + // mCurrentTransformMatrix is the transform matrix for the current texture. + // It gets computed by computeTransformMatrix each time updateTexImage is + // called. + float mCurrentTransformMatrix[16]; + + // mCurrentTimestamp is the timestamp for the current texture. It + // gets set each time updateTexImage is called. + int64_t mCurrentTimestamp; + + // mNextCrop is the crop rectangle that will be used for the next buffer + // that gets queued. It is set by calling setCrop. + Rect mNextCrop; + + // mNextTransform is the transform identifier that will be used for the next + // buffer that gets queued. It is set by calling setTransform. + uint32_t mNextTransform; + + // mNextScalingMode is the scaling mode that will be used for the next + // buffers that get queued. It is set by calling setScalingMode. + int mNextScalingMode; + + // mTexName is the name of the OpenGL texture to which streamed images will + // be bound when updateTexImage is called. It is set at construction time + // changed with a call to setTexName. + const GLuint mTexName; + + // mGraphicBufferAlloc is the connection to SurfaceFlinger that is used to + // allocate new GraphicBuffer objects. + //sp mGraphicBufferAlloc; + + // 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 mFrameAvailableListener; + + // mSynchronousMode whether we're in synchronous mode or not + bool mSynchronousMode; + + // mAllowSynchronousMode whether we allow synchronous mode or not + const bool mAllowSynchronousMode; + + // mConnectedApi indicates the API that is currently connected to this + // SurfaceTexture. It defaults to NO_CONNECTED_API (= 0), and gets updated + // by the connect and disconnect methods. + int mConnectedApi; + + // mDequeueCondition condition used for dequeueBuffer in synchronous mode + mutable Condition mDequeueCondition; + + // mQueue is a FIFO of queued buffers used in synchronous mode + typedef Vector Fifo; + Fifo mQueue; + + // mAbandoned indicates that the SurfaceTexture will no longer be used to + // consume images buffers pushed to it using the ISurfaceTexture interface. + // It is initialized to false, and set to true in the abandon method. A + // SurfaceTexture that has been abandoned will return the NO_INIT error from + // all ISurfaceTexture methods capable of returning an error. + bool mAbandoned; + + // mName is a string used to identify the SurfaceTexture in log messages. + // It is set by the setName method. + String8 mName; + + // mUseFenceSync indicates whether creation of the EGL_KHR_fence_sync + // extension should be used to prevent buffers from being dequeued before + // it's safe for them to be written. It gets set at construction time and + // never changes. + const bool mUseFenceSync; + + // mMutex is the mutex used to prevent concurrent access to the member + // variables of SurfaceTexture objects. It must be locked whenever the + // member variables are accessed. + mutable Mutex mMutex; + + // mTexTarget is the GL texture target with which the GL texture object is + // associated. It is set in the constructor and never changed. It is + // almost always GL_TEXTURE_EXTERNAL_OES except for one use case in Android + // Browser. In that case it is set to GL_TEXTURE_2D to allow + // glCopyTexSubImage to read from the texture. This is a hack to work + // around a GL driver limitation on the number of FBO attachments, which the + // browser's tile cache exceeds. + const GLenum mTexTarget; + + // mFrameCounter is the free running counter, incremented for every buffer queued + // with the surface Texture. + uint64_t mFrameCounter; + + +}; + +// ---------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_GUI_SURFACETEXTURE_H diff --git a/src/imports/nativemedia/SurfaceTexture_4_1.cpp b/src/imports/nativemedia/SurfaceTexture_4_1.cpp new file mode 100644 index 0000000..9f8fd84 --- /dev/null +++ b/src/imports/nativemedia/SurfaceTexture_4_1.cpp @@ -0,0 +1,866 @@ +/* + * Copyright (C) 2010 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 "SurfaceTexture" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS +//#define LOG_NDEBUG 0 + +#define GL_GLEXT_PROTOTYPES +#define EGL_EGLEXT_PROTOTYPES + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +// This compile option makes SurfaceTexture use the EGL_KHR_fence_sync extension +// to synchronize access to the buffers. It will cause dequeueBuffer to stall, +// waiting for the GL reads for the buffer being dequeued to complete before +// allowing the buffer to be dequeued. +#ifdef USE_FENCE_SYNC +#ifdef ALLOW_DEQUEUE_CURRENT_BUFFER +#error "USE_FENCE_SYNC and ALLOW_DEQUEUE_CURRENT_BUFFER are incompatible" +#endif +#endif + +// Macros for including the SurfaceTexture name in log messages +#define ST_LOGV(x, ...) qDebug("[V/%s] "x, mName.string(), ##__VA_ARGS__) +#define ST_LOGD(x, ...) qDebug("[D/%s] "x, mName.string(), ##__VA_ARGS__) +#define ST_LOGI(x, ...) qDebug("[I/%s] "x, mName.string(), ##__VA_ARGS__) +#define ST_LOGW(x, ...) qDebug("[W/%s] "x, mName.string(), ##__VA_ARGS__) +#define ST_LOGE(x, ...) qDebug("[E/%s] "x, mName.string(), ##__VA_ARGS__) + +namespace android { + +// Transform matrices +static float mtxIdentity[16] = { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, +}; +static float mtxFlipH[16] = { + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 1, 0, 0, 1, +}; +static float mtxFlipV[16] = { + 1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, 1, 0, + 0, 1, 0, 1, +}; +static float mtxRot90[16] = { + 0, 1, 0, 0, + -1, 0, 0, 0, + 0, 0, 1, 0, + 1, 0, 0, 1, +}; +static float mtxRot180[16] = { + -1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, 1, 0, + 1, 1, 0, 1, +}; +static float mtxRot270[16] = { + 0, -1, 0, 0, + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, 1, 0, 1, +}; + +static void mtxMul(float out[16], const float a[16], const float b[16]); + +// Get an ID that's unique within this process. +static int32_t createProcessUniqueId() { + static volatile int32_t globalCounter = 0; + return android_atomic_inc(&globalCounter); +} + +SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode, + GLenum texTarget, bool useFenceSync, const sp &bufferQueue) : + mCurrentTransform(0), + mCurrentTimestamp(0), + mFilteringEnabled(true), + mTexName(tex), +#ifdef USE_FENCE_SYNC + mUseFenceSync(useFenceSync), +#else + mUseFenceSync(false), +#endif + mTexTarget(texTarget), + mEglDisplay(EGL_NO_DISPLAY), + mEglContext(EGL_NO_CONTEXT), + mAbandoned(false), + mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT), + mAttached(true) +{ + // Choose a name using the PID and a process-unique ID. + mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId()); + ST_LOGV("SurfaceTexture"); + if (bufferQueue == 0) { + ST_LOGV("Creating a new BufferQueue"); + mBufferQueue = new BufferQueue(allowSynchronousMode); + } + else { + mBufferQueue = bufferQueue; + } + + memcpy(mCurrentTransformMatrix, mtxIdentity, + sizeof(mCurrentTransformMatrix)); + + // Note that we can't create an sp<...>(this) in a ctor that will not keep a + // reference once the ctor ends, as that would cause the refcount of 'this' + // dropping to 0 at the end of the ctor. Since all we need is a wp<...> + // that's what we create. + wp listener; + sp proxy; + listener = static_cast(this); + proxy = new BufferQueue::ProxyConsumerListener(listener); + + status_t err = mBufferQueue->consumerConnect(proxy); + if (err != NO_ERROR) { + ST_LOGE("SurfaceTexture: error connecting to BufferQueue: %s (%d)", + strerror(-err), err); + } else { + mBufferQueue->setConsumerName(mName); + mBufferQueue->setConsumerUsageBits(DEFAULT_USAGE_FLAGS); + } +} + +SurfaceTexture::~SurfaceTexture() { + ST_LOGV("~SurfaceTexture"); + + abandon(); +} + +status_t SurfaceTexture::setBufferCountServer(int bufferCount) { + Mutex::Autolock lock(mMutex); + return mBufferQueue->setBufferCountServer(bufferCount); +} + + +status_t SurfaceTexture::setDefaultBufferSize(uint32_t w, uint32_t h) +{ + Mutex::Autolock lock(mMutex); + mDefaultWidth = w; + mDefaultHeight = h; + return mBufferQueue->setDefaultBufferSize(w, h); +} + +status_t SurfaceTexture::updateTexImage() { + return SurfaceTexture::updateTexImage(NULL); +} + +status_t SurfaceTexture::updateTexImage(BufferRejecter* rejecter) { + ATRACE_CALL(); + ST_LOGV("updateTexImage"); + Mutex::Autolock lock(mMutex); + + status_t err = NO_ERROR; + + if (mAbandoned) { + ST_LOGE("updateTexImage: SurfaceTexture is abandoned!"); + return NO_INIT; + } + + if (!mAttached) { + ST_LOGE("updateTexImage: SurfaceTexture is not attached to an OpenGL " + "ES context"); + return INVALID_OPERATION; + } + + EGLDisplay dpy = eglGetCurrentDisplay(); + EGLContext ctx = eglGetCurrentContext(); + + if ((mEglDisplay != dpy && mEglDisplay != EGL_NO_DISPLAY) || + dpy == EGL_NO_DISPLAY) { + ST_LOGE("updateTexImage: invalid current EGLDisplay"); + return INVALID_OPERATION; + } + + if ((mEglContext != ctx && mEglContext != EGL_NO_CONTEXT) || + ctx == EGL_NO_CONTEXT) { + ST_LOGE("updateTexImage: invalid current EGLContext"); + return INVALID_OPERATION; + } + + mEglDisplay = dpy; + mEglContext = ctx; + + BufferQueue::BufferItem item; + + // In asynchronous mode the list is guaranteed to be one buffer + // deep, while in synchronous mode we use the oldest buffer. + err = mBufferQueue->acquireBuffer(&item); + if (err == NO_ERROR) { + int buf = item.mBuf; + // This buffer was newly allocated, so we need to clean up on our side + if (item.mGraphicBuffer != NULL) { + mEGLSlots[buf].mGraphicBuffer = 0; + if (mEGLSlots[buf].mEglImage != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(dpy, mEGLSlots[buf].mEglImage); + mEGLSlots[buf].mEglImage = EGL_NO_IMAGE_KHR; + } + mEGLSlots[buf].mGraphicBuffer = item.mGraphicBuffer; + } + + // we call the rejecter here, in case the caller has a reason to + // not accept this buffer. this is used by SurfaceFlinger to + // reject buffers which have the wrong size + if (rejecter && rejecter->reject(mEGLSlots[buf].mGraphicBuffer, item)) { + mBufferQueue->releaseBuffer(buf, dpy, mEGLSlots[buf].mFence); + mEGLSlots[buf].mFence = EGL_NO_SYNC_KHR; + glBindTexture(mTexTarget, mTexName); + return NO_ERROR; + } + + // Update the GL texture object. We may have to do this even when + // item.mGraphicBuffer == NULL, if we destroyed the EGLImage when + // detaching from a context but the buffer has not been re-allocated. + EGLImageKHR image = mEGLSlots[buf].mEglImage; + if (image == EGL_NO_IMAGE_KHR) { + if (mEGLSlots[buf].mGraphicBuffer == NULL) { + ST_LOGE("updateTexImage: buffer at slot %d is null", buf); + err = BAD_VALUE; + } else { + image = createImage(dpy, mEGLSlots[buf].mGraphicBuffer); + mEGLSlots[buf].mEglImage = image; + if (image == EGL_NO_IMAGE_KHR) { + // NOTE: if dpy was invalid, createImage() is guaranteed to + // fail. so we'd end up here. + err = UNKNOWN_ERROR; + } + } + } + + if (err == NO_ERROR) { + GLint error; + while ((error = glGetError()) != GL_NO_ERROR) { + ST_LOGW("updateTexImage: clearing GL error: %#04x", error); + } + + glBindTexture(mTexTarget, mTexName); + glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image); + + while ((error = glGetError()) != GL_NO_ERROR) { + ST_LOGE("updateTexImage: error binding external texture image %p " + "(slot %d): %#04x", image, buf, error); + err = UNKNOWN_ERROR; + } + + if (err == NO_ERROR) { + err = syncForReleaseLocked(dpy); + } + } + + if (err != NO_ERROR) { + // Release the buffer we just acquired. It's not safe to + // release the old buffer, so instead we just drop the new frame. + mBufferQueue->releaseBuffer(buf, dpy, mEGLSlots[buf].mFence); + mEGLSlots[buf].mFence = EGL_NO_SYNC_KHR; + return err; + } + + ST_LOGV("updateTexImage: (slot=%d buf=%p) -> (slot=%d buf=%p)", + mCurrentTexture, + mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0, + buf, item.mGraphicBuffer != NULL ? item.mGraphicBuffer->handle : 0); + + // release old buffer + if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { + status_t status = mBufferQueue->releaseBuffer(mCurrentTexture, dpy, + mEGLSlots[mCurrentTexture].mFence); + + mEGLSlots[mCurrentTexture].mFence = EGL_NO_SYNC_KHR; + if (status == BufferQueue::STALE_BUFFER_SLOT) { + freeBufferLocked(mCurrentTexture); + } else if (status != NO_ERROR) { + ST_LOGE("updateTexImage: released invalid buffer"); + err = status; + } + } + + // Update the SurfaceTexture state. + mCurrentTexture = buf; + mCurrentTextureBuf = mEGLSlots[buf].mGraphicBuffer; + mCurrentCrop = item.mCrop; + mCurrentTransform = item.mTransform; + mCurrentScalingMode = item.mScalingMode; + mCurrentTimestamp = item.mTimestamp; + computeCurrentTransformMatrix(); + } else { + if (err < 0) { + ALOGE("updateTexImage failed on acquire %d", err); + } + // We always bind the texture even if we don't update its contents. + glBindTexture(mTexTarget, mTexName); + return OK; + } + + return err; +} + +status_t SurfaceTexture::detachFromContext() { + ATRACE_CALL(); + ST_LOGV("detachFromContext"); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("detachFromContext: abandoned SurfaceTexture"); + return NO_INIT; + } + + if (!mAttached) { + ST_LOGE("detachFromContext: SurfaceTexture is not attached to a " + "context"); + return INVALID_OPERATION; + } + + EGLDisplay dpy = eglGetCurrentDisplay(); + EGLContext ctx = eglGetCurrentContext(); + + if (mEglDisplay != dpy && mEglDisplay != EGL_NO_DISPLAY) { + ST_LOGE("detachFromContext: invalid current EGLDisplay"); + return INVALID_OPERATION; + } + + if (mEglContext != ctx && mEglContext != EGL_NO_CONTEXT) { + ST_LOGE("detachFromContext: invalid current EGLContext"); + return INVALID_OPERATION; + } + + if (dpy != EGL_NO_DISPLAY && ctx != EGL_NO_CONTEXT) { + status_t err = syncForReleaseLocked(dpy); + if (err != OK) { + return err; + } + + glDeleteTextures(1, &mTexName); + } + + // Because we're giving up the EGLDisplay we need to free all the EGLImages + // that are associated with it. They'll be recreated when the + // SurfaceTexture gets attached to a new OpenGL ES context (and thus gets a + // new EGLDisplay). + for (int i =0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { + EGLImageKHR img = mEGLSlots[i].mEglImage; + if (img != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(mEglDisplay, img); + mEGLSlots[i].mEglImage = EGL_NO_IMAGE_KHR; + } + } + + mEglDisplay = EGL_NO_DISPLAY; + mEglContext = EGL_NO_CONTEXT; + mAttached = false; + + return OK; +} + +status_t SurfaceTexture::attachToContext(GLuint tex) { + ATRACE_CALL(); + ST_LOGV("attachToContext"); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + ST_LOGE("attachToContext: abandoned SurfaceTexture"); + return NO_INIT; + } + + if (mAttached) { + ST_LOGE("attachToContext: SurfaceTexture is already attached to a " + "context"); + return INVALID_OPERATION; + } + + EGLDisplay dpy = eglGetCurrentDisplay(); + EGLContext ctx = eglGetCurrentContext(); + + if (dpy == EGL_NO_DISPLAY) { + ST_LOGE("attachToContext: invalid current EGLDisplay"); + return INVALID_OPERATION; + } + + if (ctx == EGL_NO_CONTEXT) { + ST_LOGE("attachToContext: invalid current EGLContext"); + return INVALID_OPERATION; + } + + // We need to bind the texture regardless of whether there's a current + // buffer. + glBindTexture(mTexTarget, tex); + + if (mCurrentTextureBuf != NULL) { + // The EGLImageKHR that was associated with the slot was destroyed when + // the SurfaceTexture was detached from the old context, so we need to + // recreate it here. + EGLImageKHR image = createImage(dpy, mCurrentTextureBuf); + if (image == EGL_NO_IMAGE_KHR) { + return UNKNOWN_ERROR; + } + + // Attach the current buffer to the GL texture. + glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image); + + GLint error; + status_t err = OK; + while ((error = glGetError()) != GL_NO_ERROR) { + ST_LOGE("attachToContext: error binding external texture image %p " + "(slot %d): %#04x", image, mCurrentTexture, error); + err = UNKNOWN_ERROR; + } + + // We destroy the EGLImageKHR here because the current buffer may no + // longer be associated with one of the buffer slots, so we have + // nowhere to to store it. If the buffer is still associated with a + // slot then another EGLImageKHR will be created next time that buffer + // gets acquired in updateTexImage. + eglDestroyImageKHR(dpy, image); + + if (err != OK) { + return err; + } + } + + mEglDisplay = dpy; + mEglContext = ctx; + mTexName = tex; + mAttached = true; + + return OK; +} + +status_t SurfaceTexture::syncForReleaseLocked(EGLDisplay dpy) { + ST_LOGV("syncForReleaseLocked"); + + if (mUseFenceSync && mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { + EGLSyncKHR fence = mEGLSlots[mCurrentTexture].mFence; + if (fence != EGL_NO_SYNC_KHR) { + // There is already a fence for the current slot. We need to wait + // on that before replacing it with another fence to ensure that all + // outstanding buffer accesses have completed before the producer + // accesses it. + EGLint result = eglClientWaitSyncKHR(dpy, fence, 0, 1000000000); + if (result == EGL_FALSE) { + ST_LOGE("syncForReleaseLocked: error waiting for previous " + "fence: %#x", eglGetError()); + return UNKNOWN_ERROR; + } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { + ST_LOGE("syncForReleaseLocked: timeout waiting for previous " + "fence"); + return TIMED_OUT; + } + eglDestroySyncKHR(dpy, fence); + } + + // Create a fence for the outstanding accesses in the current OpenGL ES + // context. + fence = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, NULL); + if (fence == EGL_NO_SYNC_KHR) { + ST_LOGE("syncForReleaseLocked: error creating fence: %#x", + eglGetError()); + return UNKNOWN_ERROR; + } + glFlush(); + mEGLSlots[mCurrentTexture].mFence = fence; + } + + return OK; +} + +bool SurfaceTexture::isExternalFormat(uint32_t format) +{ + switch (format) { + // supported YUV formats + case HAL_PIXEL_FORMAT_YV12: + // Legacy/deprecated YUV formats + case HAL_PIXEL_FORMAT_YCbCr_422_SP: + case HAL_PIXEL_FORMAT_YCrCb_420_SP: + case HAL_PIXEL_FORMAT_YCbCr_422_I: + return true; + } + + // Any OEM format needs to be considered + if (format>=0x100 && format<=0x1FF) + return true; + + return false; +} + +GLenum SurfaceTexture::getCurrentTextureTarget() const { + return mTexTarget; +} + +void SurfaceTexture::getTransformMatrix(float mtx[16]) { + Mutex::Autolock lock(mMutex); + memcpy(mtx, mCurrentTransformMatrix, sizeof(mCurrentTransformMatrix)); +} + +void SurfaceTexture::setFilteringEnabled(bool enabled) { + Mutex::Autolock lock(mMutex); + bool needsRecompute = mFilteringEnabled != enabled; + mFilteringEnabled = enabled; + if (needsRecompute) { + computeCurrentTransformMatrix(); + } +} + +void SurfaceTexture::computeCurrentTransformMatrix() { + ST_LOGV("computeCurrentTransformMatrix"); + + float xform[16]; + for (int i = 0; i < 16; i++) { + xform[i] = mtxIdentity[i]; + } + if (mCurrentTransform & NATIVE_WINDOW_TRANSFORM_FLIP_H) { + float result[16]; + mtxMul(result, xform, mtxFlipH); + for (int i = 0; i < 16; i++) { + xform[i] = result[i]; + } + } + if (mCurrentTransform & NATIVE_WINDOW_TRANSFORM_FLIP_V) { + float result[16]; + mtxMul(result, xform, mtxFlipV); + for (int i = 0; i < 16; i++) { + xform[i] = result[i]; + } + } + if (mCurrentTransform & NATIVE_WINDOW_TRANSFORM_ROT_90) { + float result[16]; + mtxMul(result, xform, mtxRot90); + for (int i = 0; i < 16; i++) { + xform[i] = result[i]; + } + } + + sp& buf(mCurrentTextureBuf); + Rect cropRect = mCurrentCrop; + float tx = 0.0f, ty = 0.0f, sx = 1.0f, sy = 1.0f; + float bufferWidth = buf->getWidth(); + float bufferHeight = buf->getHeight(); + if (!cropRect.isEmpty()) { + float shrinkAmount = 0.0f; + if (mFilteringEnabled) { + // In order to prevent bilinear sampling beyond the edge of the + // crop rectangle we may need to shrink it by 2 texels in each + // dimension. Normally this would just need to take 1/2 a texel + // off each end, but because the chroma channels of YUV420 images + // are subsampled we may need to shrink the crop region by a whole + // texel on each side. + switch (buf->getPixelFormat()) { + case PIXEL_FORMAT_RGBA_8888: + case PIXEL_FORMAT_RGBX_8888: + case PIXEL_FORMAT_RGB_888: + case PIXEL_FORMAT_RGB_565: + case PIXEL_FORMAT_BGRA_8888: + case PIXEL_FORMAT_RGBA_5551: + case PIXEL_FORMAT_RGBA_4444: + // We know there's no subsampling of any channels, so we + // only need to shrink by a half a pixel. + shrinkAmount = 0.5; + + default: + // If we don't recognize the format, we must assume the + // worst case (that we care about), which is YUV420. + shrinkAmount = 1.0; + } + } + + // Only shrink the dimensions that are not the size of the buffer. + if (cropRect.width() < bufferWidth) { + tx = (float(cropRect.left) + shrinkAmount) / bufferWidth; + sx = (float(cropRect.width()) - (2.0f * shrinkAmount)) / + bufferWidth; + } + if (cropRect.height() < bufferHeight) { + ty = (float(bufferHeight - cropRect.bottom) + shrinkAmount) / + bufferHeight; + sy = (float(cropRect.height()) - (2.0f * shrinkAmount)) / + bufferHeight; + } + } + float crop[16] = { + sx, 0, 0, 0, + 0, sy, 0, 0, + 0, 0, 1, 0, + tx, ty, 0, 1, + }; + + float mtxBeforeFlipV[16]; + mtxMul(mtxBeforeFlipV, crop, xform); + + // SurfaceFlinger expects the top of its window textures to be at a Y + // coordinate of 0, so SurfaceTexture must behave the same way. We don't + // want to expose this to applications, however, so we must add an + // additional vertical flip to the transform after all the other transforms. + mtxMul(mCurrentTransformMatrix, mtxFlipV, mtxBeforeFlipV); +} + +nsecs_t SurfaceTexture::getTimestamp() { + ST_LOGV("getTimestamp"); + Mutex::Autolock lock(mMutex); + return mCurrentTimestamp; +} + +void SurfaceTexture::setFrameAvailableListener( + const sp& listener) { + ST_LOGV("setFrameAvailableListener"); + Mutex::Autolock lock(mMutex); + mFrameAvailableListener = listener; +} + +EGLImageKHR SurfaceTexture::createImage(EGLDisplay dpy, + const sp& graphicBuffer) { + EGLClientBuffer cbuf = (EGLClientBuffer)graphicBuffer->getNativeBuffer(); + EGLint attrs[] = { + EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, + EGL_NONE, + }; + EGLImageKHR image = eglCreateImageKHR(dpy, EGL_NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, cbuf, attrs); + if (image == EGL_NO_IMAGE_KHR) { + EGLint error = eglGetError(); + ST_LOGE("error creating EGLImage: %#x", error); + } + return image; +} + +sp SurfaceTexture::getCurrentBuffer() const { + Mutex::Autolock lock(mMutex); + return mCurrentTextureBuf; +} + +Rect SurfaceTexture::getCurrentCrop() const { + Mutex::Autolock lock(mMutex); + + Rect outCrop = mCurrentCrop; + if (mCurrentScalingMode == NATIVE_WINDOW_SCALING_MODE_SCALE_CROP) { + int32_t newWidth = mCurrentCrop.width(); + int32_t newHeight = mCurrentCrop.height(); + + if (newWidth * mDefaultHeight > newHeight * mDefaultWidth) { + newWidth = newHeight * mDefaultWidth / mDefaultHeight; + ST_LOGV("too wide: newWidth = %d", newWidth); + } else if (newWidth * mDefaultHeight < newHeight * mDefaultWidth) { + newHeight = newWidth * mDefaultHeight / mDefaultWidth; + ST_LOGV("too tall: newHeight = %d", newHeight); + } + + // The crop is too wide + if (newWidth < mCurrentCrop.width()) { + int32_t dw = (newWidth - mCurrentCrop.width())/2; + outCrop.left -=dw; + outCrop.right += dw; + // The crop is too tall + } else if (newHeight < mCurrentCrop.height()) { + int32_t dh = (newHeight - mCurrentCrop.height())/2; + outCrop.top -= dh; + outCrop.bottom += dh; + } + + ST_LOGV("getCurrentCrop final crop [%d,%d,%d,%d]", + outCrop.left, outCrop.top, + outCrop.right,outCrop.bottom); + } + + return outCrop; +} + +uint32_t SurfaceTexture::getCurrentTransform() const { + Mutex::Autolock lock(mMutex); + return mCurrentTransform; +} + +uint32_t SurfaceTexture::getCurrentScalingMode() const { + Mutex::Autolock lock(mMutex); + return mCurrentScalingMode; +} + +bool SurfaceTexture::isSynchronousMode() const { + Mutex::Autolock lock(mMutex); + return mBufferQueue->isSynchronousMode(); +} + +void SurfaceTexture::freeBufferLocked(int slotIndex) { + ST_LOGV("freeBufferLocked: slotIndex=%d", slotIndex); + mEGLSlots[slotIndex].mGraphicBuffer = 0; + if (slotIndex == mCurrentTexture) { + mCurrentTexture = BufferQueue::INVALID_BUFFER_SLOT; + } + EGLImageKHR img = mEGLSlots[slotIndex].mEglImage; + if (img != EGL_NO_IMAGE_KHR) { + ST_LOGV("destroying EGLImage dpy=%p img=%p", mEglDisplay, img); + eglDestroyImageKHR(mEglDisplay, img); + } + mEGLSlots[slotIndex].mEglImage = EGL_NO_IMAGE_KHR; +} + +void SurfaceTexture::abandon() { + ST_LOGV("abandon"); + Mutex::Autolock lock(mMutex); + + if (!mAbandoned) { + mAbandoned = true; + mCurrentTextureBuf.clear(); + + // destroy all egl buffers + for (int i =0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { + freeBufferLocked(i); + } + + // disconnect from the BufferQueue + mBufferQueue->consumerDisconnect(); + mBufferQueue.clear(); + } +} + +void SurfaceTexture::setName(const String8& name) { + Mutex::Autolock _l(mMutex); + mName = name; + mBufferQueue->setConsumerName(name); +} + +status_t SurfaceTexture::setDefaultBufferFormat(uint32_t defaultFormat) { + Mutex::Autolock lock(mMutex); + return mBufferQueue->setDefaultBufferFormat(defaultFormat); +} + +status_t SurfaceTexture::setConsumerUsageBits(uint32_t usage) { + Mutex::Autolock lock(mMutex); + usage |= DEFAULT_USAGE_FLAGS; + return mBufferQueue->setConsumerUsageBits(usage); +} + +status_t SurfaceTexture::setTransformHint(uint32_t hint) { + Mutex::Autolock lock(mMutex); + return mBufferQueue->setTransformHint(hint); +} + +// Used for refactoring BufferQueue from SurfaceTexture +// Should not be in final interface once users of SurfaceTexture are clean up. +status_t SurfaceTexture::setSynchronousMode(bool enabled) { + Mutex::Autolock lock(mMutex); + return mBufferQueue->setSynchronousMode(enabled); +} + +// Used for refactoring, should not be in final interface +sp SurfaceTexture::getBufferQueue() const { + Mutex::Autolock lock(mMutex); + return mBufferQueue; +} + +void SurfaceTexture::onFrameAvailable() { + ST_LOGV("onFrameAvailable"); + + sp listener; + { // scope for the lock + Mutex::Autolock lock(mMutex); + listener = mFrameAvailableListener; + } + + if (listener != NULL) { + ST_LOGV("actually calling onFrameAvailable"); + listener->onFrameAvailable(); + } +} + +void SurfaceTexture::onBuffersReleased() { + ST_LOGV("onBuffersReleased"); + + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + // Nothing to do if we're already abandoned. + return; + } + + uint32_t mask = 0; + mBufferQueue->getReleasedBuffers(&mask); + for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) { + if (mask & (1 << i)) { + freeBufferLocked(i); + } + } +} + +void SurfaceTexture::dump(String8& result) const +{ + char buffer[1024]; + dump(result, "", buffer, 1024); +} + +void SurfaceTexture::dump(String8& result, const char* prefix, + char* buffer, size_t SIZE) const +{ + Mutex::Autolock _l(mMutex); + snprintf(buffer, SIZE, "%smTexName=%d, mAbandoned=%d\n", prefix, mTexName, + int(mAbandoned)); + result.append(buffer); + + snprintf(buffer, SIZE, + "%snext : {crop=[%d,%d,%d,%d], transform=0x%02x, current=%d}\n", + prefix, mCurrentCrop.left, + mCurrentCrop.top, mCurrentCrop.right, mCurrentCrop.bottom, + mCurrentTransform, mCurrentTexture + ); + result.append(buffer); + + if (!mAbandoned) { + mBufferQueue->dump(result, prefix, buffer, SIZE); + } +} + +static void mtxMul(float out[16], const float a[16], const float b[16]) { + out[0] = a[0]*b[0] + a[4]*b[1] + a[8]*b[2] + a[12]*b[3]; + out[1] = a[1]*b[0] + a[5]*b[1] + a[9]*b[2] + a[13]*b[3]; + out[2] = a[2]*b[0] + a[6]*b[1] + a[10]*b[2] + a[14]*b[3]; + out[3] = a[3]*b[0] + a[7]*b[1] + a[11]*b[2] + a[15]*b[3]; + + out[4] = a[0]*b[4] + a[4]*b[5] + a[8]*b[6] + a[12]*b[7]; + out[5] = a[1]*b[4] + a[5]*b[5] + a[9]*b[6] + a[13]*b[7]; + out[6] = a[2]*b[4] + a[6]*b[5] + a[10]*b[6] + a[14]*b[7]; + out[7] = a[3]*b[4] + a[7]*b[5] + a[11]*b[6] + a[15]*b[7]; + + out[8] = a[0]*b[8] + a[4]*b[9] + a[8]*b[10] + a[12]*b[11]; + out[9] = a[1]*b[8] + a[5]*b[9] + a[9]*b[10] + a[13]*b[11]; + out[10] = a[2]*b[8] + a[6]*b[9] + a[10]*b[10] + a[14]*b[11]; + out[11] = a[3]*b[8] + a[7]*b[9] + a[11]*b[10] + a[15]*b[11]; + + out[12] = a[0]*b[12] + a[4]*b[13] + a[8]*b[14] + a[12]*b[15]; + out[13] = a[1]*b[12] + a[5]*b[13] + a[9]*b[14] + a[13]*b[15]; + out[14] = a[2]*b[12] + a[6]*b[13] + a[10]*b[14] + a[14]*b[15]; + out[15] = a[3]*b[12] + a[7]*b[13] + a[11]*b[14] + a[15]*b[15]; +} + +}; // namespace android diff --git a/src/imports/nativemedia/SurfaceTexture_4_1.h b/src/imports/nativemedia/SurfaceTexture_4_1.h new file mode 100644 index 0000000..33aac7f --- /dev/null +++ b/src/imports/nativemedia/SurfaceTexture_4_1.h @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2010 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_SURFACETEXTURE_H +#define ANDROID_GUI_SURFACETEXTURE_H + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +#define ANDROID_GRAPHICS_SURFACETEXTURE_JNI_ID "mSurfaceTexture" + +namespace android { +// ---------------------------------------------------------------------------- + + +class String8; + +class SurfaceTexture : public virtual RefBase, + protected BufferQueue::ConsumerListener { +public: + struct FrameAvailableListener : public virtual RefBase { + // onFrameAvailable() is called each time an additional frame becomes + // available for consumption. This means that frames that are queued + // while in asynchronous mode only trigger the callback if no previous + // frames are pending. Frames queued while in synchronous mode always + // trigger the callback. + // + // This is called without any lock held and can be called concurrently + // by multiple threads. + virtual void onFrameAvailable() = 0; + }; + + // SurfaceTexture constructs a new SurfaceTexture object. tex indicates the + // name of the OpenGL ES texture to which images are to be streamed. + // allowSynchronousMode specifies whether or not synchronous mode can be + // enabled. texTarget specifies the OpenGL ES texture target to which the + // texture will be bound in updateTexImage. useFenceSync specifies whether + // fences should be used to synchronize access to buffers if that behavior + // is enabled at compile-time. A custom bufferQueue can be specified + // if behavior for queue/dequeue/connect etc needs to be customized. + // Otherwise a default BufferQueue will be created and used. + // + // For legacy reasons, the SurfaceTexture is created in a state where it is + // considered attached to an OpenGL ES context for the purposes of the + // attachToContext and detachFromContext methods. However, despite being + // considered "attached" to a context, the specific OpenGL ES context + // doesn't get latched until the first call to updateTexImage. After that + // point, all calls to updateTexImage must be made with the same OpenGL ES + // context current. + // + // A SurfaceTexture may be detached from one OpenGL ES context and then + // attached to a different context using the detachFromContext and + // attachToContext methods, respectively. The intention of these methods is + // purely to allow a SurfaceTexture to be transferred from one consumer + // context to another. If such a transfer is not needed there is no + // requirement that either of these methods be called. + SurfaceTexture(GLuint tex, bool allowSynchronousMode = true, + GLenum texTarget = GL_TEXTURE_EXTERNAL_OES, bool useFenceSync = true, + const sp &bufferQueue = 0); + + virtual ~SurfaceTexture(); + + // updateTexImage sets the image contents of the target texture to that of + // the most recently queued buffer. + // + // This call may only be made while the OpenGL ES context to which the + // target texture belongs is bound to the calling thread. + status_t updateTexImage(); + + // setBufferCountServer set the buffer count. If the client has requested + // a buffer count using setBufferCount, the server-buffer count will + // take effect once the client sets the count back to zero. + status_t setBufferCountServer(int bufferCount); + + // getTransformMatrix retrieves the 4x4 texture coordinate transform matrix + // associated with the texture image set by the most recent call to + // updateTexImage. + // + // This transform matrix maps 2D homogeneous texture coordinates of the form + // (s, t, 0, 1) with s and t in the inclusive range [0, 1] to the texture + // coordinate that should be used to sample that location from the texture. + // Sampling the texture outside of the range of this transform is undefined. + // + // This transform is necessary to compensate for transforms that the stream + // content producer may implicitly apply to the content. By forcing users of + // a SurfaceTexture to apply this transform we avoid performing an extra + // copy of the data that would be needed to hide the transform from the + // user. + // + // The matrix is stored in column-major order so that it may be passed + // directly to OpenGL ES via the glLoadMatrixf or glUniformMatrix4fv + // functions. + void getTransformMatrix(float mtx[16]); + + // getTimestamp retrieves the timestamp associated with the texture image + // set by the most recent call to updateTexImage. + // + // The timestamp is in nanoseconds, and is monotonically increasing. Its + // other semantics (zero point, etc) are source-dependent and should be + // documented by the source. + int64_t getTimestamp(); + + // setFrameAvailableListener sets the listener object that will be notified + // when a new frame becomes available. + void setFrameAvailableListener(const sp& listener); + + // getAllocator retrieves the binder object that must be referenced as long + // as the GraphicBuffers dequeued from this SurfaceTexture are referenced. + // Holding this binder reference prevents SurfaceFlinger from freeing the + // buffers before the client is done with them. + sp getAllocator(); + + // setDefaultBufferSize is used to set the size of buffers returned by + // requestBuffers when a with and height of zero is requested. + // A call to setDefaultBufferSize() may trigger requestBuffers() to + // be called from the client. + // The width and height parameters must be no greater than the minimum of + // GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv). + // An error due to invalid dimensions might not be reported until + // updateTexImage() is called. + status_t setDefaultBufferSize(uint32_t width, uint32_t height); + + // setFilteringEnabled sets whether the transform matrix should be computed + // for use with bilinear filtering. + void setFilteringEnabled(bool enabled); + + // getCurrentBuffer returns the buffer associated with the current image. + sp getCurrentBuffer() const; + + // getCurrentTextureTarget returns the texture target of the current + // texture as returned by updateTexImage(). + GLenum getCurrentTextureTarget() const; + + // getCurrentCrop returns the cropping rectangle of the current buffer. + Rect getCurrentCrop() const; + + // getCurrentTransform returns the transform of the current buffer. + uint32_t getCurrentTransform() const; + + // getCurrentScalingMode returns the scaling mode of the current buffer. + uint32_t getCurrentScalingMode() const; + + // isSynchronousMode returns whether the SurfaceTexture is currently in + // synchronous mode. + bool isSynchronousMode() const; + + // abandon frees all the buffers and puts the SurfaceTexture into the + // 'abandoned' state. Once put in this state the SurfaceTexture can never + // leave it. When in the 'abandoned' state, all methods of the + // ISurfaceTexture interface will fail with the NO_INIT error. + // + // Note that while calling this method causes all the buffers to be freed + // from the perspective of the the SurfaceTexture, if there are additional + // references on the buffers (e.g. if a buffer is referenced by a client or + // by OpenGL ES as a texture) then those buffer will remain allocated. + void abandon(); + + // set the name of the SurfaceTexture that will be used to identify it in + // log messages. + void setName(const String8& name); + + // These functions call the corresponding BufferQueue implementation + // so the refactoring can proceed smoothly + status_t setDefaultBufferFormat(uint32_t defaultFormat); + status_t setConsumerUsageBits(uint32_t usage); + status_t setTransformHint(uint32_t hint); + virtual status_t setSynchronousMode(bool enabled); + + // getBufferQueue returns the BufferQueue object to which this + // SurfaceTexture is connected. + sp getBufferQueue() const; + + // detachFromContext detaches the SurfaceTexture from the calling thread's + // current OpenGL ES context. This context must be the same as the context + // that was current for previous calls to updateTexImage. + // + // Detaching a SurfaceTexture from an OpenGL ES context will result in the + // deletion of the OpenGL ES texture object into which the images were being + // streamed. After a SurfaceTexture has been detached from the OpenGL ES + // context calls to updateTexImage will fail returning INVALID_OPERATION + // until the SurfaceTexture is attached to a new OpenGL ES context using the + // attachToContext method. + status_t detachFromContext(); + + // attachToContext attaches a SurfaceTexture that is currently in the + // 'detached' state to the current OpenGL ES context. A SurfaceTexture is + // in the 'detached' state iff detachFromContext has successfully been + // called and no calls to attachToContext have succeeded since the last + // detachFromContext call. Calls to attachToContext made on a + // SurfaceTexture that is not in the 'detached' state will result in an + // INVALID_OPERATION error. + // + // The tex argument specifies the OpenGL ES texture object name in the + // new context into which the image contents will be streamed. A successful + // call to attachToContext will result in this texture object being bound to + // the texture target and populated with the image contents that were + // current at the time of the last call to detachFromContext. + status_t attachToContext(GLuint tex); + + // dump our state in a String + virtual void dump(String8& result) const; + virtual void dump(String8& result, const char* prefix, char* buffer, size_t SIZE) const; + +protected: + + // Implementation of the BufferQueue::ConsumerListener interface. These + // calls are used to notify the SurfaceTexture of asynchronous events in the + // BufferQueue. + virtual void onFrameAvailable(); + virtual void onBuffersReleased(); + + static bool isExternalFormat(uint32_t format); + +private: + // this version of updateTexImage() takes a functor used to reject or not + // the newly acquired buffer. + // this API is TEMPORARY and intended to be used by SurfaceFlinger only, + // which is why class Layer is made a friend of SurfaceTexture below. + class BufferRejecter { + friend class SurfaceTexture; + virtual bool reject(const sp& buf, + const BufferQueue::BufferItem& item) = 0; + protected: + virtual ~BufferRejecter() { } + }; + friend class Layer; + status_t updateTexImage(BufferRejecter* rejecter); + + // createImage creates a new EGLImage from a GraphicBuffer. + EGLImageKHR createImage(EGLDisplay dpy, + const sp& graphicBuffer); + + // freeBufferLocked frees up the given buffer slot. If the slot has been + // initialized this will release the reference to the GraphicBuffer in that + // slot and destroy the EGLImage in that slot. Otherwise it has no effect. + // + // This method must be called with mMutex locked. + void freeBufferLocked(int slotIndex); + + // computeCurrentTransformMatrix computes the transform matrix for the + // current texture. It uses mCurrentTransform and the current GraphicBuffer + // to compute this matrix and stores it in mCurrentTransformMatrix. + void computeCurrentTransformMatrix(); + + // syncForReleaseLocked performs the synchronization needed to release the + // current slot from an OpenGL ES context. If needed it will set the + // current slot's fence to guard against a producer accessing the buffer + // before the outstanding accesses have completed. + status_t syncForReleaseLocked(EGLDisplay dpy); + + // The default consumer usage flags that SurfaceTexture always sets on its + // BufferQueue instance; these will be OR:d with any additional flags passed + // from the SurfaceTexture user. In particular, SurfaceTexture will always + // consume buffers as hardware textures. + static const uint32_t DEFAULT_USAGE_FLAGS = GraphicBuffer::USAGE_HW_TEXTURE; + + // mCurrentTextureBuf is the graphic buffer of the current texture. It's + // possible that this buffer is not associated with any buffer slot, so we + // must track it separately in order to support the getCurrentBuffer method. + sp mCurrentTextureBuf; + + // mCurrentCrop is the crop rectangle that applies to the current texture. + // It gets set each time updateTexImage is called. + Rect mCurrentCrop; + + // mCurrentTransform is the transform identifier for the current texture. It + // gets set each time updateTexImage is called. + uint32_t mCurrentTransform; + + // mCurrentScalingMode is the scaling mode for the current texture. It gets + // set to each time updateTexImage is called. + uint32_t mCurrentScalingMode; + + // mCurrentTransformMatrix is the transform matrix for the current texture. + // It gets computed by computeTransformMatrix each time updateTexImage is + // called. + float mCurrentTransformMatrix[16]; + + // mCurrentTimestamp is the timestamp for the current texture. It + // gets set each time updateTexImage is called. + int64_t mCurrentTimestamp; + + uint32_t mDefaultWidth, mDefaultHeight; + + // mFilteringEnabled indicates whether the transform matrix is computed for + // use with bilinear filtering. It defaults to true and is changed by + // setFilteringEnabled(). + bool mFilteringEnabled; + + // mTexName is the name of the OpenGL texture to which streamed images will + // be bound when updateTexImage is called. It is set at construction time + // and can be changed with a call to attachToContext. + GLuint mTexName; + + // mUseFenceSync indicates whether creation of the EGL_KHR_fence_sync + // extension should be used to prevent buffers from being dequeued before + // it's safe for them to be written. It gets set at construction time and + // never changes. + const bool mUseFenceSync; + + // mTexTarget is the GL texture target with which the GL texture object is + // associated. It is set in the constructor and never changed. It is + // almost always GL_TEXTURE_EXTERNAL_OES except for one use case in Android + // Browser. In that case it is set to GL_TEXTURE_2D to allow + // glCopyTexSubImage to read from the texture. This is a hack to work + // around a GL driver limitation on the number of FBO attachments, which the + // browser's tile cache exceeds. + const GLenum mTexTarget; + + // EGLSlot contains the information and object references that + // SurfaceTexture maintains about a BufferQueue buffer slot. + struct EGLSlot { + EGLSlot() + : mEglImage(EGL_NO_IMAGE_KHR), + mFence(EGL_NO_SYNC_KHR) { + } + + sp mGraphicBuffer; + + // mEglImage is the EGLImage created from mGraphicBuffer. + EGLImageKHR mEglImage; + + // mFence is the EGL sync object that must signal before the buffer + // associated with this buffer slot may be dequeued. It is initialized + // to EGL_NO_SYNC_KHR when the buffer is created and (optionally, based + // on a compile-time option) set to a new sync object in updateTexImage. + EGLSyncKHR mFence; + }; + + // mEglDisplay is the EGLDisplay with which this SurfaceTexture is currently + // associated. It is intialized to EGL_NO_DISPLAY and gets set to the + // current display when updateTexImage is called for the first time and when + // attachToContext is called. + EGLDisplay mEglDisplay; + + // mEglContext is the OpenGL ES context with which this SurfaceTexture is + // currently associated. It is initialized to EGL_NO_CONTEXT and gets set + // to the current GL context when updateTexImage is called for the first + // time and when attachToContext is called. + EGLContext mEglContext; + + // mEGLSlots stores the buffers that have been allocated by the BufferQueue + // for each buffer slot. It is initialized to null pointers, and gets + // filled in with the result of BufferQueue::acquire when the + // client dequeues a buffer from a + // slot that has not yet been used. The buffer allocated to a slot will also + // be replaced if the requested buffer usage or geometry differs from that + // of the buffer allocated to a slot. + EGLSlot mEGLSlots[BufferQueue::NUM_BUFFER_SLOTS]; + + // mAbandoned indicates that the BufferQueue will no longer be used to + // consume images buffers pushed to it using the ISurfaceTexture interface. + // It is initialized to false, and set to true in the abandon method. A + // BufferQueue that has been abandoned will return the NO_INIT error from + // all ISurfaceTexture methods capable of returning an error. + bool mAbandoned; + + // 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 mFrameAvailableListener; + + // mCurrentTexture is the buffer slot index of the buffer that is currently + // bound to the OpenGL texture. It is initialized to INVALID_BUFFER_SLOT, + // indicating that no buffer slot is currently bound to the texture. Note, + // however, that a value of INVALID_BUFFER_SLOT does not necessarily mean + // that no buffer is bound to the texture. A call to setBufferCount will + // reset mCurrentTexture to INVALID_BUFFER_SLOT. + int mCurrentTexture; + + // The SurfaceTexture has-a BufferQueue and is responsible for creating this object + // if none is supplied + sp mBufferQueue; + + // mAttached indicates whether the SurfaceTexture is currently attached to + // an OpenGL ES context. For legacy reasons, this is initialized to true, + // indicating that the SurfaceTexture is considered to be attached to + // whatever context is current at the time of the first updateTexImage call. + // It is set to false by detachFromContext, and then set to true again by + // attachToContext. + bool mAttached; + + // mMutex is the mutex used to prevent concurrent access to the member + // variables of SurfaceTexture objects. It must be locked whenever the + // member variables are accessed. + mutable Mutex mMutex; +}; + +// ---------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_GUI_SURFACETEXTURE_H diff --git a/src/imports/nativemedia/main.cpp b/src/imports/nativemedia/main.cpp new file mode 100644 index 0000000..66775f6 --- /dev/null +++ b/src/imports/nativemedia/main.cpp @@ -0,0 +1,55 @@ +#include + +void initializeOMX(); +void render(QOpenGLContext *context, QWindow *surface); + +class Renderer : public QObject +{ + Q_OBJECT +public: + Renderer(QWindow *surface) + : surface(surface) + { + context = new QOpenGLContext; + + context->create(); + context->makeCurrent(surface); + + initializeOMX(); + qDebug() << "OMX initialized"; + } + +public slots: + void render() { + ::render(context, surface); + } + +public: + QWindow *surface; + QOpenGLContext *context; +} *renderer; + +void triggerRender() +{ + QTimer::singleShot(0, renderer, SLOT(render())); +} + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QScreen *screen = QGuiApplication::primaryScreen(); + + qDebug() << "Screen geometry:" << screen->geometry(); + + QWindow window; + window.setSurfaceType(QWindow::OpenGLSurface); + window.setGeometry(screen->geometry()); + window.show(); + + renderer = new Renderer(&window); + + return app.exec(); +} + +#include "main.moc" diff --git a/src/imports/nativemedia/nativemedia.pro b/src/imports/nativemedia/nativemedia.pro new file mode 100644 index 0000000..027608d --- /dev/null +++ b/src/imports/nativemedia/nativemedia.pro @@ -0,0 +1,21 @@ +###################################################################### +# Automatically generated by qmake (3.0) Thu Mar 7 13:00:32 2013 +###################################################################### + +CXX_MODULE = qml +TARGET = android_omx +TARGETPATH = QtAndroidOmx +IMPORT_VERSION = 1.0 + +INCLUDEPATH += $$ANDROID_BUILD_TOP/development/ndk/platforms/android-14/include/ + +LIBS += -lOpenMAXAL -lui -lgui -lutils -lcutils -lbinder + +QT += qml quick + +# Input +HEADERS += omxnode.h omxplayer.h +SOURCES += omx.cpp SurfaceTexture.cpp BufferQueue.cpp omxnode.cpp omxmodule.cpp + +load(qml_plugin) + diff --git a/src/imports/nativemedia/omx.cpp b/src/imports/nativemedia/omx.cpp new file mode 100644 index 0000000..15307df --- /dev/null +++ b/src/imports/nativemedia/omx.cpp @@ -0,0 +1,648 @@ +#include +#include + +#include +#include + +#include "omxplayer.h" + +#include + +#include + +// number of required interfaces for the MediaPlayer creation +#define NB_MAXAL_INTERFACES 3 // XAAndroidBufferQueueItf, XAStreamInformationItf and XAPlayItf + +// number of buffers in our buffer queue, an arbitrary number +#define NB_BUFFERS 8 + +// we're streaming MPEG-2 transport stream data, operate on transport stream block size +#define MPEG2_TS_PACKET_SIZE 188 + +// number of MPEG-2 transport stream blocks per buffer, an arbitrary number +#define PACKETS_PER_BUFFER 10 + +// determines how much memory we're dedicating to memory caching +#define BUFFER_SIZE (PACKETS_PER_BUFFER*MPEG2_TS_PACKET_SIZE) + +using namespace android; + +// constant to identify a buffer context which is the end of the stream to decode +static const int kEosBufferCntxt = 1980; // a magic value we can compare against + +OmxPlayer::~OmxPlayer() +{ +} + +struct OmxContext : public OmxPlayer { + // engine interfaces + XAObjectItf engineObject; + XAEngineItf engineEngine; + + // output mix interfaces + XAObjectItf outputMixObject; + + // streaming media player interfaces + XAObjectItf playerObj; + XAPlayItf playerPlayItf; + XAAndroidBufferQueueItf playerBQItf; + XAStreamInformationItf playerStreamInfoItf; + XAVolumeItf playerVolItf; + + // where we cache in memory the data to play + // note this memory is re-used by the buffer queue callback + char dataCache[BUFFER_SIZE * NB_BUFFERS]; + + // handle of the file to play + FILE *file; + + // has the app reached the end of the file + bool reachedEof; + + // For mutual exclusion between callback thread and application thread(s). + // The mutex protects reachedEof, discontinuity, + // The condition is signalled when a discontinuity is acknowledged. + + pthread_mutex_t mutex; + pthread_cond_t cond; + + sp surfaceTexture; + sp surfaceTextureClient; + + int currentFrame; + GLuint textureId; + + // for render, useful for debugging + QOpenGLShaderProgram *program; + + int vertexLocation; + int texCoordLocation; + int textureLocation; + + OmxContext() + : engineObject(0) + , engineEngine(0) + , outputMixObject(0) + , playerObj(0) + , playerPlayItf(0) + , playerBQItf(0) + , playerStreamInfoItf(0) + , playerVolItf(0) + , file(0) + , reachedEof(false) + , mutex(PTHREAD_MUTEX_INITIALIZER) + , cond(PTHREAD_COND_INITIALIZER) + , currentFrame(-1) + , textureId(0) + , program(0) + , vertexLocation(0) + , texCoordLocation(0) + , textureLocation(0) + { + } + + ~OmxContext(); + + void updateTexture() + { + if (surfaceTexture.get()) + surfaceTexture->updateTexImage(); + } + + bool hasFrame() + { + return currentFrame >= 0; + } + + void setPaused(bool paused); + + bool initialize(const QByteArray &filename); + bool enqueueInitialBuffers(bool discontinuity); + + XAresult AndroidBufferQueueCallback( + XAAndroidBufferQueueItf caller, + void *pCallbackContext, + void *pBufferContext, + void *pBufferData, + XAuint32 dataSize, + XAuint32 dataUsed, + const XAAndroidBufferItem *pItems, + XAuint32 itemsLength); + + void StreamChangeCallback(XAStreamInformationItf caller, + XAuint32 eventId, + XAuint32 streamIndex, + void * pEventData, + void * pContext ); + + void render(QOpenGLContext *context, QWindow *surface); +}; + +static XAresult AndroidBufferQueueCallback( + XAAndroidBufferQueueItf caller, + void *pCallbackContext, /* input */ + void *pBufferContext, /* input */ + void *pBufferData, /* input */ + XAuint32 dataSize, /* input */ + XAuint32 dataUsed, /* input */ + const XAAndroidBufferItem *pItems,/* input */ + XAuint32 itemsLength /* input */) +{ + return static_cast(pCallbackContext)->AndroidBufferQueueCallback( + caller, pCallbackContext, pBufferContext, pBufferData, dataSize, dataUsed, pItems, itemsLength); +} + +// AndroidBufferQueueItf callback to supply MPEG-2 TS packets to the media player +XAresult OmxContext::AndroidBufferQueueCallback( + XAAndroidBufferQueueItf caller, + void *pCallbackContext, /* input */ + void *pBufferContext, /* input */ + void *pBufferData, /* input */ + XAuint32 dataSize, /* input */ + XAuint32 dataUsed, /* input */ + const XAAndroidBufferItem *pItems,/* input */ + XAuint32 itemsLength /* input */) +{ + XAresult res; + int ok; + + // note there is never any contention on this mutex unless a discontinuity request is active + ok = pthread_mutex_lock(&mutex); + assert(0 == ok); + +#if 0 + // was a discontinuity requested? + if (discontinuity) { + // Note: can't rewind after EOS, which we send when reaching EOF + // (don't send EOS if you plan to play more content through the same player) + if (!reachedEof) { + // clear the buffer queue + res = (*playerBQItf)->Clear(playerBQItf); + assert(XA_RESULT_SUCCESS == res); + // rewind the data source so we are guaranteed to be at an appropriate point + rewind(file); + // Enqueue the initial buffers, with a discontinuity indicator on first buffer + (void) enqueueInitialBuffers(JNI_TRUE); + } + // acknowledge the discontinuity request + discontinuity = JNI_FALSE; + ok = pthread_cond_signal(&cond); + assert(0 == ok); + goto exit; + } +#endif + + if ((pBufferData == NULL) && (pBufferContext != NULL)) { + const int processedCommand = *(int *)pBufferContext; + if (kEosBufferCntxt == processedCommand) { + qDebug("EOS was processed\n"); + // our buffer with the EOS message has been consumed + assert(0 == dataSize); + goto exit; + } + } + + // pBufferData is a pointer to a buffer that we previously Enqueued + assert((dataSize > 0) && ((dataSize % MPEG2_TS_PACKET_SIZE) == 0)); + assert(dataCache <= (char *) pBufferData && (char *) pBufferData < + &dataCache[BUFFER_SIZE * NB_BUFFERS]); + assert(0 == (((char *) pBufferData - dataCache) % BUFFER_SIZE)); + + // don't bother trying to read more data once we've hit EOF + if (reachedEof) { + goto exit; + } + + size_t nbRead; + // note we do call fread from multiple threads, but never concurrently + size_t bytesRead; + bytesRead = fread(pBufferData, 1, BUFFER_SIZE, file); + if (bytesRead > 0) { + if ((bytesRead % MPEG2_TS_PACKET_SIZE) != 0) { + qDebug("Dropping last packet because it is not whole"); + } + size_t packetsRead = bytesRead / MPEG2_TS_PACKET_SIZE; + size_t bufferSize = packetsRead * MPEG2_TS_PACKET_SIZE; + res = (*caller)->Enqueue(caller, NULL /*pBufferContext*/, + pBufferData /*pData*/, + bufferSize /*dataLength*/, + NULL /*pMsg*/, + 0 /*msgLength*/); + assert(XA_RESULT_SUCCESS == res); + } else { + // EOF or I/O error, signal EOS + XAAndroidBufferItem msgEos[1]; + msgEos[0].itemKey = XA_ANDROID_ITEMKEY_EOS; + msgEos[0].itemSize = 0; + // EOS message has no parameters, so the total size of the message is the size of the key + // plus the size if itemSize, both XAuint32 + res = (*caller)->Enqueue(caller, (void *)&kEosBufferCntxt /*pBufferContext*/, + NULL /*pData*/, 0 /*dataLength*/, + msgEos /*pMsg*/, + sizeof(XAuint32)*2 /*msgLength*/); + assert(XA_RESULT_SUCCESS == res); + reachedEof = true; + } + +exit: + ok = pthread_mutex_unlock(&mutex); + assert(0 == ok); + return XA_RESULT_SUCCESS; +} + +// callback invoked whenever there is new or changed stream information +static void StreamChangeCallback(XAStreamInformationItf caller, + XAuint32 eventId, + XAuint32 streamIndex, + void * pEventData, + void * pContext ) +{ + static_cast(pContext)->StreamChangeCallback( + caller, eventId, streamIndex, pEventData, pContext); +} + +// callback invoked whenever there is new or changed stream information +void OmxContext::StreamChangeCallback(XAStreamInformationItf caller, + XAuint32 eventId, + XAuint32 streamIndex, + void * pEventData, + void * pContext ) +{ + qDebug("StreamChangeCallback called for stream %u", streamIndex); + + switch (eventId) { + case XA_STREAMCBEVENT_PROPERTYCHANGE: { + /** From spec 1.0.1: + "This event indicates that stream property change has occurred. + The streamIndex parameter identifies the stream with the property change. + The pEventData parameter for this event is not used and shall be ignored." + */ + + XAresult res; + XAuint32 domain; + res = (*caller)->QueryStreamType(caller, streamIndex, &domain); + assert(XA_RESULT_SUCCESS == res); + switch (domain) { + case XA_DOMAINTYPE_VIDEO: { + XAVideoStreamInformation videoInfo; + res = (*caller)->QueryStreamInformation(caller, streamIndex, &videoInfo); + assert(XA_RESULT_SUCCESS == res); + qDebug("Found video size %u x %u, codec ID=%u, frameRate=%u, bitRate=%u, duration=%u ms", + videoInfo.width, videoInfo.height, videoInfo.codecId, videoInfo.frameRate, + videoInfo.bitRate, videoInfo.duration); + emit videoSize(videoInfo.width, videoInfo.height); + } break; + default: + fprintf(stderr, "Unexpected domain %u\n", domain); + break; + } + } break; + default: + fprintf(stderr, "Unexpected stream event ID %u\n", eventId); + break; + } +} + +// Enqueue the initial buffers, and optionally signal a discontinuity in the first buffer +bool OmxContext::enqueueInitialBuffers(bool discontinuity) +{ + + /* Fill our cache. + * We want to read whole packets (integral multiples of MPEG2_TS_PACKET_SIZE). + * fread returns units of "elements" not bytes, so we ask for 1-byte elements + * and then check that the number of elements is a multiple of the packet size. + */ + size_t bytesRead; + bytesRead = fread(dataCache, 1, BUFFER_SIZE * NB_BUFFERS, file); + if (bytesRead <= 0) { + // could be premature EOF or I/O error + return false; + } + if ((bytesRead % MPEG2_TS_PACKET_SIZE) != 0) { + qDebug("Dropping last packet because it is not whole"); + } + size_t packetsRead = bytesRead / MPEG2_TS_PACKET_SIZE; + qDebug("Initially queueing %u packets", packetsRead); + + /* Enqueue the content of our cache before starting to play, + we don't want to starve the player */ + size_t i; + for (i = 0; i < NB_BUFFERS && packetsRead > 0; i++) { + // compute size of this buffer + size_t packetsThisBuffer = packetsRead; + if (packetsThisBuffer > PACKETS_PER_BUFFER) { + packetsThisBuffer = PACKETS_PER_BUFFER; + } + size_t bufferSize = packetsThisBuffer * MPEG2_TS_PACKET_SIZE; + XAresult res; + if (discontinuity) { + // signal discontinuity + XAAndroidBufferItem items[1]; + items[0].itemKey = XA_ANDROID_ITEMKEY_DISCONTINUITY; + items[0].itemSize = 0; + // DISCONTINUITY message has no parameters, + // so the total size of the message is the size of the key + // plus the size if itemSize, both XAuint32 + res = (*playerBQItf)->Enqueue(playerBQItf, NULL /*pBufferContext*/, + dataCache + i*BUFFER_SIZE, bufferSize, items /*pMsg*/, + sizeof(XAuint32)*2 /*msgLength*/); + //discontinuity = false; + } else { + res = (*playerBQItf)->Enqueue(playerBQItf, NULL /*pBufferContext*/, + dataCache + i*BUFFER_SIZE, bufferSize, NULL, 0); + } + assert(XA_RESULT_SUCCESS == res); + packetsRead -= packetsThisBuffer; + } + + return true; +} + +class FrameCallback : public SurfaceTexture::FrameAvailableListener +{ +public: + FrameCallback(OmxContext *ctx) + : context(ctx) + { + } + + void onFrameAvailable() + { + emit context->frameAvailable(); + ++context->currentFrame; + } + +private: + OmxContext *context; +}; + +OmxPlayer *OmxPlayer::create() +{ + return new OmxContext; +} + +bool OmxContext::initialize(const QByteArray &filename) +{ + file = fopen(filename.data(), "rb"); + if (!file) { + qWarning("Failed to open %s", filename.data()); + return false; + } + + XAresult res; + + qDebug() << "Creating engine object"; + + // create engine + res = xaCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); + assert(XA_RESULT_SUCCESS == res); + + qDebug() << "Creating engine object succeeded?" << (res == XA_RESULT_SUCCESS); + + // realize the engine + res = (*engineObject)->Realize(engineObject, XA_BOOLEAN_FALSE); + assert(XA_RESULT_SUCCESS == res); + + qDebug() << "Realizing engine object succeeded?" << (res == XA_RESULT_SUCCESS); + + // get the engine interface, which is needed in order to create other objects + res = (*engineObject)->GetInterface(engineObject, XA_IID_ENGINE, &engineEngine); + assert(XA_RESULT_SUCCESS == res); + + qDebug() << "Getting engine interface succeeded?" << (res == XA_RESULT_SUCCESS); + + // create output mix + res = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL); + assert(XA_RESULT_SUCCESS == res); + + qDebug() << "Creating output mix succeeded?" << (res == XA_RESULT_SUCCESS); + + // realize the output mix + res = (*outputMixObject)->Realize(outputMixObject, XA_BOOLEAN_FALSE); + assert(XA_RESULT_SUCCESS == res); + + qDebug() << "Realizing output mix succeeded?" << (res == XA_RESULT_SUCCESS); + + // configure data source + XADataLocator_AndroidBufferQueue loc_abq = { XA_DATALOCATOR_ANDROIDBUFFERQUEUE, NB_BUFFERS }; + XADataFormat_MIME format_mime = { + XA_DATAFORMAT_MIME, XA_ANDROID_MIME_MP2TS, XA_CONTAINERTYPE_MPEG_TS }; + XADataSource dataSrc = {&loc_abq, &format_mime}; + + // configure audio sink + XADataLocator_OutputMix loc_outmix = { XA_DATALOCATOR_OUTPUTMIX, outputMixObject }; + XADataSink audioSnk = { &loc_outmix, 0 }; + + qDebug() << "Creating SurfaceTexture"; + + glGenTextures(1, &textureId); + surfaceTexture = new android::SurfaceTexture(textureId); + + qDebug() << "Creating SurfaceTextureClient"; + + android::sp surfaceTextureClient = new android::SurfaceTextureClient(surfaceTexture); + + sp listener = new FrameCallback(this); + + surfaceTexture->setFrameAvailableListener(listener); + + // configure image video sink + XADataLocator_NativeDisplay loc_nd = { + XA_DATALOCATOR_NATIVEDISPLAY, // locatorType + // the video sink must be an ANativeWindow created from a Surface or SurfaceTexture + static_cast(surfaceTextureClient.get()), // hWindow + // must be 0 + 0 // hDisplay + }; + XADataSink imageVideoSink = {&loc_nd, 0}; + + // declare interfaces to use + XAboolean required[NB_MAXAL_INTERFACES] + = {XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE, XA_BOOLEAN_TRUE}; + XAInterfaceID iidArray[NB_MAXAL_INTERFACES] + = {XA_IID_PLAY, XA_IID_ANDROIDBUFFERQUEUESOURCE, + XA_IID_STREAMINFORMATION}; + + + qDebug() << "Creating media player for engine" << engineEngine; + + // create media player + res = (*engineEngine)->CreateMediaPlayer(engineEngine, &playerObj, &dataSrc, + 0, &audioSnk, &imageVideoSink, 0, 0, + NB_MAXAL_INTERFACES /*XAuint32 numInterfaces*/, + iidArray /*const XAInterfaceID *pInterfaceIds*/, + required /*const XAboolean *pInterfaceRequired*/); + + qDebug() << "CreateMediaPlayer succeeded?" << (res == XA_RESULT_SUCCESS); + + // realize the player + res = (*playerObj)->Realize(playerObj, XA_BOOLEAN_FALSE); + + qDebug() << "Realize media player succeeded?" << (res == XA_RESULT_SUCCESS); + + // get the play interface + res = (*playerObj)->GetInterface(playerObj, XA_IID_PLAY, &playerPlayItf); + + qDebug() << "Get play interface succeeded?" << (res == XA_RESULT_SUCCESS); + + // get the stream information interface (for video size) + res = (*playerObj)->GetInterface(playerObj, XA_IID_STREAMINFORMATION, &playerStreamInfoItf); + + qDebug() << "Get stream information interface succeeded?" << (res == XA_RESULT_SUCCESS); + + // get the volume interface + res = (*playerObj)->GetInterface(playerObj, XA_IID_VOLUME, &playerVolItf); + + qDebug() << "Get volume interface succeeded?" << (res == XA_RESULT_SUCCESS); + + // get the Android buffer queue interface + res = (*playerObj)->GetInterface(playerObj, XA_IID_ANDROIDBUFFERQUEUESOURCE, &playerBQItf); + + qDebug() << "Get buffer queue interface succeeded?" << (res == XA_RESULT_SUCCESS); + + // specify which events we want to be notified of + res = (*playerBQItf)->SetCallbackEventsMask(playerBQItf, XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED); + + qDebug() << "Setting callback events mask succeeded?" << (res == XA_RESULT_SUCCESS); + + // register the callback from which OpenMAX AL can retrieve the data to play + res = (*playerBQItf)->RegisterCallback(playerBQItf, ::AndroidBufferQueueCallback, this); + + qDebug() << "Registering buffer queue callback succeeded?" << (res == XA_RESULT_SUCCESS); + + // we want to be notified of the video size once it's found, so we register a callback for that + res = (*playerStreamInfoItf)->RegisterStreamChangeCallback(playerStreamInfoItf, + ::StreamChangeCallback, this); + + qDebug() << "Registering stream change callback succeeded?" << (res == XA_RESULT_SUCCESS); + + // enqueue the initial buffers + if (!enqueueInitialBuffers(false)) + return false; + + qDebug() << "Enqueued initial buffers"; + + // prepare the player + res = (*playerPlayItf)->SetPlayState(playerPlayItf, XA_PLAYSTATE_PAUSED); + + qDebug() << "Preparing player (setting play state paused) succeeded?" << (res == XA_RESULT_SUCCESS); + + // set the volume + res = (*playerVolItf)->SetVolumeLevel(playerVolItf, 0); + + qDebug() << "Setting volume level succeeded?" << (res == XA_RESULT_SUCCESS); + + return true; +} + +OmxContext::~OmxContext() +{ + if (!file) + return; + + // destroy streaming media player object, and invalidate all associated interfaces + if (playerObj != NULL) { + (*playerObj)->Destroy(playerObj); + playerObj = NULL; + playerPlayItf = NULL; + playerBQItf = NULL; + playerStreamInfoItf = NULL; + playerVolItf = NULL; + } + + // destroy output mix object, and invalidate all associated interfaces + if (outputMixObject != NULL) { + (*outputMixObject)->Destroy(outputMixObject); + outputMixObject = NULL; + } + + // destroy engine object, and invalidate all associated interfaces + if (engineObject != NULL) { + (*engineObject)->Destroy(engineObject); + engineObject = NULL; + engineEngine = NULL; + } +} + +void OmxContext::setPaused(bool paused) +{ + XAresult res; + + // make sure the streaming media player was created + if (NULL != playerPlayItf) { + // set the player's state + res = (*playerPlayItf)->SetPlayState(playerPlayItf, paused ? + XA_PLAYSTATE_PAUSED : XA_PLAYSTATE_PLAYING); + } +} + +void OmxContext::render(QOpenGLContext *context, QWindow *surface) +{ + context->makeCurrent(surface); + + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + + if (currentFrame >= 0) { + surfaceTexture->updateTexImage(); + + if (!program) { + qDebug() << "Received first video frame, starting to render"; + + program = new QOpenGLShaderProgram; + + program->addShaderFromSourceCode(QOpenGLShader::Vertex, + "attribute highp vec4 vertex;\n" + "attribute highp vec2 texCoord;\n" + "varying highp vec2 coord;\n" + "void main(void)\n" + "{\n" + " coord = texCoord;\n" + " gl_Position = vertex;\n" + "}"); + program->addShaderFromSourceCode(QOpenGLShader::Fragment, + "#extension GL_OES_EGL_image_external : require\n" + "uniform samplerExternalOES tex;\n" + "varying highp vec2 coord;\n" + "void main(void)\n" + "{\n" + " gl_FragColor = texture2D(tex, coord);\n" + "}"); + + program->link(); + + vertexLocation = program->attributeLocation("vertex"); + texCoordLocation = program->attributeLocation("texCoord"); + textureLocation = program->uniformLocation("tex"); + + qDebug() << "attribute locations" << vertexLocation << texCoordLocation; + qDebug() << "uniform location" << textureLocation; + } + + program->bind(); + + static GLfloat const triangleVertices[] = { + -1, -3, + 3, 1, + -1, 1 + }; + + static GLfloat const triangleTexCoords[] = { + 0, 2, + 2, 0, + 0, 0 + }; + + program->enableAttributeArray(vertexLocation); + program->setAttributeArray(vertexLocation, triangleVertices, 2); + program->enableAttributeArray(texCoordLocation); + program->setAttributeArray(texCoordLocation, triangleTexCoords, 2); + + glDrawArrays(GL_TRIANGLES, 0, 3); + + program->disableAttributeArray(vertexLocation); + program->disableAttributeArray(texCoordLocation); + } + + context->swapBuffers(surface); +} diff --git a/src/imports/nativemedia/omxmodule.cpp b/src/imports/nativemedia/omxmodule.cpp new file mode 100644 index 0000000..e8737ce --- /dev/null +++ b/src/imports/nativemedia/omxmodule.cpp @@ -0,0 +1,19 @@ +#include "omxnode.h" +#include + +class QAndroidOmxModule : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface/1.0") + +public: + virtual void registerTypes(const char *uri) + { + Q_ASSERT(QLatin1String(uri) == QLatin1String("QtAndroidOmx")); + + qmlRegisterType(uri, 1, 0, "OmxItem"); + } +}; + +#include "omxmodule.moc" + diff --git a/src/imports/nativemedia/omxnode.cpp b/src/imports/nativemedia/omxnode.cpp new file mode 100644 index 0000000..79eeb16 --- /dev/null +++ b/src/imports/nativemedia/omxnode.cpp @@ -0,0 +1,166 @@ +#include "omxnode.h" + +#include +#include + +#include + +static const char omx_texture_material_vertex[] = + "uniform highp mat4 qt_Matrix; \n" + "attribute highp vec4 qt_VertexPosition; \n" + "attribute highp vec2 qt_VertexTexCoord; \n" + "varying highp vec2 qt_TexCoord; \n" + "void main() { \n" + " qt_TexCoord = qt_VertexTexCoord; \n" + " gl_Position = qt_Matrix * qt_VertexPosition; \n" + "}"; + + +static const char omx_texture_material_fragment[] = + "#extension GL_OES_EGL_image_external : require \n" + "varying highp vec2 qt_TexCoord; \n" + "uniform samplerExternalOES qt_Texture; \n" + "uniform lowp float qt_Opacity; \n" + "void main() { \n" + " gl_FragColor = texture2D(qt_Texture, qt_TexCoord) * qt_Opacity; \n" + "}"; + +QList OmxTextureMaterial::attributes() const +{ + QList attributeList; + attributeList << "qt_VertexPosition"; + attributeList << "qt_VertexTexCoord"; + return attributeList; +} + +void OmxTextureMaterial::updateState(const OmxTextureState *newState, const OmxTextureState *oldState) +{ + Q_UNUSED(oldState); + newState->player->updateTexture(); +} + +const char *OmxTextureMaterial::vertexShader() const +{ + return omx_texture_material_vertex; +} + +const char *OmxTextureMaterial::fragmentShader() const +{ + return omx_texture_material_fragment; +} + +OmxNode::OmxNode(OmxPlayer *player) + : m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4) + , m_player(player) + , m_initialized(false) +{ + m_textureMaterial = OmxTextureMaterial::createMaterial(); + m_textureMaterial->state()->player = player; + + setGeometry(&m_geometry); + setMaterial(m_textureMaterial); + + setFlag(UsePreprocess, true); +} + +OmxNode::~OmxNode() +{ + delete m_textureMaterial; +} + +void OmxNode::preprocess() +{ +} + +void OmxNode::updateTexture() +{ + printf("OmxNode::updateTexture()\n"); +} + +void OmxNode::setRect(const QRectF &rect) +{ + if (m_rect == rect) + return; + + printf("OmxNode::setRect(%f %f %f %f)\n", rect.x(), rect.y(), rect.width(), rect.height()); + m_rect = rect; + + QRectF sourceRect(0, 0, 1, 1); + QSGGeometry::updateTexturedRectGeometry(&m_geometry, m_rect, sourceRect); +} + +OmxItem::OmxItem() + : m_player(OmxPlayer::create()) + , m_hasFrame(false) + , m_initialized(false) + , m_sourceWidth(0) + , m_sourceHeight(0) + , m_paused(false) +{ + connect(m_player, SIGNAL(frameAvailable()), this, SLOT(triggerRender())); + connect(m_player, SIGNAL(videoSize(int, int)), this, SLOT(videoSize(int, int))); + + setFlag(ItemHasContents, true); +} + +OmxItem::~OmxItem() +{ + delete m_player; +} + +void OmxItem::triggerRender() +{ + m_hasFrame = true; + update(); +} + +void OmxItem::videoSize(int w, int h) +{ + m_sourceWidth = w; + m_sourceHeight = h; + + emit sourceWidthChanged(); + emit sourceHeightChanged(); +} + +void OmxItem::setSource(const QString &source) +{ + if (m_initialized || source == m_source) + return; + + m_source = source; + + m_initialized = m_player->initialize(m_source.toLocal8Bit()); + + // start playing if not paused + if (m_initialized && !paused()) + m_player->setPaused(false); + + emit sourceChanged(); +} + +QSGNode *OmxItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + if (!m_hasFrame) + return 0; + + OmxNode *node; + if (oldNode) + node = static_cast(oldNode); + else + node = new OmxNode(m_player); + + node->setRect(boundingRect()); + node->markDirty(QSGNode::DirtyMaterial); + + return node; +} + +void OmxItem::setPaused(bool p) +{ + if (p == m_paused) + return; + m_player->setPaused(p); + m_paused = p; + emit pausedChanged(); +} diff --git a/src/imports/nativemedia/omxnode.h b/src/imports/nativemedia/omxnode.h new file mode 100644 index 0000000..976830c --- /dev/null +++ b/src/imports/nativemedia/omxnode.h @@ -0,0 +1,110 @@ +#ifndef OMXNODE_H +#define OMXNODE_H + +#include + +#include +#include + +#include "SurfaceTexture.h" + +#include "omxplayer.h" + +class QSGTexture; + +struct OmxTextureState { + OmxPlayer *player; +}; + +class OmxNode : public QSGGeometryNode +{ +public: + OmxNode(OmxPlayer *player); + ~OmxNode(); + + void preprocess(); + void updateTexture(); + + void setRect(const QRectF &rect); + inline void setRect(qreal x, qreal y, qreal w, qreal h) { setRect(QRectF(x, y, w, h)); } + + bool isTextureUpdated() const { return m_textureUpdated; } + void setTextureUpdated(bool textureUpdated) { m_textureUpdated = textureUpdated; } + +private: + bool m_textureUpdated; + + QSGGeometry m_geometry; + QSGSimpleMaterial *m_textureMaterial; + + QRectF m_rect; + OmxPlayer *m_player; + bool m_initialized; +}; + +class OmxTextureMaterial : public QSGSimpleMaterialShader +{ + QSG_DECLARE_SIMPLE_SHADER(OmxTextureMaterial, OmxTextureState) +public: + QList attributes() const; + + void updateState(const OmxTextureState *newState, const OmxTextureState *oldState); + +protected: + const char *vertexShader() const; + const char *fragmentShader() const; +}; + +class OmxItem : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged FINAL) + Q_PROPERTY(int sourceWidth READ sourceWidth NOTIFY sourceWidthChanged FINAL) + Q_PROPERTY(int sourceHeight READ sourceHeight NOTIFY sourceHeightChanged FINAL) + Q_PROPERTY(bool paused READ paused WRITE setPaused NOTIFY pausedChanged FINAL) +public: + OmxItem(); + virtual ~OmxItem(); + + QString source() { + return m_source; + } + + int sourceWidth() { + return m_sourceWidth; + } + + int sourceHeight() { + return m_sourceHeight; + } + + bool paused() { return m_paused; } + void setPaused(bool p); + + void setSource(const QString &source); + +signals: + void sourceChanged(); + void sourceWidthChanged(); + void sourceHeightChanged(); + void pausedChanged(); + +protected: + QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); + +private slots: + void triggerRender(); + void videoSize(int w, int h); + +private: + OmxPlayer *m_player; + bool m_hasFrame; + bool m_initialized; + bool m_paused; + QString m_source; + int m_sourceWidth; + int m_sourceHeight; +}; + +#endif // OMXNODE_H + diff --git a/src/imports/nativemedia/omxplayer.h b/src/imports/nativemedia/omxplayer.h new file mode 100644 index 0000000..17544c1 --- /dev/null +++ b/src/imports/nativemedia/omxplayer.h @@ -0,0 +1,24 @@ +#ifndef OMXPLAYER_H +#define OMXPLAYER_H + +#include + +class OmxPlayer : public QObject +{ + Q_OBJECT +public: + virtual ~OmxPlayer() = 0; + + virtual void updateTexture() = 0; + virtual bool initialize(const QByteArray &filename) = 0; + virtual bool hasFrame() = 0; + virtual void setPaused(bool paused) = 0; + + static OmxPlayer *create(); + +signals: + void frameAvailable(); + void videoSize(int w, int h); +}; + +#endif diff --git a/src/imports/nativemedia/qmldir b/src/imports/nativemedia/qmldir new file mode 100644 index 0000000..0c62a0d --- /dev/null +++ b/src/imports/nativemedia/qmldir @@ -0,0 +1,2 @@ +module QtAndroidOmx +plugin declarative_android_omx diff --git a/src/imports/nativemedia/test.qml b/src/imports/nativemedia/test.qml new file mode 100644 index 0000000..8bbd4ce --- /dev/null +++ b/src/imports/nativemedia/test.qml @@ -0,0 +1,35 @@ +import QtQuick 2.0 +import QtAndroidOmx 1.0 + +Rectangle { + id: root + color: "white" + + Column { + width: parent.width + anchors.verticalCenter: parent.verticalCenter + + OmxItem { + id: omx + + width: root.width * 0.5 + height: width / aspect + + anchors.horizontalCenter: parent.horizontalCenter + anchors.margins: 40 + source: "NativeMedia.ts" + + property real aspect: sourceWidth / (sourceHeight > 0 ? sourceHeight : 1) + + MouseArea { + anchors.fill: parent + onClicked: parent.paused = !parent.paused + } + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: "Video size: " + omx.sourceWidth + "x" + omx.sourceHeight + } + } +} -- cgit v1.2.3