分析 avformat_open_input 数据读取过程

------------------------------------------------------------
author: hjjdebug
date: 2024年 08月 13日 星期二 17:31:43 CST
descriptor: 分析 avformat_open_input 数据读取过程
------------------------------------------------------------
 avformat_open_input 中读取数据. 
 0 in read of  libc 中的 read 函数入口.
 1 in file_read of libavformat/file.c:114
 2 in retry_transfer_wrapper of libavformat/avio.c:370
 3 in ffurl_read of libavformat/avio.c:405
 4 in read_packet_wrapper of libavformat/aviobuf.c:521
 5 in fill_buffer of libavformat/aviobuf.c:570
 6 in avio_read of libavformat/aviobuf.c:663
 7 in av_probe_input_buffer2 of libavformat/format.c:262
 8 in init_input of libavformat/utils.c:450
 9 in avformat_open_input of libavformat/utils.c:548
10 in open_input_file of filtering_video.c:39
11 in main of filtering_video.c:212

第10层,11层是用户代码,就不用分析了, 
测试代码是ffmpeg自带的Doc/example/filtering_video.c

avformat_open_input 函数打开一个本地文件必然要做的三件事情是
1. 打开文件
2. 读取数据
3. 分析数据.

本博客针对的是

2. 读取数据,

3.分析数据,  顺便提及了一下h264文件的头部读取,它很简单,就没有读数据,见后分析.

关于1.打开文件, 可参考 avformat_open_input 打开URL的流程-CSDN博客
所附代码是精简化后的代码,帮助理解. 
汉字注释都是我的心得体会,都是关键点,就不用醒目标注了.

下面倒序分析: 

第1层: file_read
----------------------------------------
输入参数: URLContext *h
输出参数: buf, size
返回值: 读取的字节数
功能:  从URLContext 中读取数据到buf
----------------------------------------
static int file_read(URLContext *h, unsigned char *buf, int size)
{
    FileContext *c = h->priv_data;
    size = FFMIN(size, c->blocksize); //size 是32768, c->blocksize是个很大的数
    int ret = read(c->fd, buf, size); // libc 接口 read 函数
    return (ret == -1) ? AVERROR(errno) : ret;
}

现在是面向对象编程了. 给你传个对象handle, 你要先从对象中取到FileContext ,再取到fd,
这才是保留的文件描述符号,于是就能用read 取到文件数据了.

第2层: retry_transfer_wrapper
-----------------------------------------------------------
输入参数: URLContext *h, transfer_func, size_min
输出参数: buf,size
返回值: 读取的字节数
功能:  从URLContext中,通过 transfer_func 读取数据到buf
-----------------------------------------------------------

为啥我的代码看起来如此简单, 嗯! 我去掉了一些出错判断,只保留了关键部分.
static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,
                                         int size, int size_min,
                                         int (*transfer_func)(URLContext *h,
                                                              uint8_t *buf,
                                                              int size))
{
    int len = 0;
    while (len < size_min) {
        int ret = transfer_func(h, buf + len, size - len); //这里的transfer_func是file_read
        len += ret;
    }
    return len;
}


第3层: ffurl_read
------------------------------------------------------
输入参数: URLContext *h
输出参数: buf,size
返回值: 读取的字节数
功能:  从URLContext中,读取数据到buf
------------------------------------------------------
int ffurl_read(URLContext *h, unsigned char *buf, int size)
{
    return retry_transfer_wrapper(h, buf, size, 1, h->prot->url_read);
}
它调用retry_transfer_wrapper,传入了读取函数h->prot-url_read 指针
这里出现了2个对象, URLContext , URLProtocol 对象
实例测试时size 为32768

第4层: read_packet_wrapper
------------------------------------------------------
输入参数: AVIOContext *s
输出参数: buf,size
返回值: 读取的字节数
功能:  从AVIOContext中,读取数据到buf
------------------------------------------------------
static int read_packet_wrapper(AVIOContext *s, uint8_t *buf, int size)
{
    int ret = s->read_packet(s->opaque, buf, size);//这个s->read_packet就是ffurl_read
    return ret;
}
这里出现了一个高层对象AVIOContext *s;
调用s->read_packet 函数,这个函数就是ffurl_read,
并且把s->opaque 传入,这个参数是URLContext 对象, 把数据读取到buf中,size 32768
看到这是否要气疯了?! 从文件读个数据怎么这么麻烦?
我们总结一下:
对象1: AVIOContext 对象 s
对象2: URLContext 对象 h,  其中 h=s->opaque
对象3: URLProtocol 对象,这里没有给名字,就叫h->prot
之所以搞这么复杂,是因为他要支持各种协议, 例如它给你一个udp://238.1.1.1:8000, 你照样
可以打开udp网络流取读,只不过它走的不是文件读而是udp的读了. 
而且对象的函数指针,是软的!!! 付给它什么函数,它就可以调用什么函数.
对象包含对象. 
s(AVIOContext)包含h(URLContext)        h=s->opaque
h(URLContext)包含(FileContext)       c=h->priv_data
c(FileContext)包含文件描述符         fd = c->fd
我们最后用fd 来读写文件.
这就是用对象分层的好处.或者复杂处,这样就知道为什么要一层一层来了.

第5层: fill_buffer
------------------------------------------------------
输入参数: AVIOContext *s
输出参数: 无
功能:  从AVIOContext中,读取数据到它自己的缓冲区
------------------------------------------------------
static void fill_buffer(AVIOContext *s)
{   //s->max_buffer_size 等于0, 所以最大缓冲size max_buffer_size 这里给IO_BUFFER_SIZE(32768)
    int max_buffer_size = s->max_packet_size ?
                          s->max_packet_size : IO_BUFFER_SIZE;
   // s->buffer_size 是32768, 这里确定是向哪里读数据,是s->buffer 还是s->buf_end    
   // 由于一般判断条件为假,所以会选择s->buffer,即缓冲头为目标
    uint8_t *dst        = s->buf_end - s->buffer + max_buffer_size <= s->buffer_size ?
                          s->buf_end : s->buffer;
    int len             = s->buffer_size - (dst - s->buffer);

    len = read_packet_wrapper(s, dst, len); //调用上边分析过的函数
    //根据返回的长度信息,更新AVIOContext 参数
    if (len == AVERROR_EOF) {
        s->eof_reached = 1;
    } else if (len < 0) {
        s->eof_reached = 1;
        s->error= len;
    } else {
        s->pos += len;
        s->buf_ptr = dst;
        s->buf_end = dst + len;
        s->bytes_read += len;
    }
}
问: s->pos, s->bytes_read 有什么区别?
答:这里看不出区别,别处能看出来.


第6层: avio_read
------------------------------------------------------
输入参数: AVIOContext *s
输出参数: buf,size
返回值: 读取的字节数
功能:  从AVIOContext中,读取size个数据到buf中
------------------------------------------------------
int avio_read(AVIOContext *s, unsigned char *buf, int size)
{
    int len, size1;

    size1 = size;
    while (size > 0)  //读数据直到size=0
    {
        //缓存中有数据吗?
        len = FFMIN(s->buf_end - s->buf_ptr, size);
        if (len == 0 )  // 没有
        {
                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;
        }
    }
    return size1 - size;
}
搞懂了代码,原来avio_read 是如此的简单,就是填充数据到buf. 
测试的size 是2048

第7层: av_probe_input_buffer2
------------------------------------------------------------------
输入参数: AVIOContext *pb, filename,logctx,offset, max_probe_size
输出参数: AVInputFormat **fmt
返回值:  得分值,50以上为成功
功能:  从AVIOContext中,读取探测数据,分析文件格式并保存到fmt
------------------------------------------------------------------
offset: 要求探测数据需要跳过的偏移量,一般是0.
max_probe_size: 一般给0,由该函数定义
logctx: 打log时才会用到,
filename: 此处用途不大
AVIOContext *pb, 重要, 从该对象读取数据
int av_probe_input_buffer2(AVIOContext *pb, ff_const59 AVInputFormat **fmt,
                          const char *filename, void *logctx,
                          unsigned int offset, unsigned int max_probe_size)
{
    AVProbeData pd = { filename ? filename : "" };
    uint8_t *buf = NULL;
    int ret = 0, probe_size, buf_offset = 0;
    int score = 0;

    if (!max_probe_size)
        max_probe_size = PROBE_BUF_MAX; // 最大 1<<20

    // 最小 2048, 每次增加1倍
    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))) 
         {
        score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0;

        //分配内存
        if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0) goto fail;
        ret = avio_read(pb, buf + buf_offset, probe_size - buf_offset);
        buf_offset += ret;
        if (buf_offset < offset)   //读取的数据必需要大于offset
            continue;
        //确定探测数据的位置和大小    
        pd.buf_size = buf_offset - offset;
        pd.buf = &buf[offset];
        memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);

        /* Guess file format. */
        //由读取的探测数据,找到AVInputFormat 对象和得分值
        *fmt = av_probe_input_format2(&pd, 1, &score); //具体探测过程是枚举,此处忽略
        if (*fmt) { //成功
                av_log(logctx, AV_LOG_DEBUG,
                       "Format %s probed with size=%d and score=%d\n",
                       (*fmt)->name, probe_size, score);
        }
    }

    if (!*fmt)
        ret = AVERROR_INVALIDDATA;
    //把AVIOContext 数据指针回退buf_offset, 以便后边重用这段数据
    int ret2 = ffio_rewind_with_probe_data(pb, &buf, buf_offset);
    if (ret >= 0) ret = ret2;
    return ret < 0 ? ret : score;
}


第8层: init_input
------------------------------------------------------------------
输入参数: AVFormatContext *s, filename,AVDictionary**options
输出参数: AVFormatContext s->iformat
返回值: 探测的得分值 
功能:  打开文件,探测文件格式,赋值给s->iformat
------------------------------------------------------------------
/* Open input file and probe the format if necessary. */
//打开文件,探测文件格式
static int init_input(AVFormatContext *s, const char *filename,
                      AVDictionary **options)
{
    int ret;
    //该函数就是让s->pb 有值,创建AVIOContext 对象,鉴于篇幅,此处不具体分析,可参考

   // avformat_open_input 打开URL的流程-CSDN博客
    if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
        return ret;
    return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                 s, 0, s->format_probesize); // 上面的分析过程
}

第9层: avformat_open_input
------------------------------------------------------------------
输入参数: filename,AVDictionary**options, AVInputFormat *fmt
输出参数: AVFormatContext **ps
返回值:  0 成功
功能:  打开文件,探测文件格式,赋值给s, 分析文件头部,赋值给s
------------------------------------------------------------------
假设fmt=0; 输入格式需要探测
optionss=0; 没有选项要设置,简化逻辑
int avformat_open_input(AVFormatContext **ps, const char *filename,
                        ff_const59 AVInputFormat *fmt, AVDictionary **options)
{
    int i, ret = 0;
    AVDictionary *tmp = NULL;
    ID3v2ExtraMeta *id3v2_extra_meta = NULL;

    AVFormatContext *s  = avformat_alloc_context();
    if (!(s->url = av_strdup(filename ? filename : ""))) { }

    if ((ret = init_input(s, filename, &tmp)) < 0) goto fail; //上边的分析过程,为s赋值iformat
    s->probe_score = ret;
    avio_skip(s->pb, s->skip_initial_bytes);
    s->duration = s->start_time = AV_NOPTS_VALUE;

    //分配私有数据
    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;
        }
    }

    //关键函数,见补充1,指针函数调用了 ff_faw_video_read_header()

    if ((ret = s->iformat->read_header(s)) < 0)
        goto fail;

    s->internal->data_offset = avio_tell(s->pb);
    s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
    update_stream_avctx(s); //把流参数copy到 st->internal->avctx,见补充2

    for (i = 0; i < s->nb_streams; i++)
        s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id;

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

补充1: ff_raw_video_read_header 函数

------------------------------------------------------------------

对于h264 读取头部信息, 它根本就没有读取数据, 而是直接创建了一个流就完事了.

当然这个视频流从AVFormatContext 会copy 一些信息, 但其它一些关键信息可能还只是默认值

要等avformat_find_stream_info()去填充
/* MPEG-1/H.263 input */
int ff_raw_video_read_header(AVFormatContext *s)
{
    AVStream *st = avformat_new_stream(s, NULL);
    st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
    st->codecpar->codec_id = s->iformat->raw_codec_id;
    st->need_parsing = AVSTREAM_PARSE_FULL_RAW;

    FFRawVideoDemuxerContext *s1 = s->priv_data;
    st->internal->avctx->framerate = s1->framerate;
    avpriv_set_pts_info(st, 64, 1, 1200000);

    return 0;
}

------------------------------------------------------------------
补充2: update_stream_avctx 函数

------------------------------------------------------------------

功能: 把流参数copy到 st->internal->avctx
static int update_stream_avctx(AVFormatContext *s)
{
    int i, ret;
    for (i = 0; i < s->nb_streams; i++) { //h264就一个视频流
        AVStream *st = s->streams[i];
        if (!st->internal->need_context_update) continue;

        /* close parser, because it depends on the codec */
        if (st->parser && st->internal->avctx->codec_id != st->codecpar->codec_id) {
            av_parser_close(st->parser);
            st->parser = NULL;
        }

        /* update internal codec context, for the parser */
        ret = avcodec_parameters_to_context(st->internal->avctx, st->codecpar);
        if (ret < 0) return ret;
        st->internal->need_context_update = 0;
    }
    return 0;
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值