最简单的基于FFMPEG的转码程序 —— 分析

时间:2023-03-10 06:17:17
最简单的基于FFMPEG的转码程序  —— 分析

模块:

    libavcodec    - 编码解码器
       
libavdevice   - 输入输出设备的支持
       
libavfilter   - 视音频滤镜支持
       
libavformat   - 视音频等格式的解析
       
libavutil    
- 工具库
       
libpostproc   - 后期效果处理
       
libswscale    -
图像颜色、尺寸转换

1. 主函数分析:

大致流程:

调用注册函数;

open_input_file打开输入文件;

open_output_file打开输出文件;

init_filters()  过滤器初始化;

完成  avformatcontext----avpacket----avstream----avcodeccontext----avcodec  解析包逐步得到编解码类型    ,其中av_read_frame读取数据到packet;

读取的包处理:

      (1)若含有过滤器:

         时间戳设置,调用avcodec_decode_video2 或  avcodec_decode_audio4解码;

         解码成功后,设置时间戳,调用filter_encode_write_frame编码

      (2) 不含过滤器:  修改时基,将packet直接放入输出文件

更新过滤器filter_encode_write_frame(NULL, i);   更新编码器flush_encoder(i)

1.1 代码详解

  1. int_tmain(int argc, _TCHAR* argv[])
  2. {
  3. int ret;
  4. AVPacketpacket;
  5. AVFrame *frame= NULL;
  6. enum AVMediaType type;
  7. unsigned intstream_index;
  8. unsigned int i;
  9. int got_frame;
  10. int (*dec_func)(AVCodecContext *, AVFrame *, int *, const AVPacket*);    //函数指针
  11. if (argc != 3) {
  12. av_log(NULL, AV_LOG_ERROR, "Usage: %s<input file> <output file>\n", argv[0]);     //用法说明
  13. return 1;
  14. }
  15. av_register_all();                //注册函数
  16. avfilter_register_all();
  17. if ((ret = open_input_file(argv[1])) < 0)
  18. goto end;
  19. if ((ret = open_output_file(argv[2])) < 0)
  20. goto end;
  21. if ((ret = init_filters()) < 0)
  22. goto end;                                     //至此,都是输入参数检查,  end:
  23. /* read all packets */
  24. while (1) {
  25. /*      avformatcontext----avpacket----avstream----avcodeccontext----avcodec  解析包逐步得到编解码类型                */
  26. if ((ret= av_read_frame(ifmt_ctx, &packet)) < 0)   /*int av_read_frame(AVFormatContext *s, AVPacket *pkt); 读取码流中的音频若干帧或者视频一帧,s 是输入的AVFormatContext,pkt是输出的AVPacket。这里ifmt_ctx是一个AVFormatContext  */
  27. break;
  28. stream_index = packet.stream_index;  // int   stream_index:标识该AVPacket所属的视频/音频流
  29. type =ifmt_ctx->streams[packet.stream_index]->codec->codec_type;  /* AVFormatContext:AVStream **streams:视音频流;  AVStream: AVCodecContext *codec:指向该视频/音频流的AVCodecContext;       AVCodecContext:   enum AVMediaType codec_type:编解码器的类型(视频,音频...)*/
  30. av_log(NULL, AV_LOG_DEBUG, "Demuxergave frame of stream_index %u\n",
  31. stream_index);
  32. if (filter_ctx[stream_index].filter_graph) {                         //  static FilteringContext *filter_ctx  完整的过滤器结构体,包含AVFilterGraph  *filter_graph过滤器图
  33. av_log(NULL, AV_LOG_DEBUG, "Going to reencode&filter the frame\n");
  34. frame =av_frame_alloc();        //   av_frame_alloc()  初始化 AVFrame
  35. if (!frame) {
  36. ret = AVERROR(ENOMEM);
  37. break;
  38. }
  39. /* int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd);是计算 "a * b / c" 的值并分五种方式来取整.

    用在FFmpeg中,
    则是将以 "时钟基c" 表示的 数值a 转换成以 "时钟基b" 来表示。 */

  40. packet.dts = av_rescale_q_rnd(packet.dts,      //   AVPacket:nt64_t  dts解码时间戳
  41. ifmt_ctx->streams[stream_index]->time_base,
  42. ifmt_ctx->streams[stream_index]->codec->time_base,
  43. (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
  44. packet.pts = av_rescale_q_rnd(packet.pts,    //AVPacket:int64_t pts显示时间戳
  45. ifmt_ctx->streams[stream_index]->time_base,
  46. ifmt_ctx->streams[stream_index]->codec->time_base,
  47. (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
  48. dec_func = (type == AVMEDIA_TYPE_VIDEO) ? avcodec_decode_video2 :
  49. avcodec_decode_audio4; //avcodec_decodec_video2()视频解码函数,avcodec_decode_audio4 音频解码函数
  50. /*   int avcodec_decode_video2( AVCodecContext* avctx, AVFrame* picture,int* got_pitcure_ptr,const AVPacket* avpkt)
  51. avctx:解码器    picture:保存输出的视频帧  got_picture_ptr: 0表示没有可以解码的, avpkt: 包含输入buffer的AVPacket
  52. */
  53. ret =dec_func(ifmt_ctx->streams[stream_index]->codec, frame,
  54. &got_frame, &packet);
  55. if (ret < 0) {                             //解码失败
  56. av_frame_free(&frame);
  57. av_log(NULL, AV_LOG_ERROR, "Decodingfailed\n");
  58. break;
  59. }
  60. if (got_frame) {                  //解码成功
  61. //
    int64_t av_frame_get_best_effort_timestamp ( const AVFrame frame )  
  62. frame->pts = av_frame_get_best_effort_timestamp(frame);
  63. ret= filter_encode_write_frame(frame, stream_index);        //filter_encode_write_frame():编码一个AVFrame
  64. av_frame_free(&frame);
  65. if (ret< 0)
  66. goto end;
  67. } else {
  68. av_frame_free(&frame);
  69. }
  70. } else {
  71. /*     remux this frame without reencoding   重新复用frame,没有重新编码的情况下    * /
  72. packet.dts = av_rescale_q_rnd(packet.dts,
  73. ifmt_ctx->streams[stream_index]->time_base,
  74. ofmt_ctx->streams[stream_index]->time_base,
  75. (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
  76. packet.pts = av_rescale_q_rnd(packet.pts,
  77. ifmt_ctx->streams[stream_index]->time_base,
  78. ofmt_ctx->streams[stream_index]->time_base,
  79. (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
  80. ret =av_interleaved_write_frame(ofmt_ctx, &packet);  //av_interleaved_write_frame写入一个AVPacket到输出文件
  81. if (ret < 0)
  82. goto end;
  83. }
  84. av_free_packet(&packet);
  85. }
  86. /* flush filters and encoders 刷新过滤器和编码器  * /
  87. for (i = 0; i < ifmt_ctx->nb_streams; i++) {
  88. /*刷新过滤器flush filter    * /
  89. if (!filter_ctx[i].filter_graph)
  90. continue;
  91. ret =filter_encode_write_frame(NULL, i);
  92. if (ret < 0) {
  93. av_log(NULL, AV_LOG_ERROR, "Flushingfilter failed\n");
  94. goto end;
  95. }
  96. /* 刷新编码器 flush  encoder  , flush_encoder():输入文件读取完毕后,输出编码器中剩余的AVPacket* /
  97. ret = flush_encoder(i);
  98. if (ret < 0) {
  99. av_log(NULL, AV_LOG_ERROR, "Flushingencoder failed\n");
  100. goto end;
  101. }
  102. }
  103. av_write_trailer(ofmt_ctx);        //输出文件尾
  104. end:
  105. /* 释放个个结构体   packet,frame----codec----filter----ifmt_ctx,ofmt_ctx    */
  106. av_free_packet(&packet);         //av_free_packet,   释放AVPacket对象
  107. av_frame_free(&frame);           //av_frame_free,   释放AVFrame对象
  108. for (i = 0; i < ifmt_ctx->nb_streams; i++) {
  109. avcodec_close(ifmt_ctx->streams[i]->codec);
  110. if (ofmt_ctx && ofmt_ctx->nb_streams >i && ofmt_ctx->streams[i] &&ofmt_ctx->streams[i]->codec)
  111. avcodec_close(ofmt_ctx->streams[i]->codec);
  112. if(filter_ctx && filter_ctx[i].filter_graph)
  113. avfilter_graph_free(&filter_ctx[i].filter_graph);
  114. }
  115. av_free(filter_ctx);
  116. avformat_close_input(&ifmt_ctx);
  117. if (ofmt_ctx &&!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
  118. avio_close(ofmt_ctx->pb);
  119. avformat_free_context(ofmt_ctx);
  120. if (ret < 0)
  121. av_log(NULL, AV_LOG_ERROR, "Erroroccurred\n");
  122. return (ret? 1:0);
  123. }

1.2   int _tmain(int argc, _TCHAR* argv[])

   用过C的人都知道每一个C的程序都会有一个main(),但有时看别人写的程序发现主函数不是int main(),而是int _tmain(),而且头文件也不是<iostream.h>而是<stdafx.h>,会困惑吧?首先,这个_tmain() 是为了支持unicode所使用的main一个别名而已,既然是别名,应该有宏定义过的,在哪里定义的呢?就在那个让你困惑 的<stdafx.h>里,有这么两行
  #include <stdio.h>
  #include <tchar.h>
我们可以在头文件<tchar.h>里找到_tmain的宏定义
  #define _tmain main
所以,经过预编译以后, _tmain就变成main了

//_TCHAR类型是宽字符型字符串,和我们一般常用的字符串不同,它是32位或者更 高的操作系统中所使用的类型.

2. 头部

(1). extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编 译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。 这个功能十分有用处,因为在C++出现以前,很多代码都是C语言写的,而且很底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库, 需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。

  这个功能主要用在下面的情况:

    1)、C++代码调用C语言代码

    2)、在C++的头文件中使用

    3)、在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长C++,这样的情况下也会有用到

(2).   基本的过滤器使用流程是:

解码后的画面--->buffer过滤器---->其他过滤器---->buffersink过滤器--->处理完的画面

所有的过滤器形成了过滤器链,一定要的两个过滤器是buffer过滤器和buffersink过滤器,前者的作用是将解码后的画面加载到过滤器链中,后者的作用是将处理好的画面从过滤器链中读取出来.

过滤器相关的结构体:

  AVFilterGraph: 管理所有的过滤器图像

  AVFilterContext: 过滤器上下文

   AVFilter: 过滤器

    1. #include "stdafx.h"
    2. extern "C"
    3. {
    4. #include "libavcodec/avcodec.h"
    5. #include "libavformat/avformat.h"
    6. #include "libavfilter/avfiltergraph.h"
    7. #include "libavfilter/avcodec.h"
    8. #include "libavfilter/buffersink.h"
    9. #include "libavfilter/buffersrc.h"
    10. #include "libavutil/avutil.h"
    11. #include "libavutil/opt.h"
    12. #include "libavutil/pixdesc.h"
    13. };
    14. /* 三个重要的结构体:  AVFormatContext *ifmt_ctx  ,AVFormatContext *ofmt_ctx , FilteringContext *filter_ctx
    15. */

    16. static AVFormatContext *ifmt_ctx;
    17. static AVFormatContext *ofmt_ctx;
    18. typedef struct FilteringContext{
    19. AVFilterContext*buffersink_ctx;
    20. AVFilterContext*buffersrc_ctx;
    21. AVFilterGraph*filter_graph;
    22. } FilteringContext;
    23. static FilteringContext *filter_ctx;

3. static int open_input_file(const char *filename)函数———打开输入文件,获取流信息,打开解码器,打印输入输出信息

(该函数在ffmpeg文件 transcoding.c)

说明:open_input_file():打开输入文件,并初始化相关的结构体,

(1).打开媒体的的过程开始于avformat_open_input函数

  int avformat_open_input(AVFormatContext** ps, const char* filename,AVInputFormat* fmt,AVDictionary** options):

作用: 打开输入流,读取头部;   codecs没有打开,最后关闭流需要使用avformat_close_input();  正确执行返回0

参数:  ps: 指向用户提供的AVFormatContext(由avformat_alloc_context分配);可以指向NULL(当AVFormatContext由该函数分配,并写入到ps中)

    filename:   打开输入流的名字      fmt:如果非null,即特定的输入形式,否则自动检测格式      option:其他

(2). 获取相关信息 avformat_find_stream_info

  int avformat_find_stream_info(AVFormatContext* ic, AVDictionary** options)

作用:读取媒体文件的包,得到流信息

参数: ic:处理的媒体文件,即输入的 AVFormatContext ,  option 可选项;   正常执行后返回值大于等于0。

(3).打开解码器avcodec_open2

int avcodec_open2(AVCodecContext* avctx, const AVCodec * codec, AVDictionary** options),正确执行返回0

作用:初始化AVCodecContext使得使用给定的编解码器AVCodec

说明:(1). 使用函数avcodec_find_decoder_by_name(), avcodec_find_encoder_by_name(), avcodec_find_decoder() and avcodec_find_encoder()ti提供编解码器  ;   (2).该函数不是线程安全的??? ; (3).通常在解码程序之前调用,比如avcodec_decode_video2()

(4).av_dump_format

  void  av_dump_format (AVFormatContext* ic,   int index, const char* url,  int is_output)

作用:打印输入输出格式的详细信息,比如持续时间duration,比特率 bitrate,流 streams,容器 container, 程序programs,元数据 metadata, side data, 编解码   器codec ,时基 time base.

参数: ic:要分析的context,    index:流索引,    url:打印的URL,比如源文件或者目标文件, is_output:选择指定的context是input(0) or output(1)

  1. static int open_input_file(const char *filename)
  2. {
  3. int ret;
  4. unsigned int i;
  5. ifmt_ctx =NULL;
  6. /*   avformat_open_input 打开输入流    失败报错 */
  7. if ((ret = avformat_open_input(&ifmt_ctx,filename, NULL, NULL)) < 0) {
  8. av_log(NULL, AV_LOG_ERROR, "Cannot openinput file\n");
  9. return ret;
  10. }
  11. /*   avformat_open_input 获取流信息   失败报错 */
  12. if ((ret = avformat_find_stream_info(ifmt_ctx, NULL))< 0) {
  13. av_log(NULL, AV_LOG_ERROR, "Cannot findstream information\n");
  14. return ret;
  15. }
  16. for (i = 0; i < ifmt_ctx->nb_streams; i++) {
  17. AVStream*stream;
  18. AVCodecContext *codec_ctx;
  19. /*   AVFormatContext : ifmt_ctx--- AVStream: stream---AVCodecContext: codec_ctx    */
  20. stream =ifmt_ctx->streams[i];
  21. codec_ctx =stream->codec;
  22. /*Reencode video & audio and remux subtitles(字幕) etc   */
  23. if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO
  24. ||codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
  25. /* 打开解码器 open  decoder   */
  26. ret =avcodec_open2(codec_ctx,
  27. avcodec_find_decoder(codec_ctx->codec_id), NULL);
  28. if (ret < 0) {
  29. av_log(NULL, AV_LOG_ERROR, "Failed toopen decoder for stream #%u\n", i);
  30. return ret;
  31. }
  32. }
  33. }
  34. av_dump_format(ifmt_ctx, 0, filename, 0);     //打印数据信息
  35. return 0;
  36. }

4.satic int open_output_file(const char *filename)———打开输出文件;输入输出的AVStream,AVFormatContex;视频音频分别设置输出数据,其他的直接拷贝设置; 设置编码器;  打印信息到目标文件 ; 写入头文件

(该函数即ffmpeg文件中 transcoding.c)

(1).avformat_alloc_output_context2

  int  avformat_alloc_output_context2(AVFormatContext** ctx,AVOutputFormat* oformat, const char* format_name, const char* filename)

作用:分配一个用于输出格式的AVFormatContext,可以使用avformat_free_context() 释放;  正确返回大于等于0

参数:ctx:函数成功后创建的AVFormatContext结构体;

   oformat:指定AVFormatContext中的AVOutputFormat,用于确定输出格式。如果指定为NULL,可以设定后两个参数(format_name或者filename)由          FFmpeg猜测输出格式。使用该参数需要自己手动获取AVOutputFormat,相对于使用后两个参数来说要麻烦一些。

   format_name:指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等。

     filename:指定输出文件的名称。根据文件名称,FFmpeg会推测输出格式。文件名称可以是“xx.flv”,“yy.mkv”等等。

(2).avformat_new_stream

  AVStream* avformat_new_stream(AVFormatContext* s,  const AVCodec*  c)

作用:在给定的context中增加一个新的流,

(3).avcodec_copy_context

  int  avcodec_copy_context(AVCodecContext*  dest,   const AVCodecContext* src)

作用:拷贝源AVCodecContext的设置到目的AVCodecContext

(4).avformat_write_header

  int  avformat_write_header(AVFormatContext *  s, AVDictionary** options)

作用:分配流的私有数据,并将流的头部写入到输出文件;   正确返回0

  1. static int open_output_file(const char *filename)
  2. {
  3. AVStream*out_stream;
  4. AVStream*in_stream;
  5. AVCodecContext*dec_ctx, *enc_ctx;
  6. AVCodec*encoder;
  7. int ret;
  8. unsigned int i;
  9. ofmt_ctx =NULL;
  10. avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename);           //创建输出 AVFormatContext格式的ofmt_ctx,
  11. if (!ofmt_ctx) {
  12. av_log(NULL, AV_LOG_ERROR, "Could notcreate output context\n");
  13. return AVERROR_UNKNOWN;
  14. }
  15. for (i = 0; i < ifmt_ctx->nb_streams; i++) {
  16. out_stream= avformat_new_stream(ofmt_ctx, NULL);                   //创建一条流
  17. if (!out_stream) {
  18. av_log(NULL, AV_LOG_ERROR, "Failedallocating output stream\n");
  19. return AVERROR_UNKNOWN;
  20. }
  21. in_stream =ifmt_ctx->streams[i];
  22. dec_ctx =in_stream->codec;                  //dec_ctx:  输入AVFormatContext中的AVCodecContext
  23. enc_ctx =out_stream->codec;               //enc_ctx:  输出AVFormatContext中的AVCodecContext
  24. if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO
  25. ||dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
  26. /*in this example, we choose transcoding to same codec    这里使用相同的编解码器    */
  27. encoder= avcodec_find_encoder(dec_ctx->codec_id);
  28. /* In this example, we transcode to same properties(picture size,
  29. * sample rate etc.). These properties can be changed for output
  30. * streams easily using filters */
  31. if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {       //视频:设置输出的高,宽,纵横比,pix_fms ,时基
  32. enc_ctx->height = dec_ctx->height;
  33. enc_ctx->width = dec_ctx->width;
  34. enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;
  35. /* take first format from list of supported formats */
  36. enc_ctx->pix_fmt = encoder->pix_fmts[0];
  37. /* video time_base can be set to whatever is handy andsupported by encoder */
  38. enc_ctx->time_base = dec_ctx->time_base;
  39. } else {                                                                   //音频:设置输出的采样率,声道,声道数,,sample_fms...
  40. enc_ctx->sample_rate = dec_ctx->sample_rate;
  41. enc_ctx->channel_layout = dec_ctx->channel_layout;
  42. enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout);
  43. /* take first format from list of supported formats */
  44. enc_ctx->sample_fmt = encoder->sample_fmts[0];
  45. AVRationaltime_base={1, enc_ctx->sample_rate}; // AVRational time_base:根据该参数,可以把PTS转化为实际的时间(单位为秒s)
  46. enc_ctx->time_base = time_base;
  47. }
  48. /* Third parameter can be used to pass settings to encoder*/
  49. ret =avcodec_open2(enc_ctx, encoder, NULL);          // 为enc_ctx(输出AVFormatContext中的AVCodecContext)   初始化编码器
  50. if (ret < 0) {
  51. av_log(NULL, AV_LOG_ERROR, "Cannot openvideo encoder for stream #%u\n", i);
  52. return ret;
  53. }
  54. } else if(dec_ctx->codec_type == AVMEDIA_TYPE_UNKNOWN) {
  55. av_log(NULL, AV_LOG_FATAL, "Elementarystream #%d is of unknown type, cannot proceed\n", i);
  56. return AVERROR_INVALIDDATA;
  57. } else {
  58. /*假设该流一定要 if this stream must be remuxed   */
  59. ret =avcodec_copy_context(ofmt_ctx->streams[i]->codec,      //拷贝设置,即复用??
  60. ifmt_ctx->streams[i]->codec);
  61. if (ret < 0) {
  62. av_log(NULL, AV_LOG_ERROR, "Copyingstream context failed\n");
  63. return ret;
  64. }
  65. }
  66. if (ofmt_ctx->oformat->flags &AVFMT_GLOBALHEADER)
  67. enc_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER;
  68. }
  69. av_dump_format(ofmt_ctx, 0, filename, 1);     //打印ofmt_ctx的信息到文件filename
  70. if (!(ofmt_ctx->oformat->flags &AVFMT_NOFILE)) {
  71. ret =avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE);
  72. if (ret < 0) {
  73. av_log(NULL, AV_LOG_ERROR, "Could notopen output file '%s'", filename);
  74. return ret;
  75. }
  76. }
  77. /* init muxer, write output file header */
  78. ret =avformat_write_header(ofmt_ctx, NULL);       //写入头文件
  79. if (ret < 0) {
  80. av_log(NULL,AV_LOG_ERROR, "Error occurred when openingoutput file\n");
  81. return ret;
  82. }
  83. return 0;
  84. }

5.intinit_filter函数———— 初始化结构体; 视频音频分别  获取buffer过滤器,buffersink过滤器,并且创建过滤器,存储视频音频信息;  创建过滤器端; 连接参数传递的过滤器到过滤器图中,配置过滤器图的链路,  最后生成FilteringContext

(该函数即ffmpeg文件中 transcoding.c)

解码后的画面--->buffer过滤器---->其他过滤器---->buffersink过滤器--->处理完的画面

1).所有的过滤器形成了过滤器链,一定要的两个过滤器是buffer过滤器和buffersink过滤器,前者的作用是将解码后的画面加载到过滤器链中,后者  的作用是将处理好的画面从过滤器链中读取出来.

2). 过滤器相关的结构体:   AVFilterGraph: 管理所有的过滤器图像;   AVFilterContext: 过滤器上下文;    AVFilter过滤器;

3).本程序中的定义:

    typedef struct FilteringContext {
        AVFilterContext *buffersink_ctx;
        AVFilterContext *buffersrc_ctx;
        AVFilterGraph *filter_graph;
    } FilteringContext;
    static FilteringContext *filter_ctx;

AVFilterInOut

4). AVFilterInOut 结构体

最简单的基于FFMPEG的转码程序  —— 分析

作用:过滤器链的输入输出链

This is mainly useful for avfilter_graph_parse() / avfilter_graph_parse2(), where it is used to communicate open (unlinked) inputs and outputs from and to the caller. This struct specifies, per each not connected pad contained in the graph, the filter context and the pad index required for establishing a link.

(1).avfilter_inout_alloc()——分配一个AVFilterInOut; 使用 avfilter_inout_free() 释放

最简单的基于FFMPEG的转码程序  —— 分析

(2).avfilter_graph_alloc——分配一个AVFilterGraph

最简单的基于FFMPEG的转码程序  —— 分析

(3).avfilter_get_by_name——获取过滤器

最简单的基于FFMPEG的转码程序  —— 分析

(4).snprintf(),

为函数原型int snprintf(char *str, size_t size, const char *format, ...),将可变个参数(...)按照format格式化成字符串,然后将其复制到str中

  1) 如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符('\0');
  2) 如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符('\0'),返回值为格式化后的字符串的长度。
    char a[20];
    i = snprintf(a, 9, "%012d", 12345);
    printf("i = %d, a = %s", i, a);
  输出为:i = 12, a = 00000001

(5).avfilter_graph_create_filter——创建过滤器并且添加到存在的AVFilterGraph 中

最简单的基于FFMPEG的转码程序  —— 分析

(6). av_opt_set_bin   ——用于设置参数

最简单的基于FFMPEG的转码程序  —— 分析

最简单的基于FFMPEG的转码程序  —— 分析

(7).av_get_default_channel_layout ——信道数?

最简单的基于FFMPEG的转码程序  —— 分析

(8).avfilter_graph_parse_ptr——连接过滤器,即添加字符串描述的过滤器  filters到整个 过滤器图 graph中

最简单的基于FFMPEG的转码程序  —— 分析

(9).avfilter_graph_config——检查有效性以及 graph中所有链路和格式的配置

最简单的基于FFMPEG的转码程序  —— 分析

  1. static intinit_filter(FilteringContext* fctx, AVCodecContext *dec_ctx,
  2. AVCodecContext *enc_ctx, const char *filter_spec)
  3. {
  4. char args[512];
  5. int ret = 0;
  6. AVFilter*buffersrc = NULL;
  7. AVFilter*buffersink = NULL;
  8. AVFilterContext*buffersrc_ctx = NULL;
  9. AVFilterContext*buffersink_ctx = NULL;
  10. AVFilterInOut*outputs = avfilter_inout_alloc();
  11. AVFilterInOut*inputs  = avfilter_inout_alloc();
  12. AVFilterGraph*filter_graph = avfilter_graph_alloc();
  13. if (!outputs || !inputs || !filter_graph) {
  14. ret =AVERROR(ENOMEM);
  15. goto end;
  16. }                                         //初始化各个结构体
  17. /*    视频  过滤器    */
  18. if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
  19. buffersrc =avfilter_get_by_name("buffer");
  20. buffersink= avfilter_get_by_name("buffersink");
  21. if (!buffersrc || !buffersink) {
  22. av_log(NULL, AV_LOG_ERROR, "filteringsource or sink element not found\n");
  23. ret = AVERROR_UNKNOWN;
  24. goto end;
  25. }                                //获取buffer过滤器和 buffersink过滤器
  26. _snprintf(args, sizeof(args),
  27. "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
  28. dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
  29. dec_ctx->time_base.num,dec_ctx->time_base.den,
  30. dec_ctx->sample_aspect_ratio.num,
  31. dec_ctx->sample_aspect_ratio.den);         //字符数组args存储视频的相关信息
  32. ret =avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
  33. args, NULL, filter_graph);
  34. if (ret < 0) {
  35. av_log(NULL, AV_LOG_ERROR, "Cannotcreate buffer source\n");
  36. goto end;
  37. }                                                   //    创建 buffersrc过滤器
  38. ret =avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
  39. NULL, NULL, filter_graph);
  40. if (ret < 0) {
  41. av_log(NULL, AV_LOG_ERROR, "Cannotcreate buffer sink\n");
  42. goto end;
  43. }                                                        //创建 buffersink过滤器
  44. ret =av_opt_set_bin(buffersink_ctx, "pix_fmts",
  45. (uint8_t*)&enc_ctx->pix_fmt, sizeof(enc_ctx->pix_fmt),            //pix_fmts  视频像素格式
  46. AV_OPT_SEARCH_CHILDREN);
  47. if (ret < 0) {
  48. av_log(NULL, AV_LOG_ERROR, "Cannot setoutput pixel format\n");
  49. goto end;
  50. }
  51. /*    音频  过滤器    */
  52. } else if(dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
  53. buffersrc = avfilter_get_by_name("abuffer");
  54. buffersink= avfilter_get_by_name("abuffersink");
  55. if (!buffersrc || !buffersink) {
  56. av_log(NULL, AV_LOG_ERROR, "filteringsource or sink element not found\n");
  57. ret =AVERROR_UNKNOWN;
  58. goto end;
  59. }                                                        //获取buffer过滤器和 buffersink过滤器
  60. if (!dec_ctx->channel_layout)
  61. dec_ctx->channel_layout =
  62. av_get_default_channel_layout(dec_ctx->channels);      //信道
  63. _snprintf(args, sizeof(args),
  64. "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%I64x",
  65. dec_ctx->time_base.num, dec_ctx->time_base.den,dec_ctx->sample_rate,
  66. av_get_sample_fmt_name(dec_ctx->sample_fmt),
  67. dec_ctx->channel_layout);           //字符数组args存储音频的相关信息
  68. ret =avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
  69. args, NULL, filter_graph);
  70. if (ret < 0) {
  71. av_log(NULL, AV_LOG_ERROR, "Cannotcreate audio buffer source\n");
  72. goto end;
  73. }                                                     //    创建 buffer过滤器
  74. ret =avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
  75. NULL, NULL, filter_graph);
  76. if (ret < 0) {
  77. av_log(NULL, AV_LOG_ERROR, "Cannotcreate audio buffer sink\n");
  78. goto end;
  79. }                                                      //    创建 buffersink过滤器
  80. ret = av_opt_set_bin(buffersink_ctx, "sample_fmts",
  81. (uint8_t*)&enc_ctx->sample_fmt, sizeof(enc_ctx->sample_fmt),
  82. AV_OPT_SEARCH_CHILDREN);
  83. if (ret < 0) {
  84. av_log(NULL, AV_LOG_ERROR, "Cannot setoutput sample format\n");
  85. goto end;
  86. }                                            //    sample_fmts音频样本格式
  87. ret =av_opt_set_bin(buffersink_ctx, "channel_layouts",
  88. (uint8_t*)&enc_ctx->channel_layout,
  89. sizeof(enc_ctx->channel_layout),AV_OPT_SEARCH_CHILDREN);
  90. if (ret < 0) {
  91. av_log(NULL, AV_LOG_ERROR, "Cannot setoutput channel layout\n");
  92. goto end;
  93. }
  94. ret =av_opt_set_bin(buffersink_ctx, "sample_rates",
  95. (uint8_t*)&enc_ctx->sample_rate, sizeof(enc_ctx->sample_rate),
  96. AV_OPT_SEARCH_CHILDREN);
  97. if (ret < 0) {
  98. av_log(NULL, AV_LOG_ERROR, "Cannot setoutput sample rate\n");
  99. goto end;
  100. }                                          //  sample_rates  音频采样率
  101. } else {
  102. ret =AVERROR_UNKNOWN;
  103. goto end;
  104. }
  105. /*   ndpoints for the filter graph*过滤图的端设置/
  106. outputs->name       =av_strdup("in");
  107. outputs->filter_ctx = buffersrc_ctx;
  108. outputs->pad_idx    = 0;
  109. outputs->next       = NULL;
  110. inputs->name       = av_strdup("out");
  111. inputs->filter_ctx = buffersink_ctx;
  112. inputs->pad_idx    = 0;
  113. inputs->next       = NULL;
  114. if (!outputs->name || !inputs->name) {
  115. ret =AVERROR(ENOMEM);
  116. goto end;
  117. }
  118. if ((ret = avfilter_graph_parse_ptr(filter_graph,filter_spec,          // 添加字符串filter_spec描述的过滤器到过滤器图中
  119. &inputs, &outputs, NULL)) < 0)
  120. goto end;
  121. if ((ret = avfilter_graph_config(filter_graph, NULL))< 0)             //检查有效性以及 graph中所有链路和格式的配置
  122. goto end;
  123. /*   Fill FilteringContext        */
  124. fctx->buffersrc_ctx = buffersrc_ctx;
  125. fctx->buffersink_ctx = buffersink_ctx;
  126. fctx->filter_graph= filter_graph;
  127. end:
  128. avfilter_inout_free(&inputs);
  129. avfilter_inout_free(&outputs);
  130. return ret;
  131. }

6.  init_filters函数——— 针对视频和音频;分别调用相应的滤波器类型,添加到滤波器图中

(1).av_malloc_array

最简单的基于FFMPEG的转码程序  —— 分析

  1. static int init_filters(void)
  2. {
  3. const char*filter_spec;
  4. unsigned int i;
  5. int ret;
  6. filter_ctx =(FilteringContext *)av_malloc_array(ifmt_ctx->nb_streams, sizeof(*filter_ctx));  //分配过滤器 ,大小是流数目乘以*filter_ctx;FilteringContext是自定义的过滤器大结构体
  7. if (!filter_ctx)
  8. return AVERROR(ENOMEM);
  9. for (i = 0; i < ifmt_ctx->nb_streams; i++) {
  10. filter_ctx[i].buffersrc_ctx  =NULL;
  11. filter_ctx[i].buffersink_ctx= NULL;
  12. filter_ctx[i].filter_graph   =NULL;
  13. if(!(ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO                //如果不是视频或者音频,开始下一次循环直接
  14. ||ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO))
  15. continue;
  16. if (ifmt_ctx->streams[i]->codec->codec_type== AVMEDIA_TYPE_VIDEO)                //设置滤波器类型
  17. filter_spec = "null";        /* passthrough (dummy) filter for video */   ?????
  18. else
  19. filter_spec = "anull";      /* passthrough (dummy) filter for audio */
  20. ret =init_filter(&filter_ctx[i], ifmt_ctx->streams[i]->codec,                                      //调用init_filter函数,生成滤波器图
  21. ofmt_ctx->streams[i]->codec, filter_spec);
  22. if (ret)
  23. return ret;
  24. }
  25. return 0;
  26. }

7. encode_write_frame函数      ———视音频选择编码函数;  编码过滤的帧 (初始化 packet的可选项,编码,释放编码过的帧);   准备packet用于复用, 复用编码帧( 交织写一个packet到输出文件)

  Referenced by filter_encode_write_frame(), and flush_encoder()

(1).av_init_packet:默认值初始化packet的可选项;    不涉及data  和size 属性,两者需要分别初始化

最简单的基于FFMPEG的转码程序  —— 分析

(2).av_rescale_q_rnd:重新调整 64—bit的整数,根据2个有理数按照指定的舍入

最简单的基于FFMPEG的转码程序  —— 分析

(3).av_interleaved_write_frame:写一个packet到输出文件,确保正确的交织

最简单的基于FFMPEG的转码程序  —— 分析

  1. static int encode_write_frame(AVFrame *filt_frame, unsigned int stream_index, int*got_frame) {
  2. int ret;
  3. int got_frame_local;
  4. AVPacket enc_pkt;
  5. /*根据视频还是音频选择各自的 编码函数,即avcodec_encode_video2 :或者avcodec_encode_audio2  ,enc_func是函数指针  */
  6. int (*enc_func)(AVCodecContext *, AVPacket *, const AVFrame *, int*) =
  7. (ifmt_ctx->streams[stream_index]->codec->codec_type ==
  8. AVMEDIA_TYPE_VIDEO) ? avcodec_encode_video2 : avcodec_encode_audio2;
  9. if (!got_frame)                                                   //。。。。。。。。。
  10. got_frame =&got_frame_local;
  11. av_log(NULL,AV_LOG_INFO, "Encoding frame\n");
  12. /*   编码过滤的帧  encode filtered frame*/
  13. enc_pkt.data =NULL;
  14. enc_pkt.size =0;
  15. av_init_packet(&enc_pkt);                //初始化 AVPacket enc_pkt的可选项,不包括  data和size
  16. ret =enc_func(ofmt_ctx->streams[stream_index]->codec, &enc_pkt,
  17. filt_frame, got_frame);           //编码,输入的AVFrame 为 filt_frame,输出的AVPacket 为enc_pkt,其中got_frame成功编码一个AVPacket的时候设置为1
  18. av_frame_free(&filt_frame);          //释放编码过的帧
  19. if (ret < 0)
  20. return ret;
  21. if (!(*got_frame))
  22. return 0;
  23. /*   prepare packet for muxing  准备packet  用于复用    */
  24. enc_pkt.stream_index = stream_index;
  25. enc_pkt.dts =av_rescale_q_rnd(enc_pkt.dts,                                // 调整 包enc_pkt的   解码时间戳   dts
  26. ofmt_ctx->streams[stream_index]->codec->time_base,
  27. ofmt_ctx->streams[stream_index]->time_base,
  28. (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
  29. enc_pkt.pts =av_rescale_q_rnd(enc_pkt.pts,                               // 调整 包enc_pkt的   显示时间戳  pts
  30. ofmt_ctx->streams[stream_index]->codec->time_base,
  31. ofmt_ctx->streams[stream_index]->time_base,
  32. (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
  33. enc_pkt.duration = av_rescale_q(enc_pkt.duration,                     // 调整 包enc_pkt的     持续时间  duration
  34. ofmt_ctx->streams[stream_index]->codec->time_base,
  35. ofmt_ctx->streams[stream_index]->time_base);
  36. av_log(NULL,AV_LOG_DEBUG, "Muxing frame\n");
  37. /*mux encoded frame     复用 编码帧   */
  38. ret =av_interleaved_write_frame(ofmt_ctx, &enc_pkt);       //写一个packet到输出文件,确保正确的交织
  39. return ret;
  40. }

8.filter_encode_write_frame函数————将编码过的帧放入过滤器图,再从中拉出来,继而调用encode_write_frame函数编码(1).av_buffersrc_add_frame_flags: 添加一个帧到 buffer  source(AVFilterContext

最简单的基于FFMPEG的转码程序  —— 分析

(2).av_buffersink_get_frame:从buffersink(AVFilterContext )过滤过的数据中得到一个帧,并放倒frame中

最简单的基于FFMPEG的转码程序  —— 分析

(3).filt_frame->pict_type = AV_PICTURE_TYPE_NONE(其中filt_frame是一个AVFrame): 即帧的图像类型

最简单的基于FFMPEG的转码程序  —— 分析

最简单的基于FFMPEG的转码程序  —— 分析

  1. static int filter_encode_write_frame(AVFrame *frame, unsignedint stream_index)
  2. {
  3. int ret;
  4. AVFrame*filt_frame;
  5. av_log(NULL,AV_LOG_INFO, "Pushing decoded frame tofilters\n");
  6. /*将解码过的帧输入到过滤器图    push the decoded frame into the filtergraph */
  7. ret =av_buffersrc_add_frame_flags(filter_ctx[stream_index].buffersrc_ctx,      //添加帧到bunffersrc(AVFilterContext )过滤器
  8. frame,0);
  9. if (ret < 0) {
  10. av_log(NULL, AV_LOG_ERROR, "Error whilefeeding the filtergraph\n");
  11. return ret;
  12. }
  13. /*将从过滤器图中拉过滤过的帧 pull filtered frames from the filtergraph */
  14. while (1) {
  15. filt_frame= av_frame_alloc();
  16. if (!filt_frame) {
  17. ret =AVERROR(ENOMEM);
  18. break;
  19. }
  20. av_log(NULL, AV_LOG_INFO, "Pullingfiltered frame from filters\n");
  21. ret =av_buffersink_get_frame(filter_ctx[stream_index].buffersink_ctx,  filt_frame);//从buffersink(AVFilterContext )过滤器中得到一个帧
  22. if (ret < 0) {
  23. /*
  24. if nomore frames for output - returns AVERROR(EAGAIN)
  25. if flushed and no more frames for output - returns AVERROR_EOF
  26. rewrite retcode to 0 to show it as normal procedure completion
  27. */
  28. if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
  29. ret= 0;
  30. av_frame_free(&filt_frame);
  31. break;
  32. }
  33. filt_frame->pict_type = AV_PICTURE_TYPE_NONE;      //图像帧的类型 未定义
  34. ret =encode_write_frame(filt_frame, stream_index, NULL);       //编码
  35. if (ret < 0)
  36. break;
  37. }
  38. return ret;
  39. }

9.flush_encoder函数———刷新 流的编码器?

  1. static int flush_encoder(unsigned intstream_index)
  2. {
  3. int ret;
  4. int got_frame;
  5. if(!(ofmt_ctx->streams[stream_index]->codec->codec->capabilities&
  6. CODEC_CAP_DELAY))
  7. return 0;
  8. while (1) {
  9. av_log(NULL, AV_LOG_INFO, "Flushing stream #%u encoder\n", stream_index);
  10. ret =encode_write_frame(NULL, stream_index, &got_frame);
  11. if (ret < 0)
  12. break;
  13. if (!got_frame)
  14. return 0;
  15. }
  16. return ret;
  17. }