分配input port buffers、提取视频文件压缩数据、复制压缩数据到input buffer、H.264硬件解码器开始解码过程分析
2016-09-10 19:33
896 查看
这篇文章介绍Android multimedia框架中分配用于存储压缩的视频数据的input port buffers、提取视频文件中的压缩数据、复制提取后的视频压缩数据到input port buffer,之后exynos H.264解码器解码input port buffer中的压缩数据。
上面mOMX->getParameter函数调用中有一个binder进程间通讯,它最终会调用:
hComponent(handle)在allocateNode函数调用中确定,对于三星exynos、h.264的情况,GetParameter指向Exynos_H264Dec_GetParameter,这个
指针同样是在allocateNode函数调用中确定。下面看一下这个函数调用:
上面pExynosPort->portDefinition结构在allocateNode函数调用中执行如下函数设置:
2.
返回到OMXCodec::allocateBuffersOnPort,通过mOMX->getParameter函数调用得到了一个OMX_PARAM_PORTDEFINITIONTYPE结构后,计算totalSize:
size_t totalSize = def.nBufferCountActual * def.nBufferSize;
def.nBufferCountActual在allocateNode函数调用中被初始化为0. 这个变量表示buffer的数量,现在暂时还不知道这个变量是怎么设置的。
def.nBufferSize的值是DEFAULT_VIDEO_INPUT_BUFFER_SIZE:
3.
接下来执行如下函数:
OMX_BUFFERHEADERTYPE *header,这个指针是关键。OMX_AllocateBuffer是一个宏,它调用了OMX_COMPONENTTYPE中的AllocateBuffer,它是一个
函数指针,在本例中,它指向Exynos_OMX_AllocateBuffer。在OMX_AllocateBuffer宏中,有一个pAppPrivate的参数,这个参数用于直接赋值给
这里的OMX_BUFFERHEADERTYPE *header中的pAppPrivate成员。这个函数会分配参数size大小的buffer,并将地址赋值给OMX_BUFFERHEADERTYPE header
中的pBuffer(条件是这个buffer的类型不是SECURE_MEMORY,如果是这种类型,pBuffer的值是分配的buffer的fd)。 确定这个指针之后,根据它执行:
第一行的函数调用中,会以KeyedVector mBufferIDToBufferHeader当前的index为key(类型OMX::buffer_id),OMX_BUFFERHEADERTYPE *header为value组成一个键值对存入
KeyedVector mBufferIDToBufferHeader。对于KeyVector mBufferHeaderToBufferID和mBufferIDToBufferHeader是类似的,只是两者的key-value倒置了。
最后这个函数返回这个key-value的key(类型OMX::buffer_id)。这个返回值赋值给OMX::buffer_id buffer参数。
第二行根据header->pBuffer的值设置buffer_data二重指针指向的指针,这个指针就是BufferInfo中的mData。
在OMXNodeInstance::allocateBuffer的最后,根据OMX_BUFFERHEADERTYPE创建一个CodecBuffer对象,并把它插入Vector<CodecBuffer> mCodecBuffers,这个Vector在emptyBuffer的回调中有使用到
4.
返回到OMXCodec::allocateBuffersOnPort中。在mOMX->allocateBuffer函数调用中,确定了IOMX::buffer_id buffer以及info.mData,接下来将
BufferInfo info中的mBuffer成员设置为这个IOMX::buffer_id buffer,然后执行函数mPortBuffers[portIndex].push(info)将这个info push到
mPortBuffers[portIndex] Vector中,这个portIndex是input port index。
的结构,这些数据由若干个array组成,每一个array由多个NAL组成,这个函数将所有的NAL push到Vector<CodecSpecificData *> mCodecSpecificData中。
上面mInitialBufferSubmit在创建OMXCodec对象时,在OMXCodec的构造函数中被设置为true,这就是创建mVideoSource对象时它被设置为
true。另外在OMXCodec::start函数中,它也会被设置为true。因为现在是要读取本地视频文件中的压缩数据进行解压缩,所以OMXCodec::start
函数肯定在这之前被调用了,所以这个变量这时毫无疑问是true。所以执行这个if中的代码,在这些代码中,首先将这个变量设置为false,
这个变量被设置为false的唯一一处引用就是这里;
接下来执行drainInputBuffers().这个函数比较复杂,下面分析一下这个函数:
1.1 drainInputBuffers
然后检查OMXCodec中的mFlags成员有没有kUseSecureInputBuffers标志,因为if中的drainAnyInputBuffer函数调用参数感觉挺奇怪的,所以
这里先假设mFlags中没有这个标志,分析else部分的代码。在这部分代码中,mPortBuffers是一个Vector<BufferInfo>类型的数组,这里
取这个数组中的index为kPortIndexInput(它的值是0)的元素,就是说是取input的数组元素,这个Vector<BufferInfo>类型的数组元素
中包含了许多input buffer。接下来在一个循环中依次将这个Vector<BufferInfo>类型的数组元素中的input buffer(用BufferInfo对象
来表示)取出来,取BufferInfo对象的方法是执行buffers->editItemAt(i)函数,取出BufferInfo对象后,检查这个对象中的mStatus成员
是否等于OWNED_BY_US,注意在allocateBuffersOnPort函数中分配nBufferCountActual数目的buffer时,已经将info->mStatus设置为了OWNED_BY_US。
如果不等于,继续去下一个BufferInfo对象,否则执行drainInputBuffer函数,参数是当前的BufferInfo对象。
drainInputBuffer函数比较复杂,下面分析一下这个函数:
比较mMIME的值的if,这里出于容易分析的目的,先假设这个if条件不成立,执行else,在else中先检查参数BufferInfo中的mSize的
值和specifiOc对象的mSize的值,需要前者大于后者。这里先假设这个条件成立,所以接下来执行memcpy函数,将specific中的数据
拷贝到BufferInfo info中,拷贝的大小是specific->mSize。specific数据是在创建OMXCodec时,config这个codec时,根据视频文件创建的,这些
specific数据保存在一个mPortBuffers[portIndex] Vector中,这些specific数据是未解码的视频数据。所以BufferInfo info中的数据是压缩的
视频数据;
接下来执行mOMX->emptyBuffer(),这个函数调用中有一个binder进程间通讯,函数调用前面、中间的过程这里不做分析,看它的后面部分:
上面根据OMX::buffer_id buffer由函数findBufferHeader得到对应的OMX_BUFFERHEADERTYPE *,这个结构体中的pBuffer成员指向实际未解码的视频
数据。这部分数据是在OMXCodec::drainInputBuffer函数中复制过去的,这个复制操作的dest是BufferInfo的mData,这个指针是在allocateBuffersOnPort
函数中,在分配input port buffer时,由OMX_BUFFERHEADERTYPE中的pBuffer的值赋值给mData的。所以在OMXCodec::drainInputBuffer中将
压缩视频数据复制到BufferInfo中的mData指向的dest的操作,实际上是复制到OMX_BUFFERHEADERTYPE中的pBuffer所指向的dest。
OMX_EmptyThisBuffer是一个宏:
mHandle在OMXCodec::create --> OMX::allocateNode函数调用中被设置,以三星exynos为例,OMX_COMPONENTTYPE结构体中的EmptyThisBuffer函数指针
指向Exynos_OMX_EmptyThisBuffer,所以执行Exynos_OMX_EmptyThisBuffer。在这个函数中,创建一个EXYNOS_OMX_MESSAGE message,这个message
的pCmdData指向参数OMX_BUFFERHEADERTYPE *pBuffer,然后将这个message插入pExynosPort->bufferQ last队列中:
上面在H.264 硬件解码器中将压缩的视频数据块插入了input port的bufferQ中,至于解码器从这个queue中取得压缩的视频数据块,然后进行解码的过程将在下一篇文章中介绍。
注意在OMXCodec::drainInputBuffer函数中,mCodecSpecificData.size()的返回值是当前的视频文件提取出来的小压缩数据块的数目,一次取这个Vector中的一个元素,每次mCodecSpecificDataIndex加1,当mCodecSpecificDataIndex的值等于mCodecSpecificData.size()时,表示这个视频文件已经解码完了。
在OMXCodec::allocateBuffersOnPort中分配input port buffers
status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) { 1. err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
上面mOMX->getParameter函数调用中有一个binder进程间通讯,它最终会调用:
#define OMX_GetParameter( \ hComponent, \ nParamIndex, \ pComponentParameterStructure) \ ((OMX_COMPONENTTYPE*)hComponent)->GetParameter( \ hComponent, \ nParamIndex, \ pComponentParameterStructure) /* Macro End */
hComponent(handle)在allocateNode函数调用中确定,对于三星exynos、h.264的情况,GetParameter指向Exynos_H264Dec_GetParameter,这个
指针同样是在allocateNode函数调用中确定。下面看一下这个函数调用:
OMX_ERRORTYPE Exynos_OMX_GetParameter( case OMX_IndexParamPortDefinition: Exynos_OSAL_Memcpy(portDefinition, &pExynosPort->portDefinition, portDefinition->nSize);
上面pExynosPort->portDefinition结构在allocateNode函数调用中执行如下函数设置:
OSCL_EXPORT_REF OMX_ERRORTYPE Exynos_OMX_ComponentInit(OMX_HANDLETYPE hComponent, OMX_STRING componentName) /* Input port */ pExynosPort = &pExynosComponent->pExynosPort[INPUT_PORT_INDEX]; pExynosPort->portDefinition.format.video.nFrameWidth = DEFAULT_FRAME_WIDTH; pExynosPort->portDefinition.format.video.nFrameHeight= DEFAULT_FRAME_HEIGHT; /* Output port */ pExynosPort = &pExynosComponent->pExynosPort[OUTPUT_PORT_INDEX]; pExynosPort->portDefinition.format.video.nFrameWidth = DEFAULT_FRAME_WIDTH; pExynosPort->portDefinition.format.video.nFrameHeight= DEFAULT_FRAME_HEIGHT;
2.
返回到OMXCodec::allocateBuffersOnPort,通过mOMX->getParameter函数调用得到了一个OMX_PARAM_PORTDEFINITIONTYPE结构后,计算totalSize:
size_t totalSize = def.nBufferCountActual * def.nBufferSize;
def.nBufferCountActual在allocateNode函数调用中被初始化为0. 这个变量表示buffer的数量,现在暂时还不知道这个变量是怎么设置的。
def.nBufferSize的值是DEFAULT_VIDEO_INPUT_BUFFER_SIZE:
#define DEFAULT_FRAME_WIDTH 176 #define DEFAULT_FRAME_HEIGHT 144 #define DEFAULT_VIDEO_INPUT_BUFFER_SIZE (DEFAULT_FRAME_WIDTH * DEFAULT_FRAME_HEIGHT) * 2
3.
接下来执行如下函数:
err = mOMX->allocateBuffer( mNode, portIndex, def.nBufferSize, &buffer, &info.mData);这个函数调用中包含一个binder进程间通讯,看一下这个函数调用的后面部分:
status_t OMXNodeInstance::allocateBuffer( OMX_U32 portIndex, size_t size, OMX::buffer_id *buffer, void **buffer_data) {在这个函数中,通过调用如下的宏:
OMX_ERRORTYPE err = OMX_AllocateBuffer( mHandle, &header, portIndex, buffer_meta, size);其中mHandle类型是OMX_HANDLETYPE,就是一空指针,并且在这个宏函数中强制转化为了一个OMX_COMPONENTTYPE *. 这个宏函数确定
OMX_BUFFERHEADERTYPE *header,这个指针是关键。OMX_AllocateBuffer是一个宏,它调用了OMX_COMPONENTTYPE中的AllocateBuffer,它是一个
函数指针,在本例中,它指向Exynos_OMX_AllocateBuffer。在OMX_AllocateBuffer宏中,有一个pAppPrivate的参数,这个参数用于直接赋值给
这里的OMX_BUFFERHEADERTYPE *header中的pAppPrivate成员。这个函数会分配参数size大小的buffer,并将地址赋值给OMX_BUFFERHEADERTYPE header
中的pBuffer(条件是这个buffer的类型不是SECURE_MEMORY,如果是这种类型,pBuffer的值是分配的buffer的fd)。 确定这个指针之后,根据它执行:
*buffer = makeBufferID(header); *buffer_data = header->pBuffer;
第一行的函数调用中,会以KeyedVector mBufferIDToBufferHeader当前的index为key(类型OMX::buffer_id),OMX_BUFFERHEADERTYPE *header为value组成一个键值对存入
KeyedVector mBufferIDToBufferHeader。对于KeyVector mBufferHeaderToBufferID和mBufferIDToBufferHeader是类似的,只是两者的key-value倒置了。
最后这个函数返回这个key-value的key(类型OMX::buffer_id)。这个返回值赋值给OMX::buffer_id buffer参数。
第二行根据header->pBuffer的值设置buffer_data二重指针指向的指针,这个指针就是BufferInfo中的mData。
在OMXNodeInstance::allocateBuffer的最后,根据OMX_BUFFERHEADERTYPE创建一个CodecBuffer对象,并把它插入Vector<CodecBuffer> mCodecBuffers,这个Vector在emptyBuffer的回调中有使用到
4.
返回到OMXCodec::allocateBuffersOnPort中。在mOMX->allocateBuffer函数调用中,确定了IOMX::buffer_id buffer以及info.mData,接下来将
BufferInfo info中的mBuffer成员设置为这个IOMX::buffer_id buffer,然后执行函数mPortBuffers[portIndex].push(info)将这个info push到
mPortBuffers[portIndex] Vector中,这个portIndex是input port index。
在OMXCodec::configureCodec中将压缩的视频视频数据提取到Vector<CodecSpecificData *>中
status_t OMXCodec::configureCodec(const sp<MetaData> &meta) { } else if (meta->findData(kKeyHVCC, &type, &data, &size)) { if ((err = parseHEVCCodecSpecificData( data, size, &profile, &level)) != OK) { ALOGE("Malformed HEVC codec specific data."); return err; }上面parseHEVCCodecSpecificData函数对sp<MetaData> data(于mVideoTrack关联了)中的数据执行OMXCodec::addCodecSpecificData函数。对于这部分数据
的结构,这些数据由若干个array组成,每一个array由多个NAL组成,这个函数将所有的NAL push到Vector<CodecSpecificData *> mCodecSpecificData中。
在OMXCodec::read/drainInputBuffers中复制视频压缩数据到input port buffer,作为H.264解码器解码的输入数据,H.264解码器解码这个压缩的视频数据unit
在这个函数调用中,将Vector<CodecSpecificData *> mCodecSpecificData中元素中的数据(压缩的视频数据)复制到BufferInfo中的mData指向的buffer。mData和OMX_BUFFERHEADERTYPE中的pBuffer的指向是一样的,都指向exynos H.264硬件解码器中分配的input port buffer。status_t OMXCodec::read( if (mInitialBufferSubmit) { mInitialBufferSubmit = false; drainInputBuffers(); if (mState == EXECUTING) { // Otherwise mState == RECONFIGURING and this code will trigger // after the output port is reenabled. fillOutputBuffers(); }
上面mInitialBufferSubmit在创建OMXCodec对象时,在OMXCodec的构造函数中被设置为true,这就是创建mVideoSource对象时它被设置为
true。另外在OMXCodec::start函数中,它也会被设置为true。因为现在是要读取本地视频文件中的压缩数据进行解压缩,所以OMXCodec::start
函数肯定在这之前被调用了,所以这个变量这时毫无疑问是true。所以执行这个if中的代码,在这些代码中,首先将这个变量设置为false,
这个变量被设置为false的唯一一处引用就是这里;
接下来执行drainInputBuffers().这个函数比较复杂,下面分析一下这个函数:
1.1 drainInputBuffers
void OMXCodec::drainInputBuffers() { CHECK(mState == EXECUTING || mState == RECONFIGURING); if (mFlags & kUseSecureInputBuffers) { } else { Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput]; for (size_t i = 0; i < buffers->size(); ++i) { BufferInfo *info = &buffers->editItemAt(i); if (info->mStatus != OWNED_BY_US) { continue; } if (!drainInputBuffer(info)) { break; }上面先检查OMXCodec结构中的mState的值是不是EXECUTING或者RECONFIGURING,如果不是出错,这里先假设它是这两个值之一,满足条件;
然后检查OMXCodec中的mFlags成员有没有kUseSecureInputBuffers标志,因为if中的drainAnyInputBuffer函数调用参数感觉挺奇怪的,所以
这里先假设mFlags中没有这个标志,分析else部分的代码。在这部分代码中,mPortBuffers是一个Vector<BufferInfo>类型的数组,这里
取这个数组中的index为kPortIndexInput(它的值是0)的元素,就是说是取input的数组元素,这个Vector<BufferInfo>类型的数组元素
中包含了许多input buffer。接下来在一个循环中依次将这个Vector<BufferInfo>类型的数组元素中的input buffer(用BufferInfo对象
来表示)取出来,取BufferInfo对象的方法是执行buffers->editItemAt(i)函数,取出BufferInfo对象后,检查这个对象中的mStatus成员
是否等于OWNED_BY_US,注意在allocateBuffersOnPort函数中分配nBufferCountActual数目的buffer时,已经将info->mStatus设置为了OWNED_BY_US。
如果不等于,继续去下一个BufferInfo对象,否则执行drainInputBuffer函数,参数是当前的BufferInfo对象。
drainInputBuffer函数比较复杂,下面分析一下这个函数:
bool OMXCodec::drainInputBuffer(BufferInfo *info) { if (mCodecSpecificDataIndex < mCodecSpecificData.size()) { CHECK(!(mFlags & kUseSecureInputBuffers)); const CodecSpecificData *specific = mCodecSpecificData[mCodecSpecificDataIndex]; size_t size = specific->mSize; if ((!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mMIME) || !strcasecmp(MEDIA_MIMETYPE_VIDEO_HEVC, mMIME)) && !(mQuirks & kWantsNALFragments)) { } else { CHECK(info->mSize >= specific->mSize); memcpy(info->mData, specific->mData, specific->mSize); } status_t err = mOMX->emptyBuffer( mNode, info->mBuffer, 0, size, OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG, 0); info->mStatus = OWNED_BY_COMPONENT; ++mCodecSpecificDataIndex; return true;上面mCodecSpecificDataIndex从mCodecSpecificData数组中取出对应的CodecSpecificData指针出来,赋值给specific,上面有一个
比较mMIME的值的if,这里出于容易分析的目的,先假设这个if条件不成立,执行else,在else中先检查参数BufferInfo中的mSize的
值和specifiOc对象的mSize的值,需要前者大于后者。这里先假设这个条件成立,所以接下来执行memcpy函数,将specific中的数据
拷贝到BufferInfo info中,拷贝的大小是specific->mSize。specific数据是在创建OMXCodec时,config这个codec时,根据视频文件创建的,这些
specific数据保存在一个mPortBuffers[portIndex] Vector中,这些specific数据是未解码的视频数据。所以BufferInfo info中的数据是压缩的
视频数据;
接下来执行mOMX->emptyBuffer(),这个函数调用中有一个binder进程间通讯,函数调用前面、中间的过程这里不做分析,看它的后面部分:
status_t OMXNodeInstance::emptyBuffer( OMX::buffer_id buffer, OMX_U32 rangeOffset, OMX_U32 rangeLength, OMX_U32 flags, OMX_TICKS timestamp) { OMX_BUFFERHEADERTYPE *header = findBufferHeader(buffer); header->nFilledLen = rangeLength; header->nOffset = rangeOffset; header->nFlags = flags; header->nTimeStamp = timestamp; BufferMeta *buffer_meta = static_cast<BufferMeta *>(header->pAppPrivate); buffer_meta->CopyToOMX(header); OMX_ERRORTYPE err = OMX_EmptyThisBuffer(mHandle, header);
上面根据OMX::buffer_id buffer由函数findBufferHeader得到对应的OMX_BUFFERHEADERTYPE *,这个结构体中的pBuffer成员指向实际未解码的视频
数据。这部分数据是在OMXCodec::drainInputBuffer函数中复制过去的,这个复制操作的dest是BufferInfo的mData,这个指针是在allocateBuffersOnPort
函数中,在分配input port buffer时,由OMX_BUFFERHEADERTYPE中的pBuffer的值赋值给mData的。所以在OMXCodec::drainInputBuffer中将
压缩视频数据复制到BufferInfo中的mData指向的dest的操作,实际上是复制到OMX_BUFFERHEADERTYPE中的pBuffer所指向的dest。
OMX_EmptyThisBuffer是一个宏:
#define OMX_EmptyThisBuffer( \ hComponent, \ pBuffer) \ ((OMX_COMPONENTTYPE*)hComponent)->EmptyThisBuffer( \ hComponent, \ pBuffer) /* Macro End */
mHandle在OMXCodec::create --> OMX::allocateNode函数调用中被设置,以三星exynos为例,OMX_COMPONENTTYPE结构体中的EmptyThisBuffer函数指针
指向Exynos_OMX_EmptyThisBuffer,所以执行Exynos_OMX_EmptyThisBuffer。在这个函数中,创建一个EXYNOS_OMX_MESSAGE message,这个message
的pCmdData指向参数OMX_BUFFERHEADERTYPE *pBuffer,然后将这个message插入pExynosPort->bufferQ last队列中:
message->messageType = EXYNOS_OMX_CommandEmptyBuffer; message->messageParam = (OMX_U32) i; message->pCmdData = (OMX_PTR)pBuffer; ret = Exynos_OSAL_Queue(&pExynosPort->bufferQ, (void *)message);
上面在H.264 硬件解码器中将压缩的视频数据块插入了input port的bufferQ中,至于解码器从这个queue中取得压缩的视频数据块,然后进行解码的过程将在下一篇文章中介绍。
注意在OMXCodec::drainInputBuffer函数中,mCodecSpecificData.size()的返回值是当前的视频文件提取出来的小压缩数据块的数目,一次取这个Vector中的一个元素,每次mCodecSpecificDataIndex加1,当mCodecSpecificDataIndex的值等于mCodecSpecificData.size()时,表示这个视频文件已经解码完了。
相关文章推荐
- <开源项目分析>Cisco的开源视频加解码器THOR(H.264解码)
- h264解码器,s3c6410硬件mfc解码分析-H264
- 文件数据分析制作过程【1】
- 对 S3C2410 启动代码内数据复制过程的分析
- h264解码器,s3c6410硬件mfc解码分析-H264
- vlc从接收到数据流到播放视频的过程分析
- 对h.264压缩视频码流中i帧的提取(firstime)
- h264解码器,s3c6410硬件mfc解码分析-H264
- 将h.264视频流封装成flv格式文件(二.开始动手)
- 将h.264视频流封装成flv格式文件(二.开始动手)
- h.264编码压缩后的视频文件直接播放
- vlc学习计划(7)--从接收到数据流到播放视频的过程分析
- 文件数据分析制作过程【2】
- (转载)将h.264视频流封装成flv格式文件(二.开始动手)http://blog.csdn.net/yeyumin89/article/details/7932431
- 文件数据分析制作过程【3】
- AVS-M与H.264(Baseline)视频解码器结构的分析
- vlc学习计划(6)--从接收到数据流到播放视频的过程分析
- 文件数据分析制作过程【3】
- h264解码器,s3c6410硬件mfc解码分析-H264
- S3C2410-2440启动代码内数据复制过程的分析