音视频框架ffmpeg源码的简要分析

感谢http://m.2cto.com/kf/201201/116171.html这里一系列的文章

最新版的ffmpeg中发现了一个新的东西:avconv,而且ffmpeg.c与avconv.c一个模样,一研究才发现是libav下把ffmpeg改名为avconv了.

到底libav与ffmpeg现在是什么个关系?我也搞得希里糊涂的,先不管它了.

ffmpeg的主要功能是音视频的转换和处理.其功能之强大已经到了匪夷所思的地步(有点替它吹了).它的主要特点是能做到把多个输入文件中的任意几个流重新组合到输出文件中,当然输出文件也可以有多个.

所以我们就会发现,在ffmpeg.c中,有类似于如下的一些变量:

<span style="font-size:18px;">static InputStream *input_streams = NULL;

static int nb_input_streams = 0;

static InputFile *input_files = NULL;

static int nb_input_files = 0;

static OutputStream *output_streams = NULL;

static int nb_output_streams = 0;

static OutputFile *output_files = NULL;

static int nb_output_files = 0;</span>

<span style="font-size:18px;">static InputStream *input_streams = NULL;

static int nb_input_streams = 0;

static InputFile *input_files = NULL;

static int nb_input_files = 0;

static OutputStream *output_streams = NULL;

static int nb_output_streams = 0;

static OutputFile *output_files = NULL;

static int nb_output_files = 0;</span>

其中:

input_streams 是输入流的数组,nb_input_streams是输入流的个数.

InputFile 是输入文件(也可能是设备)的数组,input_files是输入文件的个数.

下面的输出相关的变量们就不用解释了. www.2cto.com

可以看出,文件和流是分别保存的.于是,可以想象,结构InputStream中应有其所属的文件在input_files中的序号,结构OutputStream中也应有其所属文件在output_files中的序号.输入流数组应是这样填充的:每当在输入文件中找到一个流时,就把它添加到input_streams中,所以一个输入文件对应的流们在input_streams中是紧靠着的,于是InputFile结构中应有其第一个流在input_streams中的开始序号和被放在input_streams中的流的个数,因为并不是一个输入文件中所有的流都会被转到输出文件中.我们看一下InputFile:

<span style="font-size:18px;">typedef struct InputFile {

AVFormatContext *ctx;

int eof_reached; /* true if eof reached */

int ist_index; /* index of first stream in input_streams */

int buffer_size; /* current total buffer size */

int64_t ts_offset;

int nb_streams; /* number of stream that ffmpeg is aware of; may be different

from ctx.nb_streams if new streams appear during av_read_frame() */

int rate_emu;

} InputFile;</span>

<span style="font-size:18px;">typedef struct InputFile {

AVFormatContext *ctx;

int eof_reached; /* true if eof reached */

int ist_index; /* index of first stream in input_streams */

int buffer_size; /* current total buffer size */

int64_t ts_offset;

int nb_streams; /* number of stream that ffmpeg is aware of; may be different

from ctx.nb_streams if new streams appear during av_read_frame() */

int rate_emu;

} InputFile;</span>

注意其中的ist_index和nb_streams。

在输出流中,除了要保存其所在的输出文件在output_files中的序号,还应保存其对应的输入流在input_streams中的序号,也应保存其在所属输出文件中的流序号.而输出文件中呢,只需保存它的第一个流在output_streams中的序号,但是为啥不保存输出文件中流的个数呢?我也不知道,但我知道肯定不用保存也不影响实现功能(嘿嘿,相当于没说).

各位看官看到这里应该明白ffmpeg是怎样做到可以把多个文件中的任意个流重新组和到输出文件中了吧?

流和文件都准备好了,下面就是转换,那么转换过程是怎样的呢?还是我来猜一猜吧:

首先打开输入文件们,然后跟据输入流们准备并打开解码器们,然后跟据输出流们准备并打开编码器们,然后创建输出文件们,然后为所有输出文件们写好头部,然后就在循环中把输入流转换到输出流并写入输出文件中,转换完后跳出循环,然后写入文件尾,最后关闭所有的输出文件.

还是先看一下主函数吧:(省略了很多无关大雅的代码)

int main(int argc, char **argv)

{

OptionsContext o = { 0 };

int64_t ti;

//与命令行分析有关的结构的初始化,下面不再罗嗦

reset_options(&o, 0);

//设置日志级别

av_log_set_flags(AV_LOG_SKIP_REPEATED);

parse_loglevel(argc, argv, options);

if (argc > 1 && !strcmp(argv[1], "-d")) {

run_as_daemon = 1;

av_log_set_callback(log_callback_null);

argc--;

argv++;

}

//注册组件们

avcodec_register_all();

#if CONFIG_AVDEVICE

avdevice_register_all();

#endif

#if CONFIG_AVFILTER

avfilter_register_all();

#endif

av_register_all();

//初始化网络,windows下需要

avformat_network_init();

show_banner();

term_init();

//分析命令行输入的参数们

parse_options(&o, argc, argv, options, opt_output_file);

//文件的转换就在此函数中发生

if (transcode(output_files, nb_output_files, input_files, nb_input_files)< 0)

exit_program(1);

exit_program(0);

return 0;

}

int main(int argc, char **argv)

{

OptionsContext o = { 0 };

int64_t ti;

//与命令行分析有关的结构的初始化,下面不再罗嗦

reset_options(&o, 0);

//设置日志级别

av_log_set_flags(AV_LOG_SKIP_REPEATED);

parse_loglevel(argc, argv, options);

if (argc > 1 && !strcmp(argv[1], "-d")) {

run_as_daemon = 1;

av_log_set_callback(log_callback_null);

argc--;

argv++;

}

//注册组件

avcodec_register_all();

#if CONFIG_AVDEVICE

avdevice_register_all();

#endif

#if CONFIG_AVFILTER

avfilter_register_all();

#endif

av_register_all();

//初始化网络,windows下需要

avformat_network_init();

show_banner();

term_init();

//分析命令行输入的参数们

parse_options(&o, argc, argv, options, opt_output_file);

//文件的转换就在此函数中发生

if (transcode(output_files, nb_output_files, input_files, nb_input_files)< 0)

exit_program(1);

exit_program(0);

return 0;

}

下面是transcode()函数,转换就发生在它里面.不废话,看注释吧,应很详细了

static int transcode(

OutputFile *output_files,//输出文件数组

int nb_output_files,//输出文件的数量

InputFile *input_files,//输入文件数组

int nb_input_files)//输入文件的数量

{

int ret, i;

AVFormatContext *is, *os;

OutputStream *ost;

InputStream *ist;

uint8_t *no_packet;

int no_packet_count = 0;

int64_t timer_start;

int key;

if (!(no_packet = av_mallocz(nb_input_files)))

exit_program(1);

//设置编码参数,打开所有输出流的编码器,打开所有输入流的解码器,写入所有输出文件的文件头,于是准备好了

ret = transcode_init(output_files, nb_output_files, input_files,nb_input_files);

if (ret < 0)

goto fail;

if (!using_stdin){

av_log(NULL, AV_LOG_INFO, "Press [q] to stop, [?] for help\n");

}

timer_start = av_gettime();

//循环,直到收到系统信号才退出

for (; received_sigterm == 0;)

{

int file_index, ist_index;

AVPacket pkt;

int64_t ipts_min;

double opts_min;

int64_t cur_time = av_gettime();

ipts_min = INT64_MAX;

opts_min = 1e100;

/* if 'q' pressed, exits */

if (!using_stdin)

{

//先查看用户按下了什么键,跟据键做出相应的反应

static int64_t last_time;

if (received_nb_signals)

break;

/* read_key() returns 0 on EOF */

if (cur_time - last_time >= 100000 && !run_as_daemon){

key = read_key();

last_time = cur_time;

}else{

<span> </span>.................................

}

/* select the stream that we must read now by looking at the

smallest output pts */

//下面这个循环的目的是找一个最小的输出pts(也就是离当前最近的)的输出流

file_index = -1;

for (i = 0; i < nb_output_streams; i++){

OutputFile *of;

int64_t ipts;

double opts;

ost = &output_streams[i];//循环每一个输出流

of = &output_files[ost->file_index];//输出流对应的输出文件

os = output_files[ost->file_index].ctx;//输出流对应的FormatContext

ist = &input_streams[ost->source_index];//输出流对应的输入流

if (ost->is_past_recording_time || //是否过了录制时间?(可能用户指定了一个录制时间段)

no_packet[ist->file_index]|| //对应的输入流这个时间内没有数据?

(os->pb && avio_tell(os->pb) >= of->limit_filesize))//是否超出了录制范围(也是用户指定的)

continue;//是的,符合上面某一条,那么再看下一个输出流吧

//判断当前输入流所在的文件是否可以使用(我也不很明白)

opts = ost->st->pts.val * av_q2d(ost->st->time_base);

ipts = ist->pts;

if (!input_files[ist->file_index].eof_reached) {

if (ipts < ipts_min){

//每找到一个pts更小的输入流就记录下来,这样循环完所有的输出流时就找到了

//pts最小的输入流,及输入文件的序号

ipts_min = ipts;

if (input_sync)

file_index = ist->file_index;

}

if (opts < opts_min){

opts_min = opts;

if (!input_sync)

file_index = ist->file_index;

}

}

//难道下面这句话的意思是:如果当前的输出流已接收的帧数,超出用户指定的输出最大帧数时,

//则当前输出流所属的输出文件对应的所有输出流,都算超过了录像时间?

if (ost->frame_number >= ost->max_frames){

int j;

for (j = 0; j < of->ctx->nb_streams; j++)

output_streams[of->ost_index + j].is_past_recording_time = 1;

continue;

}

}

/* if none, if is finished */

if (file_index < 0) {

//如果没有找到合适的输入文件

if (no_packet_count){

//如果是因为有的输入文件暂时得不到数据,则还不算是结束

no_packet_count = 0;

memset(no_packet, 0, nb_input_files);

usleep(10000);

continue;

}

//全部转换完成了,跳出大循环

break;

}

//从找到的输入文件中读出一帧(可能是音频也可能是视频),并放到fifo队列中

is = input_files[file_index].ctx;

ret = av_read_frame(is, &pkt);

if (ret == AVERROR(EAGAIN)) {

//此时发生了暂时没数据的情况

no_packet[file_index] = 1;

no_packet_count++;

continue;

}

//下文判断是否有输入文件到最后了

if (ret < 0){

input_files[file_index].eof_reached = 1;

if (opt_shortest)

break;

else

continue;

}

no_packet_count = 0;

memset(no_packet, 0, nb_input_files);

if (do_pkt_dump){

av_pkt_dump_log2(NULL, AV_LOG_DEBUG, &pkt, do_hex_dump,

is->streams[pkt.stream_index]);

}

/* the following test is needed in case new streams appear

dynamically in stream : we ignore them */

//如果在输入文件中遇到一个忽然冒出的流,那么我们不鸟它

if (pkt.stream_index >= input_files[file_index].nb_streams)

goto discard_packet;

//取得当前获得的帧对应的输入流

ist_index = input_files[file_index].ist_index + pkt.stream_index;

ist = &input_streams[ist_index];

if (ist->discard)

goto discard_packet;

//重新鼓捣一下帧的时间戳

if (pkt.dts != AV_NOPTS_VALUE)

pkt.dts += av_rescale_q(input_files[ist->file_index].ts_offset,

AV_TIME_BASE_Q, ist->st->time_base);

if (pkt.pts != AV_NOPTS_VALUE)

pkt.pts += av_rescale_q(input_files[ist->file_index].ts_offset,

AV_TIME_BASE_Q, ist->st->time_base);

if (pkt.pts != AV_NOPTS_VALUE)

pkt.pts *= ist->ts_scale;

if (pkt.dts != AV_NOPTS_VALUE)

pkt.dts *= ist->ts_scale;

if (pkt.dts != AV_NOPTS_VALUE && ist->next_pts != AV_NOPTS_VALUE

&& (is->iformat->flags & AVFMT_TS_DISCONT))

{

int64_t pkt_dts = av_rescale_q(pkt.dts, ist->st->time_base,

AV_TIME_BASE_Q);

int64_t delta = pkt_dts - ist->next_pts;

if ((delta < -1LL * dts_delta_threshold * AV_TIME_BASE

|| (delta > 1LL * dts_delta_threshold * AV_TIME_BASE

&& ist->st->codec->codec_type

!= AVMEDIA_TYPE_SUBTITLE)

|| pkt_dts + 1 < ist->pts) && !copy_ts)

{

input_files[ist->file_index].ts_offset -= delta;

av_log( NULL, AV_LOG_DEBUG,

"timestamp discontinuity %"PRId64", new offset= %"PRId64"\n",

delta, input_files[ist->file_index].ts_offset);

pkt.dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base);

if (pkt.pts != AV_NOPTS_VALUE)

pkt.pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base);

}

}

//把这一帧转换并写入到输出文件中

if (output_packet(ist, output_streams, nb_output_streams, &pkt) < 0){

av_log(NULL, AV_LOG_ERROR,

"Error while decoding stream #%d:%d\n",

ist->file_index, ist->st->index);

if (exit_on_error)

exit_program(1);

av_free_packet(&pkt);

continue;

}

discard_packet:

av_free_packet(&pkt);

/* dump report by using the output first video and audio streams */

print_report(output_files, output_streams, nb_output_streams, 0,

timer_start, cur_time);

}

//文件处理完了,把缓冲中剩余的数据写到输出文件中

for (i = 0; i < nb_input_streams; i++){

ist = &input_streams[i];

if (ist->decoding_needed){

output_packet(ist, output_streams, nb_output_streams, NULL);

}

}

flush_encoders(output_streams, nb_output_streams);

term_exit();

//为输出文件写文件尾(有的不需要).

for (i = 0; i < nb_output_files; i++){

os = output_files[i].ctx;

av_write_trailer(os);

}

/* dump report by using the first video and audio streams */

print_report(output_files, output_streams, nb_output_streams, 1,

timer_start, av_gettime());

//关闭所有的编码器

for (i = 0; i < nb_output_streams; i++){

ost = &output_streams[i];

if (ost->encoding_needed){

av_freep(&ost->st->codec->stats_in);

avcodec_close(ost->st->codec);

}

#if CONFIG_AVFILTER

avfilter_graph_free(&ost->graph);

#endif

}

//关闭所有的解码器

for (i = 0; i < nb_input_streams; i++){

ist = &input_streams[i];

if (ist->decoding_needed){

avcodec_close(ist->st->codec);

}

}

/* finished ! */

ret = 0;

fail: av_freep(&bit_buffer);

av_freep(&no_packet);

if (output_streams) {

for (i = 0; i < nb_output_streams; i++) {

ost = &output_streams[i];

if (ost) {

if (ost->stream_copy)

av_freep(&ost->st->codec->extradata);

if (ost->logfile){

fclose(ost->logfile);

ost->logfile = NULL;

}

av_fifo_free(ost->fifo); /* works even if fifo is not

initialized but set to zero */

av_freep(&ost->st->codec->subtitle_header);

av_free(ost->resample_frame.data[0]);

av_free(ost->forced_kf_pts);

if (ost->video_resample)

sws_freeContext(ost->img_resample_ctx);

swr_free(&ost->swr);

av_dict_free(&ost->opts);

}

}

}

return ret;

}

时间: 2024-11-06 01:36:04

音视频框架ffmpeg源码的简要分析的相关文章

C++音视频通讯demo源码下载

以下为视频通讯系统中的部分回调函数源码: 具体可以在 http://download.csdn.net/detail/little_rui/7969285 下载 可实现一对一.一对多.多对多的音视频通化要求,也满足文件传输,音视频文件录制等功能需求. /** *  视频数据回调函数 */ void CALLBACK VideoData_CallBack(DWORD dwUserid, LPVOID lpBuf, DWORD dwLen, BITMAPINFOHEADER bmiHeader, L

C/C++音视频库ffmpeg的数据包AVPacket分析

ffmpeg下载地址 http://www.ffmpeg.club/ AVPacket是ffmpeg用来存放编码后的视频帧数据,我们来分析一下这个结构体,先贴出ffmpeg3.2中AVPacket声明的源代码: typedef struct AVPacket { /** * A reference to the reference-counted buffer where the packet data is * stored. * May be NULL, then the packet da

最新版ffmpeg源码分析

最新版ffmpeg源码分析一:框架 (ffmpeg v0.9) 框架 最新版的ffmpeg中发现了一个新的东西:avconv,而且ffmpeg.c与avconv.c一个模样,一研究才发现是libav下把ffmpeg改名为avconv了. 到底libav与ffmpeg现在是什么个关系?我也搞得希里糊涂的,先不管它了. ffmpeg的主要功能是音视频的转换和处理.其功能之强大已经到了匪夷所思的地步(有点替它吹了).它的主要特点是能做到把多个输入文件中的任意几个流重新组合到输出文件中,当然输出文件也可

又是正版!Win下ffmpeg源码调试分析二(Step into ffmpeg from Opencv for bugs in debug mode with MSVC)

最近工作忙一直没时间写,但是看看网络上这方面的资源确实少,很多都是linux的(我更爱unix,哈哈),而且很多是直接引入上一篇文章的编译结果来做的.对于使用opencv但是又老是被ffmpeg库坑害的朋友们,可能又爱又恨,毕竟用它处理和分析视频是第一选择,不仅是因为俩者配合使用方便,而且ffmpeg几乎囊括了我所知道的所有解编码器,但是正是因为这个导致了一些bug很难定位,所以有必要考虑一下如何快速定位你的ffmpeg bug. sorry,废话多了.首先给个思路: 1.使opencv 的hi

FFmpeg源码简单分析:结构体成员管理系统-AVOption

===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFmpeg源码结构图 - 编码 [通用] FFmpeg 源码简单分析:av_register_all() FFmpeg 源码简单分析:avcodec_register_all() FFmpeg 源码简单分析:内存的分配和释放(av_malloc().av_free()等) FFmpeg 源码简单分析:常

FFmpeg源码结构图 - 解码

===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFmpeg源码结构图 - 编码 [通用] FFmpeg 源码简单分析:av_register_all() FFmpeg 源码简单分析:avcodec_register_all() FFmpeg 源码简单分析:内存的分配和释放(av_malloc().av_free()等) FFmpeg 源码简单分析:常

高清视频会议 视频聊天室源码下载

高清视频会议.视频聊天室源码简介: "SDK即时通讯平台"是一套跨平台的即时通讯解决方案,基于先进的H.264视频编码标准.AAC音频编码标准与P2P技术,支持高清视频,整合了佰锐在音视频编码.多媒体通讯领域领先的开发技术和丰富的产品经验而设计的高质量.宽适应性.分布式.模块化的网络音视频互动平台 成熟产品可提供全套系统示例源代码(包服务端,客户端)下载地址http://download.csdn.net/detail/little_rui/7969285,同时有完善的开发文档指南,且

Android 图片加载框架Universal-Image-Loader源码解析

Universal-Image-Loader(项目地址)可以说是安卓知名图片开源框架中最古老.使用率最高的一个了.一张图片的加载对于安卓应用的开发也许是件简单的事,但是如果要同时加载大量的图片,并且图片用于ListView.GridView.ViewPager等控件,如何防止出现OOM.如何防止图片错位(因为列表的View复用功能).如何更快地加载.如何让客户端程序员用最简单的操作完成本来十分复杂的图片加载工作,成了全世界安卓应用开发程序员心头的一大难题,所幸有了Universal-Image-

如何查看JDK以及JAVA框架的源码

如何查看JDK以及JAVA框架的源码 设置步骤如下: 1.点 “window”-> "Preferences" -> "Java" -> "Installed JRES" 2.此时"Installed JRES"右边是列表窗格,列出了系统中的 JRE 环境,选择你的JRE,然后点边上的 "Edit...", 会出现一个窗口(Edit JRE) 3.选中rt.jar文件的这一项:“c:\pr