stagefright使用surfaceflinger buffer 的分析

之前学习了surfaceflinger和stagefright 两大模块,
这两大模块之间联系最大的地方,就是surfaceflinger 管理显示buffer,然后stagefright 如何用这个buffer
这里总结一下这一块自己学习的知识点
平台samsung exynos ,android 4.4

实际上本文想研究的就是三个问题:
(1)如何获取显示buffer,并且怎么把它告诉给OMX
(2)解码的时候,OMX如何把这个buffer告诉给解码器,作为视频数据输出的buffer
(3)有了buffer了,buffer里面有了数据了,如何显示出来
下面对这三点分开讨论

本文参考了大牛 http://blog.csdn.net/gzzaigcn/article/category/2135451 的一些分析。
在此文章基础上做了扩展。感谢前人的肩膀。

一,如何申请显示output buffer,以及如何设置给OMX来使用

1.来看解码输出缓存在surfaceflinger下的缓存申请:
status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) {  
 if (mNativeWindow != NULL && portIndex == kPortIndexOutput)
 {  
 return allocateOutputBuffersFromNativeWindow();//使用surface渲染,为输出分配图形缓存GraphicBuffer  
}  
可以看到当申请的是NativeWindow形式的buffer,这说明了输出的buffer是需要去直接完成render的,所以输出缓存区要从本地窗口buffer进行申请:

2.allocateOutputBuffersFromNativeWindow的实现

SurfaceFlinger架构下的Buffer申请机制可以看前面提到的博文,  
先贴代码,已经去除一些细枝末节的代码:
status_t OMXCodec::allocateOutputBuffersFromNativeWindow()
{
    status_t err = mOMX->getParameter(
            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
    if (err != OK) {
        CODEC_LOGE("getParameter failed: %d", err);
        return err;
    }
    err = native_window_set_buffers_geometry(
            mNativeWindow.get(),
            def.format.video.nFrameWidth,
            def.format.video.nFrameHeight,
            def.format.video.eColorFormat);
    // Set up the native window.
    OMX_U32 usage = 0;
    err = mOMX->getGraphicBufferUsage(mNode, kPortIndexOutput, &usage);
    ALOGV("native_window_set_usage usage=0x%lx", usage);
    err = native_window_set_usage(
            mNativeWindow.get(), usage | GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_EXTERNAL_DISP);
    if (err != 0) {
        ALOGE("native_window_set_usage failed: %s (%d)", strerror(-err), -err);
        return err;
    }
    err = native_window_set_buffer_count(
            mNativeWindow.get(), def.nBufferCountActual);
    if (err != 0) {
        ALOGE("native_window_set_buffer_count failed: %s (%d)", strerror(-err),
                -err);
        return err;
}
    // Dequeue buffers and send them to OMX
    for (OMX_U32 i = 0; i < def.nBufferCountActual; i++) {
        ANativeWindowBuffer* buf;
        err = native_window_dequeue_buffer_and_wait(mNativeWindow.get(), &buf);
        if (err != 0) {
            ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), -err);
            break;
        }
        sp graphicBuffer(new GraphicBuffer(buf, false));
        BufferInfo info;
        info.mData = NULL;
        info.mSize = def.nBufferSize;
        info.mStatus = OWNED_BY_US;
        info.mMem = NULL;
        info.mMediaBuffer = new MediaBuffer(graphicBuffer);
        info.mMediaBuffer->setObserver(this);
        mPortBuffers[kPortIndexOutput].push(info);
        IOMX::buffer_id bufferId;
        err = mOMX->useGraphicBuffer(mNode, kPortIndexOutput, graphicBuffer,
                &bufferId);
        if (err != 0) {
            CODEC_LOGE("registering GraphicBuffer with OMX IL component "
                    "failed: %d", err);
            break;
        }

    }
}

下面介绍一下几个重要的步骤
 step1:获取待播放的视频格式,通过getParameter来获取底层的解码器对显示的参数,完成对显示buffer的设置:
    status_t err = mOMX->getParameter(
            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
    if (err != OK) {
        CODEC_LOGE("getParameter failed: %d", err);
        return err;
    }
    err = native_window_set_buffers_geometry(
            mNativeWindow.get(),
            def.format.video.nFrameWidth,
            def.format.video.nFrameHeight,
            def.format.video.eColorFormat);

step2:设置显示需要用到的buffer格式,比如解码器使用的个数为2个循环,故需要设置为2个。


    err = native_window_set_buffer_count(
            mNativeWindow.get(), def.nBufferCountActual);
    if (err != 0) {
        ALOGE("native_window_set_buffer_count failed: %s (%d)", strerror(-err),
                -err);
        return err;
}
 

step3: graphicBuffer的申请
这里是真正的实现当前需要的buffer的图像缓存区的内存申请,可以看到native_window_dequeue_buffer_and_wait函数,本地窗口进行dequeue_buffer操作,如果熟悉SurfaceFlinger架构的话,就知道其实质是通过匿名的Binder架构进行BufferQueue的dequeue_buffer的操作,请求SurfaceFlinger通过Gralloc模块分配帧缓存或者pmem匿名共享内存缓存区等。
    for (OMX_U32 i = 0; i < def.nBufferCountActual; i++) {
        ANativeWindowBuffer* buf;
        err = native_window_dequeue_buffer_and_wait(mNativeWindow.get(), &buf);
        if (err != 0) {
            ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), -err);
            break;
        }

Step4: 新建graphicBuffer对象,设置此对象给OMX作为解码输出的buffer

// Dequeue buffers and send them to OMX  
for (OMX_U32 i = 0; i < def.nBufferCountActual; i++) {  
    ANativeWindowBuffer* buf;  
    err = native_window_dequeue_buffer_and_wait(mNativeWindow.get(), &buf);//请求SF完成buffer的申请  
    if (err != 0) {  
        ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), -err);  
       break;  
    }  
  
    sp graphicBuffer(new GraphicBuffer(buf, false));//新建了一个应用端的图形缓存区对象保存ANativeWindowBuffer  
    BufferInfo info;  
    info.mData = NULL;  
    info.mSize = def.nBufferSize;  
    info.mStatus = OWNED_BY_US;  
    info.mMem = NULL;  
    info.mMediaBuffer = new MediaBuffer(graphicBuffer);  
    info.mMediaBuffer->setObserver(this);  
    mPortBuffers[kPortIndexOutput].push(info);//加入到输入口  
  
    IOMX::buffer_id bufferId;  
    err = mOMX->useGraphicBuffer(mNode, kPortIndexOutput, graphicBuffer,  
           &bufferId);//当前申请的图像buffer通知底层解码器作为输出buffer  


申请到的buf(应用侧的缓存区ANativeWindowBuffer)将新建一个GraphicBuffer对象,而这个buf所具备的特点是完成了对分配到的缓存区进行了在客户端亦称为应用端进行了mmap的操作,使得客户端对这个图形缓存的操作,实现了对服务端的操作。两者的物理空间实现了一致。

在完成了Buf的申请后,需要控制底层的解码器将输出端口调整为当前的buf端口中故有了mOMX->useGraphicBuffer的操作,并将最终的buffer_id切换保存到mPortBuffers[kPortIndexOutput]中去。


 Step5: OMX内部保存并设置graphicBuffer信息

mOMX->useGraphicBuffer这是BpOMX中的函数,最终通过binder机制掉用了OMX服务中的OMXNodeInstance::useGraphicBuffer
调用OMX的api,将buffer信息作为参数,设置给OMX。
status_t OMXNodeInstance::useGraphicBuffer(
        OMX_U32 portIndex, const sp& graphicBuffer,
        OMX::buffer_id *buffer) {
    Mutex::Autolock autoLock(mLock);
      OMX_STRING name = const_cast(
        "OMX.google.android.index.useAndroidNativeBuffer");
    OMX_ERRORTYPE err = OMX_GetExtensionIndex(mHandle, name, &index);
    BufferMeta *bufferMeta = new BufferMeta(graphicBuffer);
    OMX_BUFFERHEADERTYPE *header;
    OMX_VERSIONTYPE ver;
    ver.s.nVersionMajor = 1;
    ver.s.nVersionMinor = 0;
    ver.s.nRevision = 0;
    ver.s.nStep = 0;
    UseAndroidNativeBufferParams params = {
        sizeof(UseAndroidNativeBufferParams), ver, portIndex, bufferMeta,
        &header, graphicBuffer,
    };
    err = OMX_SetParameter(mHandle, index, ¶ms);  //设置参数给OMX
}


OMX_SetParameter实际为OMX的钩子函数,最终调用到了OMX里面的

SEC_OMX_VideoDecodeSetParameter---- SEC_OSAL_SetANBParameter--useAndroidNativeBuffer //这是HAL层的函数了。只有exynos平台的才有这个函数,高通平台的是其他形式的函数
OMX的细节这里就不展开细讲了。具体可以后续再讲解。
OMX_ERRORTYPE useAndroidNativeBuffer(
    SEC_OMX_BASEPORT      *pSECPort,
    OMX_BUFFERHEADERTYPE **ppBufferHdr,
    OMX_U32                nPortIndex,
    OMX_PTR                pAppPrivate,
    OMX_U32                nSizeBytes,
    OMX_U8                *pBuffer)
{
    OMX_BUFFERHEADERTYPE *temp_bufferHeader = NULL;
    for (i = 0; i < pSECPort->portDefinition.nBufferCountActual; i++) {
        if (pSECPort->bufferStateAllocate[i] == BUFFER_STATE_FREE) {
            pSECPort->bufferHeader[i] = temp_bufferHeader;
            pSECPort->bufferStateAllocate[i] = (BUFFER_STATE_ASSIGNED | HEADER_STATE_ALLOCATED);
            INIT_SET_SIZE_VERSION(temp_bufferHeader, OMX_BUFFERHEADERTYPE);
            temp_bufferHeader->pBuffer        = pBuffer;
              *ppBufferHdr = temp_bufferHeader;
            goto EXIT;
        }
    }
}

上面函数中,我们可以看到,graphicBuffer  被挂接到了 BUFFERHEADERTYPE  bufferHeader上。BUFFERHEADERTYPE  bufferHeader则被以队列的形式保存在了output结构体里。

二,解码器如何使用并填充output buffer
这一部分牵扯到OMX的运行机制和状态机。
如果对这一部分不清楚,建议先阅读一下相关文章。

OMX运行起来之后,SEC_OMX_BufferProcess  实际上是一个线程,在不断的处理输入\输出buffer,  在该线程里面,调用SEC_OutputBufferGetQueue来获得我们之前保存在队列里的BUFFERHEADERTYPE  结构,该结构中保存了一开始申请到的graphicBuffer  信息。
然后调用sec_mfc_bufferProcess钩子函数,把ES流数据input buffer以及解码完的output buffer继续往下层传递。
OMX_ERRORTYPE SEC_OMX_BufferProcess(OMX_HANDLETYPE hComponent)
{
        while ((SEC_Check_BufferProcess_State(pSECComponent)) && (!pSECComponent->bExitBufferProcessThread)) {
            SEC_OSAL_SleepMillisec(0);
            SEC_OSAL_MutexLock(outputUseBuffer->bufferMutex);
            if ((outputUseBuffer->dataValid != OMX_TRUE) &&
                (!CHECK_PORT_BEING_FLUSHED(secOutputPort))) {
                SEC_OSAL_MutexUnlock(outputUseBuffer->bufferMutex);
                ret = SEC_OutputBufferGetQueue(pSECComponent);
                if ((ret == OMX_ErrorUndefined) ||
                    (secInputPort->portState != OMX_StateIdle) ||
                    (secOutputPort->portState != OMX_StateIdle)) {
                    break;
                }
            } else {
                SEC_OSAL_MutexUnlock(outputUseBuffer->bufferMutex);
            }
            if (pSECComponent->remainOutputData == OMX_FALSE) {
                if (pSECComponent->reInputData == OMX_FALSE) {
                    SEC_OSAL_MutexLock(inputUseBuffer->bufferMutex);
                    if ((SEC_Preprocessor_InputData(pOMXComponent) == OMX_FALSE) &&
                        (!CHECK_PORT_BEING_FLUSHED(secInputPort))) {
                            SEC_OSAL_MutexUnlock(inputUseBuffer->bufferMutex);
                            ret = SEC_InputBufferGetQueue(pSECComponent);
                            break;
                    }
                    SEC_OSAL_MutexUnlock(inputUseBuffer->bufferMutex);
                }
                SEC_OSAL_MutexLock(inputUseBuffer->bufferMutex);
                SEC_OSAL_MutexLock(outputUseBuffer->bufferMutex);
                ret = pSECComponent->sec_mfc_bufferProcess(pOMXComponent, inputData, outputData);
        }

}


假如现在解码的是H264格式的视频,exynos OMX 会调用H264组件里的SEC_MFC_H264Dec_bufferProcess函数,进行一帧数据的解码。继而调用SEC_MFC_H264_Decode_Nonblock
OMX_ERRORTYPE SEC_MFC_H264_Decode_Nonblock(OMX_COMPONENTTYPE *pOMXComponent, SEC_OMX_DATA *pInputData, SEC_OMX_DATA *pOutputData)
{
                void *pOutputBuf = (void *)pOutputData->dataBuffer;
        void *pSrcBuf[3] = {NULL, };
        void *pYUVBuf[3] = {NULL, };
            if ((pSECOutputPort->bIsANBEnabled == OMX_TRUE) && (csc_method == CSC_METHOD_HW)) {
                SEC_OSAL_GetPhysANB(pOutputData->dataBuffer, pYUVBuf);
                pSrcBuf[0] = outputInfo.YPhyAddr;
                pSrcBuf[1] = outputInfo.CPhyAddr;
            }
            csc_set_dst_buffer(
                pVideoDec->csc_handle,  /* handle */
                pYUVBuf[0],             /* y addr */
                pYUVBuf[1],             /* u addr or uv addr */
                pYUVBuf[2],             /* v addr or none */
                0);                     /* ion fd */
            csc_convert(pVideoDec->csc_handle);
}


上面的代码中,                SEC_OSAL_GetPhysANB (pOutputData->dataBuffer, pYUVBuf); 的入参  pOutputData->dataBuffer实际就是graphicbuffer。 入参pYUVBuf实际上是个指向数组的指针,里面的内容就是graphicbuffer所代表的YUV buffer 的物理地址值。 SEC_OSAL_GetPhysANB 函数的作用实际上就是提取graphicbuffer的buffer的物理地址,然后进行颜色空间转换,转换完的数据,就保存在这个物理地址里面。从而实现了NativeWindow对应的buffer里面已经存储好了视频YUV数据,下面一步就是进行显示了。
介绍到这里,您可能有两个疑问
1,为什么要进行颜色空间转换?
2,SEC_OSAL_GetPhysANB 是如何提取物理地址的?
一个个的来回答
1,提到颜色空间转换,您可能会以为是YUV->rgb 这种颜色空间转换。实际上在exynos平台上并不是这样的。实际上在exynos平台上,视频文件解码出来,可能会有不同的YUV格式, 最终要交给GPU render的图像格式,也有不同的YUV格式,为了让他们能够对应理顺起来,把多种多样的解码输出格式,转换成GPU需要的格式,就要进行一个颜色空间转换。(具体细节待研究)。例如下面这个颜色空间的定义
typedef enum _SEC_OMX_COLOR_FORMATTYPE {
    OMX_SEC_COLOR_FormatNV12TPhysicalAddress = 0x7F000001, /**< Reserved region for introducing Vendor Extensions */
    OMX_SEC_COLOR_FormatNV12LPhysicalAddress = 0x7F000002,
    OMX_SEC_COLOR_FormatNV12LVirtualAddress = 0x7F000003,
    OMX_SEC_COLOR_FormatNV12Tiled            = 0x7FC00002,  /* 0x7FC00002 */
#ifdef S3D_SUPPORT
    OMX_SEC_COLOR_FormatNV12Tiled_SBS_LR     = 0x7FC00003,  /* 0x7FC00003 */
    OMX_SEC_COLOR_FormatNV12Tiled_SBS_RL     = 0x7FC00004,  /* 0x7FC00004 */
    OMX_SEC_COLOR_FormatNV12Tiled_TB_LR     = 0x7FC00005,  /* 0x7FC00005 */
    OMX_SEC_COLOR_FormatNV12Tiled_TB_RL   = 0x7FC00006,  /* 0x7FC00006 */
    OMX_SEC_COLOR_FormatYUV420SemiPlanar_SBS_LR     = 0x7FC00007,  /* 0x7FC00007 */
    OMX_SEC_COLOR_FormatYUV420SemiPlanar_SBS_RL     = 0x7FC00008,  /* 0x7FC00008 */
    OMX_SEC_COLOR_FormatYUV420SemiPlanar_TB_LR     = 0x7FC00009,  /* 0x7FC00009 */
    OMX_SEC_COLOR_FormatYUV420SemiPlanar_TB_RL   = 0x7FC0000A,  /* 0x7FC0000A */
    OMX_SEC_COLOR_FormatYUV420Planar_SBS_LR     = 0x7FC0000B,  /* 0x7FC0000B */
    OMX_SEC_COLOR_FormatYUV420Planar_SBS_RL     = 0x7FC0000C,  /* 0x7FC0000C */
    OMX_SEC_COLOR_FormatYUV420Planar_TB_LR     = 0x7FC0000D,  /* 0x7FC0000D */
    OMX_SEC_COLOR_FormatYUV420Planar_TB_RL   = 0x7FC0000E,  /* 0x7FC0000E */
#endif
    OMX_SEC_COLOR_FormatNV21LPhysicalAddress = 0x7F000010,
    OMX_SEC_COLOR_FormatNV21Linear           = 0x7F000011,
    /* for Android Native Window */
    OMX_SEC_COLOR_FormatANBYUV420SemiPlanar  = 0x100,
    /* for Android SurfaceMediaSource*/
    OMX_COLOR_FormatAndroidOpaque            = 0x7F000789
}SEC_OMX_COLOR_FORMATTYPE;

最终的转换工作交给            csc_convert(pVideoDec->csc_handle); 来处理。
这个转换工作可以交给exynos特有的一个硬件模块FIMC来完成。也可以交给协处理器NEON来用汇编完成。
 2,SEC_OSAL_GetPhysANB 是如何提取物理地址的。
实际上是调用了HAL 层的gralloc HAL 函数来获得
SEC_OSAL_GetPhysANB 
->SEC_OSAL_GetPhysANBHandle
-> mapper.getphys  // GraphicBufferMapper 类的一个重要作用,就是完成gralloc buffer的 mmap工作,交给使用surfaceflinger的client进程
->GraphicBufferMapper::getphys
->gralloc_getphys //exynos 的HAL 函数
->paddr[0] = (void*)hnd->paddr;获得物理地址
实际上这个物理地址来自于gralloc_alloc_buffer时,通过v4l2从驱动获取的
        ret = ioctl(gfd, VIDIOC_G_CTRL, &vc);
        paddr = (unsigned int)vc.value;

三,如何显示这些解码output buffer
这里是 基于surfaceflinger机制的渲染,AwesomeNativeWindowRenderer在前面的博文中也有提到过,和step3中的图形缓存申请结合在一起的话,就更能说明问题:
virtual void render(MediaBuffer *buffer) {  
   ATRACE_CALL();  
   int64_t timeUs;  
 CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs));  
    native_window_set_buffers_timestamp(mNativeWindow.get(), timeUs * 1000);  
    status_t err = mNativeWindow->queueBuffer(  
            mNativeWindow.get(), buffer->graphicBuffer().get(), -1);//直接使用queuebuffer进行渲染显示  
    if (err != 0) {  
        ALOGE("queueBuffer failed with error %s (%d)", strerror(-err),  
                -err);  
        return;  

这里使用queueBuffer完成当前图像缓存的投递,buffer->graphicBuffer().get()来获取这个本地Buffer。最终显示的核心机制将由surfacefliner去完成。
具体的render 请参考上一篇博文
 http://blog.csdn.net/wan8180192/article/details/49535373





你可能感兴趣的:(android,display,android,编解码)