ffmpeg播放流程讲解

来源:互联网 发布:java入门要多久 编辑:程序博客网 时间:2024/05/16 16:07

转自:http://blog.csdn.net/livingpark/article/details/5774476

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()函数)中发出的信号,然后再继续执行。