FFmpeg中的io分析

ffmpeg中的io之所以复杂,是因为ffmpeg基本支持所有的读写协议,最底层的有tcp,udp,文件和串口。上层又支持很多协议,譬如rtmp,http,rtsp等等。今天主要分析的就是ffmpeg对于io操作,特别是open和读操作。

从文件开始,看一个io操作的例子。这是截取的一段代码。分五部分:

  1. av_register_all进行注册
  2. avformat_open_input打开url(也叫filename),并关联了URLPROTOAL和FORMAT ,譬如URLPROTOAL为file和format为flv。
  3. av_read_frame读取一个帧,这是一个完整的帧数据。
  4. 对pkt进行处理,中间过程我们忽略了的。
  5. close结尾
int main(){
    const char *url = argv[1];
    AVFormatContext *fmt;

    //注册
    av_register_all();

    //打开文件,初始化 format context
    avformat_open_input(&(fmt), url, NULL, NULL);

    // 开始读取一个包
    AVPacket pkt;
    av_init_packet(&pkt);                //初始化AVPacket对象
    pkt.data = NULL;
    pkt.size = 0;
    /* read frames from the file */
    while (av_read_frame(fmt, &pkt) >= 0)     //从输入程序中读取一个包的数据,av_read_frame返回的是一个完整的帧数据
    {        
        // 对一个包进行处理
        do 
        {
            。。。。 //解码这个包 Decode pkt
            pkt.data += ret;
            pkt.size -= ret;
        } while (pkt.size > 0);
    }

    av_close_input_file(fmt);
}

1 注册

有四个宏关于封装,解封装和传输协议。av_register_output_format,av_register_input_format,ffurl_register_protocol分别会添加一个对象到全局链表中。通过查找链表来找对应的对象。有三个结构体:

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

封装:ff_##x##_muxer
解封装:ff_##x##_demuxer
传输协议:ff_##x##_protocol
#define REGISTER_MUXER(X,x) { \
    extern AVOutputFormat ff_##x##_muxer; \
    if(CONFIG_##X##_MUXER) av_register_output_format(&ff_##x##_muxer); }

#define REGISTER_DEMUXER(X,x) { \
    extern AVInputFormat ff_##x##_demuxer; \
    if(CONFIG_##X##_DEMUXER) av_register_input_format(&ff_##x##_demuxer); }

#define REGISTER_MUXDEMUX(X,x)  REGISTER_MUXER(X,x); REGISTER_DEMUXER(X,x)

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

熟悉的有FLV,RTMP,TCP等,我主要分析的也是这几个协议。但是奇怪的是怎么没有 REGISTER_MUXDEMUX (RTMP, rtmp);,因为rtmp对应的封装也是FLV的。

void av_register_all(void)
{
    static int initialized;

    if (initialized)
        return;
    initialized = 1;


    /* (de)muxers */
    REGISTER_MUXDEMUX (FLV, flv);

    /* protocols */
    REGISTER_PROTOCOL (FILE, file);
    ...
    REGISTER_PROTOCOL (RTMP, rtmp);
    REGISTER_PROTOCOL (TCP, tcp);
}

熟悉的有FLV,RTMP,TCP等,我主要分析的也是这几个协议。但是奇怪的是怎么没有 REGISTER_MUXDEMUX (RTMP, rtmp);,因为rtmp对应的封装也是FLV的。

void av_register_all(void)
{
    static int initialized;

    if (initialized)
        return;
    initialized = 1;


    /* (de)muxers */
    REGISTER_MUXDEMUX (FLV, flv);

    /* protocols */
    REGISTER_PROTOCOL (FILE, file);
    ...
    REGISTER_PROTOCOL (RTMP, rtmp);
    REGISTER_PROTOCOL (TCP, tcp);
}

2 open

avformat_open_input 初始化AVFormatContext,并用AVFormatContext.iformat打开filename。后进行读header操作。AVFormatContext是IO操作的上下文,所有操作都和它相关。

第一部分是初始化 AVFormatContext ,iformat的初始化。并且操作AVOption。调用这个函数一般情况下AVInputFormat *fmt和AVFormatContext **ps都为NULL。

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

    if (!s && !(s = avformat_alloc_context()))  //s  需要重新创建
        return AVERROR(ENOMEM);
    if (fmt)                 //一般情况下fmt为空
        s->iformat = fmt;

    if (options)
        av_dict_copy(&tmp, *options, 0);

    if ((ret = av_opt_set_dict(s, &tmp)) < 0)
        goto fail;

AVFormatContext和filename的关联。这个函数最重要了, 在这里确定了 URLProtocol 和 input format 确定了下来。

    if ((ret = init_input(s, filename)) < 0)
        goto fail;

初始化FORMAT的priv_data。

    s->duration = s->start_time = AV_NOPTS_VALUE;
    av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));

    /* allocate private data */
    if (s->iformat->priv_data_size > 0) {
        if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        if (s->iformat->priv_class) {
            *(const AVClass**)s->priv_data = s->iformat->priv_class;
            av_opt_set_defaults(s->priv_data);
            if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
                goto fail;
        }
    }

调用s->iformat->read_header读header,确定下来offset。

    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
        if ((ret = s->iformat->read_header(s, &ap)) < 0)
            goto fail;

    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->data_offset)
        s->data_offset = avio_tell(s->pb);

    s->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;

    if (options) {
        av_dict_free(options);
        *options = tmp;
    }
    *ps = s;
    return 0;

调用s->iformat->read_header读header,确定下来offset。

    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
        if ((ret = s->iformat->read_header(s, &ap)) < 0)
            goto fail;

    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->data_offset)
        s->data_offset = avio_tell(s->pb);

    s->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;

    if (options) {
        av_dict_free(options);
        *options = tmp;
    }
    *ps = s;
    return 0;

调用s->iformat->read_header读header,确定下来offset。

    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
        if ((ret = s->iformat->read_header(s, &ap)) < 0)
            goto fail;

    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->data_offset)
        s->data_offset = avio_tell(s->pb);

    s->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;

    if (options) {
        av_dict_free(options);
        *options = tmp;
    }
    *ps = s;
    return 0;

调用s->iformat->read_header读header,确定下来offset。

    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
        if ((ret = s->iformat->read_header(s, &ap)) < 0)
            goto fail;

    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->data_offset)
        s->data_offset = avio_tell(s->pb);

    s->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;

    if (options) {
        av_dict_free(options);
        *options = tmp;
    }
    *ps = s;
    return 0;

收尾工作。

fail:
    av_dict_free(&tmp);
    if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))
        avio_close(s->pb);
    avformat_free_context(s);
    *ps = NULL;
    return ret;
}

这里面重点在于 init_input ,这函数可是有点长啊。

avformat_alloc_context来初始化AVFormatContext,alloc后有通过AVOpetion进行初始化,这些初始化可以不用管。

2.1 init_input函数
这个函数都是static函数,不能外部调用。

init_input中一般情况下s->pb和s->iformat都是NULL,那就删除这两个不为空的操作代码后就非常简单了。先open文件,初始化AVProbeData,这个结构体很简单,它的作用就是抓取一点点数据进行分析。probe的数据是来进行判断属于哪一种FORMAT。

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

    if ((ret = avio_open(&s->pb, filename, AVIO_FLAG_READ)) < 0)
       return ret;

    return av_probe_input_buffer(s->pb, &s->iformat, filename, s, 0, 0);
}

2.2 avio_open
绕到这里才看到这个open函数。由两个函数ffurl_open和ffio_fdopen组成,这几个函数都比较小。ffurl_open主要是URLProtocol的操作,并初始化了URLContext,URLContext.prot为 URLProtocol。ffio_fdopen操作AVIOContext。AVIOContext,URLProtocol,URLContext互相关联着。

int avio_open(AVIOContext **s, const char *filename, int flags)
{
    URLContext *h;
    int err;

    err = ffurl_open(&h, filename, flags);

    err = ffio_fdopen(s, h);

    return 0;
}

2.2.1 ffurl_open


先看ffurl_open,这个函数主要针对于URLProtocol的,alloc URLContext,因为到现在为止还不知道该怎么来操作这个URL。

int ffurl_open(URLContext **puc, const char *filename, int flags)
{
    int ret = ffurl_alloc(puc, filename, flags);

    ret = ffurl_connect(*puc);
  }

#define URL_SCHEME_CHARS                        \
    "abcdefghijklmnopqrstuvwxyz"                \
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"                \
    "0123456789+-."

int ffurl_alloc(URLContext **puc, const char *filename, int flags)
{
    URLProtocol *up;
    char proto_str[128], proto_nested[128], *ptr;

    // URL_SCHEME_CHARS的定义看上面,这个函数返回不在URL_SCHEME_CHARS其中的第一个字符的索引
    size_t proto_len = strspn(filename, URL_SCHEME_CHARS);

    if (filename[proto_len] != ':' || is_dos_path(filename))
        strcpy(proto_str, "file");
    else
        av_strlcpy(proto_str, filename, FFMIN(proto_len+1, sizeof(proto_str)));

    av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));
    if ((ptr = strchr(proto_nested, '+')))
        *ptr = '\0';

    up = first_protocol;
    while (up != NULL) {
        if (!strcmp(proto_str, up->name))
            return url_alloc_for_protocol (puc, up, filename, flags);
        if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&
            !strcmp(proto_nested, up->name))
            return url_alloc_for_protocol (puc, up, filename, flags);
        up = up->next;
    }
    *puc = NULL;
    return AVERROR(ENOENT);
}

这里面有一个重要逻辑是通过filename,也叫url,来区分URLProtocol名字。第一步先找到第一个字符串,一个特殊的字符串,不在URL_SCHEME_CHARS中的就是特殊的字符串。前面代表协议的字符串就出现了,举几个例子:
rtmp://…
http://…
/data1/logs/nginxrtmp
三种协议,通过前面的几个字符。

找到了url协议名字,之后在url协议链中查找。找到后调用 url_alloc_for_protocol。malloc URLContext,后进行部分初始化,记住priv_data在这里已经初始化了并且uc->prot = up。

static int url_alloc_for_protocol (URLContext **puc, struct URLProtocol *up,
                                   const char *filename, int flags)
{
    URLContext *uc;
    int err;

    uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);
    if (!uc) {
        err = AVERROR(ENOMEM);
        goto fail;
    }
    uc->av_class = &urlcontext_class;
    uc->filename = (char *) &uc[1];
    strcpy(uc->filename, filename);
    uc->prot = up; //prot 这是啥名字呢?
    uc->flags = flags;
    uc->is_streamed = 0; /* default = not streamed */
    uc->max_packet_size = 0; /* default: stream file */
    if (up->priv_data_size) {
        uc->priv_data = av_mallocz(up->priv_data_size);
        if (up->priv_data_class) {
            *(const AVClass**)uc->priv_data = up->priv_data_class;
            av_opt_set_defaults(uc->priv_data);
        }
    }

    *puc = uc;
    return 0;
 fail:
    *puc = NULL;
    return err;
}

URLProtocol,URLContext在ffurl_alloc已经初始化了,并且已经关联了filename,但现在还没打开后。下一步分析:ffurl_connect,该打开url了。

uc->prot就是URLProtocol,奇怪的恨,prot是啥意思。真正调用了 uc->prot->url_open。

int ffurl_connect(URLContext* uc)
{
    int err = 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;
}

url操作完成后,在读之前还需要对AVIOContext进行初始化,AVFormatContext.pb为AVIOContext。这些名字命名好奇怪啊。

2.2.2 ffio_fdopen
ffio_fdopen 主要是针对于AVIOContext进行操作。
先定义了初始化buffer,buffer_size大小默认是32768。
初始化AVIOContext
ffio_init_context对AVIOContext进行初始化,特别两个回调函数read_pause和read_seek。

int ffio_fdopen(AVIOContext **s, URLContext *h)
{
    uint8_t *buffer;
    int buffer_size, max_packet_size;

    max_packet_size = h->max_packet_size;
    if (max_packet_size) {
        buffer_size = max_packet_size; /* no need to bufferize more than one packet */
    } else {
        buffer_size = IO_BUFFER_SIZE;
    }

    buffer = av_malloc(buffer_size);
    *s = av_mallocz(sizeof(AVIOContext));
    if (ffio_init_context(*s, buffer, buffer_size,
                      h->flags & AVIO_FLAG_WRITE, h,
                      (void*)ffurl_read, (void*)ffurl_write, (void*)ffurl_seek) < 0) {
        av_free(buffer);
        av_freep(s);
        return AVERROR(EIO);
    }
#if FF_API_OLD_AVIO
    (*s)->is_streamed = h->is_streamed;
#endif
    (*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;
    }
    return 0;
}
ffio_init_context重要的有三个变量:
s->write_packet = write_packet;
s->read_packet = read_packet;
s->seek = seek;
(void*)ffurl_read, (void*)ffurl_write, (void*)ffurl_seek这是三个全局函数。
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->buffer = buffer;
    s->buffer_size = buffer_size;
    s->buf_ptr = buffer;
    s->opaque = opaque;
    url_resetbuf(s, write_flag ? AVIO_FLAG_WRITE : AVIO_FLAG_READ);

    s->write_packet = write_packet;
    s->read_packet = read_packet;
    s->seek = seek;

    s->pos = 0;
    s->must_flush = 0;
    s->eof_reached = 0;
    s->error = 0;
#if FF_API_OLD_AVIO
    s->is_streamed = 0;
#endif
    s->seekable = AVIO_SEEKABLE_NORMAL;
    s->max_packet_size = 0;
    s->update_checksum= NULL;
    if(!read_packet && !write_flag){
        s->pos = buffer_size;
        s->buf_end = s->buffer + buffer_size;
    }
    s->read_pause = NULL;
    s->read_seek  = NULL;
    return 0;
}

2.3 av_probe_input_buffer


avio_open分析完成,回到init_open,最后调用了 av_probe_input_buffer 。这个函数读取了一部分文件后来判断数据内容的协议,以文件举例,来区分出是flv,mp4格式等等。

/**
 * Probe a bytestream to determine the input format. Each time a probe returns
 * with a score that is too low, the probe buffer size is increased and another
 * attempt is made. When the maximum probe size is reached, the input format
 * with the highest score is returned.
 *
 * @param pb the bytestream to probe
 * @param fmt the input format is put here
 * @param filename the filename of the stream
 * @param logctx the log context
 * @param offset the offset within the bytestream to probe from
 * @param max_probe_size the maximum probe buffer size (zero for default)
 * @return 0 in case of success, a negative value corresponding to an
 * AVERROR code otherwise
 */
 ```

定义化了max_probe_size,初始化为2048,也就是读取的最大数据。

 ```c
/** size of probe buffer, for guessing file type from file contents */
#define PROBE_BUF_MIN 2048
#define PROBE_BUF_MAX (1<<20)

int av_probe_input_buffer(AVIOContext *pb, AVInputFormat **fmt,
                          const char *filename, void *logctx,
                          unsigned int offset, unsigned int max_probe_size)
{
    AVProbeData pd = { filename ? filename : "", NULL, -offset };
    unsigned char *buf = NULL;
    int ret = 0, probe_size;

    if (!max_probe_size) {
        max_probe_size = PROBE_BUF_MAX;
    } else if (max_probe_size > PROBE_BUF_MAX) {
        max_probe_size = PROBE_BUF_MAX;
    } else if (max_probe_size < PROBE_BUF_MIN) {
        return AVERROR(EINVAL);
    }

    if (offset >= max_probe_size) {
        return AVERROR(EINVAL);
    }

2.3 av_probe_input_buffer


本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

avio_open分析完成,回到init_open,最后调用了 av_probe_input_buffer 。这个函数读取了一部分文件后来判断数据内容的协议,以文件举例,来区分出是flv,mp4格式等等。

/**
 * Probe a bytestream to determine the input format. Each time a probe returns
 * with a score that is too low, the probe buffer size is increased and another
 * attempt is made. When the maximum probe size is reached, the input format
 * with the highest score is returned.
 *
 * @param pb the bytestream to probe
 * @param fmt the input format is put here
 * @param filename the filename of the stream
 * @param logctx the log context
 * @param offset the offset within the bytestream to probe from
 * @param max_probe_size the maximum probe buffer size (zero for default)
 * @return 0 in case of success, a negative value corresponding to an
 * AVERROR code otherwise
 */
 ```

定义化了max_probe_size,初始化为2048,也就是读取的最大数据。

 ```c
/** size of probe buffer, for guessing file type from file contents */
#define PROBE_BUF_MIN 2048
#define PROBE_BUF_MAX (1<<20)

int av_probe_input_buffer(AVIOContext *pb, AVInputFormat **fmt,
                          const char *filename, void *logctx,
                          unsigned int offset, unsigned int max_probe_size)
{
    AVProbeData pd = { filename ? filename : "", NULL, -offset };
    unsigned char *buf = NULL;
    int ret = 0, probe_size;

    if (!max_probe_size) {
        max_probe_size = PROBE_BUF_MAX;
    } else if (max_probe_size > PROBE_BUF_MAX) {
        max_probe_size = PROBE_BUF_MAX;
    } else if (max_probe_size < PROBE_BUF_MIN) {
        return AVERROR(EINVAL);
    }

    if (offset >= max_probe_size) {
        return AVERROR(EINVAL);
    }

通过avio_read读取数据到 buftmp 中,后拷贝到pd中,AVProbeData是存放连续读取数据的。

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

        int ret, score = probe_size < max_probe_size ? AVPROBE_SCORE_MAX/4 : 0;
        int buf_offset = (probe_size == PROBE_BUF_MIN) ? 0 : probe_size>>1;
        void *buftmp;

        if (probe_size < offset) {
            continue;
        }

        /* read probe data */
        buftmp = av_realloc(buf, probe_size + AVPROBE_PADDING_SIZE);

        buf=buftmp;
        if ((ret = avio_read(pb, buf + buf_offset, probe_size - buf_offset)) < 0) {
            score = 0;
            ret = 0;            /* error was end of file, nothing read */
        }
        pd.buf_size += ret;
        pd.buf = &buf[offset];

        memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);

开始猜测format。

        /* guess file format */
        *fmt = av_probe_input_format2(&pd, 1, &score);
    }

把probe data放入到AVIOContext的buffer中,要注意在之前读的数据和现在数据的结合。

    if (!*fmt) {
        av_free(buf);
        return AVERROR_INVALIDDATA;
    }

    /* rewind. reuse probe buffer to avoid seeking */
    if ((ret = ffio_rewind_with_probe_data(pb, buf, pd.buf_size)) < 0)
        av_free(buf);

    return ret;
}

av_probe_input_buffer 有两个地方比较重要,avio_read 和 av_probe_input_format2。读操作后猜测format。

av_probe_input_format 有几个类似函数,主要是av_probe_input_format3。通过av_iformat_next遍历first_iformat,把多个AVInputFormat串起来的。后执行read_probe,或者根据extensions来进行比对。

AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret)
{
    AVProbeData lpd = *pd;
    AVInputFormat *fmt1 = NULL, *fmt;
    int score, score_max=0;

    if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {
        int id3len = ff_id3v2_tag_len(lpd.buf);
        if (lpd.buf_size > id3len + 16) {
            lpd.buf += id3len;
            lpd.buf_size -= id3len;
        }
    }

    //遍历所有AVInputFormat
    fmt = NULL;
    while ((fmt1 = av_iformat_next(fmt1))) {
        if (!is_opened == !(fmt1->flags & AVFMT_NOFILE))
            continue;
        score = 0;
        if (fmt1->read_probe) {
            score = fmt1->read_probe(&lpd);  //执行read_probe得到一个score
            if(!score && fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions))
                score = 1;
        } else if (fmt1->extensions) { //判断extensions
            if (av_match_ext(lpd.filename, fmt1->extensions)) {
                score = 50;
            }
        }
        if (score > score_max) { //得到最高分
            score_max = score;
            fmt = fmt1;
        }else if (score == score_max)
            fmt = NULL;
    }
    *score_ret= score_max;
    return fmt;
}

AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max)
{
    int score_ret;
    AVInputFormat *fmt= av_probe_input_format3(pd, is_opened, &score_ret);
    if(score_ret > *score_max){
        *score_max= score_ret;
        return fmt;
    }else
        return NULL;
}

AVInputFormat *av_probe_input_format(AVProbeData *pd, int is_opened){
    int score=0;
    return av_probe_input_format2(pd, is_opened, &score);
}

举个FLV的例子

AVInputFormat ff_flv_demuxer = {
    "flv",
    NULL_IF_CONFIG_SMALL("FLV format"),
    sizeof(FLVContext),
    flv_probe,
    flv_read_header,
    flv_read_packet,
    .read_seek = flv_read_seek,
    .extensions = "flv",
    .value = CODEC_ID_FLV1,
};

既有flv_probe又有extensions。想到一个问题,RTMP是如何来鉴别input format的呢?

2.4 avio_read

读操作,从AVIOContext读取size长的数据到buf中。s->read_packet是一个回调函数,在ffio_init_context函数中进行了赋值为ffurl_read。

int avio_read(AVIOContext *s, unsigned char *buf, int size)
{
    int len, size1;

    size1 = size;
    while (size > 0) {
        len = s->buf_end - s->buf_ptr;
        if (len > size)
            len = size;
        if (len == 0) {
            if(size > s->buffer_size && !s->update_checksum){
                if(s->read_packet)
                    len = s->read_packet(s->opaque, buf, size);
                if (len <= 0) {
                    /* do not modify buffer if EOF reached so that a seek back can
                    be done without rereading data */
                    s->eof_reached = 1;
                    if(len<0)
                        s->error= len;
                    break;
                } else {
                    s->pos += len;
                    size -= len;
                    buf += len;
                    s->buf_ptr = s->buffer;
                    s->buf_end = s->buffer/* + len*/;
                }
            }else{
                fill_buffer(s);
                len = s->buf_end - s->buf_ptr;
                if (len == 0)
                    break;
            }
        } else {
            memcpy(buf, s->buf_ptr, len);
            buf += len;
            s->buf_ptr += len;
            size -= len;
        }
    }
    if (size1 == size) {
        if (s->error)      return s->error;
        if (url_feof(s))   return AVERROR_EOF;
    }
    return size1 - size;
}

ffurl_read 是一个全局函数,retry_transfer_wrapper是对h->prot->url_read的封装,是URLProtocol的成员变量。

int ffurl_read(URLContext *h, unsigned char *buf, int size)
{
    if (!(h->flags & AVIO_FLAG_READ))
        return AVERROR(EIO);
    return retry_transfer_wrapper(h, buf, size, 1, h->prot->url_read);
}

static inline int retry_transfer_wrapper(URLContext *h, unsigned char *buf, int size, int size_min,
                                         int (*transfer_func)(URLContext *h, unsigned char *buf, int size))
{
    int ret, len;
    int fast_retries = 5;

    len = 0;
    while (len < size_min) {
        ret = transfer_func(h, buf+len, size-len);
        if (ret == AVERROR(EINTR))
            continue;
        if (h->flags & AVIO_FLAG_NONBLOCK)
            return ret;
        if (ret == AVERROR(EAGAIN)) {
            ret = 0;
            if (fast_retries)
                fast_retries--;
            else
                usleep(1000);
        } else if (ret < 1)
            return ret < 0 ? ret : len;
        if (ret)
           fast_retries = FFMAX(fast_retries, 2);
        len += ret;
        if (len < size && url_interrupt_cb())
            return AVERROR_EXIT;
    }
    return len;
}

retry_transfer_wrapper这名字取的忒屌啦!

3 av_read_frame

av_read_frame 读取一个包,通过av_read_frame_internal读取数据,后放入到列表AVPacketList中,最后返回列表中的包。

如果列表s->packet_buffer中有包,则通过一些时间戳的逻辑来返回pkt。这个逻辑太细了,先放过。

int av_read_frame(AVFormatContext *s, AVPacket *pkt)
{
    AVPacketList *pktl;
    int eof=0;
    const int genpts= s->flags & AVFMT_FLAG_GENPTS;

    for(;;){
        pktl = s->packet_buffer;
        if (pktl) {
            AVPacket *next_pkt= &pktl->pkt;

            if(genpts && next_pkt->dts != AV_NOPTS_VALUE){
                int wrap_bits = s->streams[next_pkt->stream_index]->pts_wrap_bits;
                while(pktl && next_pkt->pts == AV_NOPTS_VALUE){
                    if(   pktl->pkt.stream_index == next_pkt->stream_index
                       && (0 > av_compare_mod(next_pkt->dts, pktl->pkt.dts, 2LL << (wrap_bits - 1)))
                       && av_compare_mod(pktl->pkt.pts, pktl->pkt.dts, 2LL << (wrap_bits - 1))) { //not b frame
                        next_pkt->pts= pktl->pkt.dts;
                    }
                    pktl= pktl->next;
                }
                pktl = s->packet_buffer;
            }

            if(   next_pkt->pts != AV_NOPTS_VALUE
               || next_pkt->dts == AV_NOPTS_VALUE
               || !genpts || eof){
                /* read packet from packet buffer, if there is data */
                *pkt = *next_pkt;
                s->packet_buffer = pktl->next;
                av_free(pktl);
                return 0;
            }
        }

 av_read_frame_internal 读取一个包,并放入到s->packet_buffer中。

        if(genpts){
            int ret= av_read_frame_internal(s, pkt);
            if(ret<0){
                if(pktl && ret != AVERROR(EAGAIN)){
                    eof=1;
                    continue;
                }else
                    return ret;
            }

            if(av_dup_packet(add_to_pktbuf(&s->packet_buffer, pkt,
                                           &s->packet_buffer_end)) < 0)
                return AVERROR(ENOMEM);
        }else{
            assert(!s->packet_buffer);
            return av_read_frame_internal(s, pkt);
        }
    }
}

重点在于 av_read_frame_internal 了,读取一个完整的包。如果不完整,是不会返回的。av_read_frame_internal调用了av_read_packet,而av_read_packet调用了s->iformat->read_packet。由于av_read_frame_internal太复杂,不深究了。用FLV来举例,FLV解封装的结构如下:

AVInputFormat ff_flv_demuxer = {
    "flv",
    NULL_IF_CONFIG_SMALL("FLV format"),
    sizeof(FLVContext),
    flv_probe,
    flv_read_header,
    flv_read_packet,
    .read_seek = flv_read_seek,
    .extensions = "flv",
    .value = CODEC_ID_FLV1,
};

read frame时调用的就是flv_read_packet的函数。

4 av_close_input_file

最后收尾函数 av_close_input_file。分两步关闭,第一步关闭AVFormatContext,第二步关闭AVIOContext。

void av_close_input_file(AVFormatContext *s)
{
    AVIOContext *pb = (s->iformat->flags & AVFMT_NOFILE) || (s->flags & AVFMT_FLAG_CUSTOM_IO) ?
                       NULL : s->pb;
    av_close_input_stream(s);
    if (pb)
        avio_close(pb);
}

 本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值