前一节我们了解了input buffer写入的流程,知道了起播写前几笔数据时会先获取graphic buffer,这一节我们就一起来了解下dequeueBufferFromNativeWindow是如何工作的。
ACodec::BufferInfo *ACodec::dequeueBufferFromNativeWindow() {
// 判断是否有 native window
ANativeWindowBuffer *buf;
CHECK(mNativeWindow.get() != NULL);
// tunnel mode无需获取graphic buffer
if (mTunneled) {
ALOGW("dequeueBufferFromNativeWindow() should not be called in tunnel"
" video playback mode mode!");
return NULL;
}
if (mFatalError) {
ALOGW("not dequeuing from native window due to fatal error");
return NULL;
}
int fenceFd = -1;
do {
// 从 native window 获取 buffer
status_t err = mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buf, &fenceFd);
if (err != 0) {
ALOGE("dequeueBuffer failed: %s(%d).", asString(err), err);
return NULL;
}
bool stale = false;
for (size_t i = mBuffers[kPortIndexOutput].size(); i > 0;) {
i--;
// 获取一个 BufferInfo,index从大到小
BufferInfo *info = &mBuffers[kPortIndexOutput].editItemAt(i);
// 如果bufferinfo中的mGraphicBuffer不为null,且mGraphicBuffer的handle等于获取的buffer的handle
if (info->mGraphicBuffer != NULL &&
info->mGraphicBuffer->handle == buf->handle) {
// Since consumers can attach buffers to BufferQueues, it is possible
// that a known yet stale buffer can return from a surface that we
// once used. We can simply ignore this as we have already dequeued
// this buffer properly. NOTE: this does not eliminate all cases,
// e.g. it is possible that we have queued the valid buffer to the
// NW, and a stale copy of the same buffer gets dequeued - which will
// be treated as the valid buffer by ACodec.
// 如果buffer的状态不是属于native window,则说明这个buffer是过时的buffer
if (info->mStatus != BufferInfo::OWNED_BY_NATIVE_WINDOW) {
ALOGI("dequeued stale buffer %p. discarding", buf);
stale = true;
break;
}
ALOGV("dequeued buffer #%u with age %u, graphicBuffer %p",
(unsigned)(info - &mBuffers[kPortIndexOutput][0]),
mDequeueCounter - info->mDequeuedAt,
info->mGraphicBuffer->handle);
// 否则直接将bufferinfo返回,同时给bufferinfo设置write fence
info->mStatus = BufferInfo::OWNED_BY_US;
info->setWriteFence(fenceFd, "dequeueBufferFromNativeWindow");
updateRenderInfoForDequeuedBuffer(buf, fenceFd, info);
return info;
}
}
// It is also possible to receive a previously unregistered buffer
// in non-meta mode. These should be treated as stale buffers. The
// same is possible in meta mode, in which case, it will be treated
// as a normal buffer, which is not desirable.
// TODO: fix this.
if (!stale && !storingMetadataInDecodedBuffers()) {
ALOGI("dequeued unrecognized (stale) buffer %p. discarding", buf);
stale = true;
}
// 如果是过时的buffer则重新从native window获取graphic buffer
if (stale) {
// TODO: detach stale buffer, but there is no API yet to do it.
buf = NULL;
}
} while (buf == NULL);
// get oldest undequeued buffer
// 计算没有使用时间最长的buffer
BufferInfo *oldest = NULL;
for (size_t i = mBuffers[kPortIndexOutput].size(); i > 0;) {
i--;
BufferInfo *info =
&mBuffers[kPortIndexOutput].editItemAt(i);
if (info->mStatus == BufferInfo::OWNED_BY_NATIVE_WINDOW &&
(oldest == NULL ||
// avoid potential issues from counter rolling over
mDequeueCounter - info->mDequeuedAt >
mDequeueCounter - oldest->mDequeuedAt)) {
oldest = info;
}
}
// it is impossible dequeue a buffer when there are no buffers with ANW
CHECK(oldest != NULL);
// it is impossible to dequeue an unknown buffer in non-meta mode, as the
// while loop above does not complete
CHECK(storingMetadataInDecodedBuffers());
// 将获取到的graphic buffer绑定到buffer info上,并且标注上是新的graphic buffer
// discard buffer in LRU info and replace with new buffer
oldest->mGraphicBuffer = GraphicBuffer::from(buf);
oldest->mNewGraphicBuffer = true;
oldest->mStatus = BufferInfo::OWNED_BY_US;
// 设置 write fence
oldest->setWriteFence(fenceFd, "dequeueBufferFromNativeWindow for oldest");
mRenderTracker.untrackFrame(oldest->mRenderInfo);
oldest->mRenderInfo = NULL;
ALOGV("replaced oldest buffer #%u with age %u, graphicBuffer %p",
(unsigned)(oldest - &mBuffers[kPortIndexOutput][0]),
mDequeueCounter - oldest->mDequeuedAt,
oldest->mGraphicBuffer->handle);
updateRenderInfoForDequeuedBuffer(buf, fenceFd, oldest);
return oldest;
}
dequeueBufferFromNativeWindow 的代码比较长,但是如果了解buffer机制很容易就知道这段代码是什么意思了,接下来就来谈谈我的理解。
在开始解码流程之初,我们都知道output buffer没有指向实际的graphic buffer,而正常工作的状态下,一个 BufferInfo 会对应有一个 graphic buffer,但是这里的绑定关系可能会变化的,因为从native window 中获取的 buffer 可能是新的 buffer,这时候就要选择绑定到某一个 BufferInfo 上了。了解了这个内容,我们再来看代码:
mDequeueCounter
,decoder每次输出一帧,这个计数值会加一;BufferInfo有一个成员mDequeuedAt
,这个值用于记录当前BufferInfo在第几帧被使用(填上输出);有了这两个值,就可以知道当前哪个 BufferInfo 最久没有被使用了。mNewGraphicBuffer
会被置为 true,说明这是一个新的 graphic buffer,需要注册给 OMX 组件。到这 dequeueBufferFromNativeWindow 就分析完成了,拿到graphic buffer之后,就要把它送给decoder使用了。
status_t ACodec::fillBuffer(BufferInfo *info) {
status_t err;
// Even in dynamic ANW buffer mode, if the graphic buffer is not changing,
// send sPreset instead of the same graphic buffer, so that OMX server
// side doesn't update the meta. In theory it should make no difference,
// however when the same buffer is parcelled again, a new handle could be
// created on server side, and some decoder doesn't recognize the handle
// even if it's the same buffer.
if (!storingMetadataInDecodedBuffers() || !info->mNewGraphicBuffer) {
err = mOMXNode->fillBuffer(
info->mBufferID, OMXBuffer::sPreset, info->mFenceFd);
} else {
err = mOMXNode->fillBuffer(
info->mBufferID, info->mGraphicBuffer, info->mFenceFd);
}
info->mNewGraphicBuffer = false;
info->mFenceFd = -1;
if (err == OK) {
info->mStatus = BufferInfo::OWNED_BY_COMPONENT;
}
return err;
}
fillBuffer 的代码比较简单,就是调用 OMXNode 的 fillBuffer 方法,但是分为两种情况:
status_t OMXNodeInstance::fillBuffer(
IOMX::buffer_id buffer, const OMXBuffer &omxBuffer, int fenceFd) {
Mutex::Autolock autoLock(mLock);
if (mHandle == NULL) {
return DEAD_OBJECT;
}
// 找到id对应的 Buffer header
OMX_BUFFERHEADERTYPE *header = findBufferHeader(buffer, kPortIndexOutput);
if (header == NULL) {
ALOGE("b/25884056");
return BAD_VALUE;
}
// 判断buffer type是否为kBufferTypeANWBuffer
if (omxBuffer.mBufferType == OMXBuffer::kBufferTypeANWBuffer) {
// 将 graphic buffer 绑定给 bufferMeta
status_t err = updateGraphicBufferInMeta_l(
kPortIndexOutput, omxBuffer.mGraphicBuffer, buffer, header);
if (err != OK) {
CLOG_ERROR(fillBuffer, err, FULL_BUFFER(
(intptr_t)header->pBuffer, header, fenceFd));
return err;
}
} else if (omxBuffer.mBufferType != OMXBuffer::kBufferTypePreset) {
return BAD_VALUE;
}
// 重置buffer状态
header->nFilledLen = 0;
header->nOffset = 0;
header->nFlags = 0;
// 等待fence释放
// meta now owns fenceFd
status_t res = storeFenceInMeta_l(header, fenceFd, kPortIndexOutput);
if (res != OK) {
CLOG_ERROR(fillBuffer::storeFenceInMeta, res, EMPTY_BUFFER(buffer, header, fenceFd));
return res;
}
{
Mutex::Autolock _l(mDebugLock);
mOutputBuffersWithCodec.add(header);
CLOG_BUMPED_BUFFER(fillBuffer, WITH_STATS(EMPTY_BUFFER(buffer, header, fenceFd)));
}
// 传递给 OMX 组件
OMX_ERRORTYPE err = OMX_FillThisBuffer(mHandle, header);
if (err != OMX_ErrorNone) {
CLOG_ERROR(fillBuffer, err, EMPTY_BUFFER(buffer, header, fenceFd));
Mutex::Autolock _l(mDebugLock);
mOutputBuffersWithCodec.remove(header);
}
return StatusFromOMXError(err);
}
status_t OMXNodeInstance::storeFenceInMeta_l(
OMX_BUFFERHEADERTYPE *header, int fenceFd, OMX_U32 portIndex) {
// propagate fence if component supports it; wait for it otherwise
OMX_U32 metaSize = portIndex == kPortIndexInput ? header->nFilledLen : header->nAllocLen;
if (mMetadataType[portIndex] == kMetadataBufferTypeANWBuffer
&& metaSize >= sizeof(VideoNativeMetadata)) {
VideoNativeMetadata &nativeMeta = *(VideoNativeMetadata *)(header->pBuffer);
if (nativeMeta.nFenceFd >= 0) {
ALOGE("fence (%d) already exists in meta", nativeMeta.nFenceFd);
if (fenceFd >= 0) {
::close(fenceFd);
}
return ALREADY_EXISTS;
}
nativeMeta.nFenceFd = fenceFd;
} else if (fenceFd >= 0) {
CLOG_BUFFER(storeFenceInMeta, "waiting for fence %d", fenceFd);
sp<Fence> fence = new Fence(fenceFd);
return fence->wait(IOMX::kFenceTimeoutMs);
}
return OK;
}
在等待fence释放之前有个判断:
nAllocLen
是否大于等于 VideoNativeMetadata 的大小。我这里就盲猜,这是在判断 BufferHeader 是否已经绑定 graphic buffer;