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;