FFmpeg之硬解码

news2024/11/16 1:25:20

导读

    众所周知,软解码虽然兼容性一流,但是却非常依赖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;
    // 初始化JNIEnv
    if (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")查找解码器, 如果返回空,一般就是不支持硬解码了。

【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~

  

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
            // 写入y
            for(int j=0; j<avFrame->height; j++)
                fwrite(avFrame->data[0] + j * avFrame->linesize[0], 1, avFrame->width, yuv_file);
            // 写入uv
            for(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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/71126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能化煤矿-设备管理系统、故障诊断、全生命周期管理

随着智能化煤矿的建设&#xff0c;煤矿设备、传感器数量在增加、煤矿设备的自动化、智能化程度也相对提高。保证设备稳定运行&#xff0c;减少故障时间是提高煤矿安全生产的一种重要途径。另外随着信息技术的发展&#xff0c;像云计算、物联网、大数据等相关技术的运用&#xf…

vue2的 webpack-bundle-analyzer 打包体积 看优化包

1、 先安装 npm i webpack-bundle-analyzer -D 2、 vue.config 文件中 配置 /* webpack相关配置 *该对象将会被 webpack-merge 合并入最终的 webpack 配置 */ if (process.env.use_analyzer) { // 分析 config .plugin(webpack-bundle-analyzer) .use(require(webpa…

XuperSocial首个明星DApp上线,探索区块链分布式通信基础设施

12月7日&#xff0c;星际口袋上线星际社区&#xff0c;为广大藏友提供稳定、可信的交流场所。星际社区基于百度超级链团队重磅发布的去平台化社交解决方案XuperSocial搭建&#xff0c;XuperSocial是架设在百度超级链开放网络&#xff08;XuperOS&#xff09;上的DApp&#xff0…

Oracle PrimaveraUnifier 之数据要素(Data Element)

目录 一&#xff1a;什么是数据要素 二&#xff1a;常用的数据要素 一&#xff1a;什么是数据要素 数据要素是Oracle Primavera Unifier维护业务单据/表达的最小单元&#xff0c;也就是我们常说的字段&#xff0c;它将数据定义与字段标签相结合&#xff0c;成为我们用户在 P…

Runtime源码解析-类中bits

Runtime源码解析-类中bits class_rw_t ro_or_rw_ext_t 成员变量方法 初始化方法存取方法类型判断 公有方法 获取class_rw_ext_t获取/设置class_ro_t方法、属性、协议列表 class_rw_ext_tclass_ro_t总结 1. 为什么ro_or_rw_ext 会有两种类型&#xff0c;class_rw_ext_t或者cla…

Sping Boot 如何实现自动配置

Sping Boot 如何实现自动配置 Spring Boot都需要创建一个mian启动类&#xff0c;而启动类都含有SpringBootApplication注解&#xff0c;从启动类&#xff0c;一步步探索源码。 SpringBootApplication注解 Spring Boot 启动类上都有一个 SpringBootApplication注解&#xff1…

想知道CAD怎么转换为PDF吗?快来收藏这些实用的转换技巧

有一些读建筑类或者电子信息类专业的小伙伴&#xff0c;经常需要使用到CAD软件来设计和修改图纸&#xff0c;并且保存下来的文件一般默认是CAD文件格式。但有的时候&#xff0c;我们将图纸发送给其他人&#xff0c;对方的设备中没有相应的软件&#xff0c;导致无法查看&#xf…

Linux 进程间通信:匿名管道 命名管道 共享内存

进程间通信的必要性 进程间通信&#xff0c;是建立在多进程之上的。如果是单进程&#xff0c;则无法使用并发能力&#xff0c;更加无法进行多进程协同。多进程要想实现多进程协同&#xff08;目的&#xff09;&#xff0c;就必须进行进程间通信&#xff08;手段&#xff09;。…

知识图谱-KGE-语义匹配-双线性模型-2018:SimplE

【paper】 SimplE Embedding for Link Prediction in Knowledge Graphs【简介】 本文是加拿大英属哥伦比亚大学的两位学者发表在 NIPS 2018 上的工作&#xff0c;文章提出了 SimplE&#xff08;Simple Embedding&#xff09;。这篇和前面一篇差不多&#xff0c;也是对 1927 年的…

深度学习 +SLAM:SuperGlue

简介 传统SLAM的流程通常包括如下内容&#xff0c; 特征点提取描述&#xff0c;特征点匹配 异常点去除&#xff0c; 位姿估计。 在以往前人的工作中&#xff0c;SuperPoint和 D2-Net试图解决特征点检测和描述的问题。而检测之后的匹配通常通过最近邻匹配和异常点剔除的方式完…

使用 Arduino 中断 – 硬件、引脚变化和定时器

使用 Arduino 中断 – 硬件、引脚变化和定时器 查看原文 今天我们将学习中断&#xff0c;这是Arduino和其他微控制器的一个非常重要的基本功能。虽然我们将专注于Arduino Uno&#xff0c;但这里介绍的概念与其他板同样有效。 介绍 当我们设计一个项目时&#xff0c;我们通常…

Release notes for VPP 22.10

本次发布新增了212个提交&#xff0c;包括118个修复。关于本次发布的更多信息&#xff0c;请访问&#xff1a;https://gerrit.fd.io/r/gitweb?pvpp.git;ablob;fdocs/aboutvpp/releasenotes/v22.10.rst;h5dfbff5d48e957e83d7e3c2f978820c95c41a2e4;hb07e0c05e698cf5ffd1e2d2de0…

「图文教程」iOS 16测试版如何升级iOS 16正式版?

苹果iOS 16正式版已经更新到iOS 16.1.2了&#xff0c;如果你的iPhone之前为了尝鲜已经下载安装iOS 16测试版&#xff0c;该如何升级iOS 16正式版呢&#xff1f;一起来了解下吧&#xff01; 方法一、移除iOS 16 Beta描述文件 1、进入【设置】-【通用】-【VPN与设备管理】&…

python+django汽车站售票票务管理系统

1.用户需要进行注册才可以登录本系统。 2.用户登录系统后可以在通知公告中获取最新的通知或者搜索需要的通知&#xff1b;可以在车票信息中查询到所需的车票信息并且可以通过站点、票价等选项进行筛选&#xff0c;选定车票后可以直接购票。用户还可以在订单管理中进行退票操作&…

引爆全球的ChatGPT,Java、面试、刷题、双色球它都会?

大家好&#xff0c;我是二哥呀。 这两天&#xff0c;ChatGPT 引爆全球&#xff0c;不管是搞技术的&#xff0c;还是没搞技术的&#xff0c;都在玩&#xff0c;玩的不亦乐乎&#xff0c;仿佛找到了内心真正的伴侣&#xff08;&#x1f602;&#xff09;。 一开始我以为这玩意不…

图像数据的特征工程

一提到特征工程&#xff0c;我们立即想到是表格数据。但是我们也可以得到图像数据的特征&#xff0c;提取图像中最重要的方面。这样做可以更容易地找到数据和目标变量之间的映射。 这样可以使用更少的数据和训练更小的模型。更小的模型可以减少预测所需的时间。这在部署到边缘设…

【 第八章 SQL执行效率,慢日志查询,profile,explain,最左前缀法则,范围查询】

第八章 SQL执行效率&#xff0c;慢日志查询&#xff0c;profile&#xff0c;explain&#xff0c;最左前缀法则&#xff0c;范围查询 1.SQL执行效率&#xff1a; MySQL 客户端连接成功后&#xff0c;通过 show [session|global] status 命令可以提供服务器状态信息。通过如下指…

微服务框架 SpringCloud微服务架构 26 数据聚合 26.1 聚合的分类

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构26 数据聚合26.1 聚合的分类26.1.1 聚合的分类26.1.2 总结26 数据聚合 26…

基础入门 - SpringBoot 自动配置

目录 3、自动配置原理入门 3.1、引导加载自动配置类 1、SpringBootConfiguration 2、ComponentScan 3、EnableAutoConfiguration 1、AutoConfigurationPackage 2、Import(AutoConfigurationImportSelector.class) 3.2、按需开启自动配置项 3.3、修改默认配置 1、自动配…

写作人的福音——obsidian非官方插件之senGener

背景 之前的是转发作者的项目文档&#xff0c;这次应作者要求&#xff0c;写了个试用报告。 刚用这个插件的时候&#xff0c;还磕磕碰碰&#xff0c;总有点小问题&#xff0c;按快捷键不反应&#xff0c;服务器崩溃什么的&#xff0c;并不指望真的用起来&#xff0c;权当个玩具…