這一節的學習分為三塊內容,omx hidl service用法、OMX架構、ACodec中的buffer分配。
1、omx hidl service
system可以借助vndbinder來訪問vendor分區的內容,這里以omx hidl service為例子學習下hidl代碼要如何閱讀使用。
相關代碼路徑:
hardware/interfaces/media/omx/1.0/IOmx.hal
frameworks/av/media/libstagefright/omx/1.0/Omx.cpp
frameworks/av/services/mediacodec/main_codecservice.cpp
frameworks/av/media/libstagefright/OMXClient.cpp
IOmx.hal中定義相關的接口,編譯之后會在out/soong/.intermediates下生成相關的.h以及.cpp文件
Omx.cpp中會有接口的實現
main_codecservice.cpp用於啟動服務,registerAsService聲明在hidl生成文件中
OMXClient 獲取IOmx服務,IOmx::getService聲明在hidl生成文件中
2、OMX架構
2.1、獲取IOmx服務
// ACodec.cpp OMXClient client; if (client.connect(owner.c_str()) != OK) { mCodec->signalError(OMX_ErrorUndefined, NO_INIT); return false; } omx = client.interface(); // OMXClient.cpp status_t OMXClient::connect(const char* name) { using namespace ::android::hardware::media::omx::V1_0; if (name == nullptr) { name = "default"; } sp<IOmx> tOmx = IOmx::getService(name); if (tOmx.get() == nullptr) { ALOGE("Cannot obtain IOmx service."); return NO_INIT; } if (!tOmx->isRemote()) { ALOGE("IOmx service running in passthrough mode."); return NO_INIT; } mOMX = new utils::LWOmx(tOmx); ALOGI("IOmx service obtained"); return OK; }
這里的代碼還算簡單,利用OMXClient的connect方法來獲取IOmx服務,然后封裝到LWOmx當中。
LWOmx定義在 frameworks/av/media/libmedia/include/media/omx/1.0/WOmx.h,繼承於IOMX,注意這里並不是一個binder對象!為什么要把IOmx封裝到LWOmx中呢?看LWOmx的實現就知道了,LWOmx幫我們隱藏了hidl調用的細節(比如hidl callback),讓調用更簡單。
2.2、服務的使用
這里以allocateNode方法為例子來看看OMX的架構。
// ACodec.cpp sp<CodecObserver> observer = new CodecObserver(notify); sp<IOMXNode> omxNode; err = omx->allocateNode(componentName.c_str(), observer, &omxNode);
參數傳遞了一個CodecObserver對象和一個IOMXNode對象。CodecObserver繼承於BnOMXObserver,BnOMXObserver聲明在IOMX.h當中;IOMXMode同樣聲明在IOMX.h當中。
// WOmx.cpp status_t LWOmx::allocateNode( char const* name, sp<IOMXObserver> const& observer, sp<IOMXNode>* omxNode) { status_t fnStatus; status_t transStatus = toStatusT(mBase->allocateNode( name, new TWOmxObserver(observer), [&fnStatus, omxNode](Status status, sp<IOmxNode> const& node) { fnStatus = toStatusT(status); *omxNode = new LWOmxNode(node); })); return transStatus == NO_ERROR ? fnStatus : transStatus; }
LWOmx中的調用做了兩個轉換,IOMXObserver轉TWOmxObserver,IOmxNode轉LWOmxNode,乍一看很復雜!
我們先仔細看一下Omx.h中allocateNode的聲明:
Return<void> allocateNode( const hidl_string& name, const sp<IOmxObserver>& observer, allocateNode_cb _hidl_cb) override;
傳入參數為IOmxObserver,注意它和我們看到的ACodec和IOMX.h中的IOMXObserver是完全不同的,看他們的名字中的"omx"的大小寫,IOmx這是在hal中聲明的,而IOMX是其他地方聲明的binder對象。
IOmxObserver的接口實現為TWOmxObserver,為了傳遞IOMXObserver對象,TWOmxObserver中封裝了一個IOMXObserver對象
// frameworks/av/media/libmedia/include/media/omx/1.0/WOmxObserver.h struct TWOmxObserver : public IOmxObserver { sp<IOMXObserver> mBase; TWOmxObserver(sp<IOMXObserver> const& base); Return<void> onMessages(const hidl_vec<Message>& tMessages) override; };
這里注意在stagefright目錄下也有一個WOmxObserver.h,這兩個看起來內容很像但也是完全不同的,從命名空間來看stagefright目錄下的都是hidl接口的實現,media目錄下的作為工具類使用。
同樣的IOmxNode 和 IOMXNode的關系也一樣,callback拿回來的是一個hidl對象,通過LWOmxNode封裝為一個binder對象給上層使用。
接下來看看Omx.cpp中的allocateNode實現具體是怎么做的:
Return<void> Omx::allocateNode( const hidl_string& name, const sp<IOmxObserver>& observer, allocateNode_cb _hidl_cb) { using ::android::IOMXNode; using ::android::IOMXObserver; sp<OMXNodeInstance> instance; { // 1、檢查實例數量 Mutex::Autolock autoLock(mLock); if (mLiveNodes.size() == kMaxNodeInstances) { _hidl_cb(toStatus(NO_MEMORY), nullptr); return Void(); } // 2、創建OMXNodeInstance instance = new OMXNodeInstance( this, new LWOmxObserver(observer), name.c_str()); OMX_COMPONENTTYPE *handle; // 3、創建Component OMX_ERRORTYPE err = mStore->makeComponentInstance( name.c_str(), &OMXNodeInstance::kCallbacks, instance.get(), &handle); // ...... // 4、把component交給OMXNodeInstance管理 instance->setHandle(handle); // 5、從xml查找quirks // Find quirks from mParser const auto& codec = mParser.getCodecMap().find(name.c_str()); if (codec == mParser.getCodecMap().cend()) { // ...... } else { uint32_t quirks = 0; for (const auto& quirk : codec->second.quirkSet) { if (quirk == "quirk::requires-allocate-on-input-ports") { quirks |= OMXNodeInstance:: kRequiresAllocateBufferOnInputPorts; } if (quirk == "quirk::requires-allocate-on-output-ports") { quirks |= OMXNodeInstance:: kRequiresAllocateBufferOnOutputPorts; } } instance->setQuirks(quirks); } // 6、添加OMXNodeInstance到IOmx服務管理列表中 mLiveNodes.add(observer.get(), instance); mNode2Observer.add(instance.get(), observer.get()); } observer->linkToDeath(this, 0); // callback將OMXNodeInstance返回給上層 _hidl_cb(toStatus(OK), new TWOmxNode(instance)); return Void(); }
第二步創建OMXNodeInstance時會把傳進來的TWOmxObserver轉為LWOmxObserver,這里用到LWOmxObserver聲明在stagefright目錄中。
OMXNodeInstance的創建和OMXStore的makeComponentInstance方法這里不做展開,比較簡單。
IOMXNode調用某個方法的過程:
IOMXNode -> LWOmxNode -> TWOmxNode -> OMXNodeInstance
IOMXObserver的回調過程:
IOMXObserver -> LWOmxObserver -> TWOmxObserver -> CodecObserver
3、ACodec中的buffer分配
先看一下mPortMode,分為kPortIndexInput和kPortIndexOutput
mPortMode會在構造函數中被初始化為IOMX::kPortModePresetByteBuffer,configureCodec過程中可能會被修改為其他值,看看都有哪些情況:
encoder:
1、會判斷Message中是否有"android._input-metadata-buffer-type" tag,如果有則置為kPortIndexInput對應值,如果沒有就置kPortIndexInput為IOMX::kPortModePresetByteBuffer
2、如果是video,並且需要secure mode,設定kPortIndexOutput為IOMX::kPortModePresetSecureBuffer,否則保持為IOMX::kPortModePresetByteBuffer
decoder:
1、需要secure mode,設定kPortIndexInput為IOMX::kPortModePresetSecureBuffer,否則保持為IOMX::kPortModePresetByteBuffer
2、如果有surface
tunnel mode,kPortIndexOutput設定為IOMX::kPortModePresetANWBuffer,同時會調用configureTunneledVideoPlayback
非tunnel mode,kPortIndexOutput設定為IOMX::kPortModeDynamicANWBuffer
沒有surface,kPortIndexOutput保持為IOMX::kPortModePresetByteBuffer
3、如果是視頻
組件使用的是OMX.google開頭的軟解組件,kPortIndexOutput保持為IOMX::kPortModePresetByteBuffer
到allocateBuffersOnPort時(暫時只討論decoder)
1、kPortIndexOutput
1.1、有surface
1.1.1、非tunnel mode
allocateOutputMetadataBuffers分配buffer
// ACodec.cpp status_t ACodec::allocateOutputMetadataBuffers() { OMX_U32 bufferCount, bufferSize, minUndequeuedBuffers; status_t err = configureOutputBuffersFromNativeWindow( &bufferCount, &bufferSize, &minUndequeuedBuffers, mFlags & kFlagPreregisterMetadataBuffers /* preregister */); if (err != OK) return err; mNumUndequeuedBuffers = minUndequeuedBuffers; for (OMX_U32 i = 0; i < bufferCount; i++) { BufferInfo info; info.mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW; info.mFenceFd = -1; info.mRenderInfo = NULL; info.mGraphicBuffer = NULL; info.mNewGraphicBuffer = false; info.mDequeuedAt = mDequeueCounter; info.mData = new MediaCodecBuffer(mOutputFormat, new ABuffer(bufferSize)); ((VideoNativeMetadata *)info.mData->base())->nFenceFd = -1; info.mCodecData = info.mData; // useBuffer err = mOMXNode->useBuffer(kPortIndexOutput, OMXBuffer::sPreset, &info.mBufferID); mBuffers[kPortIndexOutput].push(info); ALOGV("[%s] allocated meta buffer with ID %u", mComponentName.c_str(), info.mBufferID); } mMetadataBuffersToSubmit = bufferCount - minUndequeuedBuffers; return err; }
useBuffer傳入參數為OMXBuffer::sPreset,查看OMXBuffer代碼后看到其實是:
OMXBuffer OMXBuffer::sPreset(static_cast<sp<MediaCodecBuffer> >(NULL)); OMXBuffer::OMXBuffer(const sp<MediaCodecBuffer>& codecBuffer) : mBufferType(kBufferTypePreset), mRangeOffset(codecBuffer != NULL ? codecBuffer->offset() : 0), mRangeLength(codecBuffer != NULL ? codecBuffer->size() : 0) { }
到OMXNodeInstance
status_t OMXNodeInstance::useBuffer( OMX_U32 portIndex, const OMXBuffer &omxBuffer, IOMX::buffer_id *buffer) { switch (omxBuffer.mBufferType) { case OMXBuffer::kBufferTypePreset: { if (mPortMode[portIndex] != IOMX::kPortModeDynamicANWBuffer && mPortMode[portIndex] != IOMX::kPortModeDynamicNativeHandle) { break; } return useBuffer_l(portIndex, NULL, NULL, buffer); } }
進入到useBuffer_l 發現有OMX_AllocateBuffer 和 OMX_UseBuffer兩個選擇。先看mMetaDataType,它是在setPortMode時被重新設定值,在這種情況下會被設定為kMetadataBufferTypeANWBuffer
bool isOutputGraphicMetadata = (portIndex == kPortIndexOutput) && (mMetadataType[portIndex] == kMetadataBufferTypeGrallocSource || mMetadataType[portIndex] == kMetadataBufferTypeANWBuffer);
isOutputGraphicMetaData為true,所以第一個條件不滿足,使用OMX_UseBuffer,isMetaData為true
if (isMetadata) { data = new (std::nothrow) OMX_U8[allottedSize]; if (data == NULL) { return NO_MEMORY; } memset(data, 0, allottedSize); buffer_meta = new BufferMeta( params, hParams, portIndex, false /* copy */, data); } err = OMX_UseBuffer( mHandle, &header, portIndex, buffer_meta, allottedSize, data);
到這兒發現,用於創建BufferMeta的IMemory和IHidlMemory都是null,真正用於BufferMeta的是重新分配的一塊buffer,說明在ACodec中創建的buffer 並沒有通過OMX_UseBuffer往下傳遞。回到ACodec中創建BufferInfo的地方看mStatus為OWNED_BY_NATIVE_WINDOW,意思就是真正的output buffer並不是由上層創建。所以在這種情況下播放時,上層通過getBuffer獲取的output buffer中是沒有數據的。
1.1.2、tunnel mode
allocateOutputBuffersFromNativeWindow分配buffer
status_t ACodec::allocateOutputBuffersFromNativeWindow() { OMX_U32 bufferCount, bufferSize, minUndequeuedBuffers; status_t err = configureOutputBuffersFromNativeWindow( &bufferCount, &bufferSize, &minUndequeuedBuffers, true /* preregister */); if (err != 0) return err; mNumUndequeuedBuffers = minUndequeuedBuffers; static_cast<Surface*>(mNativeWindow.get()) ->getIGraphicBufferProducer()->allowAllocation(true); // Dequeue buffers and send them to OMX for (OMX_U32 i = 0; i < bufferCount; i++) { ANativeWindowBuffer *buf; int fenceFd; err = mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buf, &fenceFd); if (err != 0) { ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), -err); break; } sp<GraphicBuffer> graphicBuffer(GraphicBuffer::from(buf)); BufferInfo info; info.mStatus = BufferInfo::OWNED_BY_US; info.mFenceFd = fenceFd; info.mIsReadFence = false; info.mRenderInfo = NULL; info.mGraphicBuffer = graphicBuffer; info.mNewGraphicBuffer = false; info.mDequeuedAt = mDequeueCounter; info.mData = new MediaCodecBuffer(mOutputFormat, new ABuffer(bufferSize)); info.mCodecData = info.mData; mBuffers[kPortIndexOutput].push(info); IOMX::buffer_id bufferId; err = mOMXNode->useBuffer(kPortIndexOutput, graphicBuffer, &bufferId); if (err != 0) { ALOGE("registering GraphicBuffer %u with OMX IL component failed: " "%d", i, err); break; } mBuffers[kPortIndexOutput].editItemAt(i).mBufferID = bufferId; } OMX_U32 cancelStart; OMX_U32 cancelEnd; if (err != OK) { cancelStart = 0; cancelEnd = mBuffers[kPortIndexOutput].size(); } else { cancelStart = bufferCount - minUndequeuedBuffers; cancelEnd = bufferCount; } for (OMX_U32 i = cancelStart; i < cancelEnd; i++) { BufferInfo *info = &mBuffers[kPortIndexOutput].editItemAt(i); if (info->mStatus == BufferInfo::OWNED_BY_US) { status_t error = cancelBufferToNativeWindow(info); if (err == 0) { err = error; } } } static_cast<Surface*>(mNativeWindow.get()) ->getIGraphicBufferProducer()->allowAllocation(false); return err; }
這里面UseBuffer的參數為GraphicBuffer,參考OMXBuffer代碼:
OMXBuffer::OMXBuffer(const sp<GraphicBuffer> &gbuf) : mBufferType(kBufferTypeANWBuffer), mGraphicBuffer(gbuf) { }
tunnel mode下portmode[out]是IOMX::kPortModePresetANWBuffer,這個portMode的設置比較隱蔽:
else if (!storingMetadataInDecodedBuffers()) { err = setPortMode(kPortIndexOutput, IOMX::kPortModePresetANWBuffer); if (err != OK) { return err; } }
進入到UseBuffer中,根據BufferType判斷會走到useGraphicBuffer_l
case OMXBuffer::kBufferTypeANWBuffer: { if (mPortMode[portIndex] != IOMX::kPortModePresetANWBuffer && mPortMode[portIndex] != IOMX::kPortModeDynamicANWBuffer) { break; } return useGraphicBuffer_l(portIndex, omxBuffer.mGraphicBuffer, buffer); }
mMetadataType在setPortMode時被置為了kMetadataBufferTypeANWBuffer,進入到useGraphicBuffer_l看到
if (mMetadataType[portIndex] != kMetadataBufferTypeInvalid) { return useGraphicBufferWithMetadata_l( portIndex, graphicBuffer, buffer); }
所以進到useGraphicBufferWithMetadata_l,
status_t OMXNodeInstance::useGraphicBufferWithMetadata_l( OMX_U32 portIndex, const sp<GraphicBuffer> &graphicBuffer, IOMX::buffer_id *buffer) { if (portIndex != kPortIndexOutput) { return BAD_VALUE; } if (mMetadataType[portIndex] != kMetadataBufferTypeGrallocSource && mMetadataType[portIndex] != kMetadataBufferTypeANWBuffer) { return BAD_VALUE; } status_t err = useBuffer_l(portIndex, NULL, NULL, buffer); if (err != OK) { return err; } OMX_BUFFERHEADERTYPE *header = findBufferHeader(*buffer, portIndex); return updateGraphicBufferInMeta_l(portIndex, graphicBuffer, *buffer, header); }
看來還是進入到了useBuffer_l當中,isOutputGraphicMetadata為true,isMetadata為true,所以使用的是OMX_UseBuffer
if (isMetadata) { data = new (std::nothrow) OMX_U8[allottedSize]; if (data == NULL) { return NO_MEMORY; } memset(data, 0, allottedSize); buffer_meta = new BufferMeta( params, hParams, portIndex, false /* copy */, data); } err = OMX_UseBuffer( mHandle, &header, portIndex, buffer_meta, allottedSize, data);
好家伙,看到這里發現和之前非tunnel mode是一樣的,但是出了useBuffer_l,再回到useGraphicBufferWithMetadata_l,看到還有一個函數updateGraphicBufferInMeta_l:
BufferMeta *bufferMeta = (BufferMeta *)(header->pAppPrivate); sp<ABuffer> data = bufferMeta->getBuffer(header, false /* limit */); bufferMeta->setGraphicBuffer(graphicBuffer); else if (metaType == kMetadataBufferTypeANWBuffer && data->capacity() >= sizeof(VideoNativeMetadata)) { VideoNativeMetadata &metadata = *(VideoNativeMetadata *)(data->data()); metadata.eType = kMetadataBufferTypeANWBuffer; metadata.pBuffer = graphicBuffer == NULL ? NULL : graphicBuffer->getNativeBuffer(); metadata.nFenceFd = -1; }
這里看到把OMX_BUFFERHEADERTYPE中的BufferMeta和上層傳來的graphicBuffer做了關聯,OMX和graphic公用一塊buffer,由於graphicBuffer是在ACodec創建,所以mStatus值為OWNED_BY_US
1.2、無surface
無surface的情況與Input的普通模式相同
2、kPortIndexInput
2.1、no secure
使用的是hidl_memory
hidl_memory hidlMemToken; auto transStatus = mAllocator[portIndex]->allocate( bufSize, [&success, &hidlMemToken]( bool s, hidl_memory const& m) { success = s; hidlMemToken = m; }); err = mOMXNode->useBuffer( portIndex, hidlMemToken, &info.mBufferID);
看看BufferType
OMXBuffer::OMXBuffer(const hidl_memory &hidlMemory) : mBufferType(kBufferTypeHidlMemory), mHidlMemory(hidlMemory) { }
進入到UseBuffer中,此時portMode為kPortModePresetByteBuffer
case OMXBuffer::kBufferTypeHidlMemory: { if (mPortMode[portIndex] != IOMX::kPortModePresetByteBuffer && mPortMode[portIndex] != IOMX::kPortModeDynamicANWBuffer && mPortMode[portIndex] != IOMX::kPortModeDynamicNativeHandle) { break; } sp<IHidlMemory> hidlMemory = mapMemory(omxBuffer.mHidlMemory); if (hidlMemory == nullptr) { ALOGE("OMXNodeInstance useBuffer() failed to map memory"); return NO_MEMORY; } return useBuffer_l(portIndex, NULL, hidlMemory, buffer); }
mMetadataType在setPortMode時被置為kMetadataBufferTypeInvalid,進入到useBuffer_l中:
isMetaData為false,isOutputGraphicMetadata為false,這時候看到if中有關於Quirks的判斷
uint32_t requiresAllocateBufferBit = (portIndex == kPortIndexInput) ? kRequiresAllocateBufferOnInputPorts : kRequiresAllocateBufferOnOutputPorts; // we use useBuffer for output metadata regardless of quirks if (!isOutputGraphicMetadata && (mQuirks & requiresAllocateBufferBit))
Quirks一般定義在media_codecs.xml中,譯為怪癖模式,在其他地方找到可以翻譯為兼容模式,示例如下:
42 <MediaCodec name="OMX.foo.bar" > 43 <Type name="something/interesting" /> 44 <Type name="something/else" /> 45 ... 46 <Quirk name="requires-allocate-on-input-ports" /> 47 <Quirk name="requires-allocate-on-output-ports" /> 48 <Quirk name="output-buffers-are-unreadable" /> 49 </MediaCodec>
這種情況下,codec xml中如果定義有Quirk則進入到OMX_AllocateBuffer當中,沒有定義Quirk則使用OMX_UseBuffer
a. OMX_AllocateBuffer
if (!isOutputGraphicMetadata && (mQuirks & requiresAllocateBufferBit)) { buffer_meta = new BufferMeta( params, hParams, portIndex, !isMetadata /* copy */, NULL /* data */); err = OMX_AllocateBuffer( mHandle, &header, portIndex, buffer_meta, allottedSize); }
利用傳下來的IHidlMemory創建BufferMeta,第四個參數copy為true,這里看看BufferMeta的構造函數:
explicit BufferMeta( const sp<IMemory> &mem, const sp<IHidlMemory> &hidlMemory, OMX_U32 portIndex, bool copy, OMX_U8 *backup) : mMem(mem), mHidlMemory(hidlMemory), mCopyFromOmx(portIndex == kPortIndexOutput && copy), mCopyToOmx(portIndex == kPortIndexInput && copy), mPortIndex(portIndex), mBackup(backup) { }
copy為true會讓mCopyToOMX或者mCopyFromOMX置為true,他們的作用就是使能copy,例如CopyToOMX,就是從上層的buffer中copy數據到OMX OMX_BUFFERHEADERTYPE
中,這個方法會在emptyBuffer_l中調用到
void CopyToOMX(const OMX_BUFFERHEADERTYPE *header) { if (!mCopyToOmx) { return; } memcpy(header->pBuffer + header->nOffset, getPointer() + header->nOffset, header->nFilledLen); }
另外再看一下buffer的所有者是OWNED_BY_US
b. OMX_UseBuffer
看到創建BufferMeta時,copy都為false,不允許數據拷貝,decoder拿不到數據,上層也拿到解碼后的數據,這明顯是不對的。
2.2、secure mode
這時候portMode為kPortModePresetSecureBuffer,直接調用OMXNodeInstance的allocateSecureBuffer獲取一個NativeHandle,BufferInfo中的data使用的睡覺哦SecureBuffer
if (mode == IOMX::kPortModePresetSecureBuffer) { void *ptr = NULL; sp<NativeHandle> native_handle; err = mOMXNode->allocateSecureBuffer( portIndex, bufSize, &info.mBufferID, &ptr, &native_handle); info.mData = (native_handle == NULL) ? new SecureBuffer(format, ptr, bufSize) : new SecureBuffer(format, native_handle, bufSize); info.mCodecData = info.mData; }
進入到OMXNodeInstance看到allocateSecureBuffer並不復雜,創建一個BufferMeta,其中不帶任何上層的buffer,之后直接調用OMX_AllocateBuffer創建一個OMX_BUFFERHEADERTYPE,返回給上層的是用BufferHeader創建的NativeHandle
BufferMeta *buffer_meta = new BufferMeta(portIndex); OMX_BUFFERHEADERTYPE *header; OMX_ERRORTYPE err = OMX_AllocateBuffer( mHandle, &header, portIndex, buffer_meta, size); if (mSecureBufferType[portIndex] == kSecureBufferTypeNativeHandle) { *buffer_data = NULL; *native_handle = NativeHandle::create( (native_handle_t *)header->pBuffer, false /* ownsHandle */); } else { *buffer_data = header->pBuffer; *native_handle = NULL; }
上層ACodec用返回的NativeHandle創建一個SecureBuffer,這里面buffer是怎么連通的,可以閱讀OMXNodeInstance::emptyBuffer的第三個case,最后其實還是調用的emptyBuffer_l。
到這里Buffer的分配大概就了解結束,做一個總結:
Input
non secure:上層分配一塊hidl memory,omxnode中創建一個BufferMeta,調用OMX_AllocateBuffer在創建OMX_BUFFERHEADERTYPE(暫不了解BufferMeta在該方法中做什么用),允許BufferMeta與OMX_BUFFERHEADERTYPE中的buffer相互做數據拷貝。
secure:調用OMX_AllocateBuffer返回一個NativeHandle,用這個handle創建SecureBuffer
Output
無surface,與input non secure相同
有surface
non tunnel
上層創建的buffer並不與底層相關聯,上層無法獲取到ouput data,omxNode中會重新創建一塊buffer,利用這塊buffer創建BufferMeta,並調用OMX_UseBuffer。既然output data並沒有送給上層,那么渲染肯定是另有途徑
tunnel mode
上層從graphic獲取buffer,omxNode同樣也會創建一塊buffer,並調用OMX_UseBuffer,但是之后會把graphic buffer與OMX_BUFFERHEADERTYPE相關聯,output data直接送給graphic