导读

前面我们已经使用NDK编译出了FFmpeg并且已经集成到了Android Studio中去,相关文章:NDK21编译ffmpeg5.0.1

众所周知,软解码虽然兼容性一流,但是却非常依赖CPU,所以性能消耗笔记大;硬解码使用内置的DSP芯片进行解码,性能高,但是兼容性一般。

虽说硬解码兼容性不太好,但是在实际开发中出于对性能的考虑我们依然会采用能硬解则硬解,不能硬解则软解兜底的方案。

我们知道安卓上可以使用MediaCodec进行硬解码,新版本FFmpeg内部也支持了MediaCodec硬解码,今天我们就使用FFMpeg在安卓上使用MediaCodec进行硬解码。

笔者测试的FFmpeg版本是最新的5.0.1,不同版本之间可以会有差异。

编译支持硬解码的FFmpeg

要编译支持硬解码的FFmpeg,在进行交叉编译时我们只需要打开以下几个属性即可:

--enable-hwaccels \
--enable-jni \
--enable-mediacodec \
--enable-decoder=h264_mediacodec \
--enable-decoder=hevc_mediacodec \
--enable-decoder=mpeg4_mediacodec \
--enable-hwaccel=h264_mediacodec \

使用FFMpeg进行硬解码

使用FFmpeg无论是硬解码还是软解码流程都是差不多的,对使用FFmpeg编解码API不熟悉的童鞋们可以回看之前发表的博客文章…

在FFmpeg源文件hwcontext.c中我们可以看出mediacodec对应的type类型是AV_HWDEVICE_TYPE_MEDIACODEC,这个AV_HWDEVICE_TYPE_MEDIACODEC很重要,
在配置硬解码器时都是需要使用到这个type。

static const char *const hw_type_names[] = {[AV_HWDEVICE_TYPE_CUDA]   = "cuda",[AV_HWDEVICE_TYPE_DRM]    = "drm",[AV_HWDEVICE_TYPE_DXVA2]  = "dxva2",[AV_HWDEVICE_TYPE_D3D11VA] = "d3d11va",[AV_HWDEVICE_TYPE_OPENCL] = "opencl",[AV_HWDEVICE_TYPE_QSV]    = "qsv",[AV_HWDEVICE_TYPE_VAAPI]  = "vaapi",[AV_HWDEVICE_TYPE_VDPAU]  = "vdpau",[AV_HWDEVICE_TYPE_VIDEOTOOLBOX] = "videotoolbox",[AV_HWDEVICE_TYPE_MEDIACODEC] = "mediacodec",[AV_HWDEVICE_TYPE_VULKAN] = "vulkan",
};

下面说说在FFMpeg配置硬解码器的大体步骤:

1、给FFMpeg设置虚拟机环境

首先在库加载函数JNI_OnLoad中调用FFmpeg的函数av_jni_set_java_vm,给FFMpeg设置虚拟机环境:

// 类库加载时自动调用
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reversed) {JNIEnv *env = NULL;// 初始化JNIEnvif (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {return JNI_FALSE;}// 设置JavaVM,否则无法进行硬解码av_jni_set_java_vm(vm, nullptr);RegisterNativeMethods(env, "com/fly/ffmpeg/practice/ffmpeg/FFmpegHWDecoder",const_cast<JNINativeMethod *>(hw_decoder_nativeMethod), sizeof(hw_decoder_nativeMethod) / sizeof (JNINativeMethod));// 返回JNI使用的版本return JNI_VERSION_1_4;
}

2、通过名字查找硬解码器

以h264为例,在安卓上它的硬解码器名字为h264_mediacodec,可以通过函数avcodec_find_decoder_by_name("h264_mediacodec")查找解码器,
如果返回空,一般就是不支持硬解码了。

3、配置硬解码器

这个配置主要是为了获取解码得到的YUV是什么格式的。

       // 配置硬解码器int i;for (i = 0;; i++) {const AVCodecHWConfig *config = avcodec_get_hw_config(avCodec, i);if (nullptr == config) {LOGCATE("获取硬解码是配置失败");return;}if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&config->device_type == AV_HWDEVICE_TYPE_MEDIACODEC) {hw_pix_fmt = config->pix_fmt;LOGCATE("硬件解码器配置成功");break;}}

4、初始化mediacodec的buffer

    avCodecContext = avcodec_alloc_context3(avCodec);avcodec_parameters_to_context(avCodecContext,avFormatContext->streams[video_index]->codecpar);avCodecContext->get_format = get_hw_format;// 硬件解码器初始化AVBufferRef *hw_device_ctx = nullptr;ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_MEDIACODEC,nullptr, nullptr, 0);if (ret < 0) {LOGCATE("Failed to create specified HW device");return;}avCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx);

5、打开解码器

和软解码一样,使用函数avcodec_open2打开解码器即可。后面的操作就是和软解码一样了。

从以上可以看出,硬解码和软解的区别就是硬解码需要多配置一点信息而已,下面贴一下主要代码:

#include "HWDecoder.h"
#include <log_cat.h>extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/codec.h>
#include <libavutil/avutil.h>
#include <libavutil/pixdesc.h>
}AVFormatContext *avFormatContext;
AVPacket *avPacket;
AVFrame *avFrame;
AVCodecContext *avCodecContext;
FILE *yuv_file;
HWDecoder::HWDecoder() {}HWDecoder::~HWDecoder() {if (nullptr != avFormatContext) {avformat_free_context(avFormatContext);avFormatContext = nullptr;}if (nullptr != avCodecContext) {avcodec_free_context(&avCodecContext);avCodecContext = nullptr;}if (nullptr != avPacket) {av_packet_free(&avPacket);avPacket = nullptr;}if (nullptr != avFrame) {av_frame_free(&avFrame);avFrame = nullptr;}if(nullptr != yuv_file){fclose(yuv_file);yuv_file = nullptr;}
}AVPixelFormat hw_pix_fmt;
static enum AVPixelFormat get_hw_format(AVCodecContext *ctx,const enum AVPixelFormat *pix_fmts)
{const enum AVPixelFormat *p;for (p = pix_fmts; *p != -1; p++) {if (*p == hw_pix_fmt)return *p;}LOGCATE("Failed to get HW surface format.\n");return AV_PIX_FMT_NONE;
}void HWDecoder::decode_video(const char *video_path, const char *yuv_path) {avFormatContext = avformat_alloc_context();int ret = avformat_open_input(&avFormatContext, video_path, nullptr, nullptr);if (ret < 0) {LOGCATE("打开媒体文件失败");return;}avformat_find_stream_info(avFormatContext, nullptr);int video_index = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);if (video_index < 0) {LOGCATE("找不到视频索引");return;}LOGCATE("找到视频索引:%d", video_index);const AVCodec *avCodec = nullptr;switch (avFormatContext->streams[video_index]->codecpar->codec_id) {// 这里以h264为例case AV_CODEC_ID_H264:avCodec = avcodec_find_decoder_by_name("h264_mediacodec");if (nullptr == avCodec) {LOGCATE("没有找到硬解码器h264_mediacodec");return;} else {// 配置硬解码器int i;for (i = 0;; i++) {const AVCodecHWConfig *config = avcodec_get_hw_config(avCodec, i);if (nullptr == config) {LOGCATE("获取硬解码是配置失败");return;}if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&config->device_type == AV_HWDEVICE_TYPE_MEDIACODEC) {hw_pix_fmt = config->pix_fmt;LOGCATE("硬件解码器配置成功");break;}}break;}}avCodecContext = avcodec_alloc_context3(avCodec);avcodec_parameters_to_context(avCodecContext,avFormatContext->streams[video_index]->codecpar);avCodecContext->get_format = get_hw_format;// 硬件解码器初始化AVBufferRef *hw_device_ctx = nullptr;ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_MEDIACODEC,nullptr, nullptr, 0);if (ret < 0) {LOGCATE("Failed to create specified HW device");return;}avCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx);// 打开解码器ret = avcodec_open2(avCodecContext, avCodec, nullptr);if (ret != 0) {LOGCATE("解码器打开失败:%s",av_err2str(ret));return;} else {LOGCATE("解码器打开成功");}avPacket = av_packet_alloc();avFrame = av_frame_alloc();yuv_file = fopen(yuv_path,"wb");while (true) {ret = av_read_frame(avFormatContext, avPacket);if (ret != 0) {LOGCATE("av_read_frame end");break;}if(avPacket->stream_index != video_index){av_packet_unref(avPacket);continue;}ret = avcodec_send_packet(avCodecContext,avPacket);if(ret == AVERROR(EAGAIN)){LOGCATD("avcodec_send_packet EAGAIN");} else if(ret < 0){LOGCATE("avcodec_send_packet fail:%s",av_err2str(ret));return;}av_packet_unref(avPacket);ret = avcodec_receive_frame(avCodecContext,avFrame);LOGCATE("avcodec_receive_frame:%d",ret);while (ret == 0){LOGCATE("获取解码数据成功:%s",av_get_pix_fmt_name(static_cast<AVPixelFormat>(avFrame->format)));LOGCATE("linesize0:%d,linesize1:%d,linesize2:%d",avFrame->linesize[0],avFrame->linesize[1],avFrame->linesize[2]);LOGCATE("width:%d,height:%d",avFrame->width,avFrame->height);ret = avcodec_receive_frame(avCodecContext,avFrame);// 如果解码出来的数据是nv12// 播放 ffplay -i d:/cap.yuv -pixel_format nv12 -framerate 25 -video_size 640x480// 写入yfor(int j=0; j<avFrame->height; j++)fwrite(avFrame->data[0] + j * avFrame->linesize[0], 1, avFrame->width, yuv_file);// 写入uvfor(int j=0; j<avFrame->height/2; j++)fwrite(avFrame->data[1] + j * avFrame->linesize[1], 1, avFrame->width, yuv_file);}}}

解码成功将YUV写入文件后可以通过ffplay播放一下,看画面是否正常,怎么播放具体看注释。

遇到的问题

1、笔者在测试的过程中发现打开解码器报错:

Generic error in an external library

经查验代码发现是没有给FFmpeg设置JavaJVM,需要调用函数设置av_jni_set_java_vmJavaJVM参数即可。

2、如果解码得到的AVFrame的格式不是NV12或者NV21的话,表示数据有可能保存在GPU中,可以通过函数av_hwframe_transfer_data将数据取出到CPU。

推荐阅读

FFmpeg连载1-开发环境搭建
FFmpeg连载2-分离视频和音频
FFmpeg连载3-视频解码
FFmpeg连载4-音频解码
FFmpeg连载5-音视频编码
FFmpeg连载6-音频重采样
FFmpeg连载8-视频合并以及替换视频背景音乐实战
ffplay调试环境搭建
ffplay整体框架
ffplay数据读取线程
ffplay音视频解码线程
ffplay音视频同步
NDK21编译ffmpeg5.0.1

关注我,一起进步,人生不止coding!!!

FFmpeg之硬解码相关推荐

  1. FFmpeg 硬件加速(硬解码)介绍

    参考地址 概述   本文主要针对ffmpeg支持的硬解码做一个总结阐述.   许多平台提供对专用硬件的访问,以执行一系列与视频相关的任务.使用此类硬件可以更快地完成某些操作,例如解码.编码或过滤,或者 ...

  2. 树莓派编译ffmpeg支持x264硬解码播放视频

    树莓派编译ffmpeg支持h264_mmal硬解码 1,x264源码编译 1.1下载x264源码,解压 1.2添加一个脚本文件config_x264_rpi.sh ,放入1.1解压之后的文件夹中 1. ...

  3. ffmpeg + cuda(cuvid) 硬解码+像素格式转换(cpu主导)实战

    注意: VAAPI 是inter gpu 提供的硬编解码接口 VDPAU 是 video decode present api for unix nvdec / ncvid 都是nivida产出的硬解 ...

  4. FFmpeg的软、硬解码方式梳理

    背景 项目中使用QT开发监控软件,集成海康.宇视.大华.华迈.以及网络流设备.品牌设备使用SDK控制,网络流设备自己使用FFmpeg库来解决.网络流设备如果同时解码多路播放,会出现CPU占用较高.操作 ...

  5. ffmpeg硬解码与软解码的压测对比

    文章目录 ffmpeg硬解码与软解码的压测 一.基本知识 二.压测实验 1. 实验条件及工具说明 2. 压测脚本 3. 实验数据结果 ffmpeg硬解码与软解码的压测 一.基本知识 本文基于intel ...

  6. ffmpeg+nvidia解码SDK+GPU实现视频流硬解码成Mat

    方法原理 rtsp流解码方式分为两种:硬解码和软解码.软解码一般通过ffmpeg编解码库实现,但是cpu占用率很高,解码一路1080p视频cpu占用率达到70%左右,对实际应用来说,严重影响机器最大解 ...

  7. 如何使用ffmpeg为Mac进行视频硬解码/硬编码(在Qt环境)

    如何使用ffmpeg为Mac进行视频硬解码/硬编码(在Qt环境) 科普 前期准备 安装ffmpeg 将ffmpeg的库文件添加到Qt项目的.pro文件中 在源文件用引入头文件 第一步:先查看本机支持哪 ...

  8. AI视频行为分析系统项目复盘——技术篇2:视频流GPU硬解码

    0 项目背景 见<AI视频行为分析系统项目复盘--技术篇1> https://blog.csdn.net/weixin_42118657/article/details/118105545 ...

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

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

最新文章

  1. java 生成并覆盖文件,基于mybatis-plus生成不被覆盖的文件并支持swagger注解
  2. OpenCL(CUDA5.0) + Visual Studio 2010 环境配置
  3. 作者:曾琛(1987-),女,就职于中国科学院计算技术研究所。
  4. python中格式化_python的format格式化
  5. Qt Creator 4.9 发布
  6. ENVI 5.3软件安装教程(附带安装包获取方式)
  7. 小米与乐视的竞争分析
  8. 关于Sentinel-2快速查询图幅号——使用MGRS_100kmSQ_ID_File_Geodatabase快速查询
  9. 企业合并_OA替换 | K2 BPM 为你解决企业“变革”大烦恼
  10. 四川职业技术学院linux,2019年四川交通职业技术学院单招中职(信息技术一类)专业技能测试大纲...
  11. 【3d建模】全网最全3dmax快捷键【附软件安装包和角色基础教程下载】
  12. 优派vx2480功能简评
  13. 阶段性小结(一)---R语言回归案例实战算法比较
  14. python判断两数互质_hide handkerchief(判断两数是否互质)
  15. android服务常驻后台,[问答] Android应用中,如何保证服务常驻内存?
  16. Matplotlib折线图线型设置
  17. The APK file does not exist on disk
  18. 北海·涠洲岛攻略(两日游)
  19. 通过yum获取rpm离线安装包
  20. 模拟演讲者视图_PPT演讲心慌慌?用演讲者视图一边看一边说

热门文章

  1. 一个错误日志记录工具类
  2. Zabbix-2.2.2 监控工具如何通过jmx监控 tomcat
  3. 在配置idea中发现没有war包的情况,提示fix:no artifacts configured
  4. Unity学习之自动寻径
  5. 怎样查询PMP成绩?
  6. Seate 1.4.2运行seata-server.bat数据库连接失败解决方案
  7. 全球及中国牙科蜡分离剂行业研究及十四五规划分析报告
  8. 【Kconfig】linux-Kconfig相关配置及分析指南
  9. 社群形态下的众筹观点
  10. OKR工作法:谷歌、领英等顶级公司的高绩效秘籍-克里斯蒂娜·沃特克