美文网首页
FFmpeg 使用MediaCodec

FFmpeg 使用MediaCodec

作者: Nothing_655f | 来源:发表于2022-04-18 15:52 被阅读0次

    FFmpeg 使用MediaCodec

    环境: ffmpeg branch-3.4 @ 2021-08-31

    FFmpeg 使用方法

    编译的时候需要修改

    include\libavutil\error.h

    static inline <unknown> av_make_error_string(char *errbuf, size_t errbuf_size, int errnum)
    

    将返回值类型改为 char *

    static inline char * av_make_error_string(char *errbuf, size_t errbuf_size, int errnum)
    

    ffmpeg中使用mediacodec的方法其实也就是常规的使用ffmpeg的方法,大致只是找查找codec的时候改成使用mediacodec插件

    修改为使用MediaCodec的关键代码为

        // 查找视频解码器
        AVCodec *vCodec = NULL;
        // 可以自动查找
        // avcodec_find_decoder(ic->streams[videoStream]->codecpar->codec_id);
        // 指定为MediaCodec
        switch (vS->codecpar->codec_id)
        {
            case AV_CODEC_ID_H264:
                vCodec = avcodec_find_decoder_by_name("h264_mediacodec");
                break;
            case AV_CODEC_ID_H265:
                vCodec = avcodec_find_decoder_by_name("h265_mediacodec");
                break;
            case AV_CODEC_ID_MPEG4:
                vCodec = avcodec_find_decoder_by_name("mpeg4_mediacodec");
                break;
            default:
                break;
        }
    

    FFmpeg 中的 MediaCodec流程

    以H264为例,在ffmpeg中可以看到其解码组件的实现为如下结构体

    FFmpeg/libavcodec/mediacodecdec.c

    #if CONFIG_H264_MEDIACODEC_DECODER
    AVCodec ff_h264_mediacodec_decoder = {
        .name           = "h264_mediacodec",
        .long_name      = NULL_IF_CONFIG_SMALL("H.264 Android MediaCodec decoder"),
        .type           = AVMEDIA_TYPE_VIDEO,
        .id             = AV_CODEC_ID_H264,
        .priv_data_size = sizeof(MediaCodecH264DecContext),
        .init           = mediacodec_decode_init,
        .decode         = mediacodec_decode_frame,
        .flush          = mediacodec_decode_flush,
        .close          = mediacodec_decode_close,
        .capabilities   = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AVOID_PROBING,
        .caps_internal  = FF_CODEC_CAP_SETS_PKT_DTS,
        .bsfs           = "h264_mp4toannexb",
    };
    #endif
    

    这里重点就是这几个方法

        .init           = mediacodec_decode_init,
        .decode         = mediacodec_decode_frame,
        .flush          = mediacodec_decode_flush,
        .close          = mediacodec_decode_close,
    

    1. 初始化 MediaCodec 解码器

    stativ av_cold int mediacodec_decode_init(AVCodecContext *avctx)
    {
        // MediaCodec H.264 解码相关属性
        MediaCodecH264DecContext *s = avctx->priv_data;
    
        // 调用 jni 创建 MediaFormat
         FFAMediaFormat *format = ff_AMediaFormat_new();   
    
        // 根据 codec_id 获取 mimeType 
        const char *codec_mime = "video/avc";
        // 获取 AVCodecContext 的 Extreadata 中的 sps & pps,存入H.264文件的头部 
        ret = h264_set_extradata(avctx, format);
    
        // 设置 MediaFormat 属性
        ff_AMediaFormat_setxxx(format, "xxx", xxx);
    
        ret = ff_mediacodec_dec_init(avctx, s->ctx)
    }
    

    2. 解码方法

    解码方法入口为 mediacodec_process_data,如下可以看到是读取数据并传入 mediacodec_process_data处理,而 mediacodec_process_data 进一步调用了 ff_mediacodec_dec_decode

    static int mediacodec_decode_frame(AVCodecContext *avctx, void *data, int *got_frame, AVPacket *avpkt)
    {
        // mediacodec 私有参数
         MediaCodecH264DecContext *s = avctx->priv_data;
        // 解码得到的数据
        AVFrame *frame    = data;
        int ret;   
    
        // 将要解码的 AVPacket 写入 fifo
        av_fifo_generic_write(s->fifo, &input_pkt, sizeof(input_pkt), NULL);
    
        while (!*got_frame) {
            /* prepare the input data */
            if (s->buffered_pkt.size <= 0) {
                // 从 fifo 读到 buffered_pkt
                av_fifo_generic_read(s->fifo, &s->buffered_pkt, sizeof(s->buffered_pkt), NULL);
            }
             // 解码
            ret = mediacodec_process_data(avctx, frame, got_frame, &s->buffered_pkt);
        }
    }
    

    3. mediacodec buffer 获取及处理

    在 ff_mediacodec_dec_decode 中的buffer 的获取同mediacodec api的使用一致,这些是通过jni 反射调用的java 的mediacodec 方法,MediaCodec API 方法使用 https://zhuanlan.zhihu.com/p/45224834

    int ff_mediacodec_dec_decode(AVCodecContext *avctx, MediaCodecDecContext *s,
                                 AVFrame *frame, int *got_frame,
                                 AVPacket *pkt)
    {
        while(...) {
            // 查找是否有可用的 输入缓冲区
            index = ff_AMediaCodec_dequeueInputBuffer(codec, input_dequeue_timeout_us);
            // 获取输入缓冲区
            data = ff_AMediaCodec_getInputBuffer(codec, index, &size);
            if (need_draining) {
                // 发送 结束信号
                status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, 0, pts, flags);
            } else {
                // 传送 要解码的数据
                status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, size, pts, 0);
            }
        }
    
        // 获取输出缓冲区
        index = ff_AMediaCodec_dequeueOutputBuffer(codec, &info, output_dequeue_timeout_us);
        if (index >= 0) {
            if (info.size) {
                if (s->surface) {
                    // 拷贝buffer index数据
                    ret = mediacodec_wrap_hw_buffer(avctx, s, index, &info, frame);
                } else {
                    // 获取解码后的数据
                    data = ff_AMediaCodec_getOutputBuffer(codec, index, &size);
                 // 转换格式
                    ret = mediacodec_wrap_sw_buffer(avctx, s, data, size, index, &info, frame);
                }
            } else {
                // 没有输出数据,释放缓冲区
                status = ff_AMediaCodec_releaseOutputBuffer(codec, index, 0);
        }
    }
    

    如上这个方法可以看到 在获取缓冲区这块是有差别的:

    在设置了Surface的前提下,是调用mediacodec_wrap_hw_buffer,在frame中填充的并不是图像数据,而只是buffer index的封装。(反之,如果没有设置Surface就可以拿到图像数据,只是效率较低)

    使用FFmpeg 的显示方法

    1、可以透过ANativeWindow的方法,可以参考如下两篇文档

    https://blog.csdn.net/Kennethdroid/article/details/107103315

    https://blog.csdn.net/ericbar/article/details/80416328

    这种方法有效率问题,内存大小和拷贝的问题,不过好处是可以拿到解码后的数据做需要的渲染

    2、通过MediaCodec Surface的方法

    在ffmpeg mediacodec.h中定义了几个函数,专门用于处理显示(因为mediacodec在surface模式下比较特殊)。

    int av_mediacodec_default_init(AVCodecContext *avctx, AVMediaCodecContext *ctx, void *surface)用于给用户设置一个surfaceAV_CODEC_HW_CONFIG_METHOD_AD_HOC体现在这)

    av_mediacodec_release_buffer(AVMediaCodecBuffer *buffer, int render)用于给用户“显示”一帧画面。(实际是调用MediaCodec的releaseOutputBuffer,render为1显示这个buffer,为0丢弃这个buffer不显示)

    小结

    1、ffmpeg的 mediacodec 支持的是有3.4分支,而4.x的分支并没有支持,所以支持性这块还是有差异

    2、mediacodec的调用通过反射和jni,新版本ndk支持natvice 调用方法 https://developer.android.google.cn/ndk/reference/group/media#amediacodec,可以优化一些性能问题,需要移植

    3、用来实现做播放器流程是需要做AV同步,用在转码的场景上应该是有性能的提升,可以获取硬解后的buffer 再对其编码,且ffmpeg 对container的支持性比较好

    相关文章

      网友评论

          本文标题:FFmpeg 使用MediaCodec

          本文链接:https://www.haomeiwen.com/subject/xqpdertx.html