FFMPEG 之 Avidec.c

1 简介

  AVI格式(Audio Video Interleave)是一种将语音和影像同步组合在一起的多媒体文件格式。在FFMPEG 框架中avidec.c 文件负责avi 格式demux , 它有parse avi header , AVI文件中audio/video 数据读取, seek  等作用。

1.1  AVI文件构成

    AVI 文件是一种RIFF文件格式,以list和chunk 形式组织,其中list 可以包含子list 和chunk,  而chunk 则表示实际的数据。 chunk  = chunck 名字(4 byte ) + chunck size(4 byte) + chunck data。  list  = "list"(4 byte)  + list size(四byte) + list type (四字节) + list data。 由此我们可知名称,size 表示都是用4个字节,每个list 都是以“LIST”开头。其构成如下图所示, 可看成由RIFF + 信息块 + 数据块 + 索引块 四部分组成, 其中索引块是为了seek ,存放的是I 帧时间,位置。下图strl 存放是audio/video 的track 信息,有几路audio/video 信息就有几个strl. 

 2 AVI 文件info 解析

  对于一个完整播放器而言,支持的文件格式应该很多,比如MKV,MP4,AVI等等,面对不同格式文件, parse 方式肯定是不一样的。对于使用者来说,如果处理不同的文件格式,用的方法都不同,那么将会复杂很多。在FFMPEG 框架中,它统一了各个文件demux 的API 接口。如下几支API 是开给使用者的,因此我们通过阅读如下几支API 来了解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,
};

2.1 avi_probe()

  AVI 文件是一种RIFF 文件格式,其组成为 RIFF(4字节) +  RIFF文件大小(4字节) + AVI(文件类型,4字节),如下图所示,前4个字节就是ASICC 缩写, byte4 ~ byte 7表示长度0x0162E574(实际数据大小,前8个字节不算), byte8 ~ byte 11 就是AVI的ASICC 。因此判断一个文件是否为AVI ,看byte 0 ~ Byte 3, byte 8 ~ Byte 11 是否为RIFF, AVI .

我们对照源码看下 AVI 文件是如何被识别的, 将AVI 文件的byte 0 ~ Byte 3, byte 8 ~ Byte 11 与  avi_header 进行比较,如果等于avi_headers[i],就判定为AVI 格式。

static int avi_probe(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]    ) && //比较byte 0 ~ 3
            AV_RL32(p->buf + 8) == AV_RL32(avi_headers[i] + 4))   //比较byte 8 ~ 11
            return AVPROBE_SCORE_MAX;

    return 0;
}

static const char avi_headers[][8] = {
    { 'R', 'I', 'F', 'F', 'A', 'V', 'I', ' '  },//avi_header[0]赋值为RIFFAVI 
    { '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 }
};

 

2.2  avi_read_header()

    avi_read_header() 函数parse AVI 文件的Info 信息, 前面说到AVI 文件是一种RIFF 文件格式,它是由List 和 chunk 组成, 下图画出几个重要的几个List  parse。avih 存放avi 文件格式的info 信息,比如文件宽高等等。 strh 存放audio/video track 信息, 有记录audio /video 就有几个strh ,当parse 到strh 时,根据子tag 判别出当前track 是video/audio/subtitle,然后创建该track AVStream,AVIStream ,并将AVStream与AVIStream 进行关联。 strf 存放的是编码器相关的信息,有些解码器需要的附加信息从此块中读出。 indx 存放的时间 -位置信息, 并调用av_add_index_entry() 将时间-位置信息存放到链表当中,当进行seek 时就会用到该值。 

 问: 如何获取AVI 文件宽高信息?

在avi 中“avih” chunk 中有记录width & height 信息。

问: 如何获取码率信息?

码率信息计算: 

/* len 为video/audio track 对应的size,在经过时间戳换算。 通俗理解为:码率 = 长度/时间    
bitrate = av_rescale(8*len, st->time_base.den, duration * st->time_base.num);

2.3  avi_read_packet()

 avi_read_packet() 函数负责parse 出一帧数据, 这分两种情况, 如果是interleaved 格式的,audio/video 数据交叉存放,我们按顺序读取就行。 但是如果是non-interleaved 格式,audio/video 分开存放, 还按顺序读取的话, 那么要么读到的都是audio/video。 这会出现没画面,或没声音的情况, 因此针对no-interleaved 格式,我们读取策略是按最近时间点读取。如下图所示, no-interleaved 比interleaved  多一个步骤, 就是调用ni_prepare_read() 函数(ni = no interleaved ) ,这函数根据以读的audio/video 数据,按照最近时间点的策略来决定下一段读取的是audio还是video,并计算出并跳到下一段要读的位置。接下来就跟interleaved 格式的步骤一样,调用av_get_packet() 来读取数据。

static int ni_prepare_read() 是一个static 函数,我们来看看里面到底是怎么实现的:

static int ni_prepare_read(AVFormatContext *s)
{
         ....
    for (i = 0; i < s->nb_streams; i++) {//遍历所有流
        AVStream *st   = s->streams[i];
        AVIStream *ast = st->priv_data;
        int64_t ts     = ast->frame_offset;//已读完数据
        int64_t last_ts;

        if (!st->nb_index_entries)
            continue;

        last_ts = st->index_entries[st->nb_index_entries - 1].timestamp;
        ts = av_rescale_q(ts, st->time_base,
                          (AVRational) { FFMAX(1, ast->sample_size),
                                         AV_TIME_BASE });// 转化为时间
        if (ts < best_ts) {//比较出下一段要读取的stream, 以及时间
            best_ts           = ts;
            best_st           = st;
            best_stream_index = i;
        }
    }
        ....
        i = av_index_search_timestamp(best_st, best_ts, AVSEEK_FLAG_ANY);//根据时间找到对应的index 位置
        ....
    }
        ....
        int64_t pos = best_st->index_entries[i].pos;
        pos += best_ast->packet_size - best_ast->remaining;//得到下一段要读的位置
        if (avio_seek(s->pb, pos + 8, SEEK_SET) < 0)//seek 到下一段要读的位置
          return AVERROR_EOF;
        ....
}

 

2.4  avi_read_seek()

使用者可以通过avi_read_seek() 函数让stream 跳到指定位置,首先根据指定stream的时间找到对应的位置, 然后通过avio_seek()跳到指定位置。ps:在这里有个没弄明白的地方,AVI seek ffmpeg 源码会遍历所有stream计算出pos_min,然后进行seek .

static int avi_read_seek(AVFormatContext *s, int stream_index,
                         int64_t timestamp, int flags)
{
        ....
    st    = s->streams[stream_index];//指定stream
    ast   = st->priv_data;
    index = av_index_search_timestamp(st,
                                      timestamp * FFMAX(ast->sample_size, 1),
                                      flags); //通过timestamp 找到相应的index
        ....

    /* find the position */
    pos       = st->index_entries[index].pos;//找到timestamp 对应的 position
    timestamp = st->index_entries[index].timestamp / FFMAX(ast->sample_size, 1);

        ....
    if (avio_seek(s->pb, pos_min, SEEK_SET) < 0) {//进行seek
        av_log(s, AV_LOG_ERROR, "Seek failed\n");
        return -1;
    }

        ....
}

2.5  avi_read_close()

avio_read_close() 函数关闭AVI 文件,释放内存和其他相关资源。 

static int avi_read_close(AVFormatContext *s)
{
    int i;
    AVIContext *avi = s->priv_data;

    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st   = s->streams[i];
        AVIStream *ast = st->priv_data;
        if (ast) {
            if (ast->sub_ctx) {
                av_freep(&ast->sub_ctx->pb);
                avformat_close_input(&ast->sub_ctx);
            }
            av_freep(&ast->sub_buffer);
            av_packet_unref(&ast->sub_pkt);
        }
    }

    av_freep(&avi->dv_demux);

    return 0;
}

 3 总结

    本文先简易介绍了AVI 文件组成,了解list 与chunk 关系,以及list 与chunk 的构成。然后分析那几支avidec.c 开出去的API,由此我们就知道AVI demux 的大貌。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值