Android FFmpeg系列04--FFmpeg调用MediaCodec进行硬解码

1、引言

在上篇文章中我们通过FFmpeg软解并渲染了本地的一个mp4视频

Android FFmpeg系列03--视频解码与渲染

本文基于之前的Demo添加了FFmpeg使用MediaCodec来硬解码的方式,包括解码出buffer再利用OpenGL进行渲染上屏和直接解码到Surface然后上屏两种方式

FFmpeg使用MediaCodec可以在解封装后拿到AVPacket再利用jni将buffer回调到java层,然后在java层调用MediaCodec;也可以直接在native层利用AMediaCodec

用于测试的mp4采用H264编码

Android FFmpeg系列04--FFmpeg调用MediaCodec进行硬解码_第1张图片

所以使用上述两种调用MediaCodec方式的时候需要先通过

“h264_mp4toannexb” filter

将AVPacket进行转换一次,相关背景可以参考H264码流之AnnexB和AVCC

不过在本系列教程中使用的FFmepg5.0.1版本,bitstream filter的相关接口都已经被移除

所以接下来采用FFmpeg在3.1之后提供的直接调用MediaCodec的C接口来实现硬解码

Android FFmpeg系列04--FFmpeg调用MediaCodec进行硬解码_第2张图片

(HWAccelIntro – FFmpeg)

可以看到目前还只支持解码而不支持编码

2、编译

在之前的编译脚本中打开如下三个配置即可(详情参考Android FFmpeg系列01--编译与集成)

--enable-jni \
--enable-mediacodec \
--enable-decoder=h264_mediacodec \

不需要再配置h264_mediacodec的硬件加速(list中已经查找不到了)

--enable-hwaccel=h264_mediacodec

Android FFmpeg系列04--FFmpeg调用MediaCodec进行硬解码_第3张图片

解码出Buffer

解码流程和软解类似,不再赘述,重点描述一下流程不一样的地方

2.1 将JVM实例设置给FFmpeg

// libavcodec/jni.h
// int av_jni_set_java_vm(void *vm, void *log_ctx);
// 方式一,在so加载的JNI_OnLoad方法中调用
// 方式二,在用到ffmpeg的模块调用即可,该方法可以多次调用,只要jvm实例相同即可
JavaVM *javaVm = nullptr;
env->GetJavaVM(&javaVm);
if (javaVm != nullptr) {
    av_jni_set_java_vm(javaVm, nullptr);
}

2.2 通过"h264_mediacodec"查找解码器

由于h264_mediacodec解码器和h264解码器id相同,所以

// 软解时
avcodec_find_decoder(id);
// 使用mediacodec硬解时
avcodec_find_decoder_by_name(“h264_mediacodec”);

之后的步骤和软解步骤完全相同,不需要做任何更改

本地测试采用软解出来的AVFrame格式是AV_PIX_FMT_YUV420P

使用h264_mediacodec硬解出来的AVFrame格式是AV_PIX_FMT_NV12

【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发

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

Android FFmpeg系列04--FFmpeg调用MediaCodec进行硬解码_第4张图片

Android FFmpeg系列04--FFmpeg调用MediaCodec进行硬解码_第5张图片

2.3 解码到Surface

解码流程和软解的流程也是类似的,这里重点描述下差异的地方

也可以参考ffmpeg5.0.1/doc/examples/hw_decode.c

要硬解到Surface上,重点是配置get_formathw_device_ctx

Android FFmpeg系列04--FFmpeg调用MediaCodec进行硬解码_第6张图片

查找type

AVHWDeviceType type = av_hwdevice_find_type_by_name("mediacodec");

2.4 查找hw_pix_fmt,能找到就是支持mediacodec解码

for (int i = 0; ; ++i) {
    const AVCodecHWConfig *config = avcodec_get_hw_config(h264Mediacodec, i);
    if (!config) {
        LOGE("Decoder: %s does not support device type: %s", h264Mediacodec->name,av_hwdevice_get_type_name(type))
        break;
    }
    if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && config->device_type == type) {
        // AV_PIX_FMT_MEDIACODEC(165)
        hw_pix_fmt = config->pix_fmt;
        LOGE("Decoder: %s support device type: %s, hw_pix_fmt: %d, AV_PIX_FMT_MEDIACODEC: %d", h264Mediacodec->name,
                         av_hwdevice_get_type_name(type), hw_pix_fmt, AV_PIX_FMT_MEDIACODEC);
        break;
    }
}

2.5 get_hw_format方法

static enum AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE;
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) {
            LOGI("get HW surface format: %d", *p);
            return *p;
        }
    }
​
    LOGE("Failed to get HW surface format");
    return AV_PIX_FMT_NONE;
}

2.6 解码器上下文设置get_format

mVideoCodecContext->get_format = get_hw_format;

2.7 配置hw_device_ctx

av_hwdevice_ctx_create(&mHwDeviceCtx, type, nullptr, nullptr, 0);
mVideoCodecContext->hw_device_ctx = av_buffer_ref(mHwDeviceCtx);

2.8 要解码到Surface上,还需要配置AVMediaCodecContext

if (surface != nullptr) {
    mMediaCodecContext = av_mediacodec_alloc_context();
    av_mediacodec_default_init(mVideoCodecContext, mMediaCodecContext, surface);
}

从源码中可以看到将生成的mediac codec ctx设置给解码器ctx的hwaccel_context

Android FFmpeg系列04--FFmpeg调用MediaCodec进行硬解码_第7张图片

上述步骤都是在打开解码器之前完成,剩下的步骤和软解一致了,只是解码出来的AVFrame的fmt为上面我们查找到的hw_pix_fmt,也就是AV_PIX_FMT_MEDIACODE

3、渲染到Surface

Android FFmpeg系列04--FFmpeg调用MediaCodec进行硬解码_第8张图片

([FFmpeg-devel] [PATCH] lavc/mediacodec: add hwaccel support)

如果需要从hw surface上拿到buffer,可以通过av_hwframe_transfer_data进行转换

Android FFmpeg系列04--FFmpeg调用MediaCodec进行硬解码_第9张图片

(Demo还是上篇文章的地址,切换到dev分支即可~)

4、参考

1.【ffmpeg mediacodec硬解初探】

ffmpeg mediacodec 硬解初探_露蛇的博客-程序员信息网 - 程序员信息网

2.【FFmpeg 调用 MediaCodec 硬解码到 Surface 上】

FFmpeg 调用 MediaCodec 硬解码到 Surface 上 - 腾讯云开发者社区-腾讯云

原文链接:Android FFmpeg系列04--FFmpeg调用MediaCodec进行硬解码

你可能感兴趣的:(程序员,音视频开发,编程,ffmpeg,android,java,webrtc,c++)