美文网首页音视频
ffmpeg_sample解读_vaapi_transcode

ffmpeg_sample解读_vaapi_transcode

作者: 刘佳阔 | 来源:发表于2020-11-20 10:16 被阅读0次

    title: ffmpeg_sample解读_vaapi_transcode
    date: 2020-10-28 10:15:02
    tags: [读书笔记]
    typora-copy-images-to: ./imgs
    typora-root-url: ./imgs


    总结

    流程图

    graph TB
    ahcc[av_hwdevice_ctx_create]
    -->oif[open_input_file]
    -->acfebn[avcodec_find_encoder_by_name]
    -->aaoc[avformat_alloc_output_context2]
    -->aac[avcodec_alloc_context3]
    -->ao[avio_open]
    -->arf{av_read_frame>0?}
    -->|yes|de[dec_enc]
    arf-->|no|ewn[encode_write]
    -->awt[av_write_trailer]
    -->release[release]
    de-->asp[avcodec_send_packet]
    -->afa[av_frame_alloc]
    -->acrf[avcodec_receive_frame]
    -->acoe[avcodec_open2]
    -->afns[avformat_new_stream]
    -->acpfc[avcodec_parameters_from_context]
    -->afwh[avformat_write_header]
    -->ew[encode_write]
    -->asf[avcodec_send_frame]
    -->acrp[avcodec_receive_packet]
    -->aprt[av_packet_rescale_ts]
    -->aiwf[av_interleaved_write_frame]
    -->ewn
    
    
    

    同样.也是两部分.第一部分初始化硬件编解码器上下文

    image-20201116193352298

    然后在用解码文件.解出帧.然后在用编码器编码.最后输出文件. 这里编码解码用了同样的硬件加速.部分.

    image-20201116193526048

    代码

    
    /**
     * @file
     * Intel VAAPI-accelerated transcoding example.
     *
     * @example vaapi_transcode.c
     * This example shows how to do VAAPI-accelerated transcoding.
     * Usage: vaapi_transcode input_stream codec output_stream
     * e.g: - vaapi_transcode input.mp4 h264_vaapi output_h264.mp4
     *      - vaapi_transcode input.mp4 vp9_vaapi output_vp9.ivf
     */
    
    #include <stdio.h>
    #include <errno.h>
    
    #include <libavutil/hwcontext.h>
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    
    static AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
    static AVBufferRef *hw_device_ctx = NULL;
    static AVCodecContext *decoder_ctx = NULL, *encoder_ctx = NULL;
    static int video_stream = -1;
    static AVStream *ost;
    static int initialized = 0;
    
    static enum AVPixelFormat get_vaapi_format(AVCodecContext *ctx,
                                               const enum AVPixelFormat *pix_fmts)
    {
        const enum AVPixelFormat *p;
    
        for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) {
            if (*p == AV_PIX_FMT_VAAPI)
                return *p;
        }
    
        fprintf(stderr, "Unable to decode this file using VA-API.\n");
        return AV_PIX_FMT_NONE;
    }
    
    /**
     * 这里都是老流程了.打开文件,初始化输入格式上下文,
     * 找到合适的流.找到解码器,
     * 用解码器创建解码上下文
     * @param filename
     * @return
     */
    static int open_input_file(const char *filename)
    {
        int ret;
        AVCodec *decoder = NULL;
        AVStream *video = NULL;
    //打开文件,初始化格式上下文
        if ((ret = avformat_open_input(&ifmt_ctx, filename, NULL, NULL)) < 0) {
            fprintf(stderr, "Cannot open input file '%s', Error code: %s\n",
                    filename, av_err2str(ret));
            return ret;
        }
    //找到流信息
        if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) {
            fprintf(stderr, "Cannot find input stream information. Error code: %s\n",
                    av_err2str(ret));
            return ret;
        }
    //找到音频流,初始化解码器
        ret = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
        if (ret < 0) {
            fprintf(stderr, "Cannot find a video stream in the input file. "
                    "Error code: %s\n", av_err2str(ret));
            return ret;
        }
        video_stream = ret;
    //解码器上下文分配控件
        if (!(decoder_ctx = avcodec_alloc_context3(decoder)))
            return AVERROR(ENOMEM);
    
        video = ifmt_ctx->streams[video_stream];
        //拷贝流中参数到解码器上下文
        if ((ret = avcodec_parameters_to_context(decoder_ctx, video->codecpar)) < 0) {
            fprintf(stderr, "avcodec_parameters_to_context error. Error code: %s\n",
                    av_err2str(ret));
            return ret;
        }
        //解码器上下文和硬件解码关联,不过是额外分配一个指针
        decoder_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
        if (!decoder_ctx->hw_device_ctx) {
            fprintf(stderr, "A hardware device reference create failed.\n");
            return AVERROR(ENOMEM);
        }
        decoder_ctx->get_format    = get_vaapi_format;
    //打开解码器上下文
        if ((ret = avcodec_open2(decoder_ctx, decoder, NULL)) < 0)
            fprintf(stderr, "Failed to open codec for decoding. Error code: %s\n",
                    av_err2str(ret));
    
        return ret;
    }
    
    static int encode_write(AVFrame *frame)
    {
        int ret = 0;
        AVPacket enc_pkt;
    
        av_init_packet(&enc_pkt);
        enc_pkt.data = NULL;
        enc_pkt.size = 0;
    //数据送入编码器,然后取出packet
        if ((ret = avcodec_send_frame(encoder_ctx, frame)) < 0) {
            fprintf(stderr, "Error during encoding. Error code: %s\n", av_err2str(ret));
            goto end;
        }
        while (1) {
            ret = avcodec_receive_packet(encoder_ctx, &enc_pkt);
            if (ret)
                break;
    
            enc_pkt.stream_index = 0;
            //转换生成的packet 的时间基相关属性,由输入流的时间基转为输出流的时间基,
            av_packet_rescale_ts(&enc_pkt, ifmt_ctx->streams[video_stream]->time_base,
                                 ofmt_ctx->streams[0]->time_base);
            //交错的把数据写出
            ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt);
            if (ret < 0) {
                fprintf(stderr, "Error during writing data to output file. "
                        "Error code: %s\n", av_err2str(ret));
                return -1;
            }
        }
    
    end:
        if (ret == AVERROR_EOF)
            return 0;
        ret = ((ret == AVERROR(EAGAIN)) ? 0:-1);
        return ret;
    }
    
    static int dec_enc(AVPacket *pkt, AVCodec *enc_codec)
    {
        AVFrame *frame;
        int ret = 0;
    //数据送入解码上下文
        ret = avcodec_send_packet(decoder_ctx, pkt);
        if (ret < 0) {
            fprintf(stderr, "Error during decoding. Error code: %s\n", av_err2str(ret));
            return ret;
        }
    
        while (ret >= 0) {
            if (!(frame = av_frame_alloc()))
                return AVERROR(ENOMEM);
    //取出解码后的数据
            ret = avcodec_receive_frame(decoder_ctx, frame);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                av_frame_free(&frame);
                return 0;
            } else if (ret < 0) {
                fprintf(stderr, "Error while decoding. Error code: %s\n", av_err2str(ret));
                goto fail;
            }
    
            if (!initialized) {
                /* we need to ref hw_frames_ctx of decoder to initialize encoder's codec.
                   Only after we get a decoded frame, can we obtain its hw_frames_ctx */
                //编码器上下文和硬件编码上下文绑定,复制的索引,防止内存泄露
                encoder_ctx->hw_frames_ctx = av_buffer_ref(decoder_ctx->hw_frames_ctx);
                if (!encoder_ctx->hw_frames_ctx) {
                    ret = AVERROR(ENOMEM);
                    goto fail;
                }
                /* set AVCodecContext Parameters for encoder, here we keep them stay
                 * the same as decoder.
                 * xxx: now the sample can't handle resolution change case.
                 */
                //设置编码器上下文参数 ,然后打开编码器,创建新的编码刘
                encoder_ctx->time_base = av_inv_q(decoder_ctx->framerate);
                encoder_ctx->pix_fmt   = AV_PIX_FMT_VAAPI;
                encoder_ctx->width     = decoder_ctx->width;
                encoder_ctx->height    = decoder_ctx->height;
    
                if ((ret = avcodec_open2(encoder_ctx, enc_codec, NULL)) < 0) {
                    fprintf(stderr, "Failed to open encode codec. Error code: %s\n",
                            av_err2str(ret));
                    goto fail;
                }
    
                if (!(ost = avformat_new_stream(ofmt_ctx, enc_codec))) {
                    fprintf(stderr, "Failed to allocate stream for output format.\n");
                    ret = AVERROR(ENOMEM);
                    goto fail;
                }
    
                ost->time_base = encoder_ctx->time_base;
                //拷贝数据流中的参数到编码器上下文
                ret = avcodec_parameters_from_context(ost->codecpar, encoder_ctx);
                if (ret < 0) {
                    fprintf(stderr, "Failed to copy the stream parameters. "
                            "Error code: %s\n", av_err2str(ret));
                    goto fail;
                }
    
                /* write the stream header */ //写出流的header
                if ((ret = avformat_write_header(ofmt_ctx, NULL)) < 0) {
                    fprintf(stderr, "Error while writing stream header. "
                            "Error code: %s\n", av_err2str(ret));
                    goto fail;
                }
    
                initialized = 1;
            }
            //编码后写出数据
            if ((ret = encode_write(frame)) < 0)
                fprintf(stderr, "Error during encoding and writing.\n");
    
    fail:
            av_frame_free(&frame);
            if (ret < 0)
                return ret;
        }
        return 0;
    }
    /**
     * 硬件实现数据转换
     * 和之前的差不多 .就是输入文件解码.然后重新编码
     * 这里使用了硬件编解码,并且是同一个硬件驱动,只需要把这个和 编解码上下文绑定就可以了
     * vaapi_transcode input.mp4 vp9_vaapi output_vp9.ivf
     * @param argc
     * @param argv
     * @return
     */
    int vaapi_transcode_main(int argc, char **argv)
    {
        int ret = 0;
        AVPacket dec_pkt;
        AVCodec *enc_codec;
    
        if (argc != 4) {
            fprintf(stderr, "Usage: %s <input file> <encode codec> <output file>\n"
                    "The output format is guessed according to the file extension.\n"
                    "\n", argv[0]);
            return -1;
        }
    //创建硬件上下文
        ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, NULL, NULL, 0);
        if (ret < 0) {
            fprintf(stderr, "Failed to create a VAAPI device. Error code: %s\n", av_err2str(ret));
            return -1;
        }
    //打开输入文件
        if ((ret = open_input_file(argv[1])) < 0)
            goto end;
    //通过名称找到编码器
        if (!(enc_codec = avcodec_find_encoder_by_name(argv[2]))) {
            fprintf(stderr, "Could not find encoder '%s'\n", argv[2]);
            ret = -1;
            goto end;
        }
    //找到输出文件的格式上下文
        if ((ret = (avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, argv[3]))) < 0) {
            fprintf(stderr, "Failed to deduce output format from file extension. Error code: "
                    "%s\n", av_err2str(ret));
            goto end;
        }
    //分配编码器空间
        if (!(encoder_ctx = avcodec_alloc_context3(enc_codec))) {
            ret = AVERROR(ENOMEM);
            goto end;
        }
    //初始化输出文件的io上下文
        ret = avio_open(&ofmt_ctx->pb, argv[3], AVIO_FLAG_WRITE);
        if (ret < 0) {
            fprintf(stderr, "Cannot open output file. "
                    "Error code: %s\n", av_err2str(ret));
            goto end;
        }
    
        /* read all packets and only transcoding video */
        while (ret >= 0) {
            //输入格式上下文中读取packet
            if ((ret = av_read_frame(ifmt_ctx, &dec_pkt)) < 0)
                break;
    //处理视频流
            if (video_stream == dec_pkt.stream_index)
                //解码后编码
                ret = dec_enc(&dec_pkt, enc_codec);
    
            av_packet_unref(&dec_pkt);
        }
    
        /* flush decoder */
        dec_pkt.data = NULL;
        dec_pkt.size = 0;
        //刷新最后的数据
        ret = dec_enc(&dec_pkt, enc_codec);
        av_packet_unref(&dec_pkt);
    
        /* flush encoder */
        ret = encode_write(NULL);
    
        /* write the trailer for output stream */
        //写入尾部到输出格式上下文中
        av_write_trailer(ofmt_ctx);
    
    end:
        avformat_close_input(&ifmt_ctx);
        avformat_close_input(&ofmt_ctx);
        avcodec_free_context(&decoder_ctx);
        avcodec_free_context(&encoder_ctx);
        av_buffer_unref(&hw_device_ctx);
        return ret;
    }
    
    

    相关文章

      网友评论

        本文标题:ffmpeg_sample解读_vaapi_transcode

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