Living Park
http://blog.csdn.net/livingpark
1. register codecs
(1)./configure时选择要编译的codec(以cook为例)
默认时已经选择,不需要指定参数
生成的config.mk文件中有 CONFIG_COOK_DECODER=yes
config.h文件中有 #define CONFIG_COOK_DECODER 1
(2)ffplay.c文件中main函数调用 avcodec_register_all();该函数在allcodecs.c文件中,它用来注册所有已选择的codecs,
REGISTER_DECODER (COOK, cook);
REGISTER_DECODER的定义在allcodecs.c文件中如下:
#define REGISTER_DECODER(X,x) { /
extern AVCodec x##_decoder; /
if(CONFIG_##X##_DECODER) avcodec_register(&x##_decoder); }
此时CONFIG_##X##_DECODER=CONFIG_COOK_DECODER,已经在config.h中定义为 1,所以会调用 avcodec_register(&x##_decoder);
void avcodec_register(AVCodec *codec)
{
AVCodec **p;
avcodec_init();
p = &first_avcodec;
while (*p != NULL) p = &(*p)->next;
*p = codec;
codec->next = NULL;
}
avcodec_register函数在utils.c文件中,它将 x##_decoder添加到codec的链表尾,即将该codec注册成功。
这时 x##_decoder=cook_decoder,cook_decoder在cook.c文件末尾中定义,如下:
AVCodec cook_decoder =
{
.name = "cook",
.type = CODEC_TYPE_AUDIO,
.id = CODEC_ID_COOK,
.priv_data_size = sizeof(COOKContext),
.init = cook_decode_init,
.close = cook_decode_close,
.decode = cook_decode_frame,
.long_name = NULL_IF_CONFIG_SMALL("COOK"),
};
x##_decoder的定义都在各个codec文件末尾定义。
2.open file
(1)ffplay.c文件中main函数最后调用cur_stream = stream_open(input_filename, file_iformat);并保存打开的VideoState
在函数stream_open<ffplay.c> 中 is->parse_tid = SDL_CreateThread(decode_thread, is);进入视频播放线程decode_thread
在线程decode_thread<ffplay.c>中 err = av_open_input_file(&ic, is->filename, is->iformat, 0, ap);打开文件
(2)在函数av_open_input_file<utils.c>中 fmt = av_probe_input_format2(pd, 1, &score);得到文件类型
在函数av_probe_input_format2<utils.c>中 遍历AVInputFormat链表,比较文件头内容和后缀,得分最高的最匹配,就是文件类型
if (fmt1->read_probe) {
score = fmt1->read_probe(pd);
}else if (fmt1->extensions) {
if (match_ext(pd->filename, fmt1->extensions)) {
score = 50;
}
}
其中fmt1->read_probe(pd);匹配加100分,后缀匹配加50分
read_probe函数中比较文件头内容,以rmvb为例:
rm_probe<rmdec.c>函数中匹配文件头
if ((p->buf[0] == '.' && p->buf[1] == 'R' &&
p->buf[2] == 'M' && p->buf[3] == 'F' &&
p->buf[4] == 0 && p->buf[5] == 0) ||
(p->buf[0] == '.' && p->buf[1] == 'r' &&
p->buf[2] == 'a' && p->buf[3] == 0xfd))
return AVPROBE_SCORE_MAX;
else
return 0;
其他demux匹配类似
(3)这里rm_probe函数的调用定义也在注册中,rm_demuxer的定义如下:
AVInputFormat rm_demuxer = {
"rm",
NULL_IF_CONFIG_SMALL("RealMedia format"),
sizeof(RMDemuxContext),
rm_probe, //read_probe <AVInputFormat>
rm_read_header,
rm_read_packet,
rm_read_close,
NULL,
rm_read_dts,
};
在main<ffplay.c> 函数中调用av_register_all();
在函数av_register_all<allformats.c>中 REGISTER_MUXDEMUX (RM, rm);
在定义REGISTER_MUXDEMUX<allformats.c>中#define REGISTER_MUXDEMUX(X,x) REGISTER_MUXER(X,x); REGISTER_DEMUXER(X,x)
在定义REGISTER_DEMUXER(X,x)<allformats.c>中
#define REGISTER_DEMUXER(X,x) { /
extern AVInputFormat x##_demuxer; /
if(CONFIG_##X##_DEMUXER) av_register_input_format(&x##_demuxer); }
调用av_register_input_format(&x##_demuxer)将x##_demuxer加入链表中。
(4)在获取文件格式后调用err = av_open_input_stream(ic_ptr, pb, filename, fmt, ap);打开文件
其中函数av_open_input_stream在utils.c文件中
3.打开codec
文件打开后调用函数err = av_find_stream_info(ic);<ffplay.c>获取需要的流信息,从流信息中得到audio_index:
for(i = 0; i < ic->nb_streams; i++) {
AVCodecContext *enc = ic->streams[i]->codec;
ic->streams[i]->discard = AVDISCARD_ALL;
switch(enc->codec_type) {
case CODEC_TYPE_AUDIO:
if (wanted_audio_stream-- >= 0 && !audio_disable)
audio_index = i;
break;
...
根据audio_index打开流:
if (audio_index >= 0) {
stream_component_open(is, audio_index);
}
在函数stream_component_open<ffplay.c>中找到decoder,
codec = avcodec_find_decoder(enc->codec_id);
然后打开找到的decoder,
if (!codec ||
avcodec_open(enc, codec) < 0)
return -1;
在函数avcodec_open<utils.c>中对decoder进行初始化
if(avctx->codec->init){
ret = avctx->codec->init(avctx);
if (ret < 0) {
goto free_and_end;
}
}
最后对文件信息进行处理。
4.处理音频信息
我们不断地从文件中得到包,同时SDL也将调用回调函数sdl_audio_callback<ffplay.c>,该函数在SDL_AudioSpec中赋给callback : wanted_spec.callback = sdl_audio_callback;
处理包时需要一个队列<ffplay.c>,
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
int abort_request;
SDL_mutex *mutex;
SDL_cond *cond;
} PacketQueue;
在ffmpeg中有一个叫AVPacketList<avformat.h>的结构体可供使用,这个结构体实际是一串包的链表。
typedef struct AVPacketList {
AVPacket pkt;
struct AVPacketList *next;
} AVPacketList;
队列初始化packet_queue_init<ffplay.c>
给队列添加packet:packet_queue_put<ffplay.c>
函数SDL_LockMutex()锁定队列的互斥量以便于我们向队列中添加东西,然后函数SDL_CondSignal()通过我们的条件变量为一个接收函数(如果它在等待)发出一个信号来告诉它有数据了,
接着就会解锁互斥量并让队列可以自由访问。
接收函数packet_queue_get<ffplay.c>
使用SDL中的函数 SDL_CondWait()来避免无限循环。基本上,所有的CondWait只等待从SDL_CondSignal()函数(或者 SDL_CondBroadcast()函数)中发出的信号,然后再继续执行。