最近在做移动端音视频编解码,首先要实现的是移动端视频的解码功能。纯的 FFmpeg 方法在移动端也能实现,但是效率上的确要慢一些,1080p 的视频还好,但是上到 2k、4k,那个解码速度(以肉眼可见的速度解码一帧),就没法忍受了。因此要搞移动端硬件解码,以加速解码速度,同时释放部分 CPU 资源。

参考 FFmpeg 源码中 examples

参考 FFmpeg 官方源码中的 examples 的相关功能实现,来实现自己的程序设计,应该是最快的思路。但是,关于视频解码,FFmpeg 官方源码中,有 decode_video.c demuxing_decoding.c hw_decode.c,这三个解码相关的文件。

其实前两个文件的方案差不多,只不过第一个针对裸 h264 流,而第二个是针对带封装的视频文件。建议新手可以参考第二个方案。

至于第三个文件,hw_decode.c,看起来是一个硬件解码的 demo,当然,它的确也是(笑),然而,这里,我们却不能参考这个。参考这个文件,我们可以实现在 Linux 或者 Windows 平台上,利用 cuvid 或者 NVIDIA 、Intel 等硬件厂商实现的硬解码功能,实现硬件解码。但是,在 Android 平台使用 MediaCodec 的解码,却没有实现。

在尝试参考 hw_decode.c 实现 MediaCodec 硬解码的过程中,在 195 行 avcodec_get_hw_config 这一步失败,没有任何 config 列表可供选择。

因此,我们还是参考 demuxing_decoding.c 来实现 Android 平台 MediaCodec 硬解码。

Then,Why?MediaCodec 架构简析

因为 Android 是个平台,其硬件厂商多种多样,而 MediaCodec 并非是一个硬件厂商,因此它并不提供硬件编解码方案。实际上,MediaCodec 更像是一个中间层,通过 openMAX 继承硬件厂商的硬件编解码能力,最终,硬件厂商通过提供符合 openMAX 规范的硬件编解码库。因此,如果仿照 hw_encode.c 来实现,必然会在 avcodec_get_hw_config 这一层找不到合适的配置。

一处改动

那么,我们就完全可以参考 demuxing_decoding.c 来实现 Android 平台 MediaCodec 硬解码功能。其实,基本上全文拷贝到 Android native 代码中,即可使用,只需要改动一处。即 165 行的 dec = avcodec_find_decoder(st->codecpar->codec_id); 改为 dec = avcodec_find_decoder_by_name("h264_mediacodec"); 即可。

为了方便,我们只解码文件中的视频流,同时简化整个流程,基本上,完整代码如下:

static  AVFrame *decode_frame = nullptr;
int testFFmpegMediaCodec(bool sw) {string filename = "/sdcard/pav/hd.mp4";AVFormatContext  *fmt_ctx_ = nullptr;int ret = 0;if (ret = avformat_open_input(&fmt_ctx_, filename.c_str(), nullptr, nullptr) < 0) {LOGE("Failed open file %s, ret = %d", filename.c_str(), ret);return -1;}if (avformat_find_stream_info(fmt_ctx_, nullptr) < 0) {LOGE("Failed to find stream information.");return -1;}int vst_idx = av_find_best_stream(fmt_ctx_, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, false);if (vst_idx < 0) {LOGE("Failed to find video stream from file %s", fmt_ctx_->filename);return -1;}AVCodec *pCodec = avcodec_find_decoder_by_name("h264_mediacodec");AVCodecContext* codec_context = avcodec_alloc_context3(pCodec);avcodec_parameters_to_context(codec_context, fmt_ctx_->streams[vst_idx]->codecpar);if (ret = avcodec_open2(codec_context, pCodec, nullptr)) {LOGE("Failed to open avcodec. ret = %d", ret);return -1;}decode_frame = av_frame_alloc();if (!decode_frame) {LOGE("Failed to allocate frame.");return -1;}decode_frame->format = codec_context->pix_fmt;decode_frame->width = codec_context->width;decode_frame->height = codec_context->height;av_frame_get_buffer(decode_frame, 0);ret = av_image_alloc(video_dst_data, video_dst_linesize, decode_frame->width, decode_frame->height, (AVPixelFormat)decode_frame->format, 1);if (ret < 0) {LOGE("Could not allocate raw video buffer.");return -1;} else {LOGD("We allocate %d for raw video buffer.", ret);}AVPacket pkt;av_init_packet(&pkt);pkt.data = nullptr;pkt.size = 0;while (av_read_frame(fmt_ctx_, &pkt) >= 0) {if (pkt.stream_index == vst_idx) {ret = avcodec_send_packet(codec_context, pkt);if (ret < 0) {LOGE("Error submitting a packet for decoding (%s)", av_err2str(ret));continue;}while (ret >= 0) {ret = avcodec_receive_frame(codec_context, decode_frame);if (ret < 0) {if (ret == AVERROR_EOF) return 0;if (ret == AVERROR(EAGAIN)) break;}// process with decode_frameav_frame_unref(decode_frame);break;}}av_packet_unref(&pkt);}return 0;
}

一个大坑

以上代码仅为示例,不保证能直接运行,可能需要做些微小的调整。然而,即使以上代码没有问题,这个 Android MediaCodec 硬解码功能仍然不能实现。这其中,有个大坑。

当我把代码调整好运行起来之后,发现,程序在解码完第一帧之后,就一直报错:Error submitting a packet for decoding,EAGAIN。这个问题困扰了我很久,直到有网友提到说,需要先把缓冲区里的已解码的帧数据取出来,然后才能再次送入数据进行解码。

最简单快捷的改法,就是把 avcodec_send_packet 后面对 ret < 0 时的 continue 去掉,去掉之后,果然能持续解码了。但是会丢掉好多帧,因为当 ret < 0 时,读取到的 packet 并未成功送入解码队列中去。

因此,这里需要修改一下整个解码逻辑。修改为,先去试图取数据,取不到了,再读数据送入解码器队列。即修改为如下:

while (true) {ret = avcodec_receive_frame(codec_context, decode_frame);if (ret == 0) {// process with decode_frameav_frame_unref(decode_frame);continue;} else if (ret == AVERROR(EAGAIN)) {ret = av_read_frame(fmt_ctx_, &pkt);if (ret == AVERROR_EOF) return 0;if (pkt.stream_index != vst_idx) {av_packet_unref(&pkt); // 注意这一句,缺失将造成内存泄漏continue;}ret = avcodec_send_packet(codec_context, pkt);if (ret < 0) {LOGE("Error submitting a packet for decoding (%s)", av_err2str(ret));continue;}}
}

仅为示例代码。具体细节请自行处理。

结语

至此,利用 FFmpeg + MediaCodec 实现的 C++ 层 Android 硬解码功能就能正常实现了。
我在 小米 MIX 2S 手机上做了下简单的测试,1080 p 的 h264 视频解码完成能达到 25 倍速(即 25 秒钟的视频解码完成需要 1 秒钟),而 4k 的 h264 视频能达到 3.5 倍速。

然而,4k 的 h264 视频解码时有较大的概率出现花屏现象。其花屏的概率、程度,可能与手机的性能、状态均有关系。第一次调通硬解码时解了下 4k 的一段视频,发现花屏严重。而第二天,刚开始工作时解了下同一段视频,发现前面若干帧基本没有花屏现象,大约从三百帧开始,出现小的花屏,最后几帧花屏严重。

对于花屏这个问题,有网友称移动平台解码 4k 视频的确很吃力,很费劲,甚至解不动。如果这样的话,那么为什么那段 4k 的视频能在 小米 MIX 2S 手机上流畅播放呢?是不是还有什么参数可以继续调整优化呢?希望有高手能给出一些提示或建议。

【AVD】FFmpeg + MediaCodec 实现 Android 硬件解码,中间有个大坑相关推荐

  1. windows下ffmpeg使用dxva2加速硬件解码

    https://blog.csdn.net/lishi_1991/article/details/109215543

  2. Android MediaCodec硬件解码视频播放

    1.MediaCodec 是什么 MediaCodec类可以访问底层媒体编解码器框架(StageFright 或 OpenMAX),即编解码组件.是Android 的低层多媒体基础设施的一部分(通常与 ...

  3. MediaCodec在Android视频硬解码组件的应用

    https://yq.aliyun.com/articles/632892 云栖社区> 博客列表> 正文 MediaCodec在Android视频硬解码组件的应用 cheenc 2018- ...

  4. MediaCodec 实现硬件解码

    MediaCodec MediaCodec 是 Android 提供的硬件编解码器,通常配合 MediaExtractor(用于解析媒体文件,获取音视频数据), MediaMuxer(用于封装已编码的 ...

  5. Mediacodec 如何硬件解码到纹理的

    Mediacodec 如何硬件解码到纹理的 背景: 网上很多关于mediacodec,surface ,surfacetexture的源码分析,以及内部原理,但是都局限于各自的内容,今天我们就从med ...

  6. rk3588 ffmpeg使用硬件解码

    rk3588 ffmpeg使用硬件解码 安装ffmpeg最新的静态构建版本 方法1 安装rkmpp 安装264 安装libdrm 下载ffmpeg源码 方法2 ppa搜索需要的软件包 安装支持rkmp ...

  7. dxva2+ffmpeg硬件解码(Windows)重要笔记3

    参考了csdn上Win32Project1_ffmpeg_dxva2这个例子,很不错,直接就可以运行. 但是,有几个问题: 1.窗口无法正常缩放,缩放后,图像大小并没有一起缩放 2.H265的编码格式 ...

  8. 基于Intel 集成显卡的 FFmpeg 调用 VAAPI 硬件解码零数据拷贝链接推理引擎工作流程的实现

    概述 在视频处理流程中,视频的解码通常在 CPU 中进行,若用户需要使用集成显卡进行深度学习推理,解码数据需要从 CPU的缓存中拷贝至集成显卡中进行推理.本文旨在通过集成显卡进行硬件解码,使用FFmp ...

  9. android jni 硬解码,Android 硬解码 MediaCodec 遇到的(部分手机绿屏)API21

    MediaCodec 是android用来做音视频编解码 下面是遇到其它的问题(解码几分钟后一直dequeueInputBuffer-1) http://blog.csdn.net/qq3773663 ...

最新文章

  1. 人民日报:大数据时代如何保证数据安全?
  2. navicat保存查询语句_MySQL数据库安装创建及Navicat客户端连接
  3. 用python解决实际问题_Python解决实际问题一--备份文件
  4. 在IAR 中出现the stack plug-in failed to set a breakpoint on main
  5. iOS Hacker 越狱后如何使用 root 运行应用
  6. 重建AD域控制器的DNS服务器
  7. 梅耶·马斯克对话邓文迪 直播首秀将上线今日头条、抖音
  8. 面对疫情,AI 能做什么?
  9. 快逸报表数据库密码加密解决方案
  10. 大话数据结构PDF/word
  11. 投资银行业务法规汇编----仅供学习
  12. Eclipse语言包安装
  13. js截取指定字符串后面的所有字符
  14. alex机器人 ser_基于Web Service的机器人远程控制系统设计
  15. 小虎电商浏览器:拼多多标题关键词怎么找?有什么工具?
  16. #Revit二次开发# 创建剖面视图
  17. css3图片倾斜3D效果
  18. BananaPi Wifi 连接
  19. 在Archlinux下安装Xilinx ISE12.3以及Digilent Adept的使用
  20. 新视野大学英语第三版第二册视听说

热门文章

  1. 二元域矩阵求逆MATLAB代码
  2. 通达信公式(股票期货专用)
  3. git 错误 10054
  4. dedecms织梦标签解析不完整,不完全解析显示的原因
  5. 轻松云上开发:从IBM Cloud 开始!
  6. DX11(四):HLSL语法
  7. 在军校学计算机出来能干什么,对军校非计算机专业计算机基础课程设置的一些思考.pdf...
  8. CSS进阶文档(其实是基础啦)
  9. 【数据分析案例】pandas + matplotlib 人货场+RFM+用户复购分析 电商水果销售
  10. HP fw1776 打印机的连接方式