模块:
libavcodec - 编码解码器
libavdevice - 输入输出设备的支持
libavfilter - 视音频滤镜支持
libavformat - 视音频等格式的解析
libavutil - 工具库
libpostproc - 后期效果处理
libswscale - 图像颜色、尺寸转换
1. 主函数分析:
大致流程:
调用注册函数;
open_input_file
更新过滤器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<input file> <output file>\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:
-
- 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_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 {
-
- 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);
- }
-
- for (i = 0; i < ifmt_ctx->nb_streams; i++) {
-
- 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;
- }
-
- ret = flush_encoder(i);
- if (ret < 0) {
- av_log(NULL, AV_LOG_ERROR, "Flushingencoder failed\n");
- goto end;
- }
- }
- av_write_trailer(ofmt_ctx); //输出文件尾
- end:
-
- 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(),而且头文件也不是<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: 过滤器
- #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"
- };
-
- 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;
3. static int open_input_file(const char *filename)函数
说明: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;
- if ((ret = avformat_open_input(&ifmt_ctx,filename, NULL, NULL)) < 0) {
- av_log(NULL, AV_LOG_ERROR, "Cannot openinput file\n");
- return ret;
- }
- 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;
- stream =ifmt_ctx->streams[i];
- codec_ctx =stream->codec;
-
- if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO
- ||codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
-
- 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)
(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);
- 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;
- enc_ctx =out_stream->codec;
- if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO
- ||dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
-
- encoder= avcodec_find_encoder(dec_ctx->codec_id);
-
- if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
- enc_ctx->height = dec_ctx->height;
- enc_ctx->width = dec_ctx->width;
- enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;
-
- enc_ctx->pix_fmt = encoder->pix_fmts[0];
-
- enc_ctx->time_base = dec_ctx->time_base;
- } else {
- 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);
-
- 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;
- }
-
- ret =avcodec_open2(enc_ctx, encoder, NULL);
- 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 {
-
- 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);
- 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;
- }
- }
-
- 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函数
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 ——用于设置参数