1467 lines
52 KiB
C++
1467 lines
52 KiB
C++
/*
|
|
* 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,
|
|
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
|