ffmpeg源码学习笔记一

最近看了一些ffmpeg源代码,记录一些笔记. 从ffplay.c的main函数开始阅读源码

int main(int argc, char **argv)
{
    ...
    //注册codec,dmx和一些protocol
    av_register_all();
    ...

    //parse一些命令后信息,保存在options中,后面经常会用到这个
    parse_options(NULL, argc, argv, options, opt_input_file);

    //后面会专门分析这个函数
    is = stream_open(input_filename, file_iformat);
    ...
}

1. av_register_all();

以 注册file协议为例: REGISTER_PROTOCOL(FILE,             file);

#define REGISTER_PROTOCOL(X, x)                                         \
    {                                                                   \
        extern URLProtocol ff_##x##_protocol;                           \
        if (CONFIG_##X##_PROTOCOL)                                      \
            ffurl_register_protocol(&ff_##x##_protocol);                \
    }

展开之后就等于:
                                        
    {                                                                   
        extern URLProtocol ff_file_protocol;                           
        if (CONFIG_FILE_PROTOCOL)                                      
            ffurl_register_protocol(&ff_file_protocol);                
    }

ff_file_protocol这个global 变量位于libavformat/file.c
URLProtocol ff_file_protocol = {
    .name                = "file",
    .url_open            = file_open,
    .url_read            = file_read,
    .url_write           = file_write,
    .url_seek            = file_seek,
    .url_close           = file_close,
    .url_get_file_handle = file_get_handle,
    .url_check           = file_check,
    .url_delete          = file_delete,
    .url_move            = file_move,
    .priv_data_size      = sizeof(FileContext),
    .priv_data_class     = &file_class,
    .url_open_dir        = file_open_dir,
    .url_read_dir        = file_read_dir,
    .url_close_dir       = file_close_dir,
};

2.stream_open

static VideoState *stream_open(const char *filename, AVInputFormat *iformat)
{
    ...
    //alloc VideoState 结构体,并初始化
    is = av_mallocz(sizeof(VideoState));
    if (!is)
        return NULL;
    av_strlcpy(is->filename, filename, sizeof(is->filename));
    is->iformat = iformat;
    is->ytop    = 0;
    is->xleft   = 0;

    //初始化video/subtitle/audio  frame queue
    if (frame_queue_init(&is->pictq, &is->videoq, VIDEO_PICTURE_QUEUE_SIZE, 1) < 0)
        goto fail;
    if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
        goto fail;
    if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
        goto fail;

    //初始化video/subtitle/audio  packet queue
    packet_queue_init(&is->videoq);
    packet_queue_init(&is->audioq);
    packet_queue_init(&is->subtitleq);

    is->continue_read_thread = SDL_CreateCond();

    //初始化video/subtitle/audio  clock
    init_clock(&is->vidclk, &is->videoq.serial);
    init_clock(&is->audclk, &is->audioq.serial);
    init_clock(&is->extclk, &is->extclk.serial);
    is->audio_clock_serial = -1;
    is->av_sync_type = av_sync_type;

    //创建read_thread
    is->read_tid     = SDL_CreateThread(read_thread, is);
    ...
}

3. read_thread

static int read_thread(void *arg)
{
    
    //最开始将所有stream id 设为无效,eof为0
    memset(st_index, -1, sizeof(st_index));
    is->last_video_stream = is->video_stream = -1;
    is->last_audio_stream = is->audio_stream = -1;
    is->last_subtitle_stream = is->subtitle_stream = -1;
    is->eof = 0;
    
    //alloc AVFormatContext,这是ffmpeg中最重要的结构体之一,
    //后续的AVInputFormat,AVStream等重要的结构体都挂在这个结构体中
    ic = avformat_alloc_context();
    if (!ic) {
        av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n");
        ret = AVERROR(ENOMEM);
        goto fail;
    }
    ic->interrupt_callback.callback = decode_interrupt_cb;
    ic->interrupt_callback.opaque = is;
    if (!av_dict_get(format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) {
        av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE);
        scan_all_pmts_set = 1;
    }
    //重要的函数,接下来会重点分析
    err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
    if (err < 0) {
        print_error(is->filename, err);
        ret = -1;
        goto fail;
    }
    ...
}


4.avformat_open_input

int avformat_open_input(AVFormatContext **ps, const char *filename,
                        AVInputFormat *fmt, AVDictionary **options)
{
    AVFormatContext *s = *ps;
    int ret = 0;
    AVDictionary *tmp = NULL;
    ID3v2ExtraMeta *id3v2_extra_meta = NULL;

    
    //AVFormatContext前面已经分配过Memory,不用再alloc
    if (!s && !(s = avformat_alloc_context()))
        return AVERROR(ENOMEM);

    //s->av_class也不等于NULL
    /*
    static const AVClass av_format_context_class = {
    .class_name     = "AVFormatContext",
    .item_name      = format_to_name,
    .option         = avformat_options,//可以查看到这里有很多option
    .version        = LIBAVUTIL_VERSION_INT,
    .child_next     = format_child_next,
    .child_class_next = format_child_class_next,
    .category       = AV_CLASS_CATEGORY_MUXER,
    .get_category   = get_category,
    };
    */
    if (!s->av_class) {
        av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n");
        return AVERROR(EINVAL);
    }
    
    //fmt是NULL
    if (fmt)
        s->iformat = fmt;
    
    //将option保存到tmp中
    if (options)
        av_dict_copy(&tmp, *options, 0);
    
    //s-pb是NULL
    if (s->pb) // must be before any goto fail
        s->flags |= AVFMT_FLAG_CUSTOM_IO;

    //将tmp中的字典,保存在s结构体中
    //ffmpeg很多结构体第一个成员就是AVClass类,其实就是修改AVClass的option.
    if ((ret = av_opt_set_dict(s, &tmp)) < 0)
        goto fail;

    //重要函数,打开播放的文件,并进行认档
    if ((ret = init_input(s, filename, &tmp)) < 0)
        goto fail;
    s->probe_score = ret;

    ...
}

5.init_input

static int init_input(AVFormatContext *s, const char *filename,
                      AVDictionary **options)
{
    int ret;
    AVProbeData pd = { filename, NULL, 0 };
    int score = AVPROBE_SCORE_RETRY;

    //s-pb为NULL
    if (s->pb) {
        s->flags |= AVFMT_FLAG_CUSTOM_IO;
        if (!s->iformat)
            return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                         s, 0, s->format_probesize);
        else if (s->iformat->flags & AVFMT_NOFILE)
            av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and "
                                      "will be ignored with AVFMT_NOFILE format.\n");
        return 0;
    }

    //s->iformat为NULL,会call av_probe_input_format2 去认档.
    /*

    AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened,
                                      int *score_ret)
    {
       ...
    
        fmt = NULL;
        while ((fmt1 = av_iformat_next(fmt1))) {
            //从传入的参数看,此时的is_opened是0,
这个地方只会认image2的格式,但是会拿到最后一个fmt,
之后由于在av_probe_input_format2 比较分数时候,
拿到的分数会小于25,av_probe_input_format2会返回NULL,
所以s->iformat还会是NULL. 后面demuxer认档,
在这儿不会被执行到,但是可以先分析一下,
后面is_opened为1时候,会call 到 read_probe认档.
            if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
                continue;
            score = 0;
            //在av_register_all时候,有注册各种demuxer,循环调用demuxer的read_probe.去认档
        //有点疑惑,为什么不是100分就退出,而是让所有的demuxer都去认一遍
        ****************************
        例如matroska格式,就会调用matroska_probe 去认档
        AVInputFormat ff_matroska_demuxer = {
        .name           = "matroska,webm",
        .long_name      = NULL_IF_CONFIG_SMALL("Matroska / WebM"),
        .extensions     = "mkv,mk3d,mka,mks",
        .priv_data_size = sizeof(MatroskaDemuxContext),
        .read_probe     = matroska_probe,
        .read_header    = matroska_read_header,
        .read_packet    = matroska_read_packet,
        .read_close     = matroska_read_close,
        .read_seek      = matroska_read_seek,
        .mime_type      = "audio/webm,audio/x-matroska,video/webm,video/x-matroska"
        };
        ****************************
        
            if (fmt1->read_probe) {
                score = fmt1->read_probe(&lpd);
                if (score)
                    av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size);
                if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {
                    if      (nodat == 0) score = FFMAX(score, 1);
                    else if (nodat == 1) score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);
                    else                 score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
                }
            } 
            //如果read_probe函数指针为NULL,就根据文件扩展名认档,扩展名match只有50分
            else if (fmt1->extensions) {
                if (av_match_ext(lpd.filename, fmt1->extensions))
                    score = AVPROBE_SCORE_EXTENSION;
            }
            if (av_match_name(lpd.mime_type, fmt1->mime_type))
                score = FFMAX(score, AVPROBE_SCORE_MIME);
            if (score > score_max) {
                score_max = score;
                fmt       = fmt1;
            } else if (score == score_max)
                fmt = NULL;
        }
        if (nodat == 1)
            score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
        *score_ret = score_max;

        return fmt;
    }
    
    */
    if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
        (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
        return score;

    //重要函数1
    if ((ret = avio_open2(&s->pb, filename, AVIO_FLAG_READ | s->avio_flags,
                          &s->interrupt_callback, options)) < 0)
        return ret;
    //在做完 av_probe_input_format2之后,iformat是NULL. 会调用av_probe_input_buffer2去认档
    if (s->iformat)
        return 0;
    //重要函数2
    return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                 s, 0, s->format_probesize);
}

6.重要函数1 avio_open2


int avio_open2(AVIOContext **s, const char *filename, int flags,
               const AVIOInterruptCB *int_cb, AVDictionary **options)
{
    URLContext *h;
    int err;

    err = ffurl_open(&h, filename, flags, int_cb, options);
    if (err < 0)
        return err;
    err = ffio_fdopen(s, h);
    if (err < 0) {
        ffurl_close(h);
        return err;
    }
    return 0;
}


int ffurl_open(URLContext **puc, const char *filename, int flags,
               const AVIOInterruptCB *int_cb, AVDictionary **options)
{
    int ret = ffurl_alloc(puc, filename, flags, int_cb);
    if (ret < 0)
        return ret;
    if (options && (*puc)->prot->priv_data_class &&
        (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0)
        goto fail;
    if ((ret = av_opt_set_dict(*puc, options)) < 0)
        goto fail;
    ret = ffurl_connect(*puc, options);
    if (!ret)
        return 0;
fail:
    ffurl_close(*puc);
    *puc = NULL;
    return ret;
}


int ffurl_alloc(URLContext **puc, const char *filename, int flags,
                const AVIOInterruptCB *int_cb)
{
    URLProtocol *p = NULL;

    //还记得在av_register_all中注册了protocols吧,就是放在这个link_list中
    if (!first_protocol) {
        av_log(NULL, AV_LOG_WARNING, "No URL Protocols are registered. "
                                     "Missing call to av_register_all()?\n");
    }
    
    //根据文件名,找到相应的协议,返回 URLProtocol 不再追了,还记得前面的 ff_file_protocol吧
    p = url_find_protocol(filename);
    if (p)
       return url_alloc_for_protocol(puc, p, filename, flags, int_cb);//alloc协议
    ...
}

//alloc url协议
static int url_alloc_for_protocol(URLContext **puc, struct URLProtocol *up,
                                  const char *filename, int flags,
                                  const AVIOInterruptCB *int_cb)
{
    URLContext *uc;
    int err;

#if CONFIG_NETWORK
    if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())
        return AVERROR(EIO);
#endif
    //从前面传递过来的flags是AVIO_FLAG_READ,
    //如果flag是 AVIO_FLAG_READ,而protocol中read函数指针为NULL,则出错退出
    //如果flag是 AVIO_FLAG_WRITE,而protocol中write函数指针为NULL,则出错退出
    if ((flags & AVIO_FLAG_READ) && !up->url_read) {
        av_log(NULL, AV_LOG_ERROR,
               "Impossible to open the '%s' protocol for reading\n", up->name);
        return AVERROR(EIO);
    }
    if ((flags & AVIO_FLAG_WRITE) && !up->url_write) {
        av_log(NULL, AV_LOG_ERROR,
               "Impossible to open the '%s' protocol for writing\n", up->name);
        return AVERROR(EIO);
    }
    //将fileName(包含协议name) 和URLContext一起分配,第一个bytes为fileName
    uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);
    if (!uc) {
        err = AVERROR(ENOMEM);
        goto fail;
    }
    /*
    const AVClass ffurl_context_class = {
    .class_name       = "URLContext",
    .item_name        = urlcontext_to_name,
    .option           = options,
    .version          = LIBAVUTIL_VERSION_INT,
    .child_next       = urlcontext_child_next,
    .child_class_next = urlcontext_child_class_next,
    };
    */
    uc->av_class = &ffurl_context_class;
    uc->filename = (char *)&uc[1];//第一个bytes为fileName
    strcpy(uc->filename, filename);//fileName copy到URLContext中
    uc->prot            = up;//如果是file,就是ff_file_protocol
    uc->flags           = flags;//Read
    uc->is_streamed     = 0; /* default = not streamed */
    uc->max_packet_size = 0; /* default: stream file */
    
    /* 如果是file协议
    URLProtocol ff_file_protocol = {
    .name                = "file",
    .url_open            = file_open,
    .url_read            = file_read,
    .url_write           = file_write,
    .url_seek            = file_seek,
    .url_close           = file_close,
    .url_get_file_handle = file_get_handle,
    .url_check           = file_check,
    .url_delete          = file_delete,
    .url_move            = file_move,
    .priv_data_size      = sizeof(FileContext),
    .priv_data_class     = &file_class,
    .url_open_dir        = file_open_dir,
    .url_read_dir        = file_read_dir,
    .url_close_dir       = file_close_dir,
    };
    */
    //.priv_data_size      = sizeof(FileContext),
    /*
    typedef struct FileContext {
        const AVClass *class;
        int fd;
        int trunc;
        int blocksize;
    #if HAVE_DIRENT_H
        DIR *dir;
    #endif
    } FileContext;
    */
    if (up->priv_data_size) {
        //在urlcontext中分配memory保存FileConext内容
        uc->priv_data = av_mallocz(up->priv_data_size);
        if (!uc->priv_data) {
            err = AVERROR(ENOMEM);
            goto fail;
        }

        //.priv_data_class     = &file_class,
        /*
        static const AVClass file_class = {
        .class_name = "file",
        .item_name  = av_default_item_name,
        .option     = file_options,
        .version    = LIBAVUTIL_VERSION_INT,
        };
        */
        if (up->priv_data_class) {
            int proto_len= strlen(up->name);//up-name:"file"
            char *start = strchr(uc->filename, ',');

            /*将file_class保存到FileContext的class中. 
             ffmpeg中经常遇到将一个结构体强制转化为结构体的第一个成员,
             由于AVClass是第一个成员,是可以这样操作.*/
            *(const AVClass **)uc->priv_data = up->priv_data_class;
            
            //设置default值,只有truncate和blocksize,fd是在后面open时候,才写入的
            /*
            static const AVOption file_options[] = {
            { "truncate", "truncate existing files on write", offsetof(FileContext,         trunc), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
    { "blocksize", "set I/O operation maximum block size", offsetof(FileContext, blocksize), AV_OPT_TYPE_INT, { .i64 = INT_MAX }, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
    { NULL }
};
            */
            av_opt_set_defaults(uc->priv_data);
            ...
}


再返回到上一层:
int ffurl_open(URLContext **puc, const char *filename, int flags,
               const AVIOInterruptCB *int_cb, AVDictionary **options)
{
    //刚刚已分析
    int ret = ffurl_alloc(puc, filename, flags, int_cb);
    
    //如果最开始有tmp option (truncate和blocksize),会改写file_options中的一些值
    if (options && (*puc)->prot->priv_data_class &&
        (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0)
        goto fail;
    //同时将options设置到ffurl_context_class 中的option中
    if ((ret = av_opt_set_dict(*puc, options)) < 0)
        goto fail;
    //调用prot的connect
    ret = ffurl_connect(*puc, options);
    ...
}


int ffurl_connect(URLContext *uc, AVDictionary **options)
{
    /*如果是file,就call file_open
    static int file_open(URLContext *h, const char *filename, int flags)
    {
    ...
    //先判断一些读写属性,
    fd = avpriv_open(filename, access, 0666);
    ...
    c->fd = fd;//保存fd
    ...
    }

    int avpriv_open(const char *filename, int flags, ...)
    {
        //很熟悉了吧
        fd = open(filename, flags, mode);
        return fd;
    }    
    */
    
    int err =
        uc->prot->url_open2 ? uc->prot->url_open2(uc,
                                                  uc->filename,
                                                  uc->flags,
                                                  options) :
        uc->prot->url_open(uc, uc->filename, uc->flags);
    if (err)
        return err;
    uc->is_connected = 1;
    /* We must be careful here as ffurl_seek() could be slow,
     * for example for http */
    if ((uc->flags & AVIO_FLAG_WRITE) || !strcmp(uc->prot->name, "file"))
        if (!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0)
            uc->is_streamed = 1;
    return 0;
}


//再回到 这个函数里的ffio_fdopen
int avio_open2(AVIOContext **s, const char *filename, int flags,
               const AVIOInterruptCB *int_cb, AVDictionary **options)
{
    URLContext *h;
    int err;

    err = ffurl_open(&h, filename, flags, int_cb, options);
    if (err < 0)
        return err;
    err = ffio_fdopen(s, h);
    if (err < 0) {
        ffurl_close(h);
        return err;
    }
    return 0;
}


int ffio_fdopen(AVIOContext **s, URLContext *h)
{
    ...
    if (max_packet_size) {
        buffer_size = max_packet_size; /* no need to bufferize more than one packet */
    } else {
        buffer_size = IO_BUFFER_SIZE;//max_packet_size应该没有指定,使用32K
    }
    buffer = av_malloc(buffer_size);

    //分配AVIOContext,并给它赋值,后面就可以直接调用avio的read/write/seek去读/写/seek

    /*
    要注意一个细节:avio_alloc_context参数中的实参h(URLContext*类型),对应的形参opaque,被赋值给了s->opaque,后面还会利用到这个参数.
    
    int ffio_init_context(AVIOContext *s,
                  unsigned char *buffer,
                  int buffer_size,
                  int write_flag,
                  void *opaque,
                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int64_t (*seek)(void *opaque, int64_t offset, int whence))
    {
        ...
        s->opaque      = opaque;
        ...
    }
    AVIOContext *avio_alloc_context(
                  unsigned char *buffer,
                  int buffer_size,
                  int write_flag,
                  void *opaque,
                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int64_t (*seek)(void *opaque, int64_t offset, int whence))
    {
        AVIOContext *s = av_mallocz(sizeof(AVIOContext));
        if (!s)
            return NULL;
        ffio_init_context(s, buffer, buffer_size, write_flag, opaque,
                      read_packet, write_packet, seek);
        return s;
    }
    */
    *s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h,
                            (int (*)(void *, uint8_t *, int)) ffurl_read,
                            (int (*)(void *, uint8_t *, int)) ffurl_write,
                            (int64_t (*)(void *, int64_t, int)) ffurl_seek);
    
    (*s)->direct = h->flags & AVIO_FLAG_DIRECT;
    (*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL;
    (*s)->max_packet_size = max_packet_size;
    if(h->prot) {
        (*s)->read_pause = (int (*)(void *, int))h->prot->url_read_pause;
        (*s)->read_seek  = (int64_t (*)(void *, int, int64_t, int))h->prot->url_read_seek;
    }

    /*
    static const AVOption ff_avio_options[] = {
        { NULL },
    };

    const AVClass ff_avio_class = {
    .class_name = "AVIOContext",
    .item_name  = av_default_item_name,
    .version    = LIBAVUTIL_VERSION_INT,
    .option     = ff_avio_options,
    .child_next = ff_avio_child_next,
    .child_class_next = ff_avio_child_class_next,
    };
    */
    (*s)->av_class = &ff_avio_class;
    return 0;
}

7.重要函数2:av_probe_input_buffer2

int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt,
                          const char *filename, void *logctx,
                          unsigned int offset, unsigned int max_probe_size)
{
    ...

    if (pb->av_class) {
        /*pb->av_class就是刚刚说的ff_avio_class
    const AVClass ff_avio_class = {
    .class_name = "AVIOContext",
    .item_name  = av_default_item_name,
    .version    = LIBAVUTIL_VERSION_INT,
    .option     = ff_avio_options,
    .child_next = ff_avio_child_next,
    .child_class_next = ff_avio_child_class_next,
    };
    av_opt_get是先拿ff_avio_options 然而这个option中没有任何属性,
当然也找不到"mine_type",紧接着会去call 
.child_next和.child_class_next 去找mine_type,
.child_next就是拿s->opaue的options也就是URLContext属性(前面有提到),
对于file, 也没有mine_type,但是http是有这个属性的.  
.child_class_next的options也是空,
所以对于file, pd.mime_type还是NULL. 
    */
        
        uint8_t *mime_type_opt = NULL;
        av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt);
        pd.mime_type = (const char *)mime_type_opt;
    }


    for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt;
         probe_size = FFMIN(probe_size << 1,
                            FFMAX(max_probe_size, probe_size + 1))) {
        ...

        /* Guess file format. */
        //这个前面分析过,去call read_probe去认档 ,此处略.至此,文件认档过程结束
        *fmt = av_probe_input_format2(&pd, 1, &score);
       ...
    }

}

 

你可能感兴趣的:(FFMPEG)