FFmpeg之硬解码
导读
前面我们已经使用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_vm
JavaJVM参数即可。
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之硬解码相关推荐
- FFmpeg 硬件加速(硬解码)介绍
参考地址 概述 本文主要针对ffmpeg支持的硬解码做一个总结阐述. 许多平台提供对专用硬件的访问,以执行一系列与视频相关的任务.使用此类硬件可以更快地完成某些操作,例如解码.编码或过滤,或者 ...
- 树莓派编译ffmpeg支持x264硬解码播放视频
树莓派编译ffmpeg支持h264_mmal硬解码 1,x264源码编译 1.1下载x264源码,解压 1.2添加一个脚本文件config_x264_rpi.sh ,放入1.1解压之后的文件夹中 1. ...
- ffmpeg + cuda(cuvid) 硬解码+像素格式转换(cpu主导)实战
注意: VAAPI 是inter gpu 提供的硬编解码接口 VDPAU 是 video decode present api for unix nvdec / ncvid 都是nivida产出的硬解 ...
- FFmpeg的软、硬解码方式梳理
背景 项目中使用QT开发监控软件,集成海康.宇视.大华.华迈.以及网络流设备.品牌设备使用SDK控制,网络流设备自己使用FFmpeg库来解决.网络流设备如果同时解码多路播放,会出现CPU占用较高.操作 ...
- ffmpeg硬解码与软解码的压测对比
文章目录 ffmpeg硬解码与软解码的压测 一.基本知识 二.压测实验 1. 实验条件及工具说明 2. 压测脚本 3. 实验数据结果 ffmpeg硬解码与软解码的压测 一.基本知识 本文基于intel ...
- ffmpeg+nvidia解码SDK+GPU实现视频流硬解码成Mat
方法原理 rtsp流解码方式分为两种:硬解码和软解码.软解码一般通过ffmpeg编解码库实现,但是cpu占用率很高,解码一路1080p视频cpu占用率达到70%左右,对实际应用来说,严重影响机器最大解 ...
- 如何使用ffmpeg为Mac进行视频硬解码/硬编码(在Qt环境)
如何使用ffmpeg为Mac进行视频硬解码/硬编码(在Qt环境) 科普 前期准备 安装ffmpeg 将ffmpeg的库文件添加到Qt项目的.pro文件中 在源文件用引入头文件 第一步:先查看本机支持哪 ...
- AI视频行为分析系统项目复盘——技术篇2:视频流GPU硬解码
0 项目背景 见<AI视频行为分析系统项目复盘--技术篇1> https://blog.csdn.net/weixin_42118657/article/details/118105545 ...
- MediaCodec在Android视频硬解码组件的应用
https://yq.aliyun.com/articles/632892 云栖社区> 博客列表> 正文 MediaCodec在Android视频硬解码组件的应用 cheenc 2018- ...
最新文章
- java 生成并覆盖文件,基于mybatis-plus生成不被覆盖的文件并支持swagger注解
- OpenCL(CUDA5.0) + Visual Studio 2010 环境配置
- 作者:曾琛(1987-),女,就职于中国科学院计算技术研究所。
- python中格式化_python的format格式化
- Qt Creator 4.9 发布
- ENVI 5.3软件安装教程(附带安装包获取方式)
- 小米与乐视的竞争分析
- 关于Sentinel-2快速查询图幅号——使用MGRS_100kmSQ_ID_File_Geodatabase快速查询
- 企业合并_OA替换 | K2 BPM 为你解决企业“变革”大烦恼
- 四川职业技术学院linux,2019年四川交通职业技术学院单招中职(信息技术一类)专业技能测试大纲...
- 【3d建模】全网最全3dmax快捷键【附软件安装包和角色基础教程下载】
- 优派vx2480功能简评
- 阶段性小结(一)---R语言回归案例实战算法比较
- python判断两数互质_hide handkerchief(判断两数是否互质)
- android服务常驻后台,[问答] Android应用中,如何保证服务常驻内存?
- Matplotlib折线图线型设置
- The APK file does not exist on disk
- 北海·涠洲岛攻略(两日游)
- 通过yum获取rpm离线安装包
- 模拟演讲者视图_PPT演讲心慌慌?用演讲者视图一边看一边说
热门文章
- 一个错误日志记录工具类
- Zabbix-2.2.2 监控工具如何通过jmx监控 tomcat
- 在配置idea中发现没有war包的情况,提示fix:no artifacts configured
- Unity学习之自动寻径
- 怎样查询PMP成绩?
- Seate 1.4.2运行seata-server.bat数据库连接失败解决方案
- 全球及中国牙科蜡分离剂行业研究及十四五规划分析报告
- 【Kconfig】linux-Kconfig相关配置及分析指南
- 社群形态下的众筹观点
- OKR工作法:谷歌、领英等顶级公司的高绩效秘籍-克里斯蒂娜·沃特克