使用Android MediaCodec 硬解码延时问题分析-程序员宅基地

技术标签: codec  解码延时  流媒体  

使用Android MediaCodec 硬解码延时问题分析
2018年03月29日 09:30:38 珠雨妮儿 阅读数:2492
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhuyunier/article/details/79730872
最近做项目用到Android native层的MediaCodec的接口对H264进行解码,通过在解码前和解码后加打印日志,发现解码耗时200多ms,和IOS的解码耗时10ms相比实在是延时好大。后来研究了两周也没能解决延时问题,很悲惨……不过还是将这过程中分析的思路记录下来,说不定以后万一灵感来了就解决了呢。

 起初在https://software.intel.com/en-us/forums/android-applications-on-intel-architecture/topic/536355和https://software.intel.com/en-us/android/articles/android-hardware-codec-mediacodec这两个网址中找到问题原因和一种解决方法。

  如果编码器类型是H.264,MediaCodec可能会有几帧的延迟(对于IA平台,它是7帧,而另一些则是3-5帧)。如果帧速率为30 FPS,则7帧将具有200毫秒的延迟。什么导致了硬件解码器的延迟?原因可能是主或高配置文件有一个B帧,并且如果当B帧引用后续缓冲区时解码器没有缓冲区,则该播放会短暂停止。英特尔已经考虑了硬件解码器的这种情况,并为主要或高配置文件提供了7个缓冲区,但是由于基线没有B帧,因此将其减少到基线的零缓冲区。其他供应商可能为所有配置文件使用4个缓冲区。
 因此,在基于IA的Android平台上,解决方案是将编码器更改为基线配置文件。如果编码器配置文件无法更改,则可行的解决方法是更改​​解码器端的配置数据。通常,配置文件位于第五个字节中,因此将基准配置文件更改为66会在IA平台上产生最低延迟。也就是说要想低延时,必须:

- 不要设置MediaFormat.KEY_MAX_WIDTH和MediaFormat.KEY_MAX_HEIGHT。

 - 将第一个SPS中的配置文件切换到基线,然后在第一个PPS后以正确的配置文件重新提交。

  先附上解码部分代码:

int OpenMetaCUDAVideoDecoderContext::OnVideoH264(uint8_t * frame, uint32_t frameSize){

DP_MSG("onVideoData=%6d, %.2X %.2X %.2X %.2X %.2X %.2X %.2X %.2X\n",
       frameSize,
       frame[0],frame[1],frame[2],frame[3],frame[4],frame[5],frame[6],frame[7]
);

int  pos = 0;
if( (frame[4] & 0X1F ) == 0X09 ){
    // this is an iframe
    frame     += 6;
    frameSize -= 6;
}

if( (frame[4] & 0X1F ) == 0X07 ){
    // this is an iframe
    iFrameFound = true;
}
else if(!iFrameFound){
    // iframe not found, do nothing
    return -1;
}

DP_MSG("video264::::%d",frameSize)

uint8_t *data = NULL;
uint8_t *pps = NULL;
uint8_t *sps = NULL;

// I know what my H.264 data source's NALUs look like so I know start code index is always 0.
// if you don't know where it starts, you can use a for loop similar to how i find the 2nd and 3rd start codes
int currentIndex = 0;
long blockLength = 0;

int nalu_type = (frame[currentIndex + 4] & 0x1F);
// if we havent already set up our format description with our SPS PPS parameters, we
// can't process any frames except type 7 that has our parameters
if (nalu_type != 7 && formatDesc == NULL)
{
    return -1;
}

if (nalu_type == 7)
{
    currentIndex = H264_findStartCode(frame,frameSize,currentIndex+4);
    size_t spsSize = currentIndex;

    // find what the second NALU type is
    nalu_type = (frame[currentIndex + 4] & 0x1F);

    DP_MSG("onVideoDataSPs=%6d, %.2X %.2X %.2X %.2X %.2X %.2X %.2X %.2X\n",
           frameSize,
           frame[0],frame[1],frame[2],frame[3],frame[4],frame[5],frame[6],frame[7]
    );

    // type 8 is the PPS parameter NALU
    if(nalu_type == 8){
        int nextIndex = H264_findStartCode(frame,frameSize,currentIndex+4);
        size_t ppsSize = nextIndex - currentIndex;

        DP_MSG("onVideoDataPPs=%6d, %.2X %.2X %.2X %.2X %.2X %.2X %.2X %.2X %.2X %.2X %.2X %.2X\n",
               frameSize,
               frame[0],frame[1],frame[2],frame[3],frame[4],frame[5],frame[6],frame[7],
               frame[spsSize+1],frame[spsSize+2],frame[spsSize+3],frame[spsSize+4]
        );

        if(decompressionSession == NULL) {
            formatDesc = AMediaFormat_new();
            AMediaFormat_setString(formatDesc, "mime", "video/avc");
            AMediaFormat_setInt32(formatDesc, AMEDIAFORMAT_KEY_WIDTH, 1920); // 视频宽度
            AMediaFormat_setInt32(formatDesc, AMEDIAFORMAT_KEY_HEIGHT, 1088); // 视频高度

            sps = (uint8_t *)malloc(spsSize);
            pps = (uint8_t *)malloc(ppsSize);
            memcpy (sps, &frame[0], spsSize);
            memcpy (pps, &frame[spsSize], ppsSize);

            AMediaFormat_setBuffer(formatDesc, "csd-0", sps, spsSize); // sps
            AMediaFormat_setBuffer(formatDesc, "csd-1", pps, ppsSize); // pps

            DP_MSG("createDecompSession ========= ")
            this->createDecompSession();

            if(sps != NULL)
            {
                free(sps);
                sps = NULL;
            }
            if(pps != NULL)
            {
                free(pps);
                pps = NULL;
            }

        }

        // now lets handle the IDR frame that (should) come after the parameter sets
        // I say "should" because that's how I expect my H264 stream to work, YMMV
        currentIndex = nextIndex;
        nalu_type = (frame[currentIndex + 4] & 0x1F);

        while(currentIndex != -1 && nalu_type != 5 && nalu_type != 1){
            currentIndex = H264_findStartCode(frame,frameSize,currentIndex+4);
            nalu_type = (frame[currentIndex + 4] & 0x1F);
        }
    }
}

int32_t afout = 0;
   nalu_type = 5; // temp
// type 5 is an IDR frame NALU.  The SPS and PPS NALUs should always be followed by an IDR (or IFrame) NALU, as far as I know
if(nalu_type == 5)
{

    DP_MSG("jniPlayTs size=%d and %d \n",AMediaFormat_getInt32(formatDesc,AMEDIAFORMAT_KEY_COLOR_FORMAT,&afout),afout);
    //DP_MSG("theNativeWindow format %p and %d \n",theNativeWindow,ANativeWindow_getFormat(theNativeWindow));
    // find the offset, or where the SPS and PPS NALUs end and the IDR frame NALU begins
    int offset = currentIndex;//
    blockLength = frameSize - offset;

    data = &frame[offset];

    DP_MSG("nalu_type ===5 ")
    //DP_MSG("nalu_type ===5 %s /n ",AMediaFormat_toString(formatDesc))

    if(decompressionSession == nullptr)
    {
        DP_MSG("decompressionSession is null")
    }

    DP_MSG("OpenMetaCUDAVideoDecoder Start decoding: frameIdentifier=%lld",frameIdentifier);
    avx_info("OpenMetaCUDAVideoDecoder Start decoding: ","frameIdentifier=%lld",frameIdentifier);

    static int64_t  llPts = 0;

    ssize_t index = -1;
    index = AMediaCodec_dequeueInputBuffer(decompressionSession, 10000);
    if(index>=0)
    {
        size_t bufsize;
        auto buf = AMediaCodec_getInputBuffer(decompressionSession, index, &bufsize);
        if(buf!= nullptr )
        {
            DP_MSG("buf!= nullptr5======ww")
            memcpy(buf,data,blockLength);

            AMediaCodec_queueInputBuffer(decompressionSession, index, 0, blockLength, (uint64_t)frameIdentifier, 0);

            llPts += 3000;
        }

    }

    AMediaCodecBufferInfo info;
    auto status = AMediaCodec_dequeueOutputBuffer(decompressionSession, &info, 0);
    if (status >= 0) {
        if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
            DP_MSG("output EOS");
        }

        size_t outsize = 0;
        u_int8_t * outData = AMediaCodec_getOutputBuffer(decompressionSession,status,&outsize);
        auto formatType = AMediaCodec_getOutputFormat(decompressionSession);
        DP_MSG("format formatType to: %s", AMediaFormat_toString(formatType));
        int colorFormat = 0;
        int width = 0;
        int height = 0;
        AMediaFormat_getInt32(formatType,"color-format",&colorFormat);
        AMediaFormat_getInt32(formatType,"width",&width);
        AMediaFormat_getInt32(formatType,"height",&height);
        DP_MSG("format color-format : %d width :%d height :%d", colorFormat,width,height);

		AMediaFormat_delete(formatType);
        DP_MSG("OpenMetaCUDAVideoDecoder End   decoding: frameIdentifier=%lld,%d,%d,%p",info.presentationTimeUs,info.size,outsize,outData)
		if(this->kController){
            //OpenMetaPixelBuffer _kPixelBuffer(pixelBuffer,sizeof(pixelBuffer));
            OpenMetaPixelBuffer _kPixelBuffer(outData,sizeof(outData));
            _kPixelBuffer.kLine[0]  = outData;
            _kPixelBuffer.kLine[1]  = outData + width * height;
            _kPixelBuffer.kSizeX    = width;
            _kPixelBuffer.kSizeY    = height;
            _kPixelBuffer.kDataType = 1;
            _kPixelBuffer.kTimeStamp = info.presentationTimeUs;

            if(this!=NULL && this->kController!=NULL)
            {
                this->kController->OnVideoImage(&_kPixelBuffer);
            }

        }
        avx_info("OpenMetaCUDAVideoDecoder End   decoding:","%lld,%d,%d,%p",info.presentationTimeUs,info.size,outsize,outData);
        AMediaCodec_releaseOutputBuffer(decompressionSession, status, info.size != 0);

    } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
        DP_MSG("output buffers changed");
    } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
        auto format = AMediaCodec_getOutputFormat(decompressionSession);
        DP_MSG("format changed to: %s", AMediaFormat_toString(format));
        AMediaFormat_delete(format);
    } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
        DP_MSG("no output buffer right now");
    } else {
        DP_MSG("unexpected info code: %zd", status);
    }
  
}

return 0;

}

  针对查到的解决办法,对以上代码进行了修改,分别是:

//将第一个sps的改为基线配置
sps = (uint8_t *)malloc(spsSize);
sps[0+5] = 66;
memcpy (sps, &frame[0], spsSize);

pps = (uint8_t *)malloc(ppsSize);
memcpy (pps, &frame[spsSize], ppsSize);

AMediaFormat_setBuffer(formatDesc, “csd-0”, sps, spsSize); // sps
AMediaFormat_setBuffer(formatDesc, “csd-1”, pps, ppsSize); // pps

//拷贝两份sps,将第一个sps的改为基线配置
sps = (uint8_t *)malloc(spsSize * 2);
memcpy (sps, &frame[0], spsSize);
sps[0+5] = 66;
memcpy (sps + spsSize, &frame[0], spsSize);

pps = (uint8_t *)malloc(ppsSize);
memcpy (pps, &frame[spsSize], ppsSize);

AMediaFormat_setBuffer(formatDesc, “csd-0”, sps, spsSize * 2); // sps
AMediaFormat_setBuffer(formatDesc, “csd-1”, pps, ppsSize); // pps
//拷贝两份sps,将第二份的第一个sps的改为基线配置
sps = (uint8_t *)malloc(spsSize * 2);
memcpy (sps, &frame[0], spsSize);
memcpy (sps + spsSize, &frame[0], spsSize);
sps[spsSize+5] = 66;

pps = (uint8_t *)malloc(ppsSize);
memcpy (pps, &frame[spsSize], ppsSize);

AMediaFormat_setBuffer(formatDesc, “csd-0”, sps, spsSize * 2); // sps
AMediaFormat_setBuffer(formatDesc, “csd-1”, pps, ppsSize); // pps
通过查看打印的开始解码与结束解码的时间发现,几种方法的时间差距并不是很大,依旧是200-250ms

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/eastbell/article/details/90672610

智能推荐

阿里2024首发一百多道Java高级岗面试题(含答案),二叉树+链表+字符串+栈和队列高频面试题合集-程序员宅基地

文章浏览阅读435次,点赞17次,收藏17次。这份文档从构建一个键值数据库的关键架构入手,不仅带你建立起全局观,还帮你迅速抓住核心主线。除此之外,还会具体讲解数据结构、线程模型、网络框架、持久化、主从同步和切片集群等,帮你搞懂底层原理。相信这对于所有层次的Redis使用者都是一份非常完美的教程了。你的支持,我的动力;祝各位前程似锦,offer不断!!!网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)

matlab 计算Ax=b的解,解线性方程组的现成工具_matlab解ax=b-程序员宅基地

文章浏览阅读1.4k次,点赞6次,收藏9次。只写了最简单的方式,其中b需要是列向量,用分号隔开元素;_matlab解ax=b

用户故事与用例:软件开发的双翼-程序员宅基地

文章浏览阅读840次,点赞29次,收藏13次。在敏捷软件开发中,理解用户需求是成功交付高质量产品的关键。本文将详细介绍两种收集和定义需求的流行技术:用户故事和用例。我们将探讨它们的理论基础、区别、联系,以及如何在实际工作中应用这些概念。

02 - ArcGIS For JavaScript-矢量数据的符号化处理(Symbol)-程序员宅基地

文章浏览阅读789次,点赞11次,收藏14次。ArcGIS中的符号化,及将矢量数据(点、线、面)通过给其设置颜色、边框、样式实例化为一个具化的对象。ArcGIS提供了Symbol去处理符号化问题。Symbol定义如何在GraphicLayer上如何显示点、线、面、文本,符号定义了几何对象所有的非地理特征方面的外观,例如图形的颜色,边框线样式,api中有许多的符号类,每个类都只允许你使用唯一的方式去制定符号。每种符号都用于一种特定的类型。

新手学Python之学习官网教程(二: Using the Python Interpreter)_useing python3 as python interpreter-程序员宅基地

文章浏览阅读1.3w次,点赞27次,收藏18次。文章目录1. 调用Python解释器1. 调用Python解释器  在Linux环境下,如果Python版本为3.6,Python解释器通常是安装在/usr/local/bin/python3.6。请把/usr/local/bin添加到系统变量PATH中,The Python interpreter is usually installed as /usr/local/bin/python3.6 on those machines where it is available; putting /usr_useing python3 as python interpreter

【机器学习笔记37】模糊聚类分析(基于最大生成树)_模糊聚类分析例题-程序员宅基地

文章浏览阅读4.3k次,点赞7次,收藏26次。【参考资料】【1】《模式识别与智能计算的MATLAB技术实现》【2】《模糊数学方法及其应用》【3】https://baike.baidu.com/item/Kruskal/10242089?fr=aladdin1. 模糊关系的矩阵定义模糊集定义及基础信息,参考之前笔记《模糊数学笔记-模糊集》1.1 模糊关系定义: 设论域U,V,称U×VU \times VU×V的一个模糊子集R..._模糊聚类分析例题

随便推点

python格式化字符串的三种方法(%,format,f-string)_在java中可以使用string.format函数,python中可以使用%或format方法来格式-程序员宅基地

文章浏览阅读10w+次,点赞111次,收藏413次。DAY 7. 格式化字符串到目前为止,我所知道的,python格式化字符串有三种方法,第一是早期就有的%,其次是2.5之后的format(),还有就是3.6添加的f字符串调试7.1 %格式化字符串%格式化字符串是python最早的,也是能兼容所有版本的一种字符串格式化方法,在一些python早期的库中,建议使用%格式化方式,他会把字符串中的格式化符按顺序后面参数替换,格式是"xxxxxx ..._在java中可以使用string.format函数,python中可以使用%或format方法来格式化字符

电信/网通双线光纤,策略路由/双线分流/双线备份,图文教程_电信双局向备份-程序员宅基地

文章浏览阅读1.9k次。其实此类教程已经讲得太多了。双线一直是ROS的一个特色,相信也有很多朋友都会做,会使用。也有很多新来的朋友想学习,可能由于没有入门的原因,或许也没有静下心来仔细的咀嚼前辈们的帖子,导致还是有很多问题的存在。每个人都有自己的意愿,想要收费的,愿意付费的那是你们的自由和权利,他人无权干涉,并非有些朋友所说的“讲得太多了,版主还怎么赚钱啊”,我扪心自问,从未靠ROS技术赚取过一分钱,一直本着己欲立而立人_电信双局向备份

最全!导入csv文件+归一化+PCA降维+K-Means聚类+效果评估+可视化 项目实战_聚类csv-程序员宅基地

文章浏览阅读4.5k次,点赞7次,收藏70次。(1)使用pandas库读取wine数据集(2)将wine数据集的数据和标签拆分开。(3)将wine数据集划分为训练集和测试集。(4)标准化wine数据集。(5)对wine数据集进行PCA降维。(6)构建聚类数目为3的K-Means模型。(7)对比真实标签和聚类标签求取FMI。(8)在聚类数目为2~10类时,确定最优聚类数目。(9)求取模型的轮廓系数,绘制轮廓系数折线图,确定最优聚类数目。(10)求取Calinski-Harabasz指数,确定最优聚类数目。_聚类csv

照片如何打包?三个方法轻松搞定!_怎么把照片压缩成文件包-程序员宅基地

文章浏览阅读495次。介绍了三种照片打包的方法,一是使用嗨格式压缩大师,可以一键批量快速压缩,自由选择压缩模式;二是使用7zip设置相关参数;三是使用WINRAR设置相关参数进行压缩。_怎么把照片压缩成文件包

探秘 Awesome FlipperZero:一款革命性的硬件黑客工具-程序员宅基地

文章浏览阅读605次,点赞9次,收藏13次。探秘 Awesome FlipperZero:一款革命性的硬件黑客工具项目地址:https://gitcode.com/djsime1/awesome-flipperzeroAwesome FlipperZero 是一个致力于汇集所有关于 FlipperZero 设备的资源、教程和项目的开源仓库。FlipperZero 是一种多用途的小型硬件设备,它为电子爱好者、安全研究人员、物联网开发者提..._flipperzero源代码

贡献一个 Laravel 树形结构管理包 ClosureTable-程序员宅基地

文章浏览阅读1k次。About优雅的树形数据结构管理包,基于Closure Table模式设计.github 欢迎不吝StarFeatures优雅的树形数据设计模式数据和结构分表,操作数据不影响结构一个Eloquent Trait操作简单无需修改表,兼容旧数据完善的树操作方法支持生成树形数据支持多棵树并存(多个根)支持节点/树修复支..._laravel tree

推荐文章

热门文章

相关标签