FFmpeg6.0 硬解码到 Surface

前面我们介绍了如何使用FFmpeg6.0调用MediaCodec进行视频硬解码得到YUV数据,那么对于熟悉Android开发的同学就会问了,在java中使用MediaCodec是可以直接硬解码到渲染到SurfaceView上的,此时如果通过FFmpeg6.0调用MediaCodec进行硬解码的话,是否也能直接解码渲染到SurfaceView上呢?答案是肯定的,更加准确地来说是解码到Surface上,今天我们就来实现这样的一个小需求。

至于FFmpeg如何可以直接解码到surfaceView上,可以参考一下这个回复:

http://mplayerhq.hu/pipermail/ffmpeg-devel/2016-March/191700.html

总的来说,使用FFMpeg直接硬解码到Surface上的步骤是在前一篇文章《FFmpeg6.0调用MediaCodec解码》 的基础上增加部分代码即可,正所谓一理通百理用就是这个道理,冰冻三尺,非一日之寒, 很多看似庞大的工程背后其实也就是日积月累,一沙一石的堆砌,学习如此,日常工作中所开发的项目更是如此。

下面我们就说说在《FFmpeg6.0调用MediaCodec解码》的基础上如何实现FFmpeg硬解码到Surface上。

  1. 将Java层Surface对象传递到NDK层,并与解码器上下文挂钩

这个步骤只需要两句代码即可,够简单吧?

// 与Surface挂钩
AVMediaCodecContext *mediaCodecContext = av_mediacodec_alloc_context();
// decoder_ctx就是解码器上下文
av_mediacodec_default_init(decoder_ctx, mediaCodecContext, surface);
  1. 给FFmpeg设置JavaVM

在上一篇文章中我们可是说过FFmpeg6.0之后是通过NDK直接调用MediaCodec进行硬编解码,不是再通过JNI调用Java的MediaCodec了,怎么这里又要给FFmpeg设置JavaVM了呢?

啪啪啪打脸来的真快…

确实如果不给FFmpeg设置JavaVM也能进行硬解码,但是解码得到的数据并不能直接渲染到Surface上,因为一般介么得到的AVFrame的数据格式一般是NV12的(也就是AV_PIX_FMT_NV12), 而如果要能直接渲染到Surface上的数据格式就要求是mediaCodec的(也就是AV_PIX_FMT_MEDIACODEC)。因此想要直接解码到Surface上的话还是需要给ffmpeg设置JavaVM的哦。

这一步骤的代码就是在函数JNI_OnLoad设置一下即可:

JNIEXPORT
jint JNI_OnLoad(JavaVM *vm, void *res) {
    av_jni_set_java_vm(vm, 0);
    return JNI_VERSION_1_4;
}

增加完这两个步骤的代码之后,我们就可以直接将视频数据解码到Surface上啦,这时候是用SurfaceView还是用TextureView那就随便你啦…

完整代码如下:

void FFDecoder::decodeVideoToSurface(const char *videoPath, jobject surface) {
    AVFormatContext *input_ctx = nullptr;
    int video_stream, ret;
    AVStream *video = nullptr;
    AVCodecContext *decoder_ctx = nullptr;
    const AVCodec *decoder = nullptr;
    AVPacket *avPacket = {nullptr};
    AVFrame *avFrame = {nullptr};
    enum AVHWDeviceType type;
    int i;
    do {
        // 查询下mediacodec
        const char *mediacodec = "mediacodec";
        type = av_hwdevice_find_type_by_name(mediacodec);
        if (type == AV_HWDEVICE_TYPE_NONE) {
            LOGD_E("FFDecoder","Device type %s is not supported.\n", mediacodec);
            while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
                LOGD_E("FFDecoder"," %s", av_hwdevice_get_type_name(type));
            break;
        }

        // 打开文件流
        if (avformat_open_input(&input_ctx, videoPath, nullptr, nullptr) != 0) {
            LOGD_E("FFDecoder","Cannot open input file '%s'\n", videoPath);
            break;
        }
        if (avformat_find_stream_info(input_ctx, nullptr) < 0) {
            LOGD_E("FFDecoder","Cannot find input stream information.\n");
            break;
        }
        // 查找视频流
        ret = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
        if (ret < 0) {
            LOGD_E("FFDecoder","Cannot find a video stream in the input file\n");
            break;
        }
        video_stream = ret;
        // 这里以h264为例
        if (!(decoder = avcodec_find_decoder_by_name("h264_mediacodec"))) {
            LOGD_E("FFDecoder","avcodec_find_decoder_by_name failed.\n");
            break;
        }
        // 配置硬解码
        for (i = 0;; i++) {
            const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
            if (!config) {
                LOGD_E("FFDecoder","Decoder %s does not support device type %s.\n",
                       decoder->name, av_hwdevice_get_type_name(type));
                break;
            }
            if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
                config->device_type == type) {
                hw_pix_fmt = config->pix_fmt;
                break;
            }
        }

        if (!(decoder_ctx = avcodec_alloc_context3(decoder))){
            LOGD_E("FFDecoder","avcodec_alloc_context3 failed\n");
            break;
        }
        video = input_ctx->streams[video_stream];
        if (avcodec_parameters_to_context(decoder_ctx, video->codecpar) < 0){
            LOGD_E("FFDecoder","avcodec_parameters_to_context failed\n");
            break;
        }

        // 硬件解码器初始化
        decoder_ctx->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);
        decoder_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);

        if (ret < 0) {
            LOGD_E("FFDecoder","Failed to create specified HW device");
            break;
        }
        // 与Surface挂钩
        AVMediaCodecContext *mediaCodecContext = av_mediacodec_alloc_context();
        av_mediacodec_default_init(decoder_ctx, mediaCodecContext, surface);

        if ((ret = avcodec_open2(decoder_ctx, decoder, NULL)) < 0) {
            LOGD_E("FFDecoder","Failed to open codec for stream #%u\n", video_stream);
            break;
        }
        // 循环读取解码
        avPacket = av_packet_alloc();
        avFrame = av_frame_alloc();
        while (ret >= 0 || ret == -11) {
            ret = av_read_frame(input_ctx, avPacket);
            if(ret != 0){
                // 读完了,空包冲刷解码器
                ret = avcodec_send_packet(decoder_ctx, nullptr);
                av_packet_unref(avPacket);
            } else{
                if(avPacket->stream_index == video_stream){
                    ret = avcodec_send_packet(decoder_ctx, avPacket);
                    av_packet_unref(avPacket);
                } else{
                    // 非视频数据
                    av_packet_unref(avPacket);
                    continue;
                }
            }

            // 获取解码后的数据
            while (ret == 0){
                LOGD_D("FFDecoder","获取解码数据成功:%s",av_get_pix_fmt_name(static_cast<AVPixelFormat>(avFrame->format)));
                LOGD_D("FFDecoder","hw_pix_fmt:%s",av_get_pix_fmt_name(static_cast<AVPixelFormat>(hw_pix_fmt)));
                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(decoder_ctx,avFrame);
                LOGD_D("FFDecoder","avcodec_receive_frame ret:%d",ret);
                if(ret == 0){
                    // 可以使用av_hwframe_transfer_data将数据从GPU拷贝到cpu
                    if (avFrame->format == hw_pix_fmt) {
                        AVMediaCodecBuffer *buffer = (AVMediaCodecBuffer *) avFrame->data[3];
                        // 渲染
                        av_mediacodec_release_buffer(buffer, 1);
                    } else{
                        LOGD_E("FFDecoder","avFrame->format error");
                       // av_hwframe_transfer_data(mediacodecFrame, avFrame, 0)???
                    }
                }
            }
        }
    } while (0);

    // 释放资源
    if(nullptr != input_ctx){
        avformat_free_context(input_ctx);
        input_ctx = nullptr;
    }
    if(nullptr != decoder_ctx){
        avcodec_free_context(&decoder_ctx);
        decoder_ctx = nullptr;
    }
    if(nullptr != avPacket){
        av_packet_free(&avPacket);
        avPacket = nullptr;
    }
    if(nullptr != avFrame){
        av_frame_free(&avFrame);
        avFrame = nullptr;
    }
}

切记切记,不要忘了设置av_jni_set_java_vm(vm, 0);如果没有设置的话,解码到的数据一般就是NV12,至于NV12数据怎么渲染呢? 那就看之前Opengl ES的系列文章啦!!!

关注我,一起进步,有全量音视频开发进阶路径、资料、踩坑记等你来学习…

思想觉悟

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/31968.html

(0)

相关推荐

发表回复

登录后才能评论