一、retry_transfer_wrapper函数
retry_transfer_wrapper函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/avio.c中:
cpp
static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,
const uint8_t *cbuf,
int size, int size_min,
int read)
{
int ret, len;
int fast_retries = 5;
int64_t wait_since = 0;
len = 0;
while (len < size_min) {
if (ff_check_interrupt(&h->interrupt_callback))
return AVERROR_EXIT;
ret = read ? h->prot->url_read (h, buf + len, size - len):
h->prot->url_write(h, cbuf + len, size - len);
if (ret == AVERROR(EINTR))
continue;
if (h->flags & AVIO_FLAG_NONBLOCK)
return ret;
if (ret == AVERROR(EAGAIN)) {
ret = 0;
if (fast_retries) {
fast_retries--;
} else {
if (h->rw_timeout) {
if (!wait_since)
wait_since = av_gettime_relative();
else if (av_gettime_relative() > wait_since + h->rw_timeout)
return AVERROR(EIO);
}
av_usleep(1000);
}
} else if (ret == AVERROR_EOF)
return (len > 0) ? len : AVERROR_EOF;
else if (ret < 0)
return ret;
if (ret) {
fast_retries = FFMAX(fast_retries, 2);
wait_since = 0;
}
len += ret;
}
return len;
}
该函数作用是:对本地媒体文件或网络流进行读取或写入。如果是读取,将读上来的数据保存到形参buf指向的缓冲区中;如果是写入,将形参cbuf指向的缓冲区中的数据写入到本地媒体文件或网络流中。
形参h:输入型参数。 指向一个url(Uniform Resource Locator)上下文结构体。
形参buf:如果是要将数据写入到本地媒体文件或网络流中,该值为NULL;如果是要读取本地媒体文件或网络流的数据,buf为输出型参数,指向"保存读上来的数据的缓冲区";
形参cbuf:如果是要读取本地媒体文件或网络流的数据,该值为NULL;如果是要将数据写入到本地媒体文件或网络流中,buf为输入型参数,指向"存放需要写入到文件或网络流中的数据的缓冲区"。
形参size:输入型参数。如果是要读取本地媒体文件或网络流的数据,size为要读取的字节数;如果是要将数据写入到本地媒体文件或网络流中,size为要写入文件的字节数。
形参size_min:输入型参数。值一般等于形参size的值。如果实际读取到或写入到文件的字节数小于size_min,会继续读取或写入,直到不小于size_min为止。
形参read:输入型参数。值为0表示是要将数据写入到本地媒体文件或网络流中;值为1表示是要读取本地媒体文件或网络流的数据。
返回值:返回一个非负数表示成功,此时返回实际读取到或实际写入文件的字节数;返回一个负数表示出错。
retry_transfer_wrapper函数内部的核心是下面语句,通过形参read的值来决定是执行h->prot->url_read还是h->prot->url_write。read的值为1会执行h->prot->url_read,表示是要读取本地媒体文件或网络流的数据;read的值为0时,会执行h->prot->url_write,表示是要将数据写入到本地媒体文件或网络流中:
cpp
ret = read ? h->prot->url_read (h, buf + len, size - len):
h->prot->url_write(h, cbuf + len, size - len);
下面分两种情况讨论:
(一)情况一:形参read的值为1
h->prot->url_read是函数指针。当形参read的值为1时,会执行语句h->prot->url_read (h, buf + len, size - len),调用不同的回调函数。h->prot->url_read指向的回调函数都有"读取"作用:
cpp
typedef struct URLProtocol {
//...
int (*url_read)( URLContext *h, unsigned char *buf, int size);
//...
}
举几个例子:
1.当执行命令:ffmpeg -i XXX.wav 获取本地媒体文件(比如wav音频文件)的信息时,FFmpeg会调用retry_transfer_wrapper函数。该函数内部执行h->prot->url_read语句时会调用libavformat/file.c中的file_read函数读取本地媒体文件的内容。关于file_read函数的用法可以参考:《FFmpeg源码:file_read、file_write函数分析》。
2.当执行命令:ffmpeg -i "rtsp:XXX" 获取rtsp流的信息时,FFmpeg会调用retry_transfer_wrapper函数。该函数内部执行h->prot->url_read语句时会调用libavformat/tcp.c中的tcp_read函数从TCP连接的另一端接收数据。
3.推流端执行FFmpeg命令:ffmpeg.exe -re -i XXX.mp4 -vcodec copy -acodec copy -f rtp_mpegts rtp://127.0.0.1:6005,拉流端通过:ffmpeg -i rtp://127.0.0.1:6005获取该基于RTP协议的MPEG-2传输流(MPEG-2 Transport Stream)的信息时,拉流端的FFmpeg会调用retry_transfer_wrapper函数。该函数内部执行h->prot->url_read语句时会调用libavformat/rtpproto.c中的rtp_read函数接收UDP数据。
(二)情况二:形参read的值为0
h->prot->url_write是函数指针。当形参read的值为0时,会执行语句h->prot->url_write(h, cbuf + len, size - len),调用不同的回调函数。h->prot->url_write指向的回调函数都有"写入"作用:
cpp
typedef struct URLProtocol {
//...
int (*url_write)(URLContext *h, const unsigned char *buf, int size);
//...
}
举个例子:
当执行命令:./ffmpeg -ar 44100 -ac 2 -f s16le -acodec pcm_s16le -i XXX.pcm XXX.wav 将某个PCM文件转为WAV格式的音频文件时,FFmpeg会调用retry_transfer_wrapper函数。该函数内部执行h->prot->url_write语句时会调用libavformat/file.c中的file_write函数将数据写入到本地媒体文件(WAV格式的音频文件)。关于file_write函数的用法可以参考:《FFmpeg源码:file_read、file_write函数分析》。
二、ffurl_read2函数
ffurl_read2函数定义在源文件libavformat/avio.c中:
cpp
int ffurl_read2(void *urlcontext, uint8_t *buf, int size)
{
URLContext *h = urlcontext;
if (!(h->flags & AVIO_FLAG_READ))
return AVERROR(EIO);
return retry_transfer_wrapper(h, buf, NULL, size, 1, 1);
}
该函数作用是:对本地媒体文件或网络流进行读取。可以看到该函数内部调用了retry_transfer_wrapper函数。
形参urlcontext:输入型参数。 指向一个url(Uniform Resource Locator)上下文结构体。
形参buf:输出型参数。保存读上来的数据的缓冲区。
形参size:输入型参数。要读取的字节数。
返回值:返回一个非负数表示成功,此时返回实际读取到的字节数;返回一个负数表示出错。
三、ffurl_write2函数
ffurl_write2函数定义在源文件libavformat/avio.c中:
cpp
int ffurl_write2(void *urlcontext, const uint8_t *buf, int size)
{
URLContext *h = urlcontext;
if (!(h->flags & AVIO_FLAG_WRITE))
return AVERROR(EIO);
/* avoid sending too big packets */
if (h->max_packet_size && size > h->max_packet_size)
return AVERROR(EIO);
return retry_transfer_wrapper(h, NULL, buf, size, size, 0);
}
该函数作用是:对本地媒体文件或网络流进行写入。可以看到该函数内部调用了retry_transfer_wrapper函数。
形参urlcontext:输入型参数。 指向一个url(Uniform Resource Locator)上下文结构体。
形参buf:输入型参数。存放需要写入到文件或网络流中的数据的缓冲区。
形参size:输入型参数。要写入文件或网络流的字节数。
返回值:返回一个非负数表示成功,此时返回实际读写入文件或网络流的字节数;返回一个负数表示出错。