FFmpeg 中的 Android MediaCodec

作者: GeorgeMR | 来源:发表于2018-12-07 20:55 被阅读21次

    MediaCodec 类可以用来访问底层媒体编解码器,即编码器/解码器的组件。它是 Android 底层多媒体支持架构的一部分。

    mediacodec.png

    一个编解码器处理输入数据以生成输出数据。它异步地处理数据,并使用一组输入和输出缓冲器。

    调用的时候需要先初始化 MediaCodec 作为视频的编码器,然后只需要不停传入原始的 YUV 数据进入编码器就可以直接输出编码好的 h264 流。解码器则对应相反。

    数据类型(Data Types)

    编解码器对 3 种数据进行操作:压缩后的数据,原始音频数据和原始视频数据。可以使用 ByteBuffers 处理所有三种数据,但对原始视频数据,可以使用 Surface 提高编解码的性能。Surface 使用本地视频缓冲区而不是映射或复制到 ByteBuffers,因此,效率更高。(通常在使用 Surface 时无法访问原始视频数据,但可以使用 ImageReader 类访问不安全的解码(原始)视频帧)。

    压缩缓冲区(Compressed Buffers)

    输入缓冲器和输出缓冲器根据格式的类型来存放已压缩的数据。对于视频类型,这是一个单一的压缩后的视频帧。对于音频数据,通常是单个访问单元(一个编码后的音频片段通常包含几毫秒音频)。

    FFmpeg 中的 MediaCodec

    FFmpeg 中仅支持 Android MediaCodec 解码,本文主要介绍 FFmpeg 中关于 MediaCodec 解码流程。后面会介绍如何在 FFmpeg 中添加 MediaCodec 编码。

    解码流程:

    1. mediacodec_deocde_init (初始化 MediaCodec 解码器)
    FFmpeg 中 MediaCodec 解码器初始化.png
    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)
    }
    
    1. mediacodec_decode_frame (解码方法)

      FFmpeg 中 MediaCodec 解码.png
    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);
        }
    }
    
    1. ff_mediacodec_dec_decode (解码)

      ff_mediacodec_dec_decode.png
    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) {
                    // 直接显示
                    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);
            }else if (ff_AMediaCodec_infoOutputFormatChanged(codec, index)) {
                // 输出格式已更改
                
                // 删除当前输出格式
                status = ff_AMediaFormat_delete(s->format);
                // 使用新的输出格式
             s->format = ff_AMediaCodec_getOutputFormat(codec);
            } else if (ff_AMediaCodec_infoOutputBuffersChanged(codec, index)) {
                // 输出缓冲区已更改
                ff_AMediaCodec_cleanOutputBuffers(codec);
            } else if (ff_AMediaCodec_infoTryAgainLater(codec, index)) {
                // 超时
            } else {
                // 解码错误
                av_log(avctx, AV_LOG_ERROR, "Failed to dequeue output buffer (status=%zd)\n", index);
                return AVERROR_EXTERNAL;
            }
    
        }
    }
    

    相关文章

      网友评论

        本文标题:FFmpeg 中的 Android MediaCodec

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