FFmpeg调用MediaCodec解码

在前面的博文中我们介绍了关于使用NDK编译FFMpeg6.0的一些坑以及相关的解决方法。

详情请参考:NDK编译ffmpeg6.0与x264的坑

在写《NDK编译ffmpeg6.0与x264的坑》一文的时候就说过了,我们编译FFmpeg6.0的目的就是为了体验一下它NDK式的MediaCodec硬解码以及硬编码。

今天我们就在android上使用FFmpeg6.0来体验一下它的硬解码,通过FFmpeg调用MediaCodec将视频数据解码为yuv数据并保存。

关于FFmpeg在android上硬解码的相关博文之前已经写过一篇博文:
ffmpeg之硬解码

只是之前的需要通过注册JNI的方式调用MediaCodec,但这在FFmpeg6.0之后不需要了。

在这里顺便提一下一个关于学习ffmpeg的方法,众所周知,其实最好的学习资料就是官方的资料,没有比官方更权威的资料了。 一般在ffmpeg的源码目录doc/examples下就有很多例子,例如我们想学习下ffmpeg硬解码的例子,就可以研究该目录下的hw_decode.c这个例子。
 

FFmpeg6.0使用MediaCodec硬解码

下面说说使用FFmpeg调用MediaCodec进行硬解码的介个步骤:

  1. 打开编译选项

首先,要让FFmpeg支持MediaCodec硬解码,在交叉编译时就要打开相关配置,主要是enable一些与MediaCodec相关的属性:
 

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

【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击788280672加群免费领取~

  1. 找到对应的解码器

一般情况下如果我们不关注软解码还是硬解码的话通过解码器ID使用avcodec_find_decoder函数获取到对应的解码器即可。 但是如果我们想要使用硬解码,一般会使用函数avcodec_find_decoder_by_name获取到对应的解码。

那么问题来了,在FFmepg中MediaCodec对应的硬解码器是啥呢?我怎么知道avcodec_find_decoder_by_name应该传递的参数是什么呢?

我们做NDK开发一定要学会妙用源码中configure这个文件,通过这个文件可以获取到很多我们想要的配置信息,最简单的,如果我们不知道有哪些可配置的编译信息, 则可以使用./configure --help进行查看。

同理,在FFmpeg的源码中,我们可以通过命令行./configure --list-decoders查看它所支持的解码器,如图还是很多的,但是也并不是说都能直接使用的,因为大多数都是第三方的库, 一般需要在编译时打开进行链接编译后才能正常使用。
 

./configure --list-decoders输出太多了,我们只关心MediaCodec相关的,我们可以使用grep过滤一下:

./configure  --list-decoders |grep mediacodec

输出如图,框起来的哪些就是可以作为函数avcodec_find_decoder_by_name参数的值,进行MediaCodec硬解码。

  1. 配置硬解码器

要使用硬解码,你还得告诉解码器,你想要输出什么样的格式数据,这个就是配置硬解码器所要干的事情, 也就是说为了告诉解码器你想要获得的最终的YUV数据格式是什么?是NV12还是NV21还是其他?

在MediaCodec中硬解码的主要配置如下:

 // 配置硬解码器
                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;
                    }
                }
  1. 初始化初始化mediacodec的buffer

我们知道MediaCodec是基于队列的方式进行工作的,因此我们还需要

// 硬件解码器初始化
    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);

后续的其他步骤就和软解码一样了,无非就是打开解码器、读取视频包、将视频包送入解码器进行解码、从解码器中循环读取解码后的数据包等。 这些在之前的FFmpeg系列文章中已经介绍过很多了,这里就不再累赘了。

通过这么一个demo可以看出,万变不离其宗,FFMpeg6.0的硬解码对比以前的貌似只是省了一个av_jni_set_java_vm步骤而已,但是其内部是绕过了JNI调用MediaCodec的, 至于性能有了多少提升呢?感兴趣的同学们可以自行测试下。

下面是完整的代码:
 

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/codec.h>
#include <libavutil/avutil.h>
#include <libavutil/pixdesc.h>
}


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;
    }
    LOGD_E("FFDecoder","Failed to get HW surface format.\n");
    return AV_PIX_FMT_NONE;
}

void FFDecoder::decodeVideo(const char *videoPath, const char *yuvPath) {
    AVFormatContext *avFormatContext = avformat_alloc_context();
    int ret = avformat_open_input(&avFormatContext, videoPath, nullptr, nullptr);
    if (ret < 0) {
        LOGD_E("FFDecoder","打开媒体文件失败");
        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) {
        LOGD_E("FFDecoder","找不到视频索引");
        return;
    }
    LOGD_E("FFDecoder","找到视频索引:%d", video_index);

    const AVCodec *avCodec = nullptr;
    AVCodecContext *avCodecContext = nullptr;
    AVPacket *avPacket = nullptr;
    AVFrame *avFrame = nullptr;
    FILE *yuv_file = 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) {
                LOGD_E("FFDecoder","没有找到硬解码器h264_mediacodec");
                return;
            } else {
                // 配置硬解码器
                int i;
                for (i = 0;; i++) {
                    const AVCodecHWConfig *config = avcodec_get_hw_config(avCodec, i);
                    if (nullptr == config) {
                        LOGD_E("FFDecoder","获取硬解码是配置失败");
                        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;
                        LOGD_E("FFDecoder","硬件解码器配置成功");
                        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) {
        LOGD_E("FFDecoder","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) {
        LOGD_E("FFDecoder","解码器打开失败:%s",av_err2str(ret));
        return;
    } else {
        LOGD_E("FFDecoder","解码器打开成功");
    }

    avPacket = av_packet_alloc();
    avFrame = av_frame_alloc();
    yuv_file = fopen(yuvPath,"wb");
    while (true) {
        ret = av_read_frame(avFormatContext, avPacket);
        if (ret != 0) {
            LOGD_D("FFDecoder","av_read_frame end");
            // todo可能解码器内还有缓存的数据,需要avcodec_send_packet空包进行冲刷
            break;
        }
        if(avPacket->stream_index != video_index){
            av_packet_unref(avPacket);
            continue;
        }
        ret = avcodec_send_packet(avCodecContext,avPacket);
        if(ret == AVERROR(EAGAIN)){
            LOGD_E("FFDecoder","avcodec_send_packet EAGAIN");
        } else if(ret < 0){
            LOGD_E("FFDecoder","avcodec_send_packet fail:%s",av_err2str(ret));
            return;
        }
        av_packet_unref(avPacket);
        ret = avcodec_receive_frame(avCodecContext,avFrame);
        LOGD_E("FFDecoder","avcodec_receive_frame:%d",ret);
        while (ret == 0){
            LOGD_D("FFDecoder","获取解码数据成功:%s",av_get_pix_fmt_name(static_cast<AVPixelFormat>(avFrame->format)));
            LOGD_D("FFDecoder","linesize0:%d,linesize1:%d,linesize2:%d",avFrame->linesize[0],avFrame->linesize[1],avFrame->linesize[2]);
            LOGD_D("FFDecoder","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);
        }
    }
    // 资源释放
    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;
    }
}

对于解码出来的YUV数据释放正常,我们可以用adb将yuv文件数据从手机中拉出来到电脑端,使用ffplay命令播放一下验证即可。 正如代码所注释的,假设我们解码得到的数据是NV12的,那么ffplay的播放命令就是:

ffplay -i yuv文件路径 -pixel_format nv12 -framerate 25 -video_size yuv宽x高
//如果解码出来的数据是nv12
//ffplay -i d:/cap.yuv -pixel_format nv12 -framerate 25 -video_size 640x480

原文 链接 FFmpeg调用MediaCodec解码 - 掘金

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

密码学:一文看懂初等数据加密一对称加密算法

文章目录 对称加密算法简述对称加密算法的由来对称加密算法的家谱数据加密标准-DES简述DES算法的消息传递模型DES算法的消息传递过程和Base64算法的消息传递模型的区别 算法的实现三重DES-DESede三重DES-DESede实现 高级数据加密标准一AES实现 国际数据加密标准-IDEA实现 基于口…

启发式算法解决TSP、0/1背包和电路板问题

1. Las Vegas 题目 设计一个 Las Vegas 随机算法&#xff0c;求解电路板布线问题。将该算法与分支限界算法结合&#xff0c;观察求解效率。 代码 python代码如下&#xff1a; # -*- coding: utf-8 -*- """ Date : 2024/1/4 Time : 16:21 Author : …

Java爬虫获取省市区镇村5级行政区划

公司有个项目需要五级行政区划,没有现成的数据,写了一段代码,从gj统计j获取的数据。记录一下。 1.引入maven解析html <!-- jsoup --> <dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.11.3&…

微信小程序自动化测试实战,支持录制回放、智能遍历

​为了满足小程序性能、功能等方面的测试需求&#xff0c;微信团队上线 小程序云测服务&#xff0c;提供丰富的自动化测试能力。其中 智能化 Monkey 服务 凭借着零代码、低成本的优势吸引不少开发者使用。 在服务使用过程中&#xff0c;我们发现开发者有更多的进阶需求&#x…

OAI openair3代码结构整理

openair3代码框架结构 OAI&#xff08;OpenAirInterface&#xff09;是一个开源的5G网络软件平台&#xff0c;用于研究和开发5G网络技术。OpenAir3是OAI项目中的一个子项目&#xff0c;专注于5G核心网络的功能实现。 一、OpenAir3的代码主要包括以下几个部分&#xff1a; NAS…

Proxmox VE 8 安装开源监控平台Centreon 23

作者&#xff1a;田逸&#xff08;formyz&#xff09; 非常好用的开源监控系统Centreon从版本号21.40以后&#xff08;包括Centreon 21.40这个版本&#xff09;&#xff0c;不在提供ISO一键式安装包&#xff0c;取而代之的是在线脚本安装和VMware虚拟机或者Oracle VirtualBox 虚…

初识MySQL

一、什么是数据库 数据库&#xff08;Database&#xff0c;简称DB&#xff09;&#xff1a;长期存放在计算机内&#xff0c;有组织、可共享的大量数据的集合&#xff0c;是一个数据“仓库”。 数据库的作用&#xff1a; 可以结构化存储大量的数据&#xff0c;方便检索和访问…

2024最新Java基础面试题大全(一)

1、String可以被继承&#xff1f; 不能被继承&#xff0c;因为String类有final修饰符&#xff0c;而final修饰的类是不能被继承的。 public final class String implements java.io.Serializable, Comparable<String>, CharSequence {// 省略...  }2、常见集合类 Java…

ChatGPT到底能做什么呢?

1、熟练掌握ChatGPT提示词技巧及各种应用方法&#xff0c;并成为工作中的助手。 2、通过案例掌握ChatGPT撰写、修改论文及工作报告&#xff0c;提供写作能力及优化工作 3、熟练掌握ChatGPT融合相关插件的应用&#xff0c;完成数据分析、编程以及深度学习等相关科研项目。 4、…

深入理解Vue3中的自定义指令

Vue3是一个流行的前端框架&#xff0c;它引入了许多新特性和改进&#xff0c;其中之一是自定义指令。自定义指令是一种强大的功能&#xff0c;可以让开发者在模板中直接操作 DOM 元素。本文将深入探讨 Vue3中的自定义指令&#xff0c;包括自定义指令的基本用法、生命周期钩子函…

【fiddler】fiddler抓包工具的使用

前言&#xff1a;我们可以通过fiddler软件&#xff0c;捕获到http请求&#xff0c;并修改请求参数 修改返回内容 fiddler下载,官网如下图 启动fiddler软件,点击file 选择 Capture Traffic 修改入参 (我们以谷歌浏览器发起请求为例) 此时会出现一个向上的箭头&#xff0c;点击…

MediaPipeUnityPlugin Win10环境搭建(22年3月的记录,新版本已完全不同,这里只做记录)

https://github.com/homuler/MediaPipeUnityPlugin You cannot build libraries for Android with the following steps. 1、安装msys2配置系统环境变量Path添加 C:\msys64\usr\bin 执行 pacman -Su 执行 pacman -S git patch unzip 2、安装Python3.9.10 勾选系统环境变量 …

stm32学习总结:6、Proteus8+STM32CubeMX+MDK仿真蜂鸣器及ADC读取电压(Proteus标签整理原理图)

stm32学习总结&#xff1a;6、Proteus8STM32CubeMXMDK仿真蜂鸣器及ADC读取电压&#xff08;Proteus标签整理原理图&#xff09; 文章目录 stm32学习总结&#xff1a;6、Proteus8STM32CubeMXMDK仿真蜂鸣器及ADC读取电压&#xff08;Proteus标签整理原理图&#xff09;一、前言二…

智能革命:揭秘AI如何重塑创新与效率的未来

1.AI技术的发展与应用 1.1 AI技术的发展 人工智能&#xff08;AI&#xff09;的概念最早可以追溯到20世纪40年代和50年代&#xff0c;当时的计算机科学家开始探索如何创建能模仿人类智能的机器。最初的AI研究集中在问题解决和符号逻辑上&#xff0c;但随着时间的推移&#xf…

若依前后端分离版关联字典值查询数据工具类使用

场景 若依管理系统导出Excel时添加没有的列和关联码表显示中文进行导出&#xff1a; 若依管理系统导出Excel时添加没有的列和关联码表显示中文进行导出_若依的导出添加额外的字段信息-CSDN博客 上面通过关联表的方式实现查询字典值&#xff0c;若依本身提供了查询redis中缓存…

透明OLED屏的稳定性:从技术角度及应用案例解析

在显示技术日新月异的今天&#xff0c;透明OLED屏以其独特的透明特性和出色的显示效果&#xff0c;吸引了众多关注。然而&#xff0c;对于这种新型技术的稳定性&#xff0c;人们难免会有所疑虑。作为一名专注于OLED技术研发的工程师&#xff0c;尼伽小编将从专业角度出发&#…

阿里云大模型「让照片跳舞」刷屏朋友圈,有哪些信息值得关注?

介绍 大家好&#xff0c;我分享聊聊阿里通义千问APP中全民舞王功能。 网络热舞结合AI视频&#xff0c;这是以后不用学习跳舞&#xff1f; 可以尝试下效果&#xff0c;一张图片生成视频。 APP快速使用 搜索下载通义千问APP 打开APP&#xff0c;选中一张照片来跳舞。 这里…

css单位介绍

当我们在编写网页或应用程序时&#xff0c;选择合适的单位来描述元素的尺寸是非常重要的。在CSS中&#xff0c;我们常常会使用像素(px)、相对像素(rpx)、字号单位(em)、根元素字号单位(rem)、百分比(%)和视口百分比(vh、vw)等单位来描述元素的大小。 像素(px)是最常见的单位&a…

Unity中Shader序列帧动画(U、V方向的走格)

文章目录 前言一、U方向的走格1、 要实现移动的效果&#xff0c;我们就会想到使用_Time2、使用floor向下取整3、把x、y缩小为原函数的 Column倍4、使用_Sequence的z控制帧动画U方向上的速度 二、U方向的走格三、最终效果1、亚丝娜2、小蓝帽3、火4、最终代码 前言 在上一篇文章…

没有一家车企能绕开「数据闭环」

作者 |张祥威 编辑 |德新 2023年&#xff0c;在比亚迪那次公布智驾数据规模后&#xff0c;智能化下半场的战斗就正式打响了。 如今&#xff0c;自动驾驶正在沿着特斯拉提出的「BEVTransformer」急速推进&#xff0c;这条技术路线短短几年就得到了验证&#xff0c;随着智驾起较…