从学龄前开始解读FFMPEG代码 之 avformat_open_input函数read_probe和read_head

开始学习前想说的话

在之前的open_input函数一二三的学习中,学龄前课程以一个代码运行的时序来进行了代码讲解,但是毕竟没有用一个较好的实例说明,而且在av_probe_input_format3函数中,学龄前学习也提到了,对于需要推测文件格式的数据源,会调用文件格式列表里面每种文件格式的read_probe函数去给一个评分,那么在avformat_open_input整个过程中会用到的read_probe和read_head具体要怎么做呢?本文以avi和flv两种视频封装格式为例子进行一下学龄前讨论学习

avi格式视频要如何probe

首先来看一下avi格式视频是怎么封装的,该格式的封装代码函数在libavformat/avidec.c中,拉到代码最后一行可以看到,该封装格式定义如下:

AVInputFormat ff_avi_demuxer = {
    .name           = "avi",
    .long_name      = NULL_IF_CONFIG_SMALL("AVI (Audio Video Interleaved)"),
    .priv_data_size = sizeof(AVIContext),
    .extensions     = "avi",
    .read_probe     = avi_probe,
    .read_header    = avi_read_header,
    .read_packet    = avi_read_packet,
    .read_close     = avi_read_close,
    .read_seek      = avi_read_seek,
    .priv_class = &demuxer_class,
};

可以看到哈,avi格式设置了自己的read_probe函数,函数名字叫avi_probe,就在同一个文件里,如下所示:

static int avi_probe(const AVProbeData *p)
{
    int i;

    /* check file header */
    for (i = 0; avi_headers[i][0]; i++)
        if (AV_RL32(p->buf    ) == AV_RL32(avi_headers[i]    ) &&
            AV_RL32(p->buf + 8) == AV_RL32(avi_headers[i] + 4))
            return AVPROBE_SCORE_MAX;

    return 0;
}

看上去非常简短哈,但是这里涉及到了一个比较重要的变量是avi_headers,一个二位数组,记录了一些headers信息,这个数组在同文件中定义了,如下所示:

static const char avi_headers[][8] = {
    { 'R', 'I', 'F', 'F', 'A', 'V', 'I', ' '  },
    { 'R', 'I', 'F', 'F', 'A', 'V', 'I', 'X'  },
    { 'R', 'I', 'F', 'F', 'A', 'V', 'I', 0x19 },
    { 'O', 'N', '2', ' ', 'O', 'N', '2', 'f'  },
    { 'R', 'I', 'F', 'F', 'A', 'M', 'V', ' '  },
    { 0 }
};

那么在probe里的这个*AV_RL32(p- >buf) == AV_RL32(avi_headers[i])*是在比较什么呢?我们知道,文件以二进制数据的方式存储,而文件头,即文件head中一般存储了关于这个文件的各种相关信息;实际上,AVI 文件是一种RIFF 文件格式,所以其文件头部的组成为 ‘RIFF’(4字节) + RIFF文件大小(4字节) + ‘AVIx’(文件类型,4字节,x可能为不同的类型,对应不同的字符)共12字节,如果要推测检查buf中是不是avi的文件头,就是将读入的文件头中的byte 0 ~ Byte 3(32位所以用了FFmpeg中的宏,是AV_RL32), byte 8 ~ Byte 11 与 avi_header 进行比较,如果对比成功,那么就证明该文件是avi文件,直接一伯分通过!否则,直接给予一个0的评分,推测条件非常严格,没有其他的中间分数。

从这个函数来看,avi的probe函数还是较为简单的,下面来看一下flv的probe函数是怎么样的

FLV格式视频是如何probe的

flv的probe函数写在了libavformat/flvdec.c文件中,该文件里有flv,有flv_live以及kux(优酷所使用的一种格式)等flv解码的格式,那么来看一下flv的probe吧

static int flv_probe(const AVProbeData *p)
{
    return probe(p, 0);
}

直接指向了该文件内的probe函数,所以实际上再跳转到probe函数中

static int probe(const AVProbeData *p, int live)
{
    const uint8_t *d = p->buf;
    unsigned offset = AV_RB32(d + 5);

    if (d[0] == 'F' &&
        d[1] == 'L' &&
        d[2] == 'V' &&
        d[3] < 5 && d[5] == 0 &&
        offset + 100 < p->buf_size &&
        offset > 8) {
        int is_live = !memcmp(d + offset + 40, "NGINX RTMP", 10);

        if (live == is_live)
            return AVPROBE_SCORE_MAX;
    }
    return 0;
}

在flv的flv_probe中跳转到了同文件中的函数probe,而在该函数中可以看到,flv采用了跟avi差不多的文件头数据的字节对比的方法,首先用AV_RB32(d + 5)这样一个宏定义获取了byte 5~byte 8的数据(从byte0开始算的话),并使用AV_RB32进行了大小端转换,转换为小端并存到变量offset中。

之后,检查开头3个字符(代表signature)是否为“FLV”,再检查第四个字符(代表flv的version)是否小于5,再判断第6个字节(Headersize的第1个字节)为0,以及offset取值大于8.进入if判断后,是live flv和flv种类的判断,通过之后直接返回一伯分!同样的,如果没有通过检查,那么直接没有余地,评分为零。

FLV文件read_header做了什么

继续上面的flv分析,flv文件里的read_header又要做什么呢?
flv的read_header也在libavformat/flvdec.c文件中,如下所示:

static int flv_read_header(AVFormatContext *s)
{
    int flags;
    FLVContext *flv = s->priv_data;
    int offset;
    int pre_tag_size = 0;

    /* Actual FLV data at 0xe40000 in KUX file */
    if(!strcmp(s->iformat->name, "kux"))
        avio_skip(s->pb, 0xe40000);

    avio_skip(s->pb, 4);
    flags = avio_r8(s->pb);

    flv->missing_streams = flags & (FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO);

    s->ctx_flags |= AVFMTCTX_NOHEADER;

    offset = avio_rb32(s->pb);
    avio_seek(s->pb, offset, SEEK_SET);

    /* Annex E. The FLV File Format
     * E.3 TheFLVFileBody
     *     Field               Type    Comment
     *     PreviousTagSize0    UI32    Always 0
     * */
    pre_tag_size = avio_rb32(s->pb);
    if (pre_tag_size) {
        av_log(s, AV_LOG_WARNING, "Read FLV header error, input file is not a standard flv format, first PreviousTagSize0 always is 0\n");
    }

    s->start_time = 0;
    flv->sum_flv_tag_size = 0;
    flv->last_keyframe_stream_index = -1;

    return 0;
}

在代码中可以看见,声明好需要使用到的变量之后,代码流程中首先对kux格式(优酷的一种视频文件格式)做好skip(从代码来看实际上只是做了一个位移嘛…感觉针对这文件类型youku是不是也没有使用什么特别的视频算法编码之类的…)之后,将pb跳过4个字节,并读取设置好头部存储的flag位。
之后,检查数据中是否有视频和音频文件并设置标记位missing_streams,然后在ctx_flags中设置好noheader位置表示头部已经移动处理了。
在此之后,设置好位移offset,再对s的pb进行重定位,判断好pretagsize,之后设置好开始时间等等变量,完成整个过程。

结束时要说的话

为什么不分析avi的read_header函数呢?因为实在是太长了,看到头疼。总体上来说,每种视频格式使用到的函数也由它们自身视频文件特点来决定,所以本文只通过几个简单举例,来了解这两个函数的主要功能大概是做什么的就可以了。
不过接下来最为头疼的还是接下来几篇学习中即将要拿来分析的avformat_find_stream_info函数,该函数用于在打开解码器之前,提取文件数据,尝试读取一些视频的packet包数据,做好文件中stream等等的各项设置,功能比较重要,总共有五百多行,预计要分成三到四篇才能讲得完…而且也是个函数挖坑重灾区,慢慢走起吧

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值