blob: 82ddbc0ff23d2a4aea8588e220631bc1b155e272 [file] [log] [blame]
/*
* 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 <media/stagefright/bqhelper/GraphicBufferSource.h>
#include <media/stagefright/bqhelper/FrameDropper.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/ColorUtils.h>
#include <media/stagefright/foundation/FileDescriptor.h>
#include <android-base/properties.h>
#include <media/hardware/MetadataBufferType.h>
#include <ui/GraphicBuffer.h>
#include <gui/BufferItem.h>
#include <gui/BufferQueue.h>
#include <gui/bufferqueue/1.0/WGraphicBufferProducer.h>
#include <gui/bufferqueue/2.0/B2HGraphicBufferProducer.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/IGraphicBufferConsumer.h>
#include <media/hardware/HardwareAPI.h>
#include <inttypes.h>
#include <functional>
#include <memory>
#include <cmath>
namespace android {
namespace {
// kTimestampFluctuation is an upper bound of timestamp fluctuation from the
// source that GraphicBufferSource allows. The unit of kTimestampFluctuation is
// frames. More specifically, GraphicBufferSource will drop a frame if
//
// expectedNewFrametimestamp - actualNewFrameTimestamp <
// (0.5 - kTimestampFluctuation) * expectedtimePeriodBetweenFrames
//
// where
// - expectedNewFrameTimestamp is the calculated ideal timestamp of the new
// incoming frame
// - actualNewFrameTimestamp is the timestamp received from the source
// - expectedTimePeriodBetweenFrames is the ideal difference of the timestamps
// of two adjacent frames
//
// See GraphicBufferSource::calculateCodecTimestamp_l() for more detail about
// how kTimestampFluctuation is used.
//
// kTimestampFluctuation should be non-negative. A higher value causes a smaller
// chance of dropping frames, but at the same time a higher bound on the
// difference between the source timestamp and the interpreted (snapped)
// timestamp.
//
// The value of 0.05 means that GraphicBufferSource expects the input timestamps
// to fluctuate no more than 5% from the regular time period.
//
// TODO: Justify the choice of this value, or make it configurable.
constexpr double kTimestampFluctuation = 0.05;
}
/**
* A copiable object managing a buffer in the buffer cache managed by the producer. This object
* holds a reference to the buffer, and maintains which buffer slot it belongs to (if any), and
* whether it is still in a buffer slot. It also maintains whether there are any outstanging acquire
* references to it (by buffers acquired from the slot) mainly so that we can keep a debug
* count of how many buffers we need to still release back to the producer.
*/
struct GraphicBufferSource::CachedBuffer {
/**
* Token that is used to track acquire counts (as opposed to all references to this object).
*/
struct Acquirable { };
/**
* Create using a buffer cached in a slot.
*/
CachedBuffer(slot_id slot, const sp<GraphicBuffer> &graphicBuffer)
: mIsCached(true),
mSlot(slot),
mGraphicBuffer(graphicBuffer),
mAcquirable(std::make_shared<Acquirable>()) {
}
/**
* Returns the cache slot that this buffer is cached in, or -1 if it is no longer cached.
*
* This assumes that -1 slot id is invalid; though, it is just a benign collision used for
* debugging. This object explicitly manages whether it is still cached.
*/
slot_id getSlot() const {
return mIsCached ? mSlot : -1;
}
/**
* Returns the cached buffer.
*/
sp<GraphicBuffer> getGraphicBuffer() const {
return mGraphicBuffer;
}
/**
* Checks whether this buffer is still in the buffer cache.
*/
bool isCached() const {
return mIsCached;
}
/**
* Checks whether this buffer has an acquired reference.
*/
bool isAcquired() const {
return mAcquirable.use_count() > 1;
}
/**
* Gets and returns a shared acquired reference.
*/
std::shared_ptr<Acquirable> getAcquirable() {
return mAcquirable;
}
private:
friend void GraphicBufferSource::discardBufferAtSlotIndex_l(ssize_t);
/**
* This method to be called when the buffer is no longer in the buffer cache.
* Called from discardBufferAtSlotIndex_l.
*/
void onDroppedFromCache() {
CHECK_DBG(mIsCached);
mIsCached = false;
}
bool mIsCached;
slot_id mSlot;
sp<GraphicBuffer> mGraphicBuffer;
std::shared_ptr<Acquirable> mAcquirable;
};
/**
* A copiable object managing a buffer acquired from the producer. This must always be a cached
* buffer. This objects also manages its acquire fence and any release fences that may be returned
* by the encoder for this buffer (this buffer may be queued to the encoder multiple times).
* If no release fences are added by the encoder, the acquire fence is returned as the release
* fence for this - as it is assumed that noone waited for the acquire fence. Otherwise, it is
* assumed that the encoder has waited for the acquire fence (or returned it as the release
* fence).
*/
struct GraphicBufferSource::AcquiredBuffer {
AcquiredBuffer(
const std::shared_ptr<CachedBuffer> &buffer,
std::function<void(AcquiredBuffer *)> onReleased,
const sp<Fence> &acquireFence)
: mBuffer(buffer),
mAcquirable(buffer->getAcquirable()),
mAcquireFence(acquireFence),
mGotReleaseFences(false),
mOnReleased(onReleased) {
}
/**
* Adds a release fence returned by the encoder to this object. If this is called with an
* valid file descriptor, it is added to the list of release fences. These are returned to the
* producer on release() as a merged fence. Regardless of the validity of the file descriptor,
* we take note that a release fence was attempted to be added and the acquire fence can now be
* assumed as acquired.
*/
void addReleaseFenceFd(int fenceFd) {
// save all release fences - these will be propagated to the producer if this buffer is
// ever released to it
if (fenceFd >= 0) {
mReleaseFenceFds.push_back(fenceFd);
}
mGotReleaseFences = true;
}
/**
* Returns the acquire fence file descriptor associated with this object.
*/
int getAcquireFenceFd() {
if (mAcquireFence == nullptr || !mAcquireFence->isValid()) {
return -1;
}
return mAcquireFence->dup();
}
/**
* Returns whether the buffer is still in the buffer cache.
*/
bool isCached() const {
return mBuffer->isCached();
}
/**
* Returns the acquired buffer.
*/
sp<GraphicBuffer> getGraphicBuffer() const {
return mBuffer->getGraphicBuffer();
}
/**
* Returns the slot that this buffer is cached at, or -1 otherwise.
*
* This assumes that -1 slot id is invalid; though, it is just a benign collision used for
* debugging. This object explicitly manages whether it is still cached.
*/
slot_id getSlot() const {
return mBuffer->getSlot();
}
/**
* Creates and returns a release fence object from the acquire fence and/or any release fences
* added. If no release fences were added (even if invalid), returns the acquire fence.
* Otherwise, it returns a merged fence from all the valid release fences added.
*/
sp<Fence> getReleaseFence() {
// If did not receive release fences, we assume this buffer was not consumed (it was
// discarded or dropped). In this case release the acquire fence as the release fence.
// We do this here to avoid a dup, close and recreation of the Fence object.
if (!mGotReleaseFences) {
return mAcquireFence;
}
sp<Fence> ret = getReleaseFence(0, mReleaseFenceFds.size());
// clear fds as fence took ownership of them
mReleaseFenceFds.clear();
return ret;
}
// this video buffer is no longer referenced by the codec (or kept for later encoding)
// it is now safe to release to the producer
~AcquiredBuffer() {
//mAcquirable.clear();
mOnReleased(this);
// mOnRelease method should call getReleaseFence() that releases all fds but just in case
ALOGW_IF(!mReleaseFenceFds.empty(), "release fences were not obtained, closing fds");
for (int fildes : mReleaseFenceFds) {
::close(fildes);
TRESPASS_DBG();
}
}
private:
std::shared_ptr<GraphicBufferSource::CachedBuffer> mBuffer;
std::shared_ptr<GraphicBufferSource::CachedBuffer::Acquirable> mAcquirable;
sp<Fence> mAcquireFence;
Vector<int> mReleaseFenceFds;
bool mGotReleaseFences;
std::function<void(AcquiredBuffer *)> mOnReleased;
/**
* Creates and returns a release fence from 0 or more release fence file descriptors in from
* the specified range in the array.
*
* @param start start index
* @param num number of release fds to merge
*/
sp<Fence> getReleaseFence(size_t start, size_t num) const {
if (num == 0) {
return Fence::NO_FENCE;
} else if (num == 1) {
return new Fence(mReleaseFenceFds[start]);
} else {
return Fence::merge("GBS::AB",
getReleaseFence(start, num >> 1),
getReleaseFence(start + (num >> 1), num - (num >> 1)));
}
}
};
struct GraphicBufferSource::ConsumerProxy : public BufferQueue::ConsumerListener {
ConsumerProxy(const wp<GraphicBufferSource> &gbs) : mGbs(gbs) {}
~ConsumerProxy() = default;
void onFrameAvailable(const BufferItem& item) override {
sp<GraphicBufferSource> gbs = mGbs.promote();
if (gbs != nullptr) {
gbs->onFrameAvailable(item);
}
}
void onBuffersReleased() override {
sp<GraphicBufferSource> gbs = mGbs.promote();
if (gbs != nullptr) {
gbs->onBuffersReleased();
}
}
void onSidebandStreamChanged() override {
sp<GraphicBufferSource> gbs = mGbs.promote();
if (gbs != nullptr) {
gbs->onSidebandStreamChanged();
}
}
private:
// Note that GraphicBufferSource is holding an sp to us, we can't hold
// an sp back to GraphicBufferSource as the circular dependency will
// make both immortal.
wp<GraphicBufferSource> mGbs;
};
GraphicBufferSource::GraphicBufferSource() :
mInitCheck(UNKNOWN_ERROR),
mNumAvailableUnacquiredBuffers(0),
mNumOutstandingAcquires(0),
mEndOfStream(false),
mEndOfStreamSent(false),
mLastDataspace(HAL_DATASPACE_UNKNOWN),
mExecuting(false),
mSuspended(false),
mLastFrameTimestampUs(-1),
mStopTimeUs(-1),
mLastActionTimeUs(-1LL),
mSkipFramesBeforeNs(-1LL),
mFrameRepeatIntervalUs(-1LL),
mRepeatLastFrameGeneration(0),
mOutstandingFrameRepeatCount(0),
mFrameRepeatBlockedOnCodecBuffer(false),
mFps(-1.0),
mCaptureFps(-1.0),
mBaseCaptureUs(-1LL),
mBaseFrameUs(-1LL),
mFrameCount(0),
mPrevCaptureUs(-1LL),
mPrevFrameUs(-1LL),
mInputBufferTimeOffsetUs(0LL) {
ALOGV("GraphicBufferSource");
String8 name("GraphicBufferSource");
BufferQueue::createBufferQueue(&mProducer, &mConsumer);
mConsumer->setConsumerName(name);
// create the consumer listener interface, and hold sp so that this
// interface lives as long as the GraphicBufferSource.
mConsumerProxy = new ConsumerProxy(this);
sp<IConsumerListener> proxy =
new BufferQueue::ProxyConsumerListener(mConsumerProxy);
mInitCheck = mConsumer->consumerConnect(proxy, false);
if (mInitCheck != NO_ERROR) {
ALOGE("Error connecting to BufferQueue: %s (%d)",
strerror(-mInitCheck), mInitCheck);
return;
}
memset(&mDefaultColorAspectsPacked, 0, sizeof(mDefaultColorAspectsPacked));
CHECK(mInitCheck == NO_ERROR);
}
GraphicBufferSource::~GraphicBufferSource() {
ALOGV("~GraphicBufferSource");
{
// all acquired buffers must be freed with the mutex locked otherwise our debug assertion
// may trigger
Mutex::Autolock autoLock(mMutex);
mAvailableBuffers.clear();
mSubmittedCodecBuffers.clear();
mLatestBuffer.mBuffer.reset();
}
if (mNumOutstandingAcquires != 0) {
ALOGW("potential buffer leak: acquired=%d", mNumOutstandingAcquires);
TRESPASS_DBG();
}
if (mConsumer != NULL) {
status_t err = mConsumer->consumerDisconnect();
if (err != NO_ERROR) {
ALOGW("consumerDisconnect failed: %d", err);
}
}
}
sp<IGraphicBufferProducer> GraphicBufferSource::getIGraphicBufferProducer() const {
return mProducer;
}
sp<::android::hardware::graphics::bufferqueue::V1_0::IGraphicBufferProducer>
GraphicBufferSource::getHGraphicBufferProducer_V1_0() const {
using TWGraphicBufferProducer = ::android::TWGraphicBufferProducer<
::android::hardware::graphics::bufferqueue::V1_0::IGraphicBufferProducer>;
return new TWGraphicBufferProducer(getIGraphicBufferProducer());
}
sp<::android::hardware::graphics::bufferqueue::V2_0::IGraphicBufferProducer>
GraphicBufferSource::getHGraphicBufferProducer() const {
return new ::android::hardware::graphics::bufferqueue::V2_0::utils::
B2HGraphicBufferProducer(getIGraphicBufferProducer());
}
status_t GraphicBufferSource::start() {
Mutex::Autolock autoLock(mMutex);
ALOGV("--> start; available=%zu, submittable=%zd",
mAvailableBuffers.size(), mFreeCodecBuffers.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 (haveAvailableBuffers_l()) {
if (!fillCodecBuffer_l()) {
ALOGV("stop load with available=%zu+%d",
mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
break;
}
}
ALOGV("done loading initial frames, available=%zu+%d",
mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
// 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 && !haveAvailableBuffers_l()) {
submitEndOfInputStream_l();
}
if (mFrameRepeatIntervalUs > 0LL && mLooper == NULL) {
mReflector = new AHandlerReflector<GraphicBufferSource>(this);
mLooper = new ALooper;
mLooper->registerHandler(mReflector);
mLooper->start();
if (mLatestBuffer.mBuffer != nullptr) {
queueFrameRepeat_l();
}
}
return OK;
}
status_t GraphicBufferSource::stop() {
ALOGV("stop");
Mutex::Autolock autoLock(mMutex);
if (mExecuting) {
// We are only interested in the transition from executing->idle,
// not loaded->idle.
mExecuting = false;
}
return OK;
}
status_t GraphicBufferSource::release(){
sp<ALooper> looper;
{
Mutex::Autolock autoLock(mMutex);
looper = mLooper;
if (mLooper != NULL) {
mLooper->unregisterHandler(mReflector->id());
mReflector.clear();
mLooper.clear();
}
ALOGV("--> release; available=%zu+%d eos=%d eosSent=%d acquired=%d",
mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers,
mEndOfStream, mEndOfStreamSent, mNumOutstandingAcquires);
// Codec is no longer executing. Releasing all buffers to bq.
mFreeCodecBuffers.clear();
mSubmittedCodecBuffers.clear();
mLatestBuffer.mBuffer.reset();
mComponent.clear();
mExecuting = false;
}
if (looper != NULL) {
looper->stop();
}
return OK;
}
status_t GraphicBufferSource::onInputBufferAdded(codec_buffer_id 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 INVALID_OPERATION;
}
ALOGV("addCodecBuffer: bufferId=%u", bufferId);
mFreeCodecBuffers.push_back(bufferId);
return OK;
}
status_t GraphicBufferSource::onInputBufferEmptied(codec_buffer_id bufferId, int fenceFd) {
Mutex::Autolock autoLock(mMutex);
FileDescriptor::Autoclose fence(fenceFd);
ssize_t cbi = mSubmittedCodecBuffers.indexOfKey(bufferId);
if (cbi < 0) {
// This should never happen.
ALOGE("onInputBufferEmptied: buffer not recognized (bufferId=%u)", bufferId);
return BAD_VALUE;
}
std::shared_ptr<AcquiredBuffer> buffer = mSubmittedCodecBuffers.valueAt(cbi);
// Move buffer to available buffers
mSubmittedCodecBuffers.removeItemsAt(cbi);
mFreeCodecBuffers.push_back(bufferId);
// 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 there is a null AcquiredBuffer, which should only ever happen for EOS.
if (buffer == nullptr) {
if (!(mEndOfStream && mEndOfStreamSent)) {
// This can happen when broken code sends us the same buffer twice in a row.
ALOGE("onInputBufferEmptied: non-EOS null buffer (bufferId=%u)", bufferId);
} else {
ALOGV("onInputBufferEmptied: EOS null buffer (bufferId=%u@%zd)", bufferId, cbi);
}
// No GraphicBuffer to deal with, no additional input or output is expected, so just return.
return BAD_VALUE;
}
if (!mExecuting) {
// this is fine since this could happen when going from Idle to Loaded
ALOGV("onInputBufferEmptied: no longer executing (bufferId=%u@%zd)", bufferId, cbi);
return OK;
}
ALOGV("onInputBufferEmptied: bufferId=%d@%zd [slot=%d, useCount=%ld, handle=%p] acquired=%d",
bufferId, cbi, buffer->getSlot(), buffer.use_count(), buffer->getGraphicBuffer()->handle,
mNumOutstandingAcquires);
buffer->addReleaseFenceFd(fence.release());
// release codec reference for video buffer just in case remove does not it
buffer.reset();
if (haveAvailableBuffers_l()) {
// Fill this codec buffer.
CHECK(!mEndOfStreamSent);
ALOGV("onInputBufferEmptied: buffer freed, feeding codec (available=%zu+%d, eos=%d)",
mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers, 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("onInputBufferEmptied: buffer freed, submitting EOS");
submitEndOfInputStream_l();
} else if (mFrameRepeatBlockedOnCodecBuffer) {
bool success = repeatLatestBuffer_l();
ALOGV("onInputBufferEmptied: completing deferred repeatLatestBuffer_l %s",
success ? "SUCCESS" : "FAILURE");
mFrameRepeatBlockedOnCodecBuffer = false;
}
// releaseReleasableBuffers_l();
return 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)) {
mComponent->dispatchDataSpaceChanged(
mLastDataspace, mDefaultColorAspectsPacked, pixelFormat);
}
}
bool GraphicBufferSource::fillCodecBuffer_l() {
CHECK(mExecuting && haveAvailableBuffers_l());
if (mFreeCodecBuffers.empty()) {
// No buffers available, bail.
ALOGV("fillCodecBuffer_l: no codec buffers, available=%zu+%d",
mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
return false;
}
VideoBuffer item;
if (mAvailableBuffers.empty()) {
ALOGV("fillCodecBuffer_l: acquiring available buffer, available=%zu+%d",
mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
if (acquireBuffer_l(&item) != OK) {
ALOGE("fillCodecBuffer_l: failed to acquire available buffer");
return false;
}
} else {
ALOGV("fillCodecBuffer_l: getting available buffer, available=%zu+%d",
mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
item = *mAvailableBuffers.begin();
mAvailableBuffers.erase(mAvailableBuffers.begin());
}
int64_t itemTimeUs = item.mTimestampNs / 1000;
// 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 1us], [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 done = false;
bool seeStopAction = 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.
done = true;
}
if (!done) {
// Find the newest action that with timestamp smaller than itemTimeUs. Then
// remove all the actions before and include the newest action.
List<ActionItem>::iterator it = mActionQueue.begin();
while (it != mActionQueue.end() && it->mActionTimeUs <= itemTimeUs
&& nextAction.mAction != ActionItem::STOP) {
nextAction = *it;
++it;
}
mActionQueue.erase(mActionQueue.begin(), it);
CHECK(itemTimeUs >= nextAction.mActionTimeUs);
switch (nextAction.mAction) {
case ActionItem::PAUSE:
{
mSuspended = 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);
// Clear the whole ActionQueue as recording is done
mActionQueue.clear();
seeStopAction = true;
break;
}
default:
TRESPASS_DBG("Unknown action type");
// return true here because we did consume an available buffer, so the
// loop in start will eventually terminate even if we hit this.
return false;
}
}
}
if (seeStopAction) {
// Clear all the buffers before setting mEndOfStream and signal EndOfInputStream.
releaseAllAvailableBuffers_l();
mEndOfStream = true;
submitEndOfInputStream_l();
return true;
}
if (mSuspended) {
return true;
}
int err = UNKNOWN_ERROR;
// only submit sample if start time is unspecified, or sample
// is queued after the specified start time
if (mSkipFramesBeforeNs < 0LL || item.mTimestampNs >= mSkipFramesBeforeNs) {
// if start time is set, offset time stamp by start time
if (mSkipFramesBeforeNs > 0) {
item.mTimestampNs -= mSkipFramesBeforeNs;
}
int64_t timeUs = item.mTimestampNs / 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;
} else {
err = submitBuffer_l(item); // this takes shared ownership of the acquired buffer on succeess
}
}
if (err != OK) {
ALOGV("submitBuffer_l failed, will release bq slot %d", item.mBuffer->getSlot());
return true;
} 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 (mFrameRepeatIntervalUs > 0LL) {
setLatestBuffer_l(item);
}
ALOGV("buffer submitted [slot=%d, useCount=%ld] acquired=%d",
item.mBuffer->getSlot(), item.mBuffer.use_count(), mNumOutstandingAcquires);
mLastFrameTimestampUs = itemTimeUs;
}
return true;
}
bool GraphicBufferSource::repeatLatestBuffer_l() {
CHECK(mExecuting && !haveAvailableBuffers_l());
if (mLatestBuffer.mBuffer == nullptr || mSuspended) {
return false;
}
if (mFreeCodecBuffers.empty()) {
// No buffers available, bail.
ALOGV("repeatLatestBuffer_l: no codec buffers.");
return false;
}
if (!mLatestBuffer.mBuffer->isCached()) {
ALOGV("repeatLatestBuffer_l: slot was discarded, but repeating our own reference");
}
// it is ok to update the timestamp of latest buffer as it is only used for submission
status_t err = submitBuffer_l(mLatestBuffer);
if (err != OK) {
return false;
}
/* 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 (--mOutstandingFrameRepeatCount > 0) {
// set up timestamp for repeat frame
mLatestBuffer.mTimestampNs += mFrameRepeatIntervalUs * 1000;
queueFrameRepeat_l();
}
return true;
}
void GraphicBufferSource::setLatestBuffer_l(const VideoBuffer &item) {
mLatestBuffer = item;
ALOGV("setLatestBuffer_l: [slot=%d, useCount=%ld]",
mLatestBuffer.mBuffer->getSlot(), mLatestBuffer.mBuffer.use_count());
mOutstandingFrameRepeatCount = kRepeatLastFrameCount;
// set up timestamp for repeat frame
mLatestBuffer.mTimestampNs += mFrameRepeatIntervalUs * 1000;
queueFrameRepeat_l();
}
void GraphicBufferSource::queueFrameRepeat_l() {
mFrameRepeatBlockedOnCodecBuffer = false;
if (mReflector != NULL) {
sp<AMessage> msg = new AMessage(kWhatRepeatLastFrame, mReflector);
msg->setInt32("generation", ++mRepeatLastFrameGeneration);
msg->post(mFrameRepeatIntervalUs);
}
}
#ifdef __clang__
__attribute__((no_sanitize("integer")))
#endif
bool GraphicBufferSource::calculateCodecTimestamp_l(
nsecs_t bufferTimeNs, int64_t *codecTimeUs) {
int64_t timeUs = bufferTimeNs / 1000;
timeUs += mInputBufferTimeOffsetUs;
if (mCaptureFps > 0.
&& (mFps > 2 * mCaptureFps
|| mCaptureFps > 2 * mFps)) {
// Time lapse or slow motion mode
if (mPrevCaptureUs < 0LL) {
// first capture
mPrevCaptureUs = mBaseCaptureUs = timeUs;
// adjust the first sample timestamp.
mPrevFrameUs = mBaseFrameUs =
std::llround((timeUs * mCaptureFps) / mFps);
mFrameCount = 0;
} else if (mSnapTimestamps) {
double nFrames = (timeUs - mPrevCaptureUs) * mCaptureFps / 1000000;
if (nFrames < 0.5 - kTimestampFluctuation) {
// skip this frame as it's too close to previous capture
ALOGD("skipping frame, timeUs %lld",
static_cast<long long>(timeUs));
return false;
}
// snap to nearest capture point
if (nFrames <= 1.0) {
nFrames = 1.0;
}
mFrameCount += std::llround(nFrames);
mPrevCaptureUs = mBaseCaptureUs + std::llround(
mFrameCount * 1000000 / mCaptureFps);
mPrevFrameUs = mBaseFrameUs + std::llround(
mFrameCount * 1000000 / mFps);
} else {
if (timeUs <= mPrevCaptureUs) {
if (mFrameDropper != NULL && mFrameDropper->disabled()) {
// Warn only, client has disabled frame drop logic possibly for image
// encoding cases where camera's ZSL mode could send out of order frames.
ALOGW("Received frame that's going backward in time");
} else {
// 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;
}
}
mPrevCaptureUs = timeUs;
mPrevFrameUs = mBaseFrameUs + std::llround(
(timeUs - mBaseCaptureUs) * (mCaptureFps / mFps));
}
ALOGV("timeUs %lld, captureUs %lld, frameUs %lld",
static_cast<long long>(timeUs),
static_cast<long long>(mPrevCaptureUs),
static_cast<long long>(mPrevFrameUs));
} else {
if (timeUs <= mPrevFrameUs) {
if (mFrameDropper != NULL && mFrameDropper->disabled()) {
// Warn only, client has disabled frame drop logic possibly for image
// encoding cases where camera's ZSL mode could send out of order frames.
ALOGW("Received frame that's going backward in time");
} else {
// 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;
}
}
mPrevFrameUs = timeUs;
}
*codecTimeUs = mPrevFrameUs;
return true;
}
status_t GraphicBufferSource::submitBuffer_l(const VideoBuffer &item) {
CHECK(!mFreeCodecBuffers.empty());
uint32_t codecBufferId = *mFreeCodecBuffers.begin();
ALOGV("submitBuffer_l [slot=%d, bufferId=%d]", item.mBuffer->getSlot(), codecBufferId);
int64_t codecTimeUs;
if (!calculateCodecTimestamp_l(item.mTimestampNs, &codecTimeUs)) {
return UNKNOWN_ERROR;
}
if ((android_dataspace)item.mDataspace != mLastDataspace) {
onDataspaceChanged_l(
item.mDataspace,
(android_pixel_format)item.mBuffer->getGraphicBuffer()->format);
}
std::shared_ptr<AcquiredBuffer> buffer = item.mBuffer;
// use a GraphicBuffer for now as component is using GraphicBuffers to hold references
// and it requires this graphic buffer to be able to hold its reference
// and thus we would need to create a new GraphicBuffer from an ANWBuffer separate from the
// acquired GraphicBuffer.
// TODO: this can be reworked globally to use ANWBuffer references
sp<GraphicBuffer> graphicBuffer = buffer->getGraphicBuffer();
status_t err = mComponent->submitBuffer(
codecBufferId, graphicBuffer, codecTimeUs, buffer->getAcquireFenceFd());
if (err != OK) {
ALOGW("WARNING: emptyGraphicBuffer failed: 0x%x", err);
return err;
}
mFreeCodecBuffers.erase(mFreeCodecBuffers.begin());
ssize_t cbix = mSubmittedCodecBuffers.add(codecBufferId, buffer);
ALOGV("emptyGraphicBuffer succeeded, bufferId=%u@%zd bufhandle=%p",
codecBufferId, cbix, graphicBuffer->handle);
return OK;
}
void GraphicBufferSource::submitEndOfInputStream_l() {
CHECK(mEndOfStream);
if (mEndOfStreamSent) {
ALOGV("EOS already sent");
return;
}
if (mFreeCodecBuffers.empty()) {
ALOGV("submitEndOfInputStream_l: no codec buffers available");
return;
}
uint32_t codecBufferId = *mFreeCodecBuffers.begin();
// We reject any additional incoming graphic buffers. There is no acquired buffer used for EOS
status_t err = mComponent->submitEos(codecBufferId);
if (err != OK) {
ALOGW("emptyDirectBuffer EOS failed: 0x%x", err);
} else {
mFreeCodecBuffers.erase(mFreeCodecBuffers.begin());
ssize_t cbix = mSubmittedCodecBuffers.add(codecBufferId, nullptr);
ALOGV("submitEndOfInputStream_l: buffer submitted, bufferId=%u@%zd", codecBufferId, cbix);
mEndOfStreamSent = true;
// no need to hold onto any buffers for frame repeating
++mRepeatLastFrameGeneration;
mLatestBuffer.mBuffer.reset();
}
}
status_t GraphicBufferSource::acquireBuffer_l(VideoBuffer *ab) {
BufferItem bi;
status_t err = mConsumer->acquireBuffer(&bi, 0);
if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
// shouldn't happen
ALOGW("acquireBuffer_l: frame was not available");
return err;
} else if (err != OK) {
ALOGW("acquireBuffer_l: failed with err=%d", err);
return err;
}
--mNumAvailableUnacquiredBuffers;
// Manage our buffer cache.
std::shared_ptr<CachedBuffer> buffer;
ssize_t bsi = mBufferSlots.indexOfKey(bi.mSlot);
if (bi.mGraphicBuffer != NULL) {
// replace/initialize slot with new buffer
ALOGV("acquireBuffer_l: %s buffer slot %d", bsi < 0 ? "setting" : "UPDATING", bi.mSlot);
if (bsi >= 0) {
discardBufferAtSlotIndex_l(bsi);
} else {
bsi = mBufferSlots.add(bi.mSlot, nullptr);
}
buffer = std::make_shared<CachedBuffer>(bi.mSlot, bi.mGraphicBuffer);
mBufferSlots.replaceValueAt(bsi, buffer);
} else {
buffer = mBufferSlots.valueAt(bsi);
}
int64_t frameNum = bi.mFrameNumber;
std::shared_ptr<AcquiredBuffer> acquiredBuffer =
std::make_shared<AcquiredBuffer>(
buffer,
[frameNum, this](AcquiredBuffer *buffer){
// AcquiredBuffer's destructor should always be called when mMutex is locked.
// If we had a reentrant mutex, we could just lock it again to ensure this.
if (mMutex.tryLock() == 0) {
TRESPASS_DBG();
mMutex.unlock();
}
// we can release buffers immediately if not using adapters
// alternately, we could add them to mSlotsToRelease, but we would
// somehow need to propagate frame number to that queue
if (buffer->isCached()) {
--mNumOutstandingAcquires;
mConsumer->releaseBuffer(
buffer->getSlot(), frameNum, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
buffer->getReleaseFence());
}
},
bi.mFence);
VideoBuffer videoBuffer{acquiredBuffer, bi.mTimestamp, bi.mDataSpace};
*ab = videoBuffer;
++mNumOutstandingAcquires;
return OK;
}
// BufferQueue::ConsumerListener callback
void GraphicBufferSource::onFrameAvailable(const BufferItem& item __unused) {
Mutex::Autolock autoLock(mMutex);
ALOGV("onFrameAvailable: executing=%d available=%zu+%d",
mExecuting, mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
++mNumAvailableUnacquiredBuffers;
// For BufferQueue we cannot acquire a buffer if we cannot immediately feed it to the codec
// UNLESS we are discarding this buffer (acquiring and immediately releasing it), which makes
// this an ugly logic.
// NOTE: We could also rely on our debug counter but that is meant only as a debug counter.
if (!areWeDiscardingAvailableBuffers_l() && mFreeCodecBuffers.empty()) {
// we may not be allowed to acquire a possibly encodable buffer, so just note that
// it is available
ALOGV("onFrameAvailable: cannot acquire buffer right now, do it later");
++mRepeatLastFrameGeneration; // cancel any pending frame repeat
return;
}
VideoBuffer buffer;
status_t err = acquireBuffer_l(&buffer);
if (err != OK) {
ALOGE("onFrameAvailable: acquireBuffer returned err=%d", err);
} else {
onBufferAcquired_l(buffer);
}
}
bool GraphicBufferSource::areWeDiscardingAvailableBuffers_l() {
return mEndOfStreamSent // already sent EOS to codec
|| mComponent == nullptr // there is no codec connected
|| (mSuspended && mActionQueue.empty()) // we are suspended and not waiting for
// any further action
|| !mExecuting;
}
void GraphicBufferSource::onBufferAcquired_l(const VideoBuffer &buffer) {
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 if (mComponent == NULL || (mSuspended && mActionQueue.empty())) {
// FIXME: if we are suspended but have a resume queued we will stop repeating the last
// frame. Is that the desired behavior?
ALOGV("onFrameAvailable: suspended, ignoring frame");
} else {
++mRepeatLastFrameGeneration; // cancel any pending frame repeat
mAvailableBuffers.push_back(buffer);
if (mExecuting) {
fillCodecBuffer_l();
}
}
}
// BufferQueue::ConsumerListener callback
void GraphicBufferSource::onBuffersReleased() {
Mutex::Autolock lock(mMutex);
uint64_t slotMask;
uint64_t releaseMask;
if (mConsumer->getReleasedBuffers(&releaseMask) != NO_ERROR) {
slotMask = 0xffffffffffffffffULL;
ALOGW("onBuffersReleased: unable to get released buffer set");
} else {
slotMask = releaseMask;
ALOGV("onBuffersReleased: 0x%016" PRIx64, slotMask);
}
AString unpopulated;
for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
if ((slotMask & 0x01) != 0) {
if (!discardBufferInSlot_l(i)) {
if (!unpopulated.empty()) {
unpopulated.append(", ");
}
unpopulated.append(i);
}
}
slotMask >>= 1;
}
if (!unpopulated.empty()) {
ALOGW("released unpopulated slots: [%s]", unpopulated.c_str());
}
}
bool GraphicBufferSource::discardBufferInSlot_l(GraphicBufferSource::slot_id i) {
ssize_t bsi = mBufferSlots.indexOfKey(i);
if (bsi < 0) {
return false;
} else {
discardBufferAtSlotIndex_l(bsi);
mBufferSlots.removeItemsAt(bsi);
return true;
}
}
void GraphicBufferSource::discardBufferAtSlotIndex_l(ssize_t bsi) {
const std::shared_ptr<CachedBuffer>& buffer = mBufferSlots.valueAt(bsi);
// use -2 if there is no latest buffer, and -1 if it is no longer cached
slot_id latestBufferSlot =
mLatestBuffer.mBuffer == nullptr ? -2 : mLatestBuffer.mBuffer->getSlot();
ALOGV("releasing acquired buffer: [slot=%d, useCount=%ld], latest: [slot=%d]",
mBufferSlots.keyAt(bsi), buffer.use_count(), latestBufferSlot);
mBufferSlots.valueAt(bsi)->onDroppedFromCache();
// If the slot of an acquired buffer is discarded, that buffer will not have to be
// released to the producer, so account it here. However, it is possible that the
// acquired buffer has already been discarded so check if it still is.
if (buffer->isAcquired()) {
--mNumOutstandingAcquires;
}
// clear the buffer reference (not technically needed as caller either replaces or deletes
// it; done here for safety).
mBufferSlots.editValueAt(bsi).reset();
CHECK_DBG(buffer == nullptr);
}
void GraphicBufferSource::releaseAllAvailableBuffers_l() {
mAvailableBuffers.clear();
while (mNumAvailableUnacquiredBuffers > 0) {
VideoBuffer item;
if (acquireBuffer_l(&item) != OK) {
ALOGW("releaseAllAvailableBuffers: failed to acquire available unacquired buffer");
break;
}
}
}
// BufferQueue::ConsumerListener callback
void GraphicBufferSource::onSidebandStreamChanged() {
ALOG_ASSERT(false, "GraphicBufferSource can't consume sideband streams");
}
status_t GraphicBufferSource::configure(
const sp<ComponentWrapper>& component,
int32_t dataSpace,
int32_t bufferCount,
uint32_t frameWidth,
uint32_t frameHeight,
uint32_t consumerUsage) {
uint64_t consumerUsage64 = static_cast<uint64_t>(consumerUsage);
return configure(component, dataSpace, bufferCount,
frameWidth, frameHeight, consumerUsage64);
}
status_t GraphicBufferSource::configure(
const sp<ComponentWrapper>& component,
int32_t dataSpace,
int32_t bufferCount,
uint32_t frameWidth,
uint32_t frameHeight,
uint64_t consumerUsage) {
if (component == NULL) {
return BAD_VALUE;
}
// 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.
status_t err = mConsumer->setMaxAcquiredBufferCount(bufferCount);
if (err != NO_ERROR) {
ALOGE("Unable to set BQ max acquired buffer count to %u: %d",
bufferCount, err);
return err;
}
{
Mutex::Autolock autoLock(mMutex);
mComponent = component;
err = mConsumer->setDefaultBufferSize(frameWidth, frameHeight);
if (err != NO_ERROR) {
ALOGE("Unable to set BQ default buffer size to %ux%u: %d",
frameWidth, frameHeight, err);
return err;
}
consumerUsage |= GRALLOC_USAGE_HW_VIDEO_ENCODER;
mConsumer->setConsumerUsageBits(consumerUsage);
// Set impl. defined format as default. Depending on the usage flags
// the device-specific implementation will derive the exact format.
err = mConsumer->setDefaultBufferFormat(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
if (err != NO_ERROR) {
ALOGE("Failed to configure surface default format ret: %d", err);
return err;
}
// Sets the default buffer data space
ALOGD("setting dataspace: %#x, acquired=%d", dataSpace, mNumOutstandingAcquires);
mConsumer->setDefaultBufferDataSpace((android_dataspace)dataSpace);
mLastDataspace = (android_dataspace)dataSpace;
mExecuting = false;
mSuspended = false;
mEndOfStream = false;
mEndOfStreamSent = false;
mSkipFramesBeforeNs = -1LL;
mFrameDropper.clear();
mFrameRepeatIntervalUs = -1LL;
mRepeatLastFrameGeneration = 0;
mOutstandingFrameRepeatCount = 0;
mLatestBuffer.mBuffer.reset();
mFrameRepeatBlockedOnCodecBuffer = false;
mFps = -1.0;
mCaptureFps = -1.0;
mBaseCaptureUs = -1LL;
mBaseFrameUs = -1LL;
mPrevCaptureUs = -1LL;
mPrevFrameUs = -1LL;
mFrameCount = 0;
mInputBufferTimeOffsetUs = 0;
mStopTimeUs = -1;
mActionQueue.clear();
}
return OK;
}
status_t 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 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 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 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;
releaseAllAvailableBuffers_l();
return OK;
} else {
mSuspended = false;
if (mExecuting && !haveAvailableBuffers_l()
&& mFrameRepeatBlockedOnCodecBuffer) {
if (repeatLatestBuffer_l()) {
ALOGV("suspend/deferred repeatLatestBuffer_l SUCCESS");
mFrameRepeatBlockedOnCodecBuffer = false;
} else {
ALOGV("suspend/deferred repeatLatestBuffer_l FAILURE");
}
}
}
}
return OK;
}
status_t GraphicBufferSource::setRepeatPreviousFrameDelayUs(int64_t repeatAfterUs) {
ALOGV("setRepeatPreviousFrameDelayUs: delayUs=%lld", (long long)repeatAfterUs);
Mutex::Autolock autoLock(mMutex);
if (mExecuting || repeatAfterUs <= 0LL) {
return INVALID_OPERATION;
}
mFrameRepeatIntervalUs = repeatAfterUs;
return OK;
}
status_t GraphicBufferSource::setTimeOffsetUs(int64_t timeOffsetUs) {
Mutex::Autolock autoLock(mMutex);
// timeOffsetUs must be negative for adjustment.
if (timeOffsetUs >= 0LL) {
return INVALID_OPERATION;
}
mInputBufferTimeOffsetUs = timeOffsetUs;
return OK;
}
status_t GraphicBufferSource::setMaxFps(float maxFps) {
ALOGV("setMaxFps: maxFps=%lld", (long long)maxFps);
Mutex::Autolock autoLock(mMutex);
if (mExecuting) {
return INVALID_OPERATION;
}
mFrameDropper = new FrameDropper();
status_t err = mFrameDropper->setMaxFrameRate(maxFps);
if (err != OK) {
mFrameDropper.clear();
return err;
}
return OK;
}
status_t GraphicBufferSource::setStartTimeUs(int64_t skipFramesBeforeUs) {
ALOGV("setStartTimeUs: skipFramesBeforeUs=%lld", (long long)skipFramesBeforeUs);
Mutex::Autolock autoLock(mMutex);
mSkipFramesBeforeNs =
(skipFramesBeforeUs > 0 && skipFramesBeforeUs <= INT64_MAX / 1000) ?
(skipFramesBeforeUs * 1000) : -1LL;
return OK;
}
status_t 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 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 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 INVALID_OPERATION;
}
mLastActionTimeUs = stopTimeUs;
ActionItem action;
action.mAction = ActionItem::STOP;
action.mActionTimeUs = stopTimeUs;
mActionQueue.push_back(action);
mStopTimeUs = stopTimeUs;
return OK;
}
status_t GraphicBufferSource::getStopTimeOffsetUs(int64_t *stopTimeOffsetUs) {
ALOGV("getStopTimeOffsetUs");
Mutex::Autolock autoLock(mMutex);
if (mStopTimeUs == -1) {
ALOGW("Fail to return stopTimeOffsetUs as stop time is not set");
return INVALID_OPERATION;
}
*stopTimeOffsetUs =
mLastFrameTimestampUs == -1 ? 0 : mStopTimeUs - mLastFrameTimestampUs;
return OK;
}
status_t GraphicBufferSource::setTimeLapseConfig(double fps, double captureFps) {
ALOGV("setTimeLapseConfig: fps=%lg, captureFps=%lg",
fps, captureFps);
Mutex::Autolock autoLock(mMutex);
if (mExecuting || !(fps > 0) || !(captureFps > 0)) {
return INVALID_OPERATION;
}
mFps = fps;
mCaptureFps = captureFps;
if (captureFps > fps) {
mSnapTimestamps = 1 == base::GetIntProperty(
"debug.stagefright.snap_timestamps", int64_t(0));
} else {
mSnapTimestamps = false;
}
return OK;
}
status_t GraphicBufferSource::setColorAspects(int32_t aspectsPacked) {
Mutex::Autolock autoLock(mMutex);
mDefaultColorAspectsPacked = aspectsPacked;
ColorAspects colorAspects = ColorUtils::unpackToColorAspects(aspectsPacked);
ALOGD("requesting color aspects (R:%d(%s), P:%d(%s), M:%d(%s), T:%d(%s))",
colorAspects.mRange, asString(colorAspects.mRange),
colorAspects.mPrimaries, asString(colorAspects.mPrimaries),
colorAspects.mMatrixCoeffs, asString(colorAspects.mMatrixCoeffs),
colorAspects.mTransfer, asString(colorAspects.mTransfer));
return OK;
}
status_t GraphicBufferSource::signalEndOfInputStream() {
Mutex::Autolock autoLock(mMutex);
ALOGV("signalEndOfInputStream: executing=%d available=%zu+%d eos=%d",
mExecuting, mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers, mEndOfStream);
if (mEndOfStream) {
ALOGE("EOS was already signaled");
return 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 start) 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 && !haveAvailableBuffers_l()) {
submitEndOfInputStream_l();
}
return 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 || haveAvailableBuffers_l()) {
break;
}
bool success = repeatLatestBuffer_l();
if (success) {
ALOGV("repeatLatestBuffer_l SUCCESS");
} else {
ALOGV("repeatLatestBuffer_l FAILURE");
mFrameRepeatBlockedOnCodecBuffer = true;
}
break;
}
default:
TRESPASS();
}
}
} // namespace android