背景
了解一些关键函数对于开发的帮助比较大。
avformat_open_input
FFMPEG 打开媒体的过程开始于 avformat_open_input,因此该函数的重要性不可忽视。
在该函数中,FFMPEG 完成了:
1.输入输出结构体 AVIOContext 的初始化;
2.输入数据的协议(例如 RTMP,或者 file)的识别(通过一套评分机制):
A 判断文件名的后缀
B 读取文件头的 数据进行比对;
使用获得最高分的文件协议对应的 URLProtocol,通过函数指针的方式,与 FFMPEG 连接(非专业用词);
剩下的就是调用该 URLProtocol 的函数进行 open,read 等操作了
以下是通过 eclipse+MinGW 调试 FFMPEG 源代码获得的函数调用关系图 :
可见最终都调用了 URLProtocol 结构体中的函数指针。
URLProtocol 结构是一大堆函数指针的集合(avio.h 文件),数据结构 URLProtocol
的功能就是完成各种输入协议的读写等操作。
但输入协议种类繁多,它是怎样做到“大一统”的呢?
原来,每个具体的输入协议都有自己对应的 URLProtocol。
比如 file 协议(FFMPEG 把文件也当做一种特殊的协议)(*file.c 文件)URLProtocol ff_pipe_protocol = { .name = "pipe", .url_open = pipe_open, .url_read = file_read, .url_write = file_write, .url_get_file_handle = file_get_handle, .url_check = file_check, }
或者 rtmp 协议(此处使用了 librtmp)( librtmp.c 文件)
URLProtocol ff_rtmp_protocol = { .name = "rtmp", .url_open = rtmp_open, .url_read = rtmp_read, .url_write = rtmp_write, .url_close = rtmp_close, .url_read_pause = rtmp_read_pause, .url_read_seek = rtmp_read_seek, .url_get_file_handle = rtmp_get_file_handle, .priv_data_size = sizeof(RTMP), .flags = URL_PROTOCOL_FLAG_NETWORK, };
可见它们把各自的函数指针都赋值给了 URLProtocol 结构体的函数指针。
因此 avformat_open_input 只需调用 url_open,url_read 这些函数就可以完成各种具体输入协议的 open,read 等操作了。
avcodec_register_all()
ffmpeg 注册复用器,编码器等的函数 av_register_all()。
该函数在所有基于 ffmpeg 的应用程序中几乎都是第一个被调用的。只有调用了该函数,才能使用复用器,编码器等。
void avcodec_register_all(void)
{
static AVOnce control = AV_ONCE_INIT;
ff_thread_once(&control, register_all);
}
...
static void register_all(void)
{
/* hardware accelerators */
REGISTER_HWACCEL(H263_VAAPI, h263_vaapi);
REGISTER_HWACCEL(H263_VIDEOTOOLBOX, h263_videotoolbox);
REGISTER_HWACCEL(H264_CUVID, h264_cuvid);
REGISTER_HWACCEL(H264_D3D11VA, h264_d3d11va);
REGISTER_HWACCEL(H264_D3D11VA2, h264_d3d11va2);
REGISTER_HWACCEL(H264_DXVA2, h264_dxva2);
...
}
可见解复用器注册都是用 REGISTER_DEMUXER (X,x)
例如: REGISTER_DEMUXER (AAC, aac)
可见复用器注册都是用 REGISTER_MUXER (X,x))
例如: REGISTER_MUXER (ADTS, adts)
既有解复用器又有复用器的话,可以用 REGISTER_MUXDEMUX (X,x));
例如: REGISTER_MUXDEMUX (AC3, ac3);
我们来看一下宏的定义,这里以解复用器为例:
#define REGISTER_DEMUXER(X,x) {
extern AVInputFormat ff_##x##_demuxer;
if(CONFIG_##X##_DEMUXER) av_register_input_format(&ff_##x##_demuxer); }
注意:define 里面的##可能不太常见,它的含义就是拼接两个字符串,
比如 #define Conn(x,y) x##y 那么 int n = Conn(123,456); 结果就是 n=123456;
我们以 REGISTER_DEMUXER (AAC, aac)为例,则它等效于
extern AVInputFormat ff_aac_demuxer;
if(CONFIG_AAC_DEMUXER)
av_register_input_format(&ff_aac_demuxer);
从上面这段代码我们可以看出,真正注册的函数是 av_register_input_format(&ff_aac_demuxer)
,查看一下 av_register_input_format()
的代码:
void av_register_input_format(AVInputFormat *format)
{
AVInputFormat **p;
p = &first_iformat;
while (*p != NULL) p = &(*p)->next;
*p = format;
format->next = NULL;
}
// libavformat/allformats.c
void av_register_input_format(AVInputFormat *format)
{
ff_thread_once(&av_format_next_init, av_format_init_next);
}
// libavutil/thread.h 根据不同的宏定义有不同的实现,但实际上都是为了让 routine 指针所指的函数只执行一次。
libavutil/thread.h:147:#define ff_thread_once(control, routine) pthread_once(control, routine)
libavutil/thread.h:162:static inline int ff_thread_once(char *control, void (*routine)(void))
static inline int ff_thread_once(char *control, void (*routine)(void))
{
if (!*control) {
routine();
*control = 1;
}
return 0;
}
实际上我并没有理解这段函数,因为这段函数其中没有使用到参数:format,我不知道ff_thread_once的内部到底执行了什么。但根据前人给出的结论:av_register_input_format()的含义,一句话概括就是:遍历链表并把当前的 Input Format 加到链 表的尾部,然后确定是不是已经初始化过了(initialized),如果没有,就调用 avcodec_register_all()注 册编解码器(这个先不分析),然后就是注册,注册,注册...直到完成所有注册。
av_read_frame()
ffmpeg 中的 av_read_frame()的作用是读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如 H.264 中 一帧压缩数据通常对应一个 NAL)。
通过 av_read_packet(***)
,读取一个包,需要说明的是此函数必须是包含整数帧的,不存在半帧的情况,以 ts 流为例,是读取一个完整的 PES 包(一个完整 pes 包包含若干视频或音频 es 包),读取完毕后,通过 av_parser_parse2(***)
分析出视频一帧(或音频若干帧),返回,下次进入循环的时候,如果上次的数据没有完全取完,则 st = s->cur_st;
不会是 NULL,即再此进入 av_parser_parse2(***)
流程,而不是下面的 av_read_packet(**)
流程,这样就保证了,如果读取一次包含了 N 帧视频数据(以视频为例),则调用 av_read_frame(***)
N 次都不会 去读数据,而是返回第一次读取的数据,直到全部解析完毕。
avcodec_decode_video2()
ffmpeg 中的 avcodec_decode_video2()的作用是解码一帧视频数据。输入一个压缩编码的结构体 AVPacket,输出一个 解码后的结构体 AVFrame。
transcode_init()
transcode_init()函数是在转换前做准备工作的.此处看一下它的真面目,不废话,看注释吧:
// 为转换过程做准备
static int transcode_init(void)
{
int ret = 0, i, j, k;
AVFormatContext *oc;
OutputStream *ost;
InputStream *ist;
char error[1024] = {0};
for (i = 0; i < nb_filtergraphs; i++) {
FilterGraph *fg = filtergraphs[i];
for (j = 0; j < fg->nb_outputs; j++) {
OutputFilter *ofilter = fg->outputs[j];
if (!ofilter->ost || ofilter->ost->source_index >= 0)
continue;
if (fg->nb_inputs != 1)
continue;
for (k = nb_input_streams-1; k >= 0 ; k--)
if (fg->inputs[0]->ist == input_streams[k])
break;
ofilter->ost->source_index = k;
}
}
/* init framerate emulation */
for (i = 0; i < nb_input_files; i++) {
InputFile *ifile = input_files[i];
if (ifile->rate_emu)
for (j = 0; j < ifile->nb_streams; j++)
input_streams[j + ifile->ist_index]->start = av_gettime_relative();
}
/* init input streams */
for (i = 0; i < nb_input_streams; i++)
if ((ret = init_input_stream(i, error, sizeof(error))) < 0) {
for (i = 0; i < nb_output_streams; i++) {
ost = output_streams[i];
avcodec_close(ost->enc_ctx);
}
goto dump_format;
}
/* open each encoder */
for (i = 0; i < nb_output_streams; i++) {
// skip streams fed from filtergraphs until we have a frame for them
if (output_streams[i]->filter)
continue;
ret = init_output_stream(output_streams[i], error, sizeof(error));
if (ret < 0)
goto dump_format;
}
/* discard unused programs */
for (i = 0; i < nb_input_files; i++) {
InputFile *ifile = input_files[i];
for (j = 0; j < ifile->ctx->nb_programs; j++) {
AVProgram *p = ifile->ctx->programs[j];
int discard = AVDISCARD_ALL;
for (k = 0; k < p->nb_stream_indexes; k++)
if (!input_streams[ifile->ist_index + p->stream_index[k]]->discard) {
discard = AVDISCARD_DEFAULT;
break;
}
p->discard = discard;
}
}
/* write headers for files with no streams */
for (i = 0; i < nb_output_files; i++) {
oc = output_files[i]->ctx;
if (oc->oformat->flags & AVFMT_NOSTREAMS && oc->nb_streams == 0) {
ret = check_init_output_file(output_files[i], i);
if (ret < 0)
goto dump_format;
}
}
dump_format:
/* dump the stream mapping */
av_log(NULL, AV_LOG_INFO, "Stream mapping:
");
for (i = 0; i < nb_input_streams; i++) {
ist = input_streams[i];
for (j = 0; j < ist->nb_filters; j++) {
if (!filtergraph_is_simple(ist->filters[j]->graph)) {
av_log(NULL, AV_LOG_INFO, " Stream #%d:%d (%s) -> %s",
ist->file_index, ist->st->index, ist->dec ? ist->dec->name : "?",
ist->filters[j]->name);
if (nb_filtergraphs > 1)
av_log(NULL, AV_LOG_INFO, " (graph %d)", ist->filters[j]->graph->index);
av_log(NULL, AV_LOG_INFO, "
");
}
}
}
for (i = 0; i < nb_output_streams; i++) {
ost = output_streams[i];
if (ost->attachment_filename) {
/* an attached file */
av_log(NULL, AV_LOG_INFO, " File %s -> Stream #%d:%d
",
ost->attachment_filename, ost->file_index, ost->index);
continue;
}
if (ost->filter && !filtergraph_is_simple(ost->filter->graph)) {
/* output from a complex graph */
av_log(NULL, AV_LOG_INFO, " %s", ost->filter->name);
if (nb_filtergraphs > 1)
av_log(NULL, AV_LOG_INFO, " (graph %d)", ost->filter->graph->index);
av_log(NULL, AV_LOG_INFO, " -> Stream #%d:%d (%s)
", ost->file_index,
ost->index, ost->enc ? ost->enc->name : "?");
continue;
}
av_log(NULL, AV_LOG_INFO, " Stream #%d:%d -> #%d:%d",
input_streams[ost->source_index]->file_index,
input_streams[ost->source_index]->st->index,
ost->file_index,
ost->index);
if (ost->sync_ist != input_streams[ost->source_index])
av_log(NULL, AV_LOG_INFO, " [sync #%d:%d]",
ost->sync_ist->file_index,
ost->sync_ist->st->index);
if (ost->stream_copy)
av_log(NULL, AV_LOG_INFO, " (copy)");
else {
const AVCodec *in_codec = input_streams[ost->source_index]->dec;
const AVCodec *out_codec = ost->enc;
const char *decoder_name = "?";
const char *in_codec_name = "?";
const char *encoder_name = "?";
const char *out_codec_name = "?";
const AVCodecDescriptor *desc;
if (in_codec) {
decoder_name = in_codec->name;
desc = avcodec_descriptor_get(in_codec->id);
if (desc)
in_codec_name = desc->name;
if (!strcmp(decoder_name, in_codec_name))
decoder_name = "native";
}
if (out_codec) {
encoder_name = out_codec->name;
desc = avcodec_descriptor_get(out_codec->id);
if (desc)
out_codec_name = desc->name;
if (!strcmp(encoder_name, out_codec_name))
encoder_name = "native";
}
av_log(NULL, AV_LOG_INFO, " (%s (%s) -> %s (%s))",
in_codec_name, decoder_name,
out_codec_name, encoder_name);
}
av_log(NULL, AV_LOG_INFO, "
");
}
if (ret) {
av_log(NULL, AV_LOG_ERROR, "%s
", error);
return ret;
}
atomic_store(&transcode_init_done, 1);
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
"); } 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{ }
/* 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"
", 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
", 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; }