Bootstrap

FFmpeg源码:avio_read_partial函数分析

=================================================================

AVIOContext结构体和其相关的函数分析:

FFmpeg源码:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析

FFmpeg源码:read_packet_wrapper、fill_buffer函数分析

FFmpeg源码:avio_read函数分析

FFmpeg源码:avio_seek函数分析

FFmpeg源码:avio_skip函数分析

FFmpeg源码:avio_tell函数分析

FFmpeg源码:ffurl_seek2、ffurl_seek、avio_size函数分析

FFmpeg源码:avio_feof函数分析

FFmpeg源码:avio_read_partial函数分析

=================================================================

一、avio_read_partial函数的声明

avio_read_partial函数声明在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的头文件libavformat/avio.h中:

/**
 * Read size bytes from AVIOContext into buf. Unlike avio_read(), this is allowed
 * to read fewer bytes than requested. The missing bytes can be read in the next
 * call. This always tries to read at least 1 byte.
 * Useful to reduce latency in certain cases.
 * @return number of bytes read or AVERROR
 */
int avio_read_partial(AVIOContext *s, unsigned char *buf, int size);

该函数的作用是:

情况一:如果该媒体文件被打开是为了进行写入,对本地媒体文件或网络流进行读取,读取size个字节,将读上来的数据保存到形参buf指向的缓冲区中。

情况二:如果该媒体文件被打开不是为了进行写入,首先判断AVIOContext输入缓冲区中的数据被读取的状态,根据结果又会分为下面两种情况:

1.如果输入缓冲区中没有数据或者数据已被读完,通过文件描述符去读取本地媒体文件中的数据,或者通过socket接收网络流中的数据,让读上来的数据填满整个AVIOContext输入缓冲区。再读取输入缓冲区的数据,尝试读取size个字节,如果输入缓冲区读完了还没到size个字节,不继续进行读取。

2.如果输入缓冲区中存在数据且数据还未被读完,读取输入缓冲区的数据,尝试读取size个字节。如果输入缓冲区读完了还没到size个字节,不继续进行读取。

该函数的用法跟avio_read函数比较相似,不同点在于:avio_read函数保证读取size个字节数据(除非出错)。而使用avio_read_partial函数,即使输入缓冲区读取完了还没到size个字节,也不会继续对本地媒体文件或网络流进行读取,而是直接返回。由于从输入缓冲区(内存)读取的速度是远高于从本地媒体文件(硬盘)或网络流(socket)读取的,所以跟avio_read函数相比,使用avio_read_partial函数在某些情况下可用于减少延迟。

形参s:既是输入型参数也是输出型参数。指向一个AVIOContext(字节流上下文结构体)变量。执行avio_read函数后,s->buf_ptr等成员会发生相应变化。

形参buf:输出型参数。保存读上来的数据的缓冲区。

形参size:输入型参数。要读取的字节数。

返回值:返回一个非负数表示成功,此时返回实际读取到的字节数;返回一个负数表示出错。

二、avio_read_partial函数的定义

avio_read_partial函数定义在源文件libavformat/aviobuf.c中:

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

    if (size < 0)
        return AVERROR(EINVAL);

    if (s->read_packet && s->write_flag) {
        len = read_packet_wrapper(s, buf, size);
        if (len > 0)
            s->pos += len;
        return len;
    }

    len = s->buf_end - s->buf_ptr;
    if (len == 0) {
        fill_buffer(s);
        len = s->buf_end - s->buf_ptr;
    }
    if (len > size)
        len = size;
    memcpy(buf, s->buf_ptr, len);
    s->buf_ptr += len;
    if (!len) {
        if (s->error)      return s->error;
        if (avio_feof(s))  return AVERROR_EOF;
    }
    return len;
}

三、avio_read_partial函数的内部实现分析

avio_read_partial函数中,首先判断要读取的字节数是否小于0,如果小于0,返回AVERROR(EINVAL)表示出错:

    if (size < 0)
        return AVERROR(EINVAL);

如果指向读取数据包的回调函数存在(s->read_packet为真),并且该媒体文件被打开是为了写入的(s->write_flag为真),通过read_packet_wrapper函数对本地媒体文件或网络流进行读取,读取size个字节,将读上来的数据保存到形参buf指向的缓冲区中,返回实际读取到的字节数:

    if (s->read_packet && s->write_flag) {
        len = read_packet_wrapper(s, buf, size);
        if (len > 0)
            s->pos += len;
        return len;
    }

 如果不满足上面情况,判断AVIOContext输入缓冲区中还有多少数据未被读取。如果输入缓冲区中没有数据或者数据已被读完,调用fill_buffer函数,通过文件描述符去读取本地媒体文件中的数据,或者通过socket接收网络流中的数据,让读上来的数据填满整个AVIOContext输入缓冲区。重新得到AVIOContext输入缓冲区中未被读取的数据的字节数,赋值给变量len:

    len = s->buf_end - s->buf_ptr;
    if (len == 0) {
        fill_buffer(s);
        len = s->buf_end - s->buf_ptr;
    }

读取输入缓冲区的数据,尝试读取size个字节。如果输入缓冲区读取完了还没到size个字节,不继续进行读取:

    if (len > size)
        len = size;
    memcpy(buf, s->buf_ptr, len);
    s->buf_ptr += len;

成功读取,返回实际读取到的字节数;否则返回负数表示出错:

    if (!len) {
        if (s->error)      return s->error;
        if (avio_feof(s))  return AVERROR_EOF;
    }
    return len;

;