1.協議操作對象結構
協議操作對象結構:
協議操作的頂層結構是AVIOContext,這個對象實現了帶緩衝的讀寫操作;FFMPEG的輸入對象AVFormatContext的pb字段指向一個AVIOContext。
AVIOContext的opaque實際指向一個URLContext對象,這個對象封裝了協議對象及協議操作對象,其中prot指向具體的協議操作對象(如URLProtocol),priv_data指向具體的協議對象(如HTTPContext或者FileContext)。
URLProtocol爲協議操作對象,針對每種協議,會有一個這樣的對象,每個協議操作對象和一個協議對象關聯,比如,文件操作對象爲ff_file_protocol,它關聯的結構體是FileContext。http協議操作對象爲ff_http_protocol,它的關聯的結構體是HttpContext。
2.代碼分析
2.1初始化AVIOContext函數調用關係
初始化AVIOFormat函數調用關係:
我們先從下面到上面來分析源碼。先分析協議中具體實現以及URLProtocol以及URLContext。
URLProtocol是FFMPEG操作文件的結構(包括文件,網絡數據流等等),包括open、close、read、write、seek等操作。
在av_register_all()函數中,通過調用REGISTER_PROTOCOL()宏,所有的URLProtocol都保存在以first_protocol爲鏈表頭的鏈表中
URLProtocol結構體的定義爲:
typedef struct URLProtocol {
const char *name;//協議的名稱
//各種協議對應的回調函數
int (*url_open)( URLContext *h, const char *url, int flags);
/**
* This callback is to be used by protocols which open further nested
* protocols. options are then to be passed to ffurl_open()/ffurl_connect()
* for those nested protocols.
*/
int (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);
int (*url_accept)(URLContext *s, URLContext **c);
int (*url_handshake)(URLContext *c);
/**
* Read data from the protocol.
* If data is immediately available (even less than size), EOF is
* reached or an error occurs (including EINTR), return immediately.
* Otherwise:
* In non-blocking mode, return AVERROR(EAGAIN) immediately.
* In blocking mode, wait for data/EOF/error with a short timeout (0.1s),
* and return AVERROR(EAGAIN) on timeout.
* Checking interrupt_callback, looping on EINTR and EAGAIN and until
* enough data has been read is left to the calling function; see
* retry_transfer_wrapper in avio.c.
*/
int (*url_read)( URLContext *h, unsigned char *buf, int size);
int (*url_write)(URLContext *h, const unsigned char *buf, int size);
int64_t (*url_seek)( URLContext *h, int64_t pos, int whence);
int (*url_close)(URLContext *h);
int (*url_read_pause)(URLContext *h, int pause);
int64_t (*url_read_seek)(URLContext *h, int stream_index,
int64_t timestamp, int flags);
int (*url_get_file_handle)(URLContext *h);
int (*url_get_multi_file_handle)(URLContext *h, int **handles,
int *numhandles);
int (*url_get_short_seek)(URLContext *h);
int (*url_shutdown)(URLContext *h, int flags);
int priv_data_size;
const AVClass *priv_data_class;
int flags;
int (*url_check)(URLContext *h, int mask);
int (*url_open_dir)(URLContext *h);
int (*url_read_dir)(URLContext *h, AVIODirEntry **next);
int (*url_close_dir)(URLContext *h);
int (*url_delete)(URLContext *h);
int (*url_move)(URLContext *h_src, URLContext *h_dst);
const char *default_whitelist;//默認白名單
} URLProtocol;
以HTTP協議爲例,ff_http_protocol
const URLProtocol ff_http_protocol = {
.name = "http",
.url_open2 = http_open,
.url_accept = http_accept,
.url_handshake = http_handshake,
.url_read = http_read,
.url_write = http_write,
.url_seek = http_seek,
.url_close = http_close,
.url_get_file_handle = http_get_file_handle,
.url_get_short_seek = http_get_short_seek,
.url_shutdown = http_shutdown,
.priv_data_size = sizeof(HTTPContext),
.priv_data_class = &http_context_class,
.flags = URL_PROTOCOL_FLAG_NETWORK,
.default_whitelist = "http,https,tls,rtp,tcp,udp,crypto,httpproxy"
};
從中可以看出,.priv_data_size的值爲sizeof(HTTPContext),即ff_http_protocol和HttpContext相關聯。
HttpContext對象的定義爲:
typedef struct HTTPContext {
const AVClass *class;
URLContext *hd;
unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end;
int line_count;
int http_code;
/**
如果使用Transfer-Encoding:chunked,也就是代表這個報文采用了分塊編碼,不然設置爲-1
Used if "Transfer-Encoding: chunked" otherwise -1.
分塊編碼:
報文中的實體需要改爲用一系列的分塊來傳輸,每個分塊包含十六進制的長度值和數據,
長度值獨佔一行,長度不包括它結尾的 CRLF(\r\n),也不包括分塊數據結尾的 CRLF。
最後一個分塊長度值必須爲 0,對應的分塊數據沒有內容,表示實體結束
如:服務端
sock.write('HTTP/1.1 200 OK\r\n');
sock.write('Transfer-Encoding: chunked\r\n');
sock.write('\r\n');
sock.write('b\r\n');//指定長度值11
sock.write('01234567890\r\n');//數據
sock.write('5\r\n');//執行下面長度值5
sock.write('12345\r\n');//數據
sock.write('0\r\n');//最後一個分塊是0
sock.write('\r\n');
**/
uint64_t chunksize;
//off buf偏移 end_off->請求頭:range的結尾值
uint64_t off, end_off, filesize;
char *location;
HTTPAuthState auth_state;
HTTPAuthState proxy_auth_state;
char *http_proxy;
char *headers;
char *mime_type;
char *user_agent;
#if FF_API_HTTP_USER_AGENT
char *user_agent_deprecated;
#endif
char *content_type;
/*
如果服務器設置正確處理連接:關閉並將關閉,也就是處理了請求頭中的Connetion
如果Connection是close的話,處理完後斷開連接。willclose設置1,在解析請求頭中
也就是這個變量代表Connection是否是close
*/
int willclose;
int seekable; /**< Control seekability, 0 = disable, 1 = enable, -1 = probe. */
int chunked_post;
/* A flag which indicates if the end of chunked encoding has been sent. */
int end_chunked_post;
/* A flag which indicates we have finished to read POST reply. */
int end_header;
/* A flag which indicates if we use persistent connections. */
int multiple_requests;
uint8_t *post_data;
int post_datalen;
int is_akamai;
int is_mediagateway;
char *cookies; ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name)
/* A dictionary containing cookies keyed by cookie name */
AVDictionary *cookie_dict;
int icy;
/* how much data was read since the last ICY metadata packet */
uint64_t icy_data_read;
/* after how many bytes of read data a new metadata packet will be found */
uint64_t icy_metaint;
char *icy_metadata_headers;
char *icy_metadata_packet;
AVDictionary *metadata;
#if CONFIG_ZLIB
int compressed;//如果服務器返回客戶端的內容正文壓縮的話
z_stream inflate_stream;
uint8_t *inflate_buffer;
#endif /* CONFIG_ZLIB */
AVDictionary *chained_options;
int send_expect_100;
char *method;
int reconnect;
int reconnect_at_eof;
int reconnect_streamed;
int reconnect_delay;
int reconnect_delay_max;
int listen;
char *resource;
int reply_code;
int is_multi_client;
HandshakeState handshake_step;
int is_connected_server;
} HTTPContext;
返回去看ff_http_protocol裏的函數指針,以url_read舉例,它指向http_read函數,看一下這個函數。
/**
*流數據讀取
**/
static int http_read(URLContext *h, uint8_t *buf, int size)
{
HTTPContext *s = h->priv_data;//URLContext的priv_data指向HttpContext
if (s->icy_metaint > 0) {
size = store_icy(h, size);
if (size < 0)
return size;
}
size = http_read_stream(h, buf, size);
if (size > 0)
s->icy_data_read += size;
return size;
}
從上面的分析中,我們知道了協議的具體實現以及URLProtocol以及URLContext結構之間的關係。下面我們開始從上面到下面的流程來分析:
avformat_open_input()函數調用init_input/utils.c/libavformat
,最後調用到avio_open()然後調用到avio_open2()/aviobuf.c/libavformat函數,然後調用到ffio_open_whitelist/aviobuf.c,下面看一下這個函數。
/**
* 按照協議名單打開協議,並初始化一個AVIOContext並賦值
**/
int ffio_open_whitelist(AVIOContext **s, const char *filename, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options,
const char *whitelist, const char *blacklist
)
{
URLContext *h;
int err;
//調用avio.c中的按照白名單打開協議
//申請協議空間與協議查找以及建立連接(調用協議中open函數,如http_open)
//調用ffurl_open()申請創建了一個URLContext對象並打開了文件(也就是建立連接等)
err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);
if (err < 0)
return err;
//調用ffio_fdopen()申請一個AVIOContext對象並賦初值(部分初值來自ffurl_open_whitelist中創建的URLContext)
err = ffio_fdopen(s, h);
if (err < 0) {//創建失敗了,關閉URLContext
ffurl_close(h);
return err;
}
return 0;
}
這個函數調用ffurl_open_whitelist來創建一個URLContext,申請協議內存以及查找協議,同時建立連接,調用ffio_fdopen函數來申請一個AVIOContext對象並賦初值。
我們看下URLContext的結構體:
typedef struct URLContext {
const AVClass *av_class; /**< information for av_log(). Set by url_open(). */
const struct URLProtocol *prot;//URLProtocol結構體
void *priv_data;
char *filename; /**< specified URL */
int flags;
int max_packet_size; /**< if non zero, the stream is packetized with this max packet size */
int is_streamed; /**< true if streamed (no seek possible), default = false */
int is_connected;
AVIOInterruptCB interrupt_callback;
int64_t rw_timeout; /**< 最大等待時間(網絡)讀/寫操作完成,在mcs,maximum time to wait for (network) read/write operation completion, in mcs */
const char *protocol_whitelist;
const char *protocol_blacklist;
int min_packet_size; /**< if non zero, the stream is packetized with this min packet size */
} URLContext;
從這裏我們先從ffurl_open_whitelist/avio.c函數開始看
/**
*按照白名單打開協議
*申請協議空間與協議查找以及建立連接(調用協議中open函數,如http_open)
**/
int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options,
const char *whitelist, const char* blacklist,
URLContext *parent)
{
AVDictionary *tmp_opts = NULL;
AVDictionaryEntry *e;
//協議內存申請以及查找filename中對應的協議
int ret = ffurl_alloc(puc, filename, flags, int_cb);
if (ret < 0)
return ret;
if (parent)//傳遞過來的是NULL
av_opt_copy(*puc, parent);
if (options &&
(ret = av_opt_set_dict(*puc, options)) < 0)
goto fail;
if (options && (*puc)->prot->priv_data_class &&
(ret = av_opt_set_dict((*puc)->priv_data, options)) < 0)
goto fail;
if (!options)
options = &tmp_opts;
av_assert0(!whitelist ||
!(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) ||
!strcmp(whitelist, e->value));
av_assert0(!blacklist ||
!(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) ||
!strcmp(blacklist, e->value));
if ((ret = av_dict_set(options, "protocol_whitelist", whitelist, 0)) < 0)
goto fail;
if ((ret = av_dict_set(options, "protocol_blacklist", blacklist, 0)) < 0)
goto fail;
if ((ret = av_opt_set_dict(*puc, options)) < 0)
goto fail;
//建立連接,打開協議(如,調http_open函數)
ret = ffurl_connect(*puc, options);
if (!ret)
return 0;
fail:
ffurl_close(*puc);
*puc = NULL;
return ret;
}
跟蹤ffurl_alloc/avio.c函數,這裏主要是協議內存申請以及查找filename中對應的協議。
/**
*協議空間申請與協議查找
**/
int ffurl_alloc(URLContext **puc, const char *filename, int flags,
const AVIOInterruptCB *int_cb)
{
const URLProtocol *p = NULL;
//查找協議
p = url_find_protocol(filename);
if (p)//如果找到協議
return url_alloc_for_protocol(puc, p, filename, flags, int_cb);
*puc = NULL;//如果沒有找到,協議不可用
if (av_strstart(filename, "https:", NULL))//如果文件名開頭爲https,ffmpeg目前不支持https協議,需要自己移植SSL
av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "
"openssl, gnutls "
"or securetransport enabled.\n");
return AVERROR_PROTOCOL_NOT_FOUND;
}
下面看下ffurl_connect/avio.c函數,這裏主要是建立連接,打開協議(如,調http_open函數)
/**
*建立連接,打開協議
**/
int ffurl_connect(URLContext *uc, AVDictionary **options)
{
int err;
AVDictionary *tmp_opts = NULL;
AVDictionaryEntry *e;
if (!options)
options = &tmp_opts;
//黑名單,白名單檢測
// Check that URLContext was initialized correctly and lists are matching if set
av_assert0(!(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) ||
(uc->protocol_whitelist && !strcmp(uc->protocol_whitelist, e->value)));
av_assert0(!(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) ||
(uc->protocol_blacklist && !strcmp(uc->protocol_blacklist, e->value)));
if (uc->protocol_whitelist && av_match_list(uc->prot->name, uc->protocol_whitelist, ',') <= 0) {
av_log(uc, AV_LOG_ERROR, "Protocol '%s' not on whitelist '%s'!\n", uc->prot->name, uc->protocol_whitelist);
return AVERROR(EINVAL);
}
if (uc->protocol_blacklist && av_match_list(uc->prot->name, uc->protocol_blacklist, ',') > 0) {
av_log(uc, AV_LOG_ERROR, "Protocol '%s' on blacklist '%s'!\n", uc->prot->name, uc->protocol_blacklist);
return AVERROR(EINVAL);
}
if (!uc->protocol_whitelist && uc->prot->default_whitelist) {
av_log(uc, AV_LOG_DEBUG, "Setting default whitelist '%s'\n", uc->prot->default_whitelist);
uc->protocol_whitelist = av_strdup(uc->prot->default_whitelist);
if (!uc->protocol_whitelist) {
return AVERROR(ENOMEM);
}
} else if (!uc->protocol_whitelist)
av_log(uc, AV_LOG_DEBUG, "No default whitelist set\n"); // This should be an error once all declare a default whitelist
if ((err = av_dict_set(options, "protocol_whitelist", uc->protocol_whitelist, 0)) < 0)
return err;
if ((err = av_dict_set(options, "protocol_blacklist", uc->protocol_blacklist, 0)) < 0)
return err;
//調用port->url_open(也就是協議中的open函數,如http_open,打開了協議
err =
uc->prot->url_open2 ? uc->prot->url_open2(uc,
uc->filename,
uc->flags,
options) :
uc->prot->url_open(uc, uc->filename, uc->flags);
av_dict_set(options, "protocol_whitelist", NULL, 0);
av_dict_set(options, "protocol_blacklist", NULL, 0);
if (err)//如果協議打開失敗了,如http_open打開失敗了,直接返回err
return err;
uc->is_connected = 1;//設置is_conneced爲true,設置協議已經建立連接
/* We must be careful here as ffurl_seek() could be slow,
* for example for http */
if ((uc->flags & AVIO_FLAG_WRITE) || !strcmp(uc->prot->name, "file"))
if (!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0)
uc->is_streamed = 1;
return 0;
}
我們回到ffio_open_whitelist繼續從ffio_fdopen函數開始看,這裏主要是申請一個AVIOContext對象並賦初值(部分初值來自ffurl_open_whitelist中創建的URLContext)。
/**
*申請一個AVIOContext對象並賦值
*/
int ffio_fdopen(AVIOContext **s, URLContext *h)
{
AVIOInternal *internal = NULL;
uint8_t *buffer = NULL;
int buffer_size, max_packet_size;
max_packet_size = h->max_packet_size;
if (max_packet_size) {
buffer_size = max_packet_size; /* no need to bufferize more than one packet */
} else {
buffer_size = IO_BUFFER_SIZE;
}
//申請了一個讀寫緩衝buffer(結合文件協議,buffer大小爲IO_BUFFER_SIZE)
buffer = av_malloc(buffer_size);//buffer_size的值取自哪裏?
if (!buffer)
return AVERROR(ENOMEM);
internal = av_mallocz(sizeof(*internal));
if (!internal)
goto fail;
internal->h = h;
//對AVIOContext賦值,同時把申請到的buffer以及ffurl_read,ffurl_write,ffurl_seek的地址作爲入參被傳入
*s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE,
internal, io_read_packet, io_write_packet, io_seek);
if (!*s)//創建AVIOContext失敗的話,直接報錯
goto fail;
(*s)->protocol_whitelist = av_strdup(h->protocol_whitelist);
if (!(*s)->protocol_whitelist && h->protocol_whitelist) {
avio_closep(s);
goto fail;
}
(*s)->protocol_blacklist = av_strdup(h->protocol_blacklist);
if (!(*s)->protocol_blacklist && h->protocol_blacklist) {
avio_closep(s);
goto fail;
}
(*s)->direct = h->flags & AVIO_FLAG_DIRECT;
(*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL;
(*s)->max_packet_size = max_packet_size;
(*s)->min_packet_size = h->min_packet_size;//協議中獲取的urlcontext賦值給AVIOContext
if(h->prot) {//如果URLProtStruct不爲空
(*s)->read_pause = io_read_pause;//把ffurl_pause賦值給AVIOContext的read_pause函數
(*s)->read_seek = io_read_seek;//把ffurl_seek賦值給AVIOContext的read_seek函數
if (h->prot->url_read_seek)
(*s)->seekable |= AVIO_SEEKABLE_TIME;
}
(*s)->short_seek_get = io_short_seek;//根據協議調用prot(protocol)中的short_seek函數
(*s)->av_class = &ff_avio_class;
return 0;
fail:
av_freep(&internal);
av_freep(&buffer);
return AVERROR(ENOMEM);
}
從上面可以看到調用av_malloc(buffer_size)申請了一個讀寫緩衝buffer(結合文件協議,buffer大小爲IO_BUFFER_SIZE)。
調用avio_alloc_context,來對AVIOContext賦值,同時把申請到的buffer以及ffurl_read,ffurl_write,ffurl_seek的地址作爲入參被傳入。
AVIOContext *avio_alloc_context(
unsigned char *buffer,
int buffer_size,
int write_flag,
void *opaque,
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
AVIOContext *s = av_mallocz(sizeof(AVIOContext));//申請AVIOContext內存
if (!s)
return NULL;
//把傳進來的參數(函數指針)指向剛創建的AVIOContext結構體對應的變量(函數指針)中
ffio_init_context(s, buffer, buffer_size, write_flag, opaque,
read_packet, write_packet, seek);
return s;
}