最簡單的基於FFMPEG的轉碼程序 —— 分析

轉載地址1
轉載地址2
模塊:

   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 代碼詳解

int_tmain(int argc, _TCHAR* argv[])
{
int ret;
AVPacketpacket;
AVFrame *frame= NULL;
enum AVMediaType type;
unsigned intstream_index;
unsigned int i;
int got_frame;
int (dec_func)(AVCodecContext , AVFrame , int , const AVPacket*); //函數指針
if (argc != 3) {
av_log(NULL, AV_LOG_ERROR, “Usage: %s \n”, argv[0]); //用法說明
return 1;
}
av_register_all(); //註冊函數
avfilter_register_all();
if ((ret = open_input_file(argv[1])) < 0)
goto end;
if ((ret = open_output_file(argv[2])) < 0)
goto end;
if ((ret = init_filters()) < 0)
goto end; //至此,都是輸入參數檢查, end:
/* read all packets */
while (1) {
/* avformatcontext—-avpacket—-avstream—-avcodeccontext—-avcodec 解析包逐步得到編解碼類型 */
if ((ret= av_read_frame(ifmt_ctx, &packet)) < 0) /int av_read_frame(AVFormatContext *s, AVPacket *pkt); 讀取碼流中的音頻若干幀或者視頻一幀,s 是輸入的AVFormatContext,pkt是輸出的AVPacket。這裏ifmt_ctx是一個AVFormatContext /
break;
stream_index = packet.stream_index; // int stream_index:標識該AVPacket所屬的視頻/音頻流
type =ifmt_ctx->streams[packet.stream_index]->codec->codec_type; /* AVFormatContext:AVStream *streams:視音頻流; AVStream: AVCodecContext *codec:指向該視頻/音頻流的AVCodecContext; AVCodecContext: enum AVMediaType codec_type:編解碼器的類型(視頻,音頻…)/
av_log(NULL, AV_LOG_DEBUG, “Demuxergave frame of stream_index %u\n”,
stream_index);
if (filter_ctx[stream_index].filter_graph) { // static FilteringContext *filter_ctx 完整的過濾器結構體,包含AVFilterGraph *filter_graph過濾器圖
av_log(NULL, AV_LOG_DEBUG, “Going to reencode&filter the frame\n”);
frame =av_frame_alloc(); // av_frame_alloc() 初始化 AVFrame
if (!frame) {
ret = AVERROR(ENOMEM);
break;
}
/* int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd);是計算 “a * b / c” 的值並分五種方式來取整.
用在FFmpeg中, 則是將以 “時鐘基c” 表示的 數值a 轉換成以 “時鐘基b” 來表示。 */
packet.dts = av_rescale_q_rnd(packet.dts, // AVPacket:nt64_t dts解碼時間戳
ifmt_ctx->streams[stream_index]->time_base,
ifmt_ctx->streams[stream_index]->codec->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
packet.pts = av_rescale_q_rnd(packet.pts, //AVPacket:int64_t pts顯示時間戳
ifmt_ctx->streams[stream_index]->time_base,
ifmt_ctx->streams[stream_index]->codec->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
dec_func = (type == AVMEDIA_TYPE_VIDEO) ? avcodec_decode_video2 :
avcodec_decode_audio4; //avcodec_decodec_video2()視頻解碼函數,avcodec_decode_audio4 音頻解碼函數
/* int avcodec_decode_video2( AVCodecContext* avctx, AVFrame* picture,int* got_pitcure_ptr,const AVPacket* avpkt)
avctx:解碼器 picture:保存輸出的視頻幀 got_picture_ptr: 0表示沒有可以解碼的, avpkt: 包含輸入buffer的AVPacket
*/
ret =dec_func(ifmt_ctx->streams[stream_index]->codec, frame,
&got_frame, &packet);
if (ret < 0) { //解碼失敗
av_frame_free(&frame);
av_log(NULL, AV_LOG_ERROR, “Decodingfailed\n”);
break;
}
if (got_frame) { //解碼成功
//
int64_t av_frame_get_best_effort_timestamp ( const AVFrame * frame )

            frame->pts = av_frame_get_best_effort_timestamp(frame);  
            ret= filter_encode_write_frame(frame, stream_index);        //filter_encode_write_frame():編碼一個AVFrame
           av_frame_free(&frame);  
            if (ret< 0)  
               goto end;  
        } else {  
           av_frame_free(&frame);  
        }  
    } else {  
       /*     remux this frame without reencoding   重新複用frame,沒有重新編碼的情況下    * / 
       packet.dts = av_rescale_q_rnd(packet.dts,  
               ifmt_ctx->streams[stream_index]->time_base,  
               ofmt_ctx->streams[stream_index]->time_base,  
                (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));  
       packet.pts = av_rescale_q_rnd(packet.pts,  
               ifmt_ctx->streams[stream_index]->time_base,  
               ofmt_ctx->streams[stream_index]->time_base,  
                (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));  
        ret =av_interleaved_write_frame(ofmt_ctx, &packet);  //av_interleaved_write_frame寫入一個AVPacket到輸出文件
        if (ret < 0)  
            goto end;  
    }  
   av_free_packet(&packet);  
}  

/* flush filters and encoders 刷新過濾器和編碼器 * /
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
/*刷新過濾器flush filter * /
if (!filter_ctx[i].filter_graph)
continue;
ret =filter_encode_write_frame(NULL, i);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Flushingfilter failed\n”);
goto end;
}
/* 刷新編碼器 flush encoder , flush_encoder():輸入文件讀取完畢後,輸出編碼器中剩餘的AVPacket* /
ret = flush_encoder(i);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Flushingencoder failed\n”);
goto end;
}
}
av_write_trailer(ofmt_ctx); //輸出文件尾
end:
/* 釋放個個結構體 packet,frame—-codec—-filter—-ifmt_ctx,ofmt_ctx */
av_free_packet(&packet); //av_free_packet, 釋放AVPacket對象
av_frame_free(&frame); //av_frame_free, 釋放AVFrame對象
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
avcodec_close(ifmt_ctx->streams[i]->codec);
if (ofmt_ctx && ofmt_ctx->nb_streams >i && ofmt_ctx->streams[i] &&ofmt_ctx->streams[i]->codec)
avcodec_close(ofmt_ctx->streams[i]->codec);
if(filter_ctx && filter_ctx[i].filter_graph)
avfilter_graph_free(&filter_ctx[i].filter_graph);
}
av_free(filter_ctx);
avformat_close_input(&ifmt_ctx);
if (ofmt_ctx &&!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
avio_close(ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);
if (ret < 0)
av_log(NULL, AV_LOG_ERROR, “Erroroccurred\n”);
return (ret? 1:0);
}
1.2 int _tmain(int argc, _TCHAR* argv[])

   用過C的人都知道每一個C的程序都會有一個main(),但有時看別人寫的程序發現主函數不是int main(),而是int _tmain(),而且頭文件也不是

include “stdafx.h”

extern “C”
{

include “libavcodec/avcodec.h”

include “libavformat/avformat.h”

include “libavfilter/avfiltergraph.h”

include “libavfilter/avcodec.h”

include “libavfilter/buffersink.h”

include “libavfilter/buffersrc.h”

include “libavutil/avutil.h”

include “libavutil/opt.h”

include “libavutil/pixdesc.h”

};
/* 三個重要的結構體: AVFormatContext *ifmt_ctx ,AVFormatContext *ofmt_ctx , FilteringContext *filter_ctx
*/

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

  1. 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)

static int open_input_file(const char *filename)
{
int ret;
unsigned int i;
ifmt_ctx =NULL;
/* avformat_open_input 打開輸入流 失敗報錯 */
if ((ret = avformat_open_input(&ifmt_ctx,filename, NULL, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, “Cannot openinput file\n”);
return ret;
}
/* avformat_open_input 獲取流信息 失敗報錯 */
if ((ret = avformat_find_stream_info(ifmt_ctx, NULL))< 0) {
av_log(NULL, AV_LOG_ERROR, “Cannot findstream information\n”);
return ret;
}
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream*stream;
AVCodecContext *codec_ctx;
/* AVFormatContext : ifmt_ctx--- AVStream: stream---AVCodecContext: codec_ctx */
stream =ifmt_ctx->streams[i];
codec_ctx =stream->codec;
/*Reencode video & audio and remux subtitles(字幕) etc */
if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO
||codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
/* 打開解碼器 open decoder */
ret =avcodec_open2(codec_ctx,
avcodec_find_decoder(codec_ctx->codec_id), NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Failed toopen decoder for stream #%u\n”, i);
return ret;
}
}
}
av_dump_format(ifmt_ctx, 0, filename, 0); //打印數據信息
return 0;
}

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

static int open_output_file(const char *filename)
{
AVStream*out_stream;
AVStream*in_stream;
AVCodecContext*dec_ctx, *enc_ctx;
AVCodec*encoder;
int ret;
unsigned int i;
ofmt_ctx =NULL;
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename); //創建輸出 AVFormatContext格式的ofmt_ctx,
if (!ofmt_ctx) {
av_log(NULL, AV_LOG_ERROR, “Could notcreate output context\n”);
return AVERROR_UNKNOWN;
}
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
out_stream= avformat_new_stream(ofmt_ctx, NULL); //創建一條流
if (!out_stream) {
av_log(NULL, AV_LOG_ERROR, “Failedallocating output stream\n”);
return AVERROR_UNKNOWN;
}
in_stream =ifmt_ctx->streams[i];
dec_ctx =in_stream->codec; //dec_ctx: 輸入AVFormatContext中的AVCodecContext
enc_ctx =out_stream->codec; //enc_ctx: 輸出AVFormatContext中的AVCodecContext
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO
||dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
/*in this example, we choose transcoding to same codec 這裏使用相同的編解碼器 */
encoder= avcodec_find_encoder(dec_ctx->codec_id);
/* In this example, we transcode to same properties(picture size,
* sample rate etc.). These properties can be changed for output
* streams easily using filters */
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { //視頻:設置輸出的高,寬,縱橫比,pix_fms ,時基
enc_ctx->height = dec_ctx->height;
enc_ctx->width = dec_ctx->width;
enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;
/* take first format from list of supported formats */
enc_ctx->pix_fmt = encoder->pix_fmts[0];
/* video time_base can be set to whatever is handy andsupported by encoder */
enc_ctx->time_base = dec_ctx->time_base;
} else {    //音頻:設置輸出的採樣率,聲道,聲道數,,sample_fms…
enc_ctx->sample_rate = dec_ctx->sample_rate;
enc_ctx->channel_layout = dec_ctx->channel_layout;
enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout);
/* take first format from list of supported formats */
enc_ctx->sample_fmt = encoder->sample_fmts[0];
AVRationaltime_base={1, enc_ctx->sample_rate}; // AVRational time_base:根據該參數,可以把PTS轉化爲實際的時間(單位爲秒s)
enc_ctx->time_base = time_base;
}
/* Third parameter can be used to pass settings to encoder*/
ret =avcodec_open2(enc_ctx, encoder, NULL); // 爲enc_ctx(輸出AVFormatContext中的AVCodecContext) 初始化編碼器
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Cannot openvideo encoder for stream #%u\n”, i);
return ret;
}
} else if(dec_ctx->codec_type == AVMEDIA_TYPE_UNKNOWN) {
av_log(NULL, AV_LOG_FATAL, “Elementarystream #%d is of unknown type, cannot proceed\n”, i);
return AVERROR_INVALIDDATA;
} else {
/*假設該流一定要 if this stream must be remuxed */
ret =avcodec_copy_context(ofmt_ctx->streams[i]->codec, //拷貝設置,即複用??
ifmt_ctx->streams[i]->codec);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Copyingstream context failed\n”);
return ret;
}
}
if (ofmt_ctx->oformat->flags &AVFMT_GLOBALHEADER)
enc_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
av_dump_format(ofmt_ctx, 0, filename, 1); //打印ofmt_ctx的信息到文件filename
if (!(ofmt_ctx->oformat->flags &AVFMT_NOFILE)) {
ret =avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Could notopen output file ‘%s’”, filename);
return ret;
}
}
/* init muxer, write output file header */
ret =avformat_write_header(ofmt_ctx, NULL); //寫入頭文件
if (ret < 0) {
av_log(NULL,AV_LOG_ERROR, “Error occurred when openingoutput file\n”);
return ret;
}
return 0;
}

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 結構體

作用:過濾器鏈的輸入輸出鏈

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() 釋放

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

(3).avfilter_get_by_name——獲取過濾器

(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 中

(6). av_opt_set_bin ——用於設置參數

(7).av_get_default_channel_layout ——信道數?

(8).avfilter_graph_parse_ptr——連接過濾器,即添加字符串描述的過濾器 filters到整個 過濾器圖 graph中

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

static intinit_filter(FilteringContext* fctx, AVCodecContext *dec_ctx,
AVCodecContext *enc_ctx, const char *filter_spec)
{
char args[512];
int ret = 0;
AVFilter*buffersrc = NULL;
AVFilter*buffersink = NULL;
AVFilterContext*buffersrc_ctx = NULL;
AVFilterContext*buffersink_ctx = NULL;
AVFilterInOut*outputs = avfilter_inout_alloc();
AVFilterInOut*inputs = avfilter_inout_alloc();
AVFilterGraph*filter_graph = avfilter_graph_alloc();
if (!outputs || !inputs || !filter_graph) {
ret =AVERROR(ENOMEM);
goto end;
} //初始化各個結構體
/* 視頻 過濾器 */
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
buffersrc =avfilter_get_by_name(“buffer”);
buffersink= avfilter_get_by_name(“buffersink”);
if (!buffersrc || !buffersink) {
av_log(NULL, AV_LOG_ERROR, “filteringsource or sink element not found\n”);
ret = AVERROR_UNKNOWN;
goto end;
} //獲取buffer過濾器和 buffersink過濾器
_snprintf(args, sizeof(args),
“video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d”,
dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
dec_ctx->time_base.num,dec_ctx->time_base.den,
dec_ctx->sample_aspect_ratio.num,
dec_ctx->sample_aspect_ratio.den); //字符數組args存儲視頻的相關信息
ret =avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, “in”,
args, NULL, filter_graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Cannotcreate buffer source\n”);
goto end;
} // 創建 buffersrc過濾器
ret =avfilter_graph_create_filter(&buffersink_ctx, buffersink, “out”,
NULL, NULL, filter_graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Cannotcreate buffer sink\n”);
goto end;
} //創建 buffersink過濾器
ret =av_opt_set_bin(buffersink_ctx, “pix_fmts”,
(uint8_t*)&enc_ctx->pix_fmt, sizeof(enc_ctx->pix_fmt), //pix_fmts 視頻像素格式
AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Cannot setoutput pixel format\n”);
goto end;
}
/* 音頻 過濾器 */
} else if(dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
buffersrc = avfilter_get_by_name(“abuffer”);
buffersink= avfilter_get_by_name(“abuffersink”);
if (!buffersrc || !buffersink) {
av_log(NULL, AV_LOG_ERROR, “filteringsource or sink element not found\n”);
ret =AVERROR_UNKNOWN;
goto end;
} //獲取buffer過濾器和 buffersink過濾器
if (!dec_ctx->channel_layout)
dec_ctx->channel_layout =
av_get_default_channel_layout(dec_ctx->channels); //信道
_snprintf(args, sizeof(args),
“time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%I64x”,
dec_ctx->time_base.num, dec_ctx->time_base.den,dec_ctx->sample_rate,
av_get_sample_fmt_name(dec_ctx->sample_fmt),
dec_ctx->channel_layout); //字符數組args存儲音頻的相關信息
ret =avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, “in”,
args, NULL, filter_graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Cannotcreate audio buffer source\n”);
goto end;
} // 創建 buffer過濾器
ret =avfilter_graph_create_filter(&buffersink_ctx, buffersink, “out”,
NULL, NULL, filter_graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Cannotcreate audio buffer sink\n”);
goto end;
} // 創建 buffersink過濾器
ret = av_opt_set_bin(buffersink_ctx, “sample_fmts”,
(uint8_t*)&enc_ctx->sample_fmt, sizeof(enc_ctx->sample_fmt),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Cannot setoutput sample format\n”);
goto end;
} // sample_fmts音頻樣本格式
ret =av_opt_set_bin(buffersink_ctx, “channel_layouts”,
(uint8_t*)&enc_ctx->channel_layout,
sizeof(enc_ctx->channel_layout),AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Cannot setoutput channel layout\n”);
goto end;
}
ret =av_opt_set_bin(buffersink_ctx, “sample_rates”,
(uint8_t*)&enc_ctx->sample_rate, sizeof(enc_ctx->sample_rate),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Cannot setoutput sample rate\n”);
goto end;
} // sample_rates 音頻採樣率
} else {
ret =AVERROR_UNKNOWN;
goto end;
}
/* ndpoints for the filter graph*過濾圖的端設置/
outputs->name =av_strdup(“in”);
outputs->filter_ctx = buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
inputs->name = av_strdup(“out”);
inputs->filter_ctx = buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
if (!outputs->name || !inputs->name) {
ret =AVERROR(ENOMEM);
goto end;
}
if ((ret = avfilter_graph_parse_ptr(filter_graph,filter_spec, // 添加字符串filter_spec描述的過濾器到過濾器圖中
&inputs, &outputs, NULL)) < 0)
goto end;
if ((ret = avfilter_graph_config(filter_graph, NULL))< 0) //檢查有效性以及 graph中所有鏈路和格式的配置
goto end;
/* Fill FilteringContext */
fctx->buffersrc_ctx = buffersrc_ctx;
fctx->buffersink_ctx = buffersink_ctx;
fctx->filter_graph= filter_graph;
end:
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
return ret;
}

  1. init_filters函數——— 針對視頻和音頻;分別調用相應的濾波器類型,添加到濾波器圖中

(1).av_malloc_array

static int init_filters(void)
{
const char*filter_spec;
unsigned int i;
int ret;
filter_ctx =(FilteringContext *)av_malloc_array(ifmt_ctx->nb_streams, sizeof(*filter_ctx)); //分配過濾器 ,大小是流數目乘以*filter_ctx;FilteringContext是自定義的過濾器大結構體
if (!filter_ctx)
return AVERROR(ENOMEM);
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
filter_ctx[i].buffersrc_ctx =NULL;
filter_ctx[i].buffersink_ctx= NULL;
filter_ctx[i].filter_graph =NULL;
if(!(ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO //如果不是視頻或者音頻,開始下一次循環直接
||ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO))
continue;
if (ifmt_ctx->streams[i]->codec->codec_type== AVMEDIA_TYPE_VIDEO) //設置濾波器類型
filter_spec = “null”; /* passthrough (dummy) filter for video */ ?????
else
filter_spec = “anull”; /* passthrough (dummy) filter for audio */
ret =init_filter(&filter_ctx[i], ifmt_ctx->streams[i]->codec, //調用init_filter函數,生成濾波器圖
ofmt_ctx->streams[i]->codec, filter_spec);
if (ret)
return ret;
}
return 0;
}

  1. encode_write_frame函數 ———視音頻選擇編碼函數; 編碼過濾的幀 (初始化 packet的可選項,編碼,釋放編碼過的幀); 準備packet用於複用, 複用編碼幀( 交織寫一個packet到輸出文件)

      Referenced by filter_encode_write_frame(), and flush_encoder()

(1).av_init_packet:默認值初始化packet的可選項; 不涉及data 和size 屬性,兩者需要分別初始化

(2).av_rescale_q_rnd:重新調整 64—bit的整數,根據2個有理數按照指定的舍入

(3).av_interleaved_write_frame:寫一個packet到輸出文件,確保正確的交織

static int encode_write_frame(AVFrame *filt_frame, unsigned int stream_index, int*got_frame) {
int ret;
int got_frame_local;
AVPacket enc_pkt;
/*根據視頻還是音頻選擇各自的 編碼函數,即avcodec_encode_video2 :或者avcodec_encode_audio2 ,enc_func是函數指針 */
int (enc_func)(AVCodecContext , AVPacket , const AVFrame , int*) =
(ifmt_ctx->streams[stream_index]->codec->codec_type ==
AVMEDIA_TYPE_VIDEO) ? avcodec_encode_video2 : avcodec_encode_audio2;
if (!got_frame) //。。。。。。。。。
got_frame =&got_frame_local;
av_log(NULL,AV_LOG_INFO, “Encoding frame\n”);
/* 編碼過濾的幀 encode filtered frame*/
enc_pkt.data =NULL;
enc_pkt.size =0;
av_init_packet(&enc_pkt); //初始化 AVPacket enc_pkt的可選項,不包括 data和size
ret =enc_func(ofmt_ctx->streams[stream_index]->codec, &enc_pkt,
filt_frame, got_frame); //編碼,輸入的AVFrame 爲 filt_frame,輸出的AVPacket 爲enc_pkt,其中got_frame成功編碼一個AVPacket的時候設置爲1
av_frame_free(&filt_frame); //釋放編碼過的幀
if (ret < 0)
return ret;
if (!(*got_frame))
return 0;
/* prepare packet for muxing 準備packet 用於複用 */
enc_pkt.stream_index = stream_index;
enc_pkt.dts =av_rescale_q_rnd(enc_pkt.dts, // 調整 包enc_pkt的 解碼時間戳 dts
ofmt_ctx->streams[stream_index]->codec->time_base,
ofmt_ctx->streams[stream_index]->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
enc_pkt.pts =av_rescale_q_rnd(enc_pkt.pts, // 調整 包enc_pkt的 顯示時間戳 pts
ofmt_ctx->streams[stream_index]->codec->time_base,
ofmt_ctx->streams[stream_index]->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
enc_pkt.duration = av_rescale_q(enc_pkt.duration, // 調整 包enc_pkt的 持續時間 duration
ofmt_ctx->streams[stream_index]->codec->time_base,
ofmt_ctx->streams[stream_index]->time_base);
av_log(NULL,AV_LOG_DEBUG, “Muxing frame\n”);
/*mux encoded frame 複用 編碼幀 */
ret =av_interleaved_write_frame(ofmt_ctx, &enc_pkt); //寫一個packet到輸出文件,確保正確的交織
return ret;
}

8.filter_encode_write_frame函數————將編碼過的幀放入過濾器圖,再從中拉出來,繼而調用encode_write_frame函數編碼(1).av_buffersrc_add_frame_flags: 添加一個幀到 buffer source(AVFilterContext )

(2).av_buffersink_get_frame:從buffersink(AVFilterContext )過濾過的數據中得到一個幀,並放倒frame中

(3).filt_frame->pict_type = AV_PICTURE_TYPE_NONE(其中filt_frame是一個AVFrame): 即幀的圖像類型

static int filter_encode_write_frame(AVFrame *frame, unsignedint stream_index)
{
int ret;
AVFrame*filt_frame;
av_log(NULL,AV_LOG_INFO, “Pushing decoded frame tofilters\n”);
/*將解碼過的幀輸入到過濾器圖 push the decoded frame into the filtergraph */
ret =av_buffersrc_add_frame_flags(filter_ctx[stream_index].buffersrc_ctx, //添加幀到bunffersrc(AVFilterContext )過濾器
frame,0);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, “Error whilefeeding the filtergraph\n”);
return ret;
}
/*將從過濾器圖中拉過濾過的幀 pull filtered frames from the filtergraph */
while (1) {
filt_frame= av_frame_alloc();
if (!filt_frame) {
ret =AVERROR(ENOMEM);
break;
}
av_log(NULL, AV_LOG_INFO, “Pullingfiltered frame from filters\n”);
ret =av_buffersink_get_frame(filter_ctx[stream_index].buffersink_ctx, filt_frame);//從buffersink(AVFilterContext )過濾器中得到一個幀
if (ret < 0) {
/*
if nomore frames for output - returns AVERROR(EAGAIN)
if flushed and no more frames for output - returns AVERROR_EOF
rewrite retcode to 0 to show it as normal procedure completion
*/
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
ret= 0;
av_frame_free(&filt_frame);
break;
}
filt_frame->pict_type = AV_PICTURE_TYPE_NONE; //圖像幀的類型 未定義
ret =encode_write_frame(filt_frame, stream_index, NULL); //編碼
if (ret < 0)
break;
}
return ret;
}

9.flush_encoder函數———刷新 流的編碼器?

static int flush_encoder(unsigned intstream_index)
{
int ret;
int got_frame;
if(!(ofmt_ctx->streams[stream_index]->codec->codec->capabilities&
CODEC_CAP_DELAY))
return 0;
while (1) {
av_log(NULL, AV_LOG_INFO, “Flushing stream #%u encoder\n”, stream_index);
ret =encode_write_frame(NULL, stream_index, &got_frame);
if (ret < 0)
break;
if (!got_frame)
return 0;
}
return ret;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章