基于新版FFmpeg(FFmpeg 6.1)的音视频转码

1 项目介绍

这是一个使用FFmpeg进行视频转码的小项目,为了简单起见,项目中的音视频的编码标准使用原编码标准。需要注意的是,由于不同音频编码标准对于一帧音频的样本数(nb_samples)不同,例如,AAC一般为1024,MP3一般为1152。因此,如果需要转换音频的编码标准,则需要进行重采样。

2 项目中使用的FFmpeg函数介绍

FFmpeg库常用函数介绍(一)-CSDN博客

FFmpeg库常用函数介绍(二)-CSDN博客

FFmpeg库常用函数介绍(三)-CSDN博客

3 视频转码流程

3.1 打开输入文件

打开输入文件的主要任务如下:

  1. 初始化输入文件的AVFormatContext以及寻找流信息这些获取输入文件信息的一些操作。
  2. 遍历输入流,寻找音视频的解码器,创建解码器上下文,初始化解码器上下文的参数,打开解码器。

在初始化解码器上下文AVCodecContext参数时,有一个需要注意的点。AVCodecParameters中没有pkt_timebase字段,因此AVCodecContext中的该字段只有默认值,需要找到输入流中对应的时间基给它赋值,以便后面创建buffer/abuffer滤镜器实例传递参数时使用。

3.2 打开输出文件

打开输出文件的主要任务如下:

  1. 分配输出文件的AVFormatContext,创建输出音视频流。
  2. 寻找音视频的编码器,创建编码器上下文,初始化编码器上下文的参数,打开编码器,将AVCodecContext的参数拷贝给AVCodecParameters。
  3. 打开输出文件,写入文件头。

在初始化编码器上下文AVCodecContext参数时,有几个需要注意的点:

  1. 初始化解码器上下文时可以将输入流的AVCodecParameters拷贝给解码器上下文AVCodecContext,因为输入流的AVCodecParameters里存储的就是编码器的相关信息。但是转码时使用的可能是不同的编码器,因此初始化时就需要一个个成员赋值。
  2. 需要通过判断输出格式是否需要全局头来确定AVCodecContext里的flags的值。如果需要全局头,就需要或上AV_CODEC_FLAG_GLOBAL_HEADER来让FFmpeg在编码时自动加上全局头。
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)

    enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

3.3 初始化滤镜

之所以需要滤镜是因为需要将解码后的视频帧的像素格式转换为输出格式支持的像素格式,以及将解码后的音频帧的采样格式转换为输出格式支持的采样格式,然后再进行编码。

初始化滤镜的主要任务如下:

  1. 对于视频,创建null滤镜(假滤镜),因为并不需要使用滤镜,只是改变像素格式。创建buffer滤镜(滤镜图的输入节点,用于缓存原始的视频帧供滤镜图读取)和buffersink滤镜(滤镜图的输出节点,用于缓存滤镜图处理后的视频帧)。创建buffer滤镜和buffersink滤镜实例(创建buffer滤镜实例时需要提供参数,参数包含视频分辨率、像素格式、时间基以及像素纵横比),并将其添加到滤镜图中去。设置buffersink滤镜实例的参数pix_fmts。
  2. 对于音频,创建anull滤镜(假滤镜)。创建abuffer滤镜和abuffersink滤镜。创建abuffer滤镜和abuffersink滤镜实例(创建abuffer滤镜时需要提供参数,参数包含音频的时间基、采样率、采样格式以及通道布局),并将其添加到滤镜图中去。设置abuffersink滤镜实例的参数sample_fmts、ch_layouts以及sample_rates。
  3. 创建并初始化buffer/abuffer和buffersink/abuffersink滤镜实例的引脚AVFilterInOut。解析字符串描述的滤镜图null/anull,并将其添加到现有的滤镜图中去。最后将滤镜图中的滤镜连接起来。

3.4 转码

转码的主要任务如下:

  1. 解码。读取编码帧,将编码帧送入解码器中,接收解码后的帧。
  2. 使用滤镜进行处理。将解码后的帧送入滤镜图进行处理,接收经滤镜图处理后的音视频帧。
  3. 编码写入。将滤镜图处理后的音视频帧送入编码器,接收编码后的音视频帧,将编码后的音视频帧写入输出文件。
  4. 刷新解码器、滤镜器和编码器。该操作的作用是清空解码器、滤镜器和编码器中的缓冲数据。刷新解码器:将输入数据置为NULL刷新解码器,接收解码后的数据,然后送入滤镜器处理,然后进行编码写入。刷新滤镜器:将输入数据置为NULL刷新滤镜器,接收滤镜器处理后的数据,然后进行编码写入。刷新编码器:将输入数据置为NULL刷新编码器,然后将编码后的音视频帧写入输出文件。
  5. 写入文件尾

在进行编码时,有一个需要注意的点。需要对pts进行转换,将pts转换为以编码器时间基表示的pts。但是解码后的帧和滤镜器处理后的帧(AVFrame)里的时间基都是默认值0。因此要对解码后和滤镜器处理后的帧里的时间基进行赋值,以便后面转换pts使用。

在将编码后的帧写入输出文件时有一个需要注意的点。需要将以编码器时间基表示的时间戳pts、dts以及duration转换为以输出格式时间基表示。

4 问题汇总

4.1 段错误

问题描述:调用avcodec_receive_packet函数接收编码后的音视频帧时,出现段错误。

原因:用于存储编码后的音视频帧的AVPacket结构体没有初始化。

解决办法:对AVPacket结构体进行初始化。

4.2 打开视频编码器失败

问题描述:调用avcodec_open2打开视频编码器失败。

错误信息:[libx264 @ 0x56217c499880] The encoder timebase is not set.

原因:编码器的时间基为{0, 1}。因为输入视频流里的参数里的framerate为{0, 1},导致设置的time_base也为{0, 1}。

解决办法:调用av_guess_frame_rate对视频流的帧率进行设置。

4.3 写入编码的帧到输出文件中失败

问题描述:调用av_interleaved_write_frame函数将编码后的音视频帧写入到输出文件中失败。

原因:待写入的帧的pts、dts以及duration时间戳有问题,值为0或者负值。因为调用av_packet_rescale_ts函数进行时间戳转换时原宿时间基写错了,导致AVPacket中帧的时间戳有问题。

解决办法:调用av_packet_rescale_ts函数,对编码后的音视频帧的时间戳进行转换。源时间基是编码器的时间基,宿时间基是输出格式里对应音频或者视频流的时间基。

4.4 使用libx264进行编码时出现警告

问题描述:使用libx264进行编码时出现警告,内容是强制的帧类型5被更改为帧类型3。

错误信息:[libx264 @ 0x563e9de51540] forced frame type (5) at 134 was changed to frame type (3)

原因:根据警告信息可以看出,原本是强制的帧类型(AVFrame里的pict_type)5被更改为了帧类型3。

解决办法:将AVFrame里的pict_type字段的值改为AV_PICTURE_TYPE_NONE。

4.5 刷新编码器时,编码器里的编码的帧写入输出文件失败

问题描述:刷新编码器时,编码器里有缓存的数据,将缓存的编码的帧写入输出文件时失败。

原因:pts、dts和duration时间戳有问题(不是AV_NOPTS_VALUE,只是值不对)。使用av_packet_rescale_ts进行时间戳转换时源宿时间基写错了,源时间基应该是编码器的时间基,宿时间基应该是输出格式里对应音频或者视频流的时间基。

解决办法:将源宿时间基修改为正确的值。

4.6 转码后的视频文件只有一个视频流

问题描述:转码后的视频文件只有一个视频流,并且无法播放。

原因:编码后的AVPacket里的流索引(stream_index)并不会被设置,仍然保持默认值0,导致编码的音视频帧都被写入到视频流里去了,所以只有一个视频流。

解决办法:手动设置编码后的音视频帧的AVPacket里的stream_index。

4.7 编码的帧写入输出文件时,报错时间戳不是单调递增的

问题描述:将编码的音视频帧写入输出文件时报错,内容为提供的dts时间戳不是单调递增的。

错误信息:[mp4 @ 0x555df2a72800] Application provided invalid, non monotonically increasing dts to muxer in stream 0: 176640 >= -3686400

原因:和上面只有一个视频流的原因一样,是因为AVPacket里的stream_index没有设置,无论音频帧还是视频帧,stream_index的值都为默认值0。因此音视频帧被写入到一个流里,导致时间戳非单调递增。

解决办法:手动设置编码后的音视频帧的AVPacket里的stream_index。

4.8 转码后的视频文件,声音断断续续

问题描述:播放转码后的视频文件,声音断断续续不连贯。

原因:音频帧的时间戳有问题。AVPacket里的stream_index还没有设置,默认值为0,在编码的音频帧到来时,直接使用stream_index来获取流的时间基,获取到的是视频流的时间基。将编码的以编码器时间基表示的音频帧的时间戳转换为视频流时间基表示的时间戳,显然有问题。

解决办法:使用正确的流索引获取正确的流的时间基。

4.9 转码后的视频文件,画面卡住不动

问题描述:播放转码后的视频文件,画面卡住不动,很久才会切换到下一个画面,并且视频时常变长了几百倍。

原因:编码后的视频帧的pts时间戳有问题。经滤镜器处理后的视频帧的pts时间戳是没有问题的,但是经过编码的时间戳却有问题。因为经滤镜器处理后的pts时间戳在送入编码器处理前没有进行转换。

解决办法:应该将由buffersink滤镜器时间基表示的pts时间戳转换为由编码器表示的pts时间戳,然后再送入编码器进行编码。

4.10 转码后的视频文件时长变长了近一半

问题描述:视频转码完成后,时长变长了近一半,前面和原来的视频一样,也可以正常播放,后面多出来的没有内容。

原因:刷新编码器时,音频帧的时间戳有问题。AVPacket里的stream_index还没有设置,默认值为0,在获取到编码器缓冲区中编码的音频帧后,直接使用stream_index来获取流的时间基,获取到的是视频流的时间基。将编码的以编码器时间基表示的音频帧的时间戳转换为视频流时间基表示的时间戳,显然有问题。

解决办法:使用正确的流索引获取正确的流的时间基。

5 代码

extern "C"
{
    #include "libavformat/avformat.h"
    #include "libavcodec/avcodec.h"
    #include "libavfilter/avfilter.h"
    #include "libavutil/opt.h"
    #include "libavfilter/buffersrc.h"
    #include "libavfilter/buffersink.h"
};

//打开输入文件
int open_input_file(int &audio_stream_index, int &video_stream_index, const char *filename, AVFormatContext **in_fmt_ctx,
AVCodecContext **audio_dec_ctx, AVCodecContext **video_dec_ctx)
{
    //初始化输入AVFormatContext
    if (avformat_open_input(in_fmt_ctx, filename, NULL, NULL) < 0)
    {
        printf("failed to alloc input AVFormatContext\n");
        return -1;
    }
    //寻找流信息
    if (avformat_find_stream_info(*in_fmt_ctx, NULL) < 0)
    {
        //destroy(in_fmt_ctx);
        printf("failed to find stream information\n");
        return -1;
    }
    //格式化输出输入文件信息
    av_dump_format(*in_fmt_ctx, 0, filename, 0);

    for (int i = 0; i < (*in_fmt_ctx)->nb_streams; i++)
    {
        //视频流
        if ((*in_fmt_ctx)->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            video_stream_index = i;
            //寻找视频解码器
            const AVCodec *video_codec = avcodec_find_decoder((*in_fmt_ctx)->streams[i]->codecpar->codec_id);
            if (!video_codec)
            {
                printf("failed to find video decoder\n");
                return -1;
            }
            //分配视频AVCodecContext
            *video_dec_ctx = avcodec_alloc_context3(video_codec);
            if (!(*video_dec_ctx))
            {
                printf("failed to alloc video decoder AVCodecContext\n");
                return -1;
            }
            //拷贝AVCodecParameters给AVCodecContext
            if (avcodec_parameters_to_context(*video_dec_ctx, (*in_fmt_ctx)->streams[i]->codecpar) < 0)
            {
                printf("failed to copy AVCodecParameters to AVCodecContext\n");
                return -1;
            }
            (*video_dec_ctx)->pkt_timebase = (*in_fmt_ctx)->streams[i]->time_base;
            //打开视频解码器
            if (avcodec_open2(*video_dec_ctx, video_codec, NULL) < 0)
            {
                printf("failed to open video decoder AVCodecContext\n");
                return -1;
            }
        }
        //音频流
        else if ((*in_fmt_ctx)->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            audio_stream_index = i;
            //寻找音频解码器
            const AVCodec *audio_codec = avcodec_find_decoder((*in_fmt_ctx)->streams[i]->codecpar->codec_id);
            if (!audio_codec)
            {
                printf("failed to find audio decoder\n");
                return -1;
            }
            //分配音频AVCodecContext
            *audio_dec_ctx = avcodec_alloc_context3(audio_codec);
            if (!(*audio_dec_ctx))
            {
                printf("failed to alloc audio decoder AVCodecContext\n");
                return -1;
            }
            //拷贝AVCodecParameters给AVCodecContext
            if (avcodec_parameters_to_context(*audio_dec_ctx, (*in_fmt_ctx)->streams[i]->codecpar) < 0)
            {
                printf("failed to copy AVCodecParameters to AVCodecContext\n");
                return -1;
            }
            (*audio_dec_ctx)->pkt_timebase = (*in_fmt_ctx)->streams[i]->time_base;
            //打开音频解码器
            if (avcodec_open2(*audio_dec_ctx, audio_codec, NULL) < 0)
            {
                printf("falied to open audio decoder AVCodecContext\n");
                return -1;
            }
        }
    }

    return 0;
}

//打开输出文件
int open_output_file(const char *filename, AVFormatContext **in_fmt_ctx, AVFormatContext **out_fmt_ctx, AVCodecContext **audio_enc_ctx, AVCodecContext **video_enc_ctx)
{
    //分配输出AVFormatContext
    if (avformat_alloc_output_context2(out_fmt_ctx, NULL, NULL, filename) < 0)
    {
        printf("failed to alloc output AVFormatContext\n");
        return -1;
    }
    
    for (int i = 0; i < (*in_fmt_ctx)->nb_streams; i++)
    {
        if ((*in_fmt_ctx)->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            //创建输出流
            AVStream *out_stream = avformat_new_stream(*out_fmt_ctx, NULL);
            if (!out_stream)
            {
                printf("failed to create output stream\n");
                return -1;
            }

            //寻找编码器
            const AVCodec *video_codec = avcodec_find_encoder((*in_fmt_ctx)->streams[i]->codecpar->codec_id);
            if (!video_codec)
            {
                printf("failed to find video encoder\n");
                return -1;
            }
            //分配视频AVCodecContext
            *video_enc_ctx = avcodec_alloc_context3(video_codec);
            if (!(*video_enc_ctx))
            {
                printf("failed to alloc video encoder AVCodecContext\n");
                return -1;
            }
            
            //初始化视频AVCodecContext
            (*video_enc_ctx)->height = (*in_fmt_ctx)->streams[i]->codecpar->height;
            (*video_enc_ctx)->width = (*in_fmt_ctx)->streams[i]->codecpar->width;
            (*in_fmt_ctx)->streams[i]->codecpar->framerate = av_guess_frame_rate(*in_fmt_ctx, (*in_fmt_ctx)->streams[i], NULL);
            //视频流的时间基是1 / 帧率
            (*video_enc_ctx)->time_base = av_inv_q((*in_fmt_ctx)->streams[i]->codecpar->framerate);
            (*video_enc_ctx)->sample_aspect_ratio = (*in_fmt_ctx)->streams[i]->codecpar->sample_aspect_ratio;
            if (video_codec->pix_fmts)
            {
                (*video_enc_ctx)->pix_fmt = video_codec->pix_fmts[0];
            }

            // //全局头
            if ((*out_fmt_ctx)->oformat->flags & AVFMT_GLOBALHEADER)
            {
                (*video_enc_ctx)->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
            }

            //打开视频编码器
            if (avcodec_open2(*video_enc_ctx, video_codec, NULL) < 0)
            {
                printf("failed to open video AVCodecContext\n");
                return -1;
            }

            //拷贝AVCodecContext给AVCodecParameters
            if (avcodec_parameters_from_context((*out_fmt_ctx)->streams[i]->codecpar, *video_enc_ctx) < 0)
            {
                printf("failed to copy AVCodecContext to AVCodecParameters\n");
                return -1;
            }
        }
        else if ((*in_fmt_ctx)->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            //创建输出流
            AVStream *out_stream = avformat_new_stream(*out_fmt_ctx, NULL);
            if (!out_stream)
            {
                printf("failed to create output stream\n");
                return -1;
            }
            //寻找编码器
            const AVCodec *audio_codec = avcodec_find_encoder((*in_fmt_ctx)->streams[i]->codecpar->codec_id);
            //分配音频AVCodecContext
            *audio_enc_ctx = avcodec_alloc_context3(audio_codec);
            if (!(*audio_enc_ctx))
            {
                printf("failed to alloc audio encoder AVCodecContext\n");
                return -1;
            }

            //初始化音频AVCodecContext
            //音频流的时间基是1 / 采样率
            (*audio_enc_ctx)->time_base = {1, (*in_fmt_ctx)->streams[i]->codecpar->sample_rate};
            if (audio_codec->sample_fmts)
            {
                (*audio_enc_ctx)->sample_fmt = audio_codec->sample_fmts[0];
            }
            (*audio_enc_ctx)->sample_rate = (*in_fmt_ctx)->streams[i]->codecpar->sample_rate;
            //立体声
            (*audio_enc_ctx)->channel_layout = AV_CH_LAYOUT_STEREO;
            //拷贝通道布局
            if (av_channel_layout_copy(&(*audio_enc_ctx)->ch_layout, &(*audio_enc_ctx)->ch_layout) < 0)
            {
                printf("failed to copy channel layout\n");
                return -1;
            }

            //全局头
            if ((*out_fmt_ctx)->oformat->flags & AVFMT_GLOBALHEADER)
            {
                (*audio_enc_ctx)->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
            }

            //打开音频编码器
            if (avcodec_open2(*audio_enc_ctx, audio_codec, NULL) < 0)
            {
                printf("failed to open audio AVCodecContext\n");
                return -1;
            }

            //拷贝AVCodecContext给AVCodecParameters
            if (avcodec_parameters_from_context((*out_fmt_ctx)->streams[i]->codecpar, *audio_enc_ctx) < 0)
            {
                printf("failed to copy AVCodecContext to AVCodecParameters\n");
                return -1;
            }
        }
    }

    if (!((*out_fmt_ctx)->oformat->flags & AVFMT_NOFILE))
    {
        //打开输出文件
        if (avio_open2(&(*out_fmt_ctx)->pb, filename, AVIO_FLAG_WRITE, NULL, NULL) < 0)
        {
            printf("failed to open output file\n");
            return -1;
        }
    }

    //写入文件头
    if (avformat_write_header(*out_fmt_ctx, NULL) < 0)
    {
        printf("failed to write header\n");
        return -1;
    }

    //格式化输出输出文件信息
    av_dump_format(*out_fmt_ctx, 0, filename, 1);

    return 0;
}

//初始化滤镜
int init_filters(AVFormatContext **in_fmt_ctx, AVCodecContext **audio_enc_ctx, AVCodecContext **video_enc_ctx,
AVCodecContext **audio_dec_ctx, AVCodecContext **video_dec_ctx,
AVFilterGraph **audio_filter_graph, AVFilterGraph **video_filter_graph, AVFilterContext **audio_abuffer_filter_ctx,
AVFilterContext **audio_abuffersink_filter_ctx, AVFilterContext **video_buffer_filter_ctx, AVFilterContext **video_buffersink_filter_ctx,
AVFilterInOut **audio_output_port, AVFilterInOut **audio_input_port, AVFilterInOut **video_output_port, AVFilterInOut **video_input_port)
{
    for (int i = 0; i < (*in_fmt_ctx)->nb_streams; i++)
    {
        if ((*in_fmt_ctx)->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            //分配视频滤镜图
            *video_filter_graph = avfilter_graph_alloc();
            if (!(*video_filter_graph))
            {
                printf("failed to alloc video filter graph\n");
                return -1;
            }
            //获取视频buffer滤镜
            const AVFilter *video_buffer_filter = avfilter_get_by_name("buffer");
            if (!video_buffer_filter)
            {
                printf("failed to get buffer filter\n");
                return -1;
            }
            //获取视频buffersink滤镜
            const AVFilter *video_buffersink_filter = avfilter_get_by_name("buffersink");
            if (!video_buffersink_filter)
            {
                printf("failed to get buffersink filter\n");
                return -1;
            }
            char args[4096] = {0};
            snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
            (*video_dec_ctx)->width, (*video_dec_ctx)->height, (*video_dec_ctx)->pix_fmt,
            (*video_dec_ctx)->pkt_timebase.num, (*video_dec_ctx)->pkt_timebase.den,
            (*video_dec_ctx)->sample_aspect_ratio.num, (*video_dec_ctx)->sample_aspect_ratio.den);
            //分配buffer AVFilterContext并将其添加到滤镜图中去
            if (avfilter_graph_create_filter(video_buffer_filter_ctx, video_buffer_filter, "in", args, NULL, *video_filter_graph) < 0)
            {
                printf("failed to alloc video buffer filter context\n");
                return -1;
            }
            //分配buffersink AVFilterContext并将其添加到滤镜图中去
            if (avfilter_graph_create_filter(video_buffersink_filter_ctx, video_buffersink_filter, "out", NULL, NULL, *video_filter_graph) < 0)
            {
                printf("failed to alloc video buffersink filter context\n");
                return -1;
            }
            //设置pix_fmt选项的值
            if (av_opt_set_bin(*video_buffersink_filter_ctx, "pix_fmts", (uint8_t *)&(*video_enc_ctx)->pix_fmt, sizeof(AVPixelFormat), AV_OPT_SEARCH_CHILDREN) < 0)
            {
                printf("failed to set video buffersink filter context option\n");
                return -1;
            }
            //分配buffer滤镜的输出引脚
            *video_output_port = avfilter_inout_alloc();
            if (!(*video_output_port))
            {
                printf("failed to alloc buffer output AVFilterInOut\n");
                return -1;
            }
            //初始化引脚
            (*video_output_port)->filter_ctx = *video_buffer_filter_ctx;
            //连接到字符串描述的滤镜图的第一个滤镜的输入端,该滤镜的输入端默认为"in"
            //字符串常量存储在只读数据段,同样的值只能存储一份
            //又有不只一个指针指向"in"和"out",因此需要拷贝
            (*video_output_port)->name = av_strdup("in");
            (*video_output_port)->next = NULL;
            (*video_output_port)->pad_idx =0;
            //分配buffersink滤镜的输入引脚
            *video_input_port = avfilter_inout_alloc();
            if (!(*video_input_port))
            {
                printf("failed to alloc buffersink input AVFilterInOut\n");
                return -1;
            }
            //初始化引脚
            (*video_input_port)->filter_ctx = *video_buffersink_filter_ctx;
            //连接到字符串描述的滤镜图的最后一个滤镜的输出端,该滤镜的输出端默认为"out"
            (*video_input_port)->name = av_strdup("out");
            (*video_input_port)->next = NULL;
            (*video_input_port)->pad_idx = 0;
            //将字符串描述的滤镜图插入到现存的滤镜图中去
            if (avfilter_graph_parse_ptr(*video_filter_graph, "null", video_input_port, video_output_port, NULL) < 0)
            {
                printf("failed to insert the filter graph described by string to the existed filter graph\n");
                return -1;
            }
            //为滤镜图中的滤镜建立连接
            if (avfilter_graph_config(*video_filter_graph, NULL) < 0)
            {
                printf("failed to link all the filter\n");
                return -1;
            }
        }
        else if ((*in_fmt_ctx)->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            //分配音频滤镜图
            *audio_filter_graph = avfilter_graph_alloc();
            if (!(*audio_filter_graph))
            {
                printf("failed to alloc audio filter graph\n");
                return -1;
            }
            //获取音频abuffer滤镜
            const AVFilter *audio_abuffer_filter = avfilter_get_by_name("abuffer");
            if (!audio_abuffer_filter)
            {
                printf("failed to get abuffer filter\n");
                return -1;
            }
            //获取音频abuffersink滤镜
            const AVFilter *audio_abuffersink_filter = avfilter_get_by_name("abuffersink");
            if (!audio_abuffersink_filter)
            {
                printf("failed to get abuffersink filter\n");
                return -1;
            }
            char ch_layout_desc[1028] = {0};
            //获取通道布局描述
            if (av_channel_layout_describe(&(*audio_dec_ctx)->ch_layout, ch_layout_desc, sizeof(ch_layout_desc)) < 0)
            {
                printf("failed to get channel layout description\n");
                return -1;
            }
            char args[4096] = {0};
            snprintf(args, sizeof(args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=%s",
            (*audio_dec_ctx)->time_base.num, (*audio_dec_ctx)->time_base.den, (*audio_dec_ctx)->sample_rate,
            av_get_sample_fmt_name((*audio_dec_ctx)->sample_fmt), ch_layout_desc);
            //分配abuffer AVFilterContext并将其添加到滤镜图中去
            if (avfilter_graph_create_filter(audio_abuffer_filter_ctx, audio_abuffer_filter, "in", args, NULL, *audio_filter_graph) < 0)
            {
                printf("failed to alloc audio abuffer filter context\n");
                return -1;
            }
            //分配abuffersink AVFilterContext并将其添加到滤镜图中去
            if (avfilter_graph_create_filter(audio_abuffersink_filter_ctx, audio_abuffersink_filter, "out", NULL, NULL, *audio_filter_graph) < 0)
            {
                printf("failed to alloc audio abuffersink filter context\n");
                return -1;
            }
            //设置sample_rates选项的值
            if (av_opt_set_bin(*audio_abuffersink_filter_ctx, "sample_rates", (uint8_t *)&(*audio_enc_ctx)->sample_rate, sizeof(int), AV_OPT_SEARCH_CHILDREN) < 0)
            {
                printf("failed to set audio abuffersink filter context option\n");
                return -1;
            }
            //设置sample_fmts选项的值
            if (av_opt_set_bin(*audio_abuffersink_filter_ctx, "sample_fmts", (uint8_t *)&(*audio_enc_ctx)->sample_fmt, sizeof(AVSampleFormat), AV_OPT_SEARCH_CHILDREN) < 0)
            {
                printf("failed to set audio abuffersink filter context option\n");
                return -1;
            }
            memset(args, 0, sizeof(args));
            if (av_channel_layout_describe(&(*audio_enc_ctx)->ch_layout, args, sizeof(args)) < 0)
            {
                printf("failed to get channel layout description\n");
                return -1;
            }
            //设置ch_layouts选项的值
            if (av_opt_set(*audio_abuffersink_filter_ctx, "ch_layouts", args, AV_OPT_SEARCH_CHILDREN) < 0)
            {
                printf("failed to set audio abuffersink filter context option\n");
                return -1;
            }
            //分配abuffer滤镜的输出引脚
            *audio_output_port = avfilter_inout_alloc();
            if (!(*audio_output_port))
            {
                printf("failed to alloc abuffer output AVFilterInOut\n");
                return -1;
            }
            //初始化引脚
            (*audio_output_port)->filter_ctx = *audio_abuffer_filter_ctx;
            (*audio_output_port)->name = av_strdup("in");
            (*audio_output_port)->next = NULL;
            (*audio_output_port)->pad_idx = 0;
            //分配abuffersink滤镜的输入引脚
            *audio_input_port = avfilter_inout_alloc();
            if (!(*audio_input_port))
            {
                printf("failed to alloc abuffersink input AVFilterInOut\n");
                return -1;
            }
            //初始化引脚
            (*audio_input_port)->filter_ctx = *audio_abuffersink_filter_ctx;
            (*audio_input_port)->name = av_strdup("out");
            (*audio_input_port)->next = NULL;
            (*audio_input_port)->pad_idx = 0;
            //将字符串描述的滤镜图插入到现存的滤镜图中去
            if (avfilter_graph_parse_ptr(*audio_filter_graph, "anull", audio_input_port, audio_output_port, NULL) < 0)
            {
                printf("failed to insert the filter graph described by string to the existed filter graph\n");
                return -1;
            }
            //为滤镜图中的滤镜建立连接
            if (avfilter_graph_config(*audio_filter_graph, NULL) < 0)
            {
                printf("failed to link all the filter\n");
                return -1;
            }
        }
    }

    return 0;
}

//编码,交错写入
int encode_write(AVFrame *filtered_frame, AVCodecContext *enc_ctx, AVFormatContext *out_fmt_ctx, int stream_id)
{
    filtered_frame->pts = av_rescale_q_rnd(filtered_frame->pts, filtered_frame->time_base, enc_ctx->time_base, (AVRounding)AV_ROUND_NEAR_INF);
    //将滤镜器处理后的音视频帧送入编码器进行编码
    if (avcodec_send_frame(enc_ctx, filtered_frame) < 0)
    {
        printf("failed to send frame to the encoder\n");
        return -1;
    }

    int ret = 0;
    AVPacket encoded_pkt;
    memset(&encoded_pkt, 0, sizeof(encoded_pkt));
    while (ret >= 0)
    {
        //接收编码后的音视频帧
        ret = avcodec_receive_packet(enc_ctx, &encoded_pkt);
        if (ret < 0)
        {
            if ((ret == AVERROR(EAGAIN)) || (ret == AVERROR_EOF))
            {
                continue;
            }

            printf("failed to receive encoded packet from the encoder\n");
            av_packet_unref(&encoded_pkt);
            return -1;
        }

        //对packet中的音视频帧的时间戳进行转换
        av_packet_rescale_ts(&encoded_pkt, enc_ctx->time_base, out_fmt_ctx->streams[stream_id]->time_base);
        //交错写入
        encoded_pkt.stream_index = stream_id;
        if (av_interleaved_write_frame(out_fmt_ctx, &encoded_pkt) < 0)
        {
            printf("failed to write encoded frame\n");
            av_packet_unref(&encoded_pkt);
            return -1;
        }

        av_packet_unref(&encoded_pkt);
    }

    return 0;
}

//将解码的音视频帧送入滤镜器处理,编码,交错写入
int filter_encode_write(AVFrame *decoded_frame, AVFilterContext *buffer_filter_ctx, AVFilterContext *buffersink_filter_ctx, AVCodecContext *enc_ctx,
AVFormatContext *out_fmt_ctx, int stream_id)
{
    AVFrame filtered_frame;
    memset(&filtered_frame, 0, sizeof(filtered_frame));
    //将解码的音视频帧送入buffer/abuffer滤镜器
    if (av_buffersrc_add_frame_flags(buffer_filter_ctx, decoded_frame, 0) < 0)
    {
        printf("failed to send frame to the buffersrc filter\n");
        return -1;
    }

    int ret = 0;
    while (ret >= 0)
    {
        //获取滤镜器处理后的帧
        ret = av_buffersink_get_frame(buffersink_filter_ctx, &filtered_frame);
        if (ret < 0)
        {
            if ((ret == AVERROR(EAGAIN)) || (ret == AVERROR_EOF))
            {
                continue;
            }

            printf("failed to filter frame\n");
            av_frame_unref(&filtered_frame);
            return -1;
        }

        filtered_frame.pict_type = AV_PICTURE_TYPE_NONE;
        filtered_frame.time_base = av_buffersink_get_time_base(buffersink_filter_ctx);
        //编码,交错写入
        if (encode_write(&filtered_frame, enc_ctx, out_fmt_ctx, stream_id) < 0)
        {
            av_frame_unref(&filtered_frame);
            return -1;
        }

        av_frame_unref(&filtered_frame);
    }

    return 0;
}

//转码
int transcoding(int audio_stream_index, int video_stream_index, AVFormatContext *in_fmt_ctx, AVCodecContext *audio_dec_ctx, AVCodecContext *video_dec_ctx,
AVFormatContext *out_fmt_ctx, AVCodecContext *audio_enc_ctx, AVCodecContext *video_enc_ctx,
AVFilterContext *audio_abuffer_filter_ctx, AVFilterContext *audio_abuffersink_filter_ctx,
AVFilterContext *video_buffer_filter_ctx, AVFilterContext *video_buffersink_filter_ctx)
{
    int stream_id = 0;
    AVPacket encoded_pkt;
    AVFrame decoded_frame;
    AVFrame filtered_frame;
    memset(&encoded_pkt, 0, sizeof(encoded_pkt));
    memset(&decoded_frame, 0, sizeof(decoded_frame));
    memset(&filtered_frame, 0, sizeof(filtered_frame));
    AVFilterContext *buffer_filter_ctx = NULL, *buffersink_filter_ctx = NULL;
    AVCodecContext *enc_ctx = NULL, *dec_ctx = NULL;
    //读取编码的帧
    while ((av_read_frame(in_fmt_ctx, &encoded_pkt) >= 0))
    {
        if (encoded_pkt.stream_index == audio_stream_index)
        {
            buffer_filter_ctx = audio_abuffer_filter_ctx;
            buffersink_filter_ctx = audio_abuffersink_filter_ctx;
            enc_ctx = audio_enc_ctx;
            dec_ctx = audio_dec_ctx;
            stream_id = audio_stream_index;
        }
        else if (encoded_pkt.stream_index == video_stream_index)
        {
            buffer_filter_ctx = video_buffer_filter_ctx;
            buffersink_filter_ctx = video_buffersink_filter_ctx;
            enc_ctx = video_enc_ctx;
            dec_ctx = video_dec_ctx;
            stream_id = video_stream_index;
        }
        
        if ((encoded_pkt.stream_index != audio_stream_index) && (encoded_pkt.stream_index != video_stream_index))
        {
            continue;
        }

        //将编码的音视频帧送入解码器
        if (avcodec_send_packet(dec_ctx, &encoded_pkt) < 0)
        {
            printf("failed to send encoded packet to the decoder\n");
            av_packet_unref(&encoded_pkt);
            return -1;
        }

        int ret = 0;
        while (ret >= 0)
        {
            //接收解码的音视频帧
            ret = avcodec_receive_frame(dec_ctx, &decoded_frame);
            if (ret < 0)
            {
                if ((ret ==  AVERROR(EAGAIN)) || (ret ==  AVERROR_EOF))
                {
                    continue;
                }

                printf("failed to decode packet\n");
                av_packet_unref(&encoded_pkt);
                av_frame_unref(&decoded_frame);
                return -1;
            }

            //使用滤镜器处理,编码,交错写入
            if (filter_encode_write(&decoded_frame, buffer_filter_ctx, buffersink_filter_ctx, enc_ctx, out_fmt_ctx, stream_id) < 0)
            {
                av_packet_unref(&encoded_pkt);
                av_frame_unref(&decoded_frame);
                return -1;
            }

            av_packet_unref(&encoded_pkt);
            av_frame_unref(&decoded_frame);
        }
    }

    for (int i = 0; i < in_fmt_ctx->nb_streams; i++)
    {
        if (in_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            buffer_filter_ctx = audio_abuffer_filter_ctx;
            buffersink_filter_ctx = audio_abuffersink_filter_ctx;
            enc_ctx = audio_enc_ctx;
            dec_ctx = audio_dec_ctx;
            stream_id = i;
        }
        else if (in_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            buffer_filter_ctx = video_buffer_filter_ctx;
            buffersink_filter_ctx = video_buffersink_filter_ctx;
            enc_ctx = video_enc_ctx;
            dec_ctx = video_dec_ctx;
            stream_id = i;
        }

        //刷新解码器
        if (avcodec_send_packet(dec_ctx, NULL) < 0)
        {
            printf("failed to send null to flush decoder\n");
            return -1;
        }

        int ret = 0;
        while (ret >= 0)
        {
            ret = avcodec_receive_frame(dec_ctx, &decoded_frame);
            if (ret < 0)
            {
                if ((ret == AVERROR(EAGAIN)) || (ret ==  AVERROR_EOF))
                {
                    continue;
                }

                printf("failed to receive flushed frame\n");
                av_frame_unref(&decoded_frame);
                return -1;
            }

            if (filter_encode_write(&decoded_frame, buffer_filter_ctx, buffersink_filter_ctx, enc_ctx, out_fmt_ctx, stream_id) < 0)
            {
                av_frame_unref(&decoded_frame);
                return -1;
            }

            av_frame_unref(&decoded_frame);
        }

        //刷新滤镜器
        if (av_buffersrc_add_frame_flags(buffer_filter_ctx, NULL, 0) < 0)
        {
            printf("failed to send null to flush filter\n");
            return -1;
        }

        ret = 0;
        while (ret >= 0)
        {
            ret = av_buffersink_get_frame(buffersink_filter_ctx, &filtered_frame);
            if (ret < 0)
            {
                if ((ret == AVERROR(EAGAIN)) || (ret ==  AVERROR_EOF))
                {
                    continue;
                }

                printf("failed to receive flushed frame\n");
                av_frame_unref(&filtered_frame);
                return -1;
            }

            if (encode_write(&filtered_frame, enc_ctx, out_fmt_ctx, stream_id) < 0)
            {
                av_frame_unref(&filtered_frame);
                return -1;
            }

            av_frame_unref(&filtered_frame);
        }

        //刷新编码器
        if (avcodec_send_frame(enc_ctx, NULL) < 0)
        {
            printf("failed to send null to flush encoder\n");
            return -1;
        }

        ret = 0;
        while (ret >= 0)
        {
            ret = avcodec_receive_packet(enc_ctx, &encoded_pkt);
            if (ret < 0)
            {
                if ((ret == AVERROR(EAGAIN)) || (ret ==  AVERROR_EOF))
                {
                    continue;
                }

                printf("failed to receive flushed packet\n");
                av_packet_unref(&encoded_pkt);
                return -1;
            }

            av_packet_rescale_ts(&encoded_pkt, enc_ctx->time_base, out_fmt_ctx->streams[stream_id]->time_base);
            encoded_pkt.stream_index = stream_id;

            if (av_interleaved_write_frame(out_fmt_ctx, &encoded_pkt) < 0)
            {
                printf("failed to write encoded frame\n");
                av_packet_unref(&encoded_pkt);
                return -1;
            }

            av_packet_unref(&encoded_pkt);
        }
    }

    //写入文件尾
    if (av_write_trailer(out_fmt_ctx) < 0)
    {
        printf("failed to write tail\n");
        return -1;
    }

    return 0;
}

void destroy(AVFormatContext **in_fmt_ctx, AVFormatContext **out_fmt_ctx, AVCodecContext **audio_dec_ctx, AVCodecContext **video_dec_ctx,
AVCodecContext **audio_enc_ctx, AVCodecContext **video_enc_ctx, AVFilterGraph **audio_filter_graph, AVFilterGraph **video_filter_graph)
{
    avformat_close_input(in_fmt_ctx);
    if (out_fmt_ctx && !((*out_fmt_ctx)->oformat->flags & AVFMT_NOFILE))
    {
        avio_close((*out_fmt_ctx)->pb);
    }
    avformat_free_context(*out_fmt_ctx);
    avcodec_free_context(audio_dec_ctx);
    avcodec_free_context(video_dec_ctx);
    avcodec_free_context(audio_enc_ctx);
    avcodec_free_context(video_enc_ctx);
    avfilter_graph_free(audio_filter_graph);
    avfilter_graph_free(video_filter_graph);
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        printf("argument passing error\nUsage: ./main input_video.mp4 output_video.mp4\n");
        return 1;
    }

    int audio_stream_index = 0, video_stream_index = 0;
    AVFormatContext *in_fmt_ctx = NULL, *out_fmt_ctx = NULL;
    AVCodecContext *audio_dec_ctx = NULL, *video_dec_ctx = NULL;
    AVCodecContext *audio_enc_ctx = NULL, *video_enc_ctx = NULL;
    AVFilterGraph *audio_filter_graph = NULL, *video_filter_graph = NULL;
    AVFilterContext *audio_abuffer_filter_ctx = NULL, *audio_abuffersink_filter_ctx = NULL, *video_buffer_filter_ctx = NULL, *video_buffersink_filter_ctx = NULL;
    AVFilterInOut *audio_output_port = NULL, *audio_input_port = NULL, *video_output_port = NULL, *video_input_port = NULL;

    //打开输入文件
    if (open_input_file(audio_stream_index, video_stream_index, argv[1], &in_fmt_ctx, &audio_dec_ctx, &video_dec_ctx) < 0)
    {
        destroy(&in_fmt_ctx, &out_fmt_ctx, &audio_dec_ctx, &video_dec_ctx, &audio_enc_ctx, &video_enc_ctx, &audio_filter_graph, &video_filter_graph);
        return -1;
    }

    //打开输出文件
    if (open_output_file(argv[2], &in_fmt_ctx, &out_fmt_ctx, &audio_enc_ctx, &video_enc_ctx) < 0)
    {
        destroy(&in_fmt_ctx, &out_fmt_ctx, &audio_dec_ctx, &video_dec_ctx, &audio_enc_ctx, &video_enc_ctx, &audio_filter_graph, &video_filter_graph);
        return -1;
    }

    //初始化滤镜
    if (init_filters(&in_fmt_ctx, &audio_enc_ctx, &video_enc_ctx, &audio_dec_ctx, &video_dec_ctx, &audio_filter_graph, &video_filter_graph,
    &audio_abuffer_filter_ctx, &audio_abuffersink_filter_ctx, &video_buffer_filter_ctx, &video_buffersink_filter_ctx, &audio_output_port,
    &audio_input_port, &video_output_port, &video_input_port) < 0)
    {
        destroy(&in_fmt_ctx, &out_fmt_ctx, &audio_dec_ctx, &video_dec_ctx, &audio_enc_ctx, &video_enc_ctx, &audio_filter_graph, &video_filter_graph);
        return -1;
    }

    //转码
    if (transcoding(audio_stream_index, video_stream_index, in_fmt_ctx, audio_dec_ctx, video_dec_ctx, out_fmt_ctx, audio_enc_ctx, video_enc_ctx,
    audio_abuffer_filter_ctx, audio_abuffersink_filter_ctx, video_buffer_filter_ctx, video_buffersink_filter_ctx) < 0)
    {
        destroy(&in_fmt_ctx, &out_fmt_ctx, &audio_dec_ctx, &video_dec_ctx, &audio_enc_ctx, &video_enc_ctx, &audio_filter_graph, &video_filter_graph);
        return -1;
    }

    destroy(&in_fmt_ctx, &out_fmt_ctx, &audio_dec_ctx, &video_dec_ctx, &audio_enc_ctx, &video_enc_ctx, &audio_filter_graph, &video_filter_graph);
    return 0;
}

项目代码及使用方法:FFmpeg_Learning_Projects/Video_Transcoding at master · zn111111/FFmpeg_Learning_Projects (github.com)

  • 13
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值