Remove GraphicBufferSource wrapper from OMX HAL: 1
Copy GraphicBufferSource impl to OMX HAL
Bug: 35449087
Test: Compiles
Change-Id: I793c918396da7c97ca9383cf6959a5334bfa5a75
diff --git a/media/libstagefright/omx/hal/1.0/impl/GraphicBufferSource.cpp b/media/libstagefright/omx/hal/1.0/impl/GraphicBufferSource.cpp
new file mode 100644
index 0000000..2f457ac
--- /dev/null
+++ b/media/libstagefright/omx/hal/1.0/impl/GraphicBufferSource.cpp
@@ -0,0 +1,1203 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include <inttypes.h>
+
+#define LOG_TAG "GraphicBufferSource"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#define STRINGIFY_ENUMS // for asString in HardwareAPI.h/VideoAPI.h
+
+#include "GraphicBufferSource.h"
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/ColorUtils.h>
+
+#include <media/hardware/MetadataBufferType.h>
+#include <ui/GraphicBuffer.h>
+#include <gui/BufferItem.h>
+#include <HardwareAPI.h>
+#include "omx/OMXUtils.h"
+#include <OMX_Component.h>
+#include <OMX_IndexExt.h>
+#include "OMXBuffer.h"
+
+#include <inttypes.h>
+#include "FrameDropper.h"
+
+namespace android {
+
+static const OMX_U32 kPortIndexInput = 0;
+
+class GraphicBufferSource::OmxBufferSource : public BnOMXBufferSource {
+public:
+ GraphicBufferSource* mSource;
+
+ OmxBufferSource(GraphicBufferSource* source): mSource(source) {
+ }
+
+ Status onOmxExecuting() override {
+ return mSource->onOmxExecuting();
+ }
+
+ Status onOmxIdle() override {
+ return mSource->onOmxIdle();
+ }
+
+ Status onOmxLoaded() override {
+ return mSource->onOmxLoaded();
+ }
+
+ Status onInputBufferAdded(int bufferId) override {
+ return mSource->onInputBufferAdded(bufferId);
+ }
+
+ Status onInputBufferEmptied(
+ int bufferId, const OMXFenceParcelable& fenceParcel) override {
+ return mSource->onInputBufferEmptied(bufferId, fenceParcel);
+ }
+};
+
+GraphicBufferSource::GraphicBufferSource() :
+ mInitCheck(UNKNOWN_ERROR),
+ mExecuting(false),
+ mSuspended(false),
+ mStopTimeUs(-1),
+ mLastDataSpace(HAL_DATASPACE_UNKNOWN),
+ mNumFramesAvailable(0),
+ mNumBufferAcquired(0),
+ mEndOfStream(false),
+ mEndOfStreamSent(false),
+ mLastActionTimeUs(-1ll),
+ mPrevOriginalTimeUs(-1ll),
+ mSkipFramesBeforeNs(-1ll),
+ mRepeatAfterUs(-1ll),
+ mRepeatLastFrameGeneration(0),
+ mRepeatLastFrameTimestamp(-1ll),
+ mRepeatLastFrameCount(0),
+ mLatestBufferId(-1),
+ mLatestBufferFrameNum(0),
+ mLatestBufferFence(Fence::NO_FENCE),
+ mRepeatBufferDeferred(false),
+ mTimePerCaptureUs(-1ll),
+ mTimePerFrameUs(-1ll),
+ mPrevCaptureUs(-1ll),
+ mPrevFrameUs(-1ll),
+ mInputBufferTimeOffsetUs(0ll),
+ mOmxBufferSource(new OmxBufferSource(this)) {
+ ALOGV("GraphicBufferSource");
+
+ String8 name("GraphicBufferSource");
+
+ BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+ mConsumer->setConsumerName(name);
+
+ // 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<BufferQueue::ConsumerListener> listener =
+ static_cast<BufferQueue::ConsumerListener*>(this);
+ sp<IConsumerListener> proxy =
+ new BufferQueue::ProxyConsumerListener(listener);
+
+ mInitCheck = mConsumer->consumerConnect(proxy, false);
+ if (mInitCheck != NO_ERROR) {
+ ALOGE("Error connecting to BufferQueue: %s (%d)",
+ strerror(-mInitCheck), mInitCheck);
+ return;
+ }
+
+ memset(&mColorAspects, 0, sizeof(mColorAspects));
+
+ CHECK(mInitCheck == NO_ERROR);
+}
+
+GraphicBufferSource::~GraphicBufferSource() {
+ ALOGV("~GraphicBufferSource");
+ if (mLatestBufferId >= 0) {
+ releaseBuffer(mLatestBufferId, mLatestBufferFrameNum, mLatestBufferFence);
+ }
+ if (mNumBufferAcquired != 0) {
+ ALOGW("potential buffer leak (acquired %d)", mNumBufferAcquired);
+ }
+ if (mConsumer != NULL) {
+ status_t err = mConsumer->consumerDisconnect();
+ if (err != NO_ERROR) {
+ ALOGW("consumerDisconnect failed: %d", err);
+ }
+ }
+}
+
+Status GraphicBufferSource::onOmxExecuting() {
+ Mutex::Autolock autoLock(mMutex);
+ ALOGV("--> executing; avail=%zu, codec vec size=%zd",
+ mNumFramesAvailable, mCodecBuffers.size());
+ CHECK(!mExecuting);
+ mExecuting = true;
+ mLastDataSpace = HAL_DATASPACE_UNKNOWN;
+ ALOGV("clearing last dataSpace");
+
+ // Start by loading up as many buffers as possible. We want to do this,
+ // rather than just submit the first buffer, to avoid a degenerate case:
+ // if all BQ buffers arrive before we start executing, and we only submit
+ // one here, the other BQ buffers will just sit until we get notified
+ // that the codec buffer has been released. We'd then acquire and
+ // submit a single additional buffer, repeatedly, never using more than
+ // one codec buffer simultaneously. (We could instead try to submit
+ // all BQ buffers whenever any codec buffer is freed, but if we get the
+ // initial conditions right that will never be useful.)
+ while (mNumFramesAvailable) {
+ if (!fillCodecBuffer_l()) {
+ ALOGV("stop load with frames available (codecAvail=%d)",
+ isCodecBufferAvailable_l());
+ break;
+ }
+ }
+
+ ALOGV("done loading initial frames, avail=%zu", mNumFramesAvailable);
+
+ // If EOS has already been signaled, and there are no more frames to
+ // submit, try to send EOS now as well.
+ if (mStopTimeUs == -1 && mEndOfStream && mNumFramesAvailable == 0) {
+ submitEndOfInputStream_l();
+ }
+
+ if (mRepeatAfterUs > 0ll && mLooper == NULL) {
+ mReflector = new AHandlerReflector<GraphicBufferSource>(this);
+
+ mLooper = new ALooper;
+ mLooper->registerHandler(mReflector);
+ mLooper->start();
+
+ if (mLatestBufferId >= 0) {
+ sp<AMessage> msg =
+ new AMessage(kWhatRepeatLastFrame, mReflector);
+
+ msg->setInt32("generation", ++mRepeatLastFrameGeneration);
+ msg->post(mRepeatAfterUs);
+ }
+ }
+
+ return Status::ok();
+}
+
+Status GraphicBufferSource::onOmxIdle() {
+ ALOGV("omxIdle");
+
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mExecuting) {
+ // We are only interested in the transition from executing->idle,
+ // not loaded->idle.
+ mExecuting = false;
+ }
+ return Status::ok();
+}
+
+Status GraphicBufferSource::onOmxLoaded(){
+ Mutex::Autolock autoLock(mMutex);
+ if (!mExecuting) {
+ // This can happen if something failed very early.
+ ALOGW("Dropped back down to Loaded without Executing");
+ }
+
+ if (mLooper != NULL) {
+ mLooper->unregisterHandler(mReflector->id());
+ mReflector.clear();
+
+ mLooper->stop();
+ mLooper.clear();
+ }
+
+ ALOGV("--> loaded; avail=%zu eos=%d eosSent=%d acquired=%d",
+ mNumFramesAvailable, mEndOfStream, mEndOfStreamSent, mNumBufferAcquired);
+
+ // Codec is no longer executing. Releasing all buffers to bq.
+ for (int i = (int)mCodecBuffers.size() - 1; i >= 0; --i) {
+ if (mCodecBuffers[i].mGraphicBuffer != NULL) {
+ int id = mCodecBuffers[i].mSlot;
+ if (id != mLatestBufferId) {
+ ALOGV("releasing buffer for codec: slot=%d, useCount=%d, latest=%d",
+ id, mBufferUseCount[id], mLatestBufferId);
+ sp<Fence> fence = new Fence(-1);
+ releaseBuffer(id, mCodecBuffers[i].mFrameNumber, fence);
+ mBufferUseCount[id] = 0;
+ }
+ }
+ }
+ // Also release the latest buffer
+ if (mLatestBufferId >= 0) {
+ releaseBuffer(mLatestBufferId, mLatestBufferFrameNum, mLatestBufferFence);
+ mBufferUseCount[mLatestBufferId] = 0;
+ mLatestBufferId = -1;
+ }
+
+ mCodecBuffers.clear();
+ mOMXNode.clear();
+ mExecuting = false;
+
+ return Status::ok();
+}
+
+Status GraphicBufferSource::onInputBufferAdded(int32_t bufferID) {
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mExecuting) {
+ // This should never happen -- buffers can only be allocated when
+ // transitioning from "loaded" to "idle".
+ ALOGE("addCodecBuffer: buffer added while executing");
+ return Status::fromServiceSpecificError(INVALID_OPERATION);
+ }
+
+ ALOGV("addCodecBuffer: bufferID=%u", bufferID);
+
+ CodecBuffer codecBuffer;
+ codecBuffer.mBufferID = bufferID;
+ mCodecBuffers.add(codecBuffer);
+ return Status::ok();
+}
+
+Status GraphicBufferSource::onInputBufferEmptied(
+ int32_t bufferID, const OMXFenceParcelable &fenceParcel) {
+ int fenceFd = fenceParcel.get();
+
+ Mutex::Autolock autoLock(mMutex);
+ if (!mExecuting) {
+ if (fenceFd >= 0) {
+ ::close(fenceFd);
+ }
+ return Status::fromServiceSpecificError(INVALID_OPERATION);
+ }
+
+ int cbi = findMatchingCodecBuffer_l(bufferID);
+ if (cbi < 0) {
+ // This should never happen.
+ ALOGE("codecBufferEmptied: buffer not recognized (bufferID=%u)", bufferID);
+ if (fenceFd >= 0) {
+ ::close(fenceFd);
+ }
+ return Status::fromServiceSpecificError(BAD_VALUE);
+ }
+
+ ALOGV("codecBufferEmptied: bufferID=%u, cbi=%d", bufferID, cbi);
+ CodecBuffer& codecBuffer(mCodecBuffers.editItemAt(cbi));
+
+ // header->nFilledLen may not be the original value, so we can't compare
+ // that to zero to see of this was the EOS buffer. Instead we just
+ // see if the GraphicBuffer reference was null, which should only ever
+ // happen for EOS.
+ if (codecBuffer.mGraphicBuffer == NULL) {
+ if (!(mEndOfStream && mEndOfStreamSent)) {
+ // This can happen when broken code sends us the same buffer
+ // twice in a row.
+ ALOGE("ERROR: codecBufferEmptied on non-EOS null buffer "
+ "(buffer emptied twice?)");
+ }
+ // No GraphicBuffer to deal with, no additional input or output is
+ // expected, so just return.
+ if (fenceFd >= 0) {
+ ::close(fenceFd);
+ }
+ return Status::fromServiceSpecificError(BAD_VALUE);
+ }
+
+ // Find matching entry in our cached copy of the BufferQueue slots.
+ // If we find a match, release that slot. If we don't, the BufferQueue
+ // has dropped that GraphicBuffer, and there's nothing for us to release.
+ int id = codecBuffer.mSlot;
+ sp<Fence> fence = new Fence(fenceFd);
+ if (mBufferSlot[id] != NULL &&
+ mBufferSlot[id]->handle == codecBuffer.mGraphicBuffer->handle) {
+ mBufferUseCount[id]--;
+
+ if (mBufferUseCount[id] < 0) {
+ ALOGW("mBufferUseCount for bq slot %d < 0 (=%d)", id, mBufferUseCount[id]);
+ mBufferUseCount[id] = 0;
+ }
+ if (id != mLatestBufferId && mBufferUseCount[id] == 0) {
+ releaseBuffer(id, codecBuffer.mFrameNumber, fence);
+ }
+ ALOGV("codecBufferEmptied: slot=%d, cbi=%d, useCount=%d, acquired=%d, handle=%p",
+ id, cbi, mBufferUseCount[id], mNumBufferAcquired, mBufferSlot[id]->handle);
+ } else {
+ ALOGV("codecBufferEmptied: no match for emptied buffer, "
+ "slot=%d, cbi=%d, useCount=%d, acquired=%d",
+ id, cbi, mBufferUseCount[id], mNumBufferAcquired);
+ // we will not reuse codec buffer, so there is no need to wait for fence
+ }
+
+ // Mark the codec buffer as available by clearing the GraphicBuffer ref.
+ codecBuffer.mGraphicBuffer = NULL;
+
+ if (mNumFramesAvailable) {
+ // Fill this codec buffer.
+ CHECK(!mEndOfStreamSent);
+ ALOGV("buffer freed, %zu frames avail (eos=%d)",
+ mNumFramesAvailable, mEndOfStream);
+ fillCodecBuffer_l();
+ } else if (mEndOfStream && mStopTimeUs == -1) {
+ // No frames available, but EOS is pending and no stop time, so use this buffer to
+ // send that.
+ ALOGV("buffer freed, EOS pending");
+ submitEndOfInputStream_l();
+ } else if (mRepeatBufferDeferred) {
+ bool success = repeatLatestBuffer_l();
+ if (success) {
+ ALOGV("deferred repeatLatestBuffer_l SUCCESS");
+ } else {
+ ALOGV("deferred repeatLatestBuffer_l FAILURE");
+ }
+ mRepeatBufferDeferred = false;
+ }
+
+ return Status::ok();
+}
+
+void GraphicBufferSource::onDataSpaceChanged_l(
+ android_dataspace dataSpace, android_pixel_format pixelFormat) {
+ ALOGD("got buffer with new dataSpace #%x", dataSpace);
+ mLastDataSpace = dataSpace;
+
+ if (ColorUtils::convertDataSpaceToV0(dataSpace)) {
+ omx_message msg;
+ msg.type = omx_message::EVENT;
+ msg.fenceFd = -1;
+ msg.u.event_data.event = OMX_EventDataSpaceChanged;
+ msg.u.event_data.data1 = mLastDataSpace;
+ msg.u.event_data.data2 = ColorUtils::packToU32(mColorAspects);
+ msg.u.event_data.data3 = pixelFormat;
+
+ mOMXNode->dispatchMessage(msg);
+ }
+}
+
+bool GraphicBufferSource::fillCodecBuffer_l() {
+ CHECK(mExecuting && mNumFramesAvailable > 0);
+
+ if (mSuspended && mActionQueue.empty()) {
+ return false;
+ }
+
+ int cbi = findAvailableCodecBuffer_l();
+ if (cbi < 0) {
+ // No buffers available, bail.
+ ALOGV("fillCodecBuffer_l: no codec buffers, avail now %zu",
+ mNumFramesAvailable);
+ return false;
+ }
+
+ ALOGV("fillCodecBuffer_l: acquiring buffer, avail=%zu",
+ mNumFramesAvailable);
+ BufferItem item;
+ status_t err = acquireBuffer(&item);
+ if (err != OK) {
+ ALOGE("fillCodecBuffer_l: acquireBuffer returned err=%d", err);
+ return false;
+ }
+
+ int64_t itemTimeUs = item.mTimestamp / 1000;
+
+ mNumFramesAvailable--;
+
+ // Process ActionItem in the Queue if there is any. If a buffer's timestamp
+ // is smaller than the first action's timestamp, no action need to be performed.
+ // If buffer's timestamp is larger or equal than the last action's timestamp,
+ // only the last action needs to be performed as all the acitions before the
+ // the action are overridden by the last action. For the other cases, traverse
+ // the Queue to find the newest action that with timestamp smaller or equal to
+ // the buffer's timestamp. For example, an action queue like
+ // [pause, 1s], [resume 2us], [pause 3us], [resume 4us], [pause 5us].... Upon
+ // receiving a buffer with timestamp 3.5us, only the action [pause, 3us] needs
+ // to be handled and [pause, 1us], [resume 2us] will be discarded.
+ bool dropped = false;
+ bool done = false;
+ if (!mActionQueue.empty()) {
+ // First scan to check if bufferTimestamp is smaller than first action's timestamp.
+ ActionItem nextAction = *(mActionQueue.begin());
+ if (itemTimeUs < nextAction.mActionTimeUs) {
+ ALOGV("No action. buffer timestamp %lld us < action timestamp: %lld us",
+ (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
+ // All the actions are ahead. No action need to perform now.
+ // Release the buffer if is in suspended state, or process the buffer
+ // if not in suspended state.
+ dropped = mSuspended;
+ done = true;
+ }
+
+ if (!done) {
+ List<ActionItem>::iterator it = mActionQueue.begin();
+ while(it != mActionQueue.end()) {
+ nextAction = *it;
+ mActionQueue.erase(it);
+ if (nextAction.mActionTimeUs > itemTimeUs) {
+ break;
+ }
+ ++it;
+ }
+
+ CHECK(itemTimeUs >= nextAction.mActionTimeUs);
+ switch (nextAction.mAction) {
+ case ActionItem::PAUSE:
+ {
+ mSuspended = true;
+ dropped = true;
+ ALOGV("RUNNING/PAUSE -> PAUSE at buffer %lld us PAUSE Time: %lld us",
+ (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
+ break;
+ }
+ case ActionItem::RESUME:
+ {
+ mSuspended = false;
+ ALOGV("PAUSE/RUNNING -> RUNNING at buffer %lld us RESUME Time: %lld us",
+ (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
+ break;
+ }
+ case ActionItem::STOP:
+ {
+ ALOGV("RUNNING/PAUSE -> STOP at buffer %lld us STOP Time: %lld us",
+ (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
+ dropped = true;
+ // Clear the whole ActionQueue as recording is done
+ mActionQueue.clear();
+ submitEndOfInputStream_l();
+ break;
+ }
+ default:
+ ALOGE("Unknown action type");
+ return false;
+ }
+ }
+ }
+
+ if (dropped) {
+ releaseBuffer(item.mSlot, item.mFrameNumber, item.mFence);
+ return true;
+ }
+
+ if (item.mDataSpace != mLastDataSpace) {
+ onDataSpaceChanged_l(
+ item.mDataSpace, (android_pixel_format)mBufferSlot[item.mSlot]->getPixelFormat());
+ }
+
+ err = UNKNOWN_ERROR;
+
+ // only submit sample if start time is unspecified, or sample
+ // is queued after the specified start time
+ if (mSkipFramesBeforeNs < 0ll || item.mTimestamp >= mSkipFramesBeforeNs) {
+ // if start time is set, offset time stamp by start time
+ if (mSkipFramesBeforeNs > 0) {
+ item.mTimestamp -= mSkipFramesBeforeNs;
+ }
+
+ int64_t timeUs = item.mTimestamp / 1000;
+ if (mFrameDropper != NULL && mFrameDropper->shouldDrop(timeUs)) {
+ ALOGV("skipping frame (%lld) to meet max framerate", static_cast<long long>(timeUs));
+ // set err to OK so that the skipped frame can still be saved as the lastest frame
+ err = OK;
+ dropped = true;
+ } else {
+ err = submitBuffer_l(item, cbi);
+ }
+ }
+
+ if (err != OK) {
+ ALOGV("submitBuffer_l failed, releasing bq slot %d", item.mSlot);
+ releaseBuffer(item.mSlot, item.mFrameNumber, item.mFence);
+ } else {
+ // Don't set the last buffer id if we're not repeating,
+ // we'll be holding on to the last buffer for nothing.
+ if (mRepeatAfterUs > 0ll) {
+ setLatestBuffer_l(item);
+ }
+ if (!dropped) {
+ ++mBufferUseCount[item.mSlot];
+ }
+ ALOGV("buffer submitted: slot=%d, cbi=%d, useCount=%d, acquired=%d",
+ item.mSlot, cbi, mBufferUseCount[item.mSlot], mNumBufferAcquired);
+ }
+
+ return true;
+}
+
+bool GraphicBufferSource::repeatLatestBuffer_l() {
+ CHECK(mExecuting && mNumFramesAvailable == 0);
+
+ if (mLatestBufferId < 0 || mSuspended) {
+ return false;
+ }
+ if (mBufferSlot[mLatestBufferId] == NULL) {
+ // This can happen if the remote side disconnects, causing
+ // onBuffersReleased() to NULL out our copy of the slots. The
+ // buffer is gone, so we have nothing to show.
+ //
+ // To be on the safe side we try to release the buffer.
+ ALOGD("repeatLatestBuffer_l: slot was NULL");
+ mConsumer->releaseBuffer(
+ mLatestBufferId,
+ mLatestBufferFrameNum,
+ EGL_NO_DISPLAY,
+ EGL_NO_SYNC_KHR,
+ mLatestBufferFence);
+ mLatestBufferId = -1;
+ mLatestBufferFrameNum = 0;
+ mLatestBufferFence = Fence::NO_FENCE;
+ return false;
+ }
+
+ int cbi = findAvailableCodecBuffer_l();
+ if (cbi < 0) {
+ // No buffers available, bail.
+ ALOGV("repeatLatestBuffer_l: no codec buffers.");
+ return false;
+ }
+
+ BufferItem item;
+ item.mSlot = mLatestBufferId;
+ item.mFrameNumber = mLatestBufferFrameNum;
+ item.mTimestamp = mRepeatLastFrameTimestamp;
+ item.mFence = mLatestBufferFence;
+
+ status_t err = submitBuffer_l(item, cbi);
+
+ if (err != OK) {
+ return false;
+ }
+
+ ++mBufferUseCount[item.mSlot];
+
+ /* repeat last frame up to kRepeatLastFrameCount times.
+ * in case of static scene, a single repeat might not get rid of encoder
+ * ghosting completely, refresh a couple more times to get better quality
+ */
+ if (--mRepeatLastFrameCount > 0) {
+ mRepeatLastFrameTimestamp = item.mTimestamp + mRepeatAfterUs * 1000;
+
+ if (mReflector != NULL) {
+ sp<AMessage> msg = new AMessage(kWhatRepeatLastFrame, mReflector);
+ msg->setInt32("generation", ++mRepeatLastFrameGeneration);
+ msg->post(mRepeatAfterUs);
+ }
+ }
+
+ return true;
+}
+
+void GraphicBufferSource::setLatestBuffer_l(const BufferItem &item) {
+ if (mLatestBufferId >= 0 && mBufferUseCount[mLatestBufferId] == 0) {
+ releaseBuffer(mLatestBufferId, mLatestBufferFrameNum, mLatestBufferFence);
+ // mLatestBufferFence will be set to new fence just below
+ }
+
+ mLatestBufferId = item.mSlot;
+ mLatestBufferFrameNum = item.mFrameNumber;
+ mRepeatLastFrameTimestamp = item.mTimestamp + mRepeatAfterUs * 1000;
+
+ ALOGV("setLatestBuffer_l: slot=%d, useCount=%d",
+ item.mSlot, mBufferUseCount[item.mSlot]);
+
+ mRepeatBufferDeferred = false;
+ mRepeatLastFrameCount = kRepeatLastFrameCount;
+ mLatestBufferFence = item.mFence;
+
+ if (mReflector != NULL) {
+ sp<AMessage> msg = new AMessage(kWhatRepeatLastFrame, mReflector);
+ msg->setInt32("generation", ++mRepeatLastFrameGeneration);
+ msg->post(mRepeatAfterUs);
+ }
+}
+
+bool GraphicBufferSource::getTimestamp(
+ const BufferItem &item, int64_t *codecTimeUs) {
+ int64_t timeUs = item.mTimestamp / 1000;
+ timeUs += mInputBufferTimeOffsetUs;
+
+ if (mTimePerCaptureUs > 0ll
+ && (mTimePerCaptureUs > 2 * mTimePerFrameUs
+ || mTimePerFrameUs > 2 * mTimePerCaptureUs)) {
+ // Time lapse or slow motion mode
+ if (mPrevCaptureUs < 0ll) {
+ // first capture
+ mPrevCaptureUs = timeUs;
+ // adjust the first sample timestamp.
+ mPrevFrameUs = (timeUs * mTimePerFrameUs) / mTimePerCaptureUs;
+ } else {
+ // snap to nearest capture point
+ int64_t nFrames = (timeUs + mTimePerCaptureUs / 2 - mPrevCaptureUs)
+ / mTimePerCaptureUs;
+ if (nFrames <= 0) {
+ // skip this frame as it's too close to previous capture
+ ALOGV("skipping frame, timeUs %lld", static_cast<long long>(timeUs));
+ return false;
+ }
+ mPrevCaptureUs = mPrevCaptureUs + nFrames * mTimePerCaptureUs;
+ mPrevFrameUs += mTimePerFrameUs * nFrames;
+ }
+
+ ALOGV("timeUs %lld, captureUs %lld, frameUs %lld",
+ static_cast<long long>(timeUs),
+ static_cast<long long>(mPrevCaptureUs),
+ static_cast<long long>(mPrevFrameUs));
+
+ *codecTimeUs = mPrevFrameUs;
+ return true;
+ } else {
+ int64_t originalTimeUs = timeUs;
+ if (originalTimeUs <= mPrevOriginalTimeUs) {
+ // Drop the frame if it's going backward in time. Bad timestamp
+ // could disrupt encoder's rate control completely.
+ ALOGW("Dropping frame that's going backward in time");
+ return false;
+ }
+
+ mPrevOriginalTimeUs = originalTimeUs;
+ }
+
+ *codecTimeUs = timeUs;
+ return true;
+}
+
+status_t GraphicBufferSource::submitBuffer_l(const BufferItem &item, int cbi) {
+ ALOGV("submitBuffer_l: slot=%d, cbi=%d", item.mSlot, cbi);
+
+ int64_t codecTimeUs;
+ if (!getTimestamp(item, &codecTimeUs)) {
+ return UNKNOWN_ERROR;
+ }
+
+ CodecBuffer& codecBuffer(mCodecBuffers.editItemAt(cbi));
+ codecBuffer.mGraphicBuffer = mBufferSlot[item.mSlot];
+ codecBuffer.mSlot = item.mSlot;
+ codecBuffer.mFrameNumber = item.mFrameNumber;
+
+ IOMX::buffer_id bufferID = codecBuffer.mBufferID;
+ const sp<GraphicBuffer> &buffer = codecBuffer.mGraphicBuffer;
+ int fenceID = item.mFence->isValid() ? item.mFence->dup() : -1;
+
+ status_t err = mOMXNode->emptyBuffer(
+ bufferID, buffer, OMX_BUFFERFLAG_ENDOFFRAME, codecTimeUs, fenceID);
+
+ if (err != OK) {
+ ALOGW("WARNING: emptyGraphicBuffer failed: 0x%x", err);
+ codecBuffer.mGraphicBuffer = NULL;
+ return err;
+ }
+
+ ALOGV("emptyGraphicBuffer succeeded, bufferID=%u buf=%p bufhandle=%p",
+ bufferID, buffer->getNativeBuffer(), buffer->handle);
+ return OK;
+}
+
+void GraphicBufferSource::submitEndOfInputStream_l() {
+ CHECK(mEndOfStream);
+ if (mEndOfStreamSent) {
+ ALOGV("EOS already sent");
+ return;
+ }
+
+ int cbi = findAvailableCodecBuffer_l();
+ if (cbi < 0) {
+ ALOGV("submitEndOfInputStream_l: no codec buffers available");
+ return;
+ }
+
+ // We reject any additional incoming graphic buffers, so there's no need
+ // to stick a placeholder into codecBuffer.mGraphicBuffer to mark it as
+ // in-use.
+ CodecBuffer& codecBuffer(mCodecBuffers.editItemAt(cbi));
+ IOMX::buffer_id bufferID = codecBuffer.mBufferID;
+
+ status_t err = mOMXNode->emptyBuffer(
+ bufferID, (sp<GraphicBuffer>)NULL,
+ OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_EOS,
+ 0 /* timestamp */, -1 /* fenceFd */);
+ if (err != OK) {
+ ALOGW("emptyDirectBuffer EOS failed: 0x%x", err);
+ } else {
+ ALOGV("submitEndOfInputStream_l: buffer submitted, bufferID=%u cbi=%d",
+ bufferID, cbi);
+ mEndOfStreamSent = true;
+ }
+}
+
+int GraphicBufferSource::findAvailableCodecBuffer_l() {
+ CHECK(mCodecBuffers.size() > 0);
+
+ for (int i = (int)mCodecBuffers.size() - 1; i>= 0; --i) {
+ if (mCodecBuffers[i].mGraphicBuffer == NULL) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int GraphicBufferSource::findMatchingCodecBuffer_l(IOMX::buffer_id bufferID) {
+ for (int i = (int)mCodecBuffers.size() - 1; i>= 0; --i) {
+ if (mCodecBuffers[i].mBufferID == bufferID) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+status_t GraphicBufferSource::acquireBuffer(BufferItem *bi) {
+ status_t err = mConsumer->acquireBuffer(bi, 0);
+ if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
+ // shouldn't happen
+ ALOGW("acquireBuffer: frame was not available");
+ return err;
+ } else if (err != OK) {
+ ALOGW("acquireBuffer: failed with err=%d", err);
+ return err;
+ }
+ // If this is the first time we're seeing this buffer, add it to our
+ // slot table.
+ if (bi->mGraphicBuffer != NULL) {
+ ALOGV("acquireBuffer: setting mBufferSlot %d", bi->mSlot);
+ mBufferSlot[bi->mSlot] = bi->mGraphicBuffer;
+ mBufferUseCount[bi->mSlot] = 0;
+ }
+ mNumBufferAcquired++;
+ return OK;
+}
+
+/*
+ * Releases an acquired buffer back to the consumer.
+ *
+ * id: buffer slot to release
+ * frameNum: frame number of the frame being released
+ * fence: fence of the frame being released
+ */
+void GraphicBufferSource::releaseBuffer(
+ int id, uint64_t frameNum, const sp<Fence> &fence) {
+ ALOGV("releaseBuffer: slot=%d", id);
+ mConsumer->releaseBuffer(
+ id, frameNum, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, fence);
+ mNumBufferAcquired--;
+}
+
+// BufferQueue::ConsumerListener callback
+void GraphicBufferSource::onFrameAvailable(const BufferItem& /*item*/) {
+ Mutex::Autolock autoLock(mMutex);
+
+ ALOGV("onFrameAvailable exec=%d avail=%zu",
+ mExecuting, mNumFramesAvailable);
+
+ if (mOMXNode == NULL || mEndOfStreamSent || (mSuspended && mActionQueue.empty())) {
+ if (mEndOfStreamSent) {
+ // This should only be possible if a new buffer was queued after
+ // EOS was signaled, i.e. the app is misbehaving.
+
+ ALOGW("onFrameAvailable: EOS is sent, ignoring frame");
+ } else {
+ ALOGV("onFrameAvailable: suspended, ignoring frame");
+ }
+
+ BufferItem item;
+ status_t err = acquireBuffer(&item);
+ if (err == OK) {
+ releaseBuffer(item.mSlot, item.mFrameNumber, item.mFence);
+ } else {
+ ALOGE("onFrameAvailable: acquireBuffer returned err=%d", err);
+ }
+ return;
+ }
+
+ mNumFramesAvailable++;
+
+ mRepeatBufferDeferred = false;
+ ++mRepeatLastFrameGeneration;
+
+ if (mExecuting) {
+ fillCodecBuffer_l();
+ }
+}
+
+// BufferQueue::ConsumerListener callback
+void GraphicBufferSource::onBuffersReleased() {
+ Mutex::Autolock lock(mMutex);
+
+ uint64_t slotMask;
+ if (mConsumer->getReleasedBuffers(&slotMask) != NO_ERROR) {
+ ALOGW("onBuffersReleased: unable to get released buffer set");
+ slotMask = 0xffffffffffffffffULL;
+ }
+
+ ALOGV("onBuffersReleased: 0x%016" PRIx64, slotMask);
+
+ for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+ if ((slotMask & 0x01) != 0) {
+ // Last buffer (if set) is always acquired even if its use count
+ // is 0, because we could have skipped that frame but kept it for
+ // repeating. Otherwise a buffer is only acquired if use count>0.
+ if (mBufferSlot[i] != NULL &&
+ (mBufferUseCount[i] > 0 || mLatestBufferId == i)) {
+ ALOGV("releasing acquired buffer: slot=%d, useCount=%d, latest=%d",
+ i, mBufferUseCount[i], mLatestBufferId);
+ mNumBufferAcquired--;
+ }
+ if (mLatestBufferId == i) {
+ mLatestBufferId = -1;
+ }
+ mBufferSlot[i] = NULL;
+ mBufferUseCount[i] = 0;
+ }
+ slotMask >>= 1;
+ }
+}
+
+// BufferQueue::ConsumerListener callback
+void GraphicBufferSource::onSidebandStreamChanged() {
+ ALOG_ASSERT(false, "GraphicBufferSource can't consume sideband streams");
+}
+
+Status GraphicBufferSource::configure(
+ const sp<IOMXNode>& omxNode, int32_t dataSpace) {
+ if (omxNode == NULL) {
+ return Status::fromServiceSpecificError(BAD_VALUE);
+ }
+
+ // Do setInputSurface() first, the node will try to enable metadata
+ // mode on input, and does necessary error checking. If this fails,
+ // we can't use this input surface on the node.
+ status_t err = omxNode->setInputSurface(mOmxBufferSource);
+ if (err != NO_ERROR) {
+ ALOGE("Unable to set input surface: %d", err);
+ return Status::fromServiceSpecificError(err);
+ }
+
+ // use consumer usage bits queried from encoder, but always add
+ // HW_VIDEO_ENCODER for backward compatibility.
+ uint32_t consumerUsage;
+ if (omxNode->getParameter(
+ (OMX_INDEXTYPE)OMX_IndexParamConsumerUsageBits,
+ &consumerUsage, sizeof(consumerUsage)) != OK) {
+ consumerUsage = 0;
+ }
+
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ InitOMXParams(&def);
+ def.nPortIndex = kPortIndexInput;
+
+ err = omxNode->getParameter(
+ OMX_IndexParamPortDefinition, &def, sizeof(def));
+ if (err != NO_ERROR) {
+ ALOGE("Failed to get port definition: %d", err);
+ return Status::fromServiceSpecificError(UNKNOWN_ERROR);
+ }
+
+ // Call setMaxAcquiredBufferCount without lock.
+ // setMaxAcquiredBufferCount could call back to onBuffersReleased
+ // if the buffer count change results in releasing of existing buffers,
+ // which would lead to deadlock.
+ err = mConsumer->setMaxAcquiredBufferCount(def.nBufferCountActual);
+ if (err != NO_ERROR) {
+ ALOGE("Unable to set BQ max acquired buffer count to %u: %d",
+ def.nBufferCountActual, err);
+ return Status::fromServiceSpecificError(err);
+ }
+
+ {
+ Mutex::Autolock autoLock(mMutex);
+ mOMXNode = omxNode;
+
+ err = mConsumer->setDefaultBufferSize(
+ def.format.video.nFrameWidth,
+ def.format.video.nFrameHeight);
+ if (err != NO_ERROR) {
+ ALOGE("Unable to set BQ default buffer size to %ux%u: %d",
+ def.format.video.nFrameWidth,
+ def.format.video.nFrameHeight,
+ err);
+ return Status::fromServiceSpecificError(err);
+ }
+
+ consumerUsage |= GRALLOC_USAGE_HW_VIDEO_ENCODER;
+ mConsumer->setConsumerUsageBits(consumerUsage);
+
+ // Sets the default buffer data space
+ ALOGD("setting dataspace: %#x, acquired=%d", dataSpace, mNumBufferAcquired);
+ mConsumer->setDefaultBufferDataSpace((android_dataspace)dataSpace);
+ mLastDataSpace = (android_dataspace)dataSpace;
+
+ mExecuting = false;
+ mSuspended = false;
+ mEndOfStream = false;
+ mEndOfStreamSent = false;
+ mPrevOriginalTimeUs = -1ll;
+ mSkipFramesBeforeNs = -1ll;
+ mRepeatAfterUs = -1ll;
+ mRepeatLastFrameGeneration = 0;
+ mRepeatLastFrameTimestamp = -1ll;
+ mRepeatLastFrameCount = 0;
+ mLatestBufferId = -1;
+ mLatestBufferFrameNum = 0;
+ mLatestBufferFence = Fence::NO_FENCE;
+ mRepeatBufferDeferred = false;
+ mTimePerCaptureUs = -1ll;
+ mTimePerFrameUs = -1ll;
+ mPrevCaptureUs = -1ll;
+ mPrevFrameUs = -1ll;
+ mInputBufferTimeOffsetUs = 0;
+ mStopTimeUs = -1;
+ mActionQueue.clear();
+ }
+
+ return Status::ok();
+}
+
+Status GraphicBufferSource::setSuspend(bool suspend, int64_t suspendStartTimeUs) {
+ ALOGV("setSuspend=%d at time %lld us", suspend, (long long)suspendStartTimeUs);
+
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mStopTimeUs != -1) {
+ ALOGE("setSuspend failed as STOP action is pending");
+ return Status::fromServiceSpecificError(INVALID_OPERATION);
+ }
+
+ // Push the action to the queue.
+ if (suspendStartTimeUs != -1) {
+ // suspendStartTimeUs must be smaller or equal to current systemTime.
+ int64_t currentSystemTimeUs = systemTime() / 1000;
+ if (suspendStartTimeUs > currentSystemTimeUs) {
+ ALOGE("setSuspend failed. %lld is larger than current system time %lld us",
+ (long long)suspendStartTimeUs, (long long)currentSystemTimeUs);
+ return Status::fromServiceSpecificError(INVALID_OPERATION);
+ }
+ if (mLastActionTimeUs != -1 && suspendStartTimeUs < mLastActionTimeUs) {
+ ALOGE("setSuspend failed. %lld is smaller than last action time %lld us",
+ (long long)suspendStartTimeUs, (long long)mLastActionTimeUs);
+ return Status::fromServiceSpecificError(INVALID_OPERATION);
+ }
+ mLastActionTimeUs = suspendStartTimeUs;
+ ActionItem action;
+ action.mAction = suspend ? ActionItem::PAUSE : ActionItem::RESUME;
+ action.mActionTimeUs = suspendStartTimeUs;
+ ALOGV("Push %s action into actionQueue", suspend ? "PAUSE" : "RESUME");
+ mActionQueue.push_back(action);
+ } else {
+ if (suspend) {
+ mSuspended = true;
+
+ while (mNumFramesAvailable > 0) {
+ BufferItem item;
+ status_t err = acquireBuffer(&item);
+
+ if (err != OK) {
+ ALOGE("setSuspend: acquireBuffer returned err=%d", err);
+ break;
+ }
+
+ --mNumFramesAvailable;
+
+ releaseBuffer(item.mSlot, item.mFrameNumber, item.mFence);
+ }
+ return Status::ok();
+ } else {
+
+ mSuspended = false;
+
+ if (mExecuting && mNumFramesAvailable == 0 && mRepeatBufferDeferred) {
+ if (repeatLatestBuffer_l()) {
+ ALOGV("suspend/deferred repeatLatestBuffer_l SUCCESS");
+
+ mRepeatBufferDeferred = false;
+ } else {
+ ALOGV("suspend/deferred repeatLatestBuffer_l FAILURE");
+ }
+ }
+ }
+ }
+ return Status::ok();
+}
+
+Status GraphicBufferSource::setRepeatPreviousFrameDelayUs(int64_t repeatAfterUs) {
+ ALOGV("setRepeatPreviousFrameDelayUs: delayUs=%lld", (long long)repeatAfterUs);
+
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mExecuting || repeatAfterUs <= 0ll) {
+ return Status::fromServiceSpecificError(INVALID_OPERATION);
+ }
+
+ mRepeatAfterUs = repeatAfterUs;
+ return Status::ok();
+}
+
+Status GraphicBufferSource::setTimeOffsetUs(int64_t timeOffsetUs) {
+ Mutex::Autolock autoLock(mMutex);
+
+ // timeOffsetUs must be negative for adjustment.
+ if (timeOffsetUs >= 0ll) {
+ return Status::fromServiceSpecificError(INVALID_OPERATION);
+ }
+
+ mInputBufferTimeOffsetUs = timeOffsetUs;
+ return Status::ok();
+}
+
+Status GraphicBufferSource::setMaxFps(float maxFps) {
+ ALOGV("setMaxFps: maxFps=%lld", (long long)maxFps);
+
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mExecuting) {
+ return Status::fromServiceSpecificError(INVALID_OPERATION);
+ }
+
+ mFrameDropper = new FrameDropper();
+ status_t err = mFrameDropper->setMaxFrameRate(maxFps);
+ if (err != OK) {
+ mFrameDropper.clear();
+ return Status::fromServiceSpecificError(err);
+ }
+
+ return Status::ok();
+}
+
+Status GraphicBufferSource::setStartTimeUs(int64_t skipFramesBeforeUs) {
+ ALOGV("setStartTimeUs: skipFramesBeforeUs=%lld", (long long)skipFramesBeforeUs);
+
+ Mutex::Autolock autoLock(mMutex);
+
+ mSkipFramesBeforeNs =
+ (skipFramesBeforeUs > 0) ? (skipFramesBeforeUs * 1000) : -1ll;
+
+ return Status::ok();
+}
+
+Status GraphicBufferSource::setStopTimeUs(int64_t stopTimeUs) {
+ ALOGV("setStopTimeUs: %lld us", (long long)stopTimeUs);
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mStopTimeUs != -1) {
+ // Ignore if stop time has already been set
+ return Status::ok();
+ }
+
+ // stopTimeUs must be smaller or equal to current systemTime.
+ int64_t currentSystemTimeUs = systemTime() / 1000;
+ if (stopTimeUs > currentSystemTimeUs) {
+ ALOGE("setStopTimeUs failed. %lld is larger than current system time %lld us",
+ (long long)stopTimeUs, (long long)currentSystemTimeUs);
+ return Status::fromServiceSpecificError(INVALID_OPERATION);
+ }
+ if (mLastActionTimeUs != -1 && stopTimeUs < mLastActionTimeUs) {
+ ALOGE("setSuspend failed. %lld is smaller than last action time %lld us",
+ (long long)stopTimeUs, (long long)mLastActionTimeUs);
+ return Status::fromServiceSpecificError(INVALID_OPERATION);
+ }
+ mLastActionTimeUs = stopTimeUs;
+ ActionItem action;
+ action.mAction = ActionItem::STOP;
+ action.mActionTimeUs = stopTimeUs;
+ mActionQueue.push_back(action);
+ mStopTimeUs = stopTimeUs;
+ return Status::ok();
+}
+
+Status GraphicBufferSource::setTimeLapseConfig(int64_t timePerFrameUs, int64_t timePerCaptureUs) {
+ ALOGV("setTimeLapseConfig: timePerFrameUs=%lld, timePerCaptureUs=%lld",
+ (long long)timePerFrameUs, (long long)timePerCaptureUs);
+
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mExecuting || timePerFrameUs <= 0ll || timePerCaptureUs <= 0ll) {
+ return Status::fromServiceSpecificError(INVALID_OPERATION);
+ }
+
+ mTimePerFrameUs = timePerFrameUs;
+ mTimePerCaptureUs = timePerCaptureUs;
+
+ return Status::ok();
+}
+
+Status GraphicBufferSource::setColorAspects(int32_t aspectsPacked) {
+ Mutex::Autolock autoLock(mMutex);
+ mColorAspects = ColorUtils::unpackToColorAspects(aspectsPacked);
+ ALOGD("requesting color aspects (R:%d(%s), P:%d(%s), M:%d(%s), T:%d(%s))",
+ mColorAspects.mRange, asString(mColorAspects.mRange),
+ mColorAspects.mPrimaries, asString(mColorAspects.mPrimaries),
+ mColorAspects.mMatrixCoeffs, asString(mColorAspects.mMatrixCoeffs),
+ mColorAspects.mTransfer, asString(mColorAspects.mTransfer));
+
+ return Status::ok();
+}
+
+Status GraphicBufferSource::signalEndOfInputStream() {
+ Mutex::Autolock autoLock(mMutex);
+ ALOGV("signalEndOfInputStream: exec=%d avail=%zu eos=%d",
+ mExecuting, mNumFramesAvailable, mEndOfStream);
+
+ if (mEndOfStream) {
+ ALOGE("EOS was already signaled");
+ return Status::fromStatusT(INVALID_OPERATION);
+ }
+
+ // Set the end-of-stream flag. If no frames are pending from the
+ // BufferQueue, and a codec buffer is available, and we're executing,
+ // and there is no stop timestamp, we initiate the EOS from here.
+ // Otherwise, we'll let codecBufferEmptied() (or omxExecuting) do it.
+ //
+ // Note: if there are no pending frames and all codec buffers are
+ // available, we *must* submit the EOS from here or we'll just
+ // stall since no future events are expected.
+ mEndOfStream = true;
+
+ if (mStopTimeUs == -1 && mExecuting && mNumFramesAvailable == 0) {
+ submitEndOfInputStream_l();
+ }
+
+ return Status::ok();
+}
+
+void GraphicBufferSource::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRepeatLastFrame:
+ {
+ Mutex::Autolock autoLock(mMutex);
+
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mRepeatLastFrameGeneration) {
+ // stale
+ break;
+ }
+
+ if (!mExecuting || mNumFramesAvailable > 0) {
+ break;
+ }
+
+ bool success = repeatLatestBuffer_l();
+
+ if (success) {
+ ALOGV("repeatLatestBuffer_l SUCCESS");
+ } else {
+ ALOGV("repeatLatestBuffer_l FAILURE");
+ mRepeatBufferDeferred = true;
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/omx/hal/1.0/impl/GraphicBufferSource.h b/media/libstagefright/omx/hal/1.0/impl/GraphicBufferSource.h
new file mode 100644
index 0000000..475548e
--- /dev/null
+++ b/media/libstagefright/omx/hal/1.0/impl/GraphicBufferSource.h
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2013 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 GRAPHIC_BUFFER_SOURCE_H_
+
+#define GRAPHIC_BUFFER_SOURCE_H_
+
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/BufferQueue.h>
+#include <utils/RefBase.h>
+
+#include <VideoAPI.h>
+#include <media/IOMX.h>
+#include <media/OMXFenceParcelable.h>
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <media/stagefright/foundation/ALooper.h>
+
+#include <android/BnGraphicBufferSource.h>
+#include <android/BnOMXBufferSource.h>
+
+namespace android {
+
+using ::android::binder::Status;
+
+struct FrameDropper;
+
+/*
+ * This class is used to feed OMX codecs from a Surface via BufferQueue.
+ *
+ * Instances of the class don't run on a dedicated thread. Instead,
+ * various events trigger data movement:
+ *
+ * - Availability of a new frame of data from the BufferQueue (notified
+ * via the onFrameAvailable callback).
+ * - The return of a codec buffer (via OnEmptyBufferDone).
+ * - Application signaling end-of-stream.
+ * - Transition to or from "executing" state.
+ *
+ * Frames of data (and, perhaps, the end-of-stream indication) can arrive
+ * before the codec is in the "executing" state, so we need to queue
+ * things up until we're ready to go.
+ */
+class GraphicBufferSource : public BnGraphicBufferSource,
+ public BufferQueue::ConsumerListener {
+public:
+ GraphicBufferSource();
+
+ virtual ~GraphicBufferSource();
+
+ // We can't throw an exception if the constructor fails, so we just set
+ // this and require that the caller test the value.
+ status_t initCheck() const {
+ return mInitCheck;
+ }
+
+ // Returns the handle to the producer side of the BufferQueue. Buffers
+ // queued on this will be received by GraphicBufferSource.
+ sp<IGraphicBufferProducer> getIGraphicBufferProducer() const {
+ return mProducer;
+ }
+
+ // This is called when OMX transitions to OMX_StateExecuting, which means
+ // we can start handing it buffers. If we already have buffers of data
+ // sitting in the BufferQueue, this will send them to the codec.
+ Status onOmxExecuting();
+
+ // This is called when OMX transitions to OMX_StateIdle, indicating that
+ // the codec is meant to return all buffers back to the client for them
+ // to be freed. Do NOT submit any more buffers to the component.
+ Status onOmxIdle();
+
+ // This is called when OMX transitions to OMX_StateLoaded, indicating that
+ // we are shutting down.
+ Status onOmxLoaded();
+
+ // A "codec buffer", i.e. a buffer that can be used to pass data into
+ // the encoder, has been allocated. (This call does not call back into
+ // OMXNodeInstance.)
+ Status onInputBufferAdded(int32_t bufferID);
+
+ // Called from OnEmptyBufferDone. If we have a BQ buffer available,
+ // fill it with a new frame of data; otherwise, just mark it as available.
+ Status onInputBufferEmptied(
+ int32_t bufferID, const OMXFenceParcelable& fenceParcel);
+
+ // Configure the buffer source to be used with an OMX node with the default
+ // data space.
+ Status configure(const sp<IOMXNode>& omxNode, int32_t dataSpace) override;
+
+ // This is called after the last input frame has been submitted or buffer
+ // timestamp is greater or equal than stopTimeUs. We need to submit an empty
+ // buffer with the EOS flag set. If we don't have a codec buffer ready,
+ // we just set the mEndOfStream flag.
+ Status signalEndOfInputStream() override;
+
+ // If suspend is true, all incoming buffers (including those currently
+ // in the BufferQueue) with timestamp larger than timeUs will be discarded
+ // until the suspension is lifted. If suspend is false, all incoming buffers
+ // including those currently in the BufferQueue) with timestamp larger than
+ // timeUs will be processed. timeUs uses SYSTEM_TIME_MONOTONIC time base.
+ Status setSuspend(bool suspend, int64_t timeUs) override;
+
+ // Specifies the interval after which we requeue the buffer previously
+ // queued to the encoder. This is useful in the case of surface flinger
+ // providing the input surface if the resulting encoded stream is to
+ // be displayed "live". If we were not to push through the extra frame
+ // the decoder on the remote end would be unable to decode the latest frame.
+ // This API must be called before transitioning the encoder to "executing"
+ // state and once this behaviour is specified it cannot be reset.
+ Status setRepeatPreviousFrameDelayUs(int64_t repeatAfterUs) override;
+
+ // Sets the input buffer timestamp offset.
+ // When set, the sample's timestamp will be adjusted with the timeOffsetUs.
+ Status setTimeOffsetUs(int64_t timeOffsetUs) override;
+
+ // When set, the max frame rate fed to the encoder will be capped at maxFps.
+ Status setMaxFps(float maxFps) override;
+
+ // Sets the time lapse (or slow motion) parameters.
+ // When set, the sample's timestamp will be modified to playback framerate,
+ // and capture timestamp will be modified to capture rate.
+ Status setTimeLapseConfig(int64_t timePerFrameUs, int64_t timePerCaptureUs) override;
+
+ // Sets the start time us (in system time), samples before which should
+ // be dropped and not submitted to encoder
+ Status setStartTimeUs(int64_t startTimeUs) override;
+
+ // Sets the stop time us (in system time), samples after which should be dropped
+ // and not submitted to encoder. timeUs uses SYSTEM_TIME_MONOTONIC time base.
+ Status setStopTimeUs(int64_t stopTimeUs) override;
+
+ // Sets the desired color aspects, e.g. to be used when producer does not specify a dataspace.
+ Status setColorAspects(int32_t aspectsPacked) override;
+
+protected:
+ // BufferQueue::ConsumerListener interface, called when a new frame of
+ // data is available. If we're executing and a codec buffer is
+ // available, we acquire the buffer, copy the GraphicBuffer reference
+ // into the codec buffer, and call Empty[This]Buffer. If we're not yet
+ // executing or there's no codec buffer available, we just increment
+ // mNumFramesAvailable and return.
+ void onFrameAvailable(const BufferItem& item) override;
+
+ // BufferQueue::ConsumerListener interface, called when the client has
+ // released one or more GraphicBuffers. We clear out the appropriate
+ // set of mBufferSlot entries.
+ void onBuffersReleased() override;
+
+ // BufferQueue::ConsumerListener interface, called when the client has
+ // changed the sideband stream. GraphicBufferSource doesn't handle sideband
+ // streams so this is a no-op (and should never be called).
+ void onSidebandStreamChanged() override;
+
+private:
+
+ // Keep track of codec input buffers. They may either be available
+ // (mGraphicBuffer == NULL) or in use by the codec.
+ struct CodecBuffer {
+ IOMX::buffer_id mBufferID;
+
+ // buffer producer's frame-number for buffer
+ uint64_t mFrameNumber;
+
+ // buffer producer's buffer slot for buffer
+ int mSlot;
+
+ sp<GraphicBuffer> mGraphicBuffer;
+ };
+
+ // Returns the index of an available codec buffer. If none are
+ // available, returns -1. Mutex must be held by caller.
+ int findAvailableCodecBuffer_l();
+
+ // Returns true if a codec buffer is available.
+ bool isCodecBufferAvailable_l() {
+ return findAvailableCodecBuffer_l() >= 0;
+ }
+
+ // Finds the mCodecBuffers entry that matches. Returns -1 if not found.
+ int findMatchingCodecBuffer_l(IOMX::buffer_id bufferID);
+
+ // Fills a codec buffer with a frame from the BufferQueue. This must
+ // only be called when we know that a frame of data is ready (i.e. we're
+ // in the onFrameAvailable callback, or if we're in codecBufferEmptied
+ // and mNumFramesAvailable is nonzero). Returns without doing anything if
+ // we don't have a codec buffer available.
+ //
+ // Returns true if we successfully filled a codec buffer with a BQ buffer.
+ bool fillCodecBuffer_l();
+
+ // Marks the mCodecBuffers entry as in-use, copies the GraphicBuffer
+ // reference into the codec buffer, and submits the data to the codec.
+ status_t submitBuffer_l(const BufferItem &item, int cbi);
+
+ // Submits an empty buffer, with the EOS flag set. Returns without
+ // doing anything if we don't have a codec buffer available.
+ void submitEndOfInputStream_l();
+
+ // Acquire buffer from the consumer
+ status_t acquireBuffer(BufferItem *bi);
+
+ // Release buffer to the consumer
+ void releaseBuffer(int id, uint64_t frameNum, const sp<Fence> &fence);
+
+ void setLatestBuffer_l(const BufferItem &item);
+ bool repeatLatestBuffer_l();
+ bool getTimestamp(const BufferItem &item, int64_t *codecTimeUs);
+
+ // called when the data space of the input buffer changes
+ void onDataSpaceChanged_l(android_dataspace dataSpace, android_pixel_format pixelFormat);
+
+ // Lock, covers all member variables.
+ mutable Mutex mMutex;
+
+ // Used to report constructor failure.
+ status_t mInitCheck;
+
+ // Pointer back to the IOMXNode that created us. We send buffers here.
+ sp<IOMXNode> mOMXNode;
+
+ // Set by omxExecuting() / omxIdling().
+ bool mExecuting;
+
+ bool mSuspended;
+
+ // The time to stop sending buffers.
+ int64_t mStopTimeUs;
+
+ // Last dataspace seen
+ android_dataspace mLastDataSpace;
+
+ // Our BufferQueue interfaces. mProducer is passed to the producer through
+ // getIGraphicBufferProducer, and mConsumer is used internally to retrieve
+ // the buffers queued by the producer.
+ sp<IGraphicBufferProducer> mProducer;
+ sp<IGraphicBufferConsumer> mConsumer;
+
+ // Number of frames pending in BufferQueue that haven't yet been
+ // forwarded to the codec.
+ size_t mNumFramesAvailable;
+
+ // Number of frames acquired from consumer (debug only)
+ int32_t mNumBufferAcquired;
+
+ // Set to true if we want to send end-of-stream after we run out of
+ // frames in BufferQueue.
+ bool mEndOfStream;
+ bool mEndOfStreamSent;
+
+ // Cache of GraphicBuffers from the buffer queue. When the codec
+ // is done processing a GraphicBuffer, we can use this to map back
+ // to a slot number.
+ sp<GraphicBuffer> mBufferSlot[BufferQueue::NUM_BUFFER_SLOTS];
+ int32_t mBufferUseCount[BufferQueue::NUM_BUFFER_SLOTS];
+
+ // Tracks codec buffers.
+ Vector<CodecBuffer> mCodecBuffers;
+
+ struct ActionItem {
+ typedef enum {
+ PAUSE,
+ RESUME,
+ STOP
+ } ActionType;
+ ActionType mAction;
+ int64_t mActionTimeUs;
+ };
+
+ // Maintain last action timestamp to ensure all the action timestamps are
+ // monotonically increasing.
+ int64_t mLastActionTimeUs;
+
+ // An action queue that queue up all the actions sent to GraphicBufferSource.
+ // STOP action should only show up at the end of the list as all the actions
+ // after a STOP action will be discarded. mActionQueue is protected by mMutex.
+ List<ActionItem> mActionQueue;
+
+ ////
+ friend struct AHandlerReflector<GraphicBufferSource>;
+
+ enum {
+ kWhatRepeatLastFrame,
+ };
+ enum {
+ kRepeatLastFrameCount = 10,
+ };
+
+ int64_t mPrevOriginalTimeUs;
+ int64_t mSkipFramesBeforeNs;
+
+ sp<FrameDropper> mFrameDropper;
+
+ sp<ALooper> mLooper;
+ sp<AHandlerReflector<GraphicBufferSource> > mReflector;
+
+ int64_t mRepeatAfterUs;
+ int32_t mRepeatLastFrameGeneration;
+ int64_t mRepeatLastFrameTimestamp;
+ int32_t mRepeatLastFrameCount;
+
+ int mLatestBufferId;
+ uint64_t mLatestBufferFrameNum;
+ sp<Fence> mLatestBufferFence;
+
+ // The previous buffer should've been repeated but
+ // no codec buffer was available at the time.
+ bool mRepeatBufferDeferred;
+
+ // Time lapse / slow motion configuration
+ int64_t mTimePerCaptureUs;
+ int64_t mTimePerFrameUs;
+ int64_t mPrevCaptureUs;
+ int64_t mPrevFrameUs;
+
+ int64_t mInputBufferTimeOffsetUs;
+
+ ColorAspects mColorAspects;
+
+ class OmxBufferSource;
+ sp<OmxBufferSource> mOmxBufferSource;
+
+ void onMessageReceived(const sp<AMessage> &msg);
+
+ DISALLOW_EVIL_CONSTRUCTORS(GraphicBufferSource);
+};
+
+} // namespace android
+
+#endif // GRAPHIC_BUFFER_SOURCE_H_