背景
ffmpeg有自己的一套网络IO代码,但是相对比较简单,现在需要使用自研的网络库接管ffmpeg的网络IO功能。方法有两种:
- 在原本的url前面加127.0.0.1的方式,走本地代理的方式
- 新增自己的协议,替换原本的http协议
第一种方法比较简单,本文主要介绍第二种方法。
首先介绍下libavformat协议框架,然后在介绍下如何在ffmpeg中新增一个自定义的协议。
libavformat协议框架
相关文件
libavformat/protocol_list.c
libavformat/protocols.c
libavformat/avio.c
protocol_list.c 是在执行 ./configure 根据参数而动态生成的代码。默认参数生成的代码如下:
static const URLProtocol *url_protocols[] = {
...
&ff_hls_protocol,
&ff_http_protocol,
&ff_httpproxy_protocol,
&ff_rtmp_protocol,
&ff_rtmpt_protocol,
&ff_rtp_protocol,
&ff_srtp_protocol,
&ff_tcp_protocol,
&ff_udp_protocol,
&ff_unix_protocol,
NULL };
url_protocols是一个静态数组,存储了http,tcp,udp,rtmp等常用协议的实现。
做一个与本文无关的友情链接 ffmpeg支持的所有协议说明
关键的URLProtocol现在出场
typedef struct URLProtocol {
const char *name;
int (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);
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_shutdown)(URLContext *h, int flags);
int (*url_close)(URLContext *h);
int priv_data_size;
const AVClass *priv_data_class;
int flags;
......
} URLProtocol;
URLProtocol可以理解成一个通用的接口,avformat中的每种协议都实现URLProtocol中自己感兴趣的函数。 此处URLProtocol就是用C语言用函数指针实现了C++多态的一种技术。
先来简单了解下HTTP协议。HTTP协议是应用层协议,需要依赖下一层的传输层协议来做二进制数据的传输。
所以,当用户使用http或https请求资源时,ffmpeg如何找到具体的对应协议的处理代码呢?
在libavformat/avio.c的
static const struct URLProtocol *url_find_protocol(const char *filename)
{
...
for (i = 0; protocols[i]; i++) {
const URLProtocol *up = protocols[i];
if (!strcmp(proto_str, up->name)) { // 通过URLProtocol中的name字段来做匹配
av_freep(&protocols);
return up;
}
}
...
return NULL;
}
在此,看下libavformat/http.c中协议注册的代码,则可以很清楚的看到http协议的name字段是http
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"
};
同理libavformat/tcp.c中的tcp协议也是一样的
const URLProtocol ff_tcp_protocol = {
.name = "tcp",
.url_open = tcp_open,
.url_accept = tcp_accept,
.url_read = tcp_read,
.url_write = tcp_write,
.url_close = tcp_close,
.url_get_file_handle = tcp_get_file_handle,
.url_get_short_seek = tcp_get_window_size,
.url_shutdown = tcp_shutdown,
.priv_data_size = sizeof(TCPContext),
.flags = URL_PROTOCOL_FLAG_NETWORK,
.priv_data_class = &tcp_class,
};
则形如下面的url在请求时,url_find_protocol通过匹配字符http则找到了http协议的处理函数
http://1.2.3.4/stream-123.flv
在libavformat/http.c中的http_open_cnx_internal函数,则根据请求url中的http或https来决定下一层传输层的协议
static int http_open_cnx_internal(URLContext *h, AVDictionary **options) {
const char *lower_proto = "tcp"; // 默认使用tcp协议传输
if (!strcmp(proto, "https")) {
lower_proto = "tls"; // 应用层是https协议,传输层使用tls协议
use_proxy = 0;
if (port < 0)
port = 443; // tls默认443端口
}
if (port < 0)
port = 80; // tcp默认80端口
ff_url_join(buf, sizeof(buf), lower_proto, NULL, hostname, port, NULL);
}
通过ff_url_join的拼接形成了
tcp://116.162.86.92:80
然后再次调用avio.c中的ffurl_open_whitelist函数,进而又通过url_find_protocol函数查找到tcp协议的处理函数。
在ffmpeg中新增自定义的live协议
新增自定义的live协议的步骤是:
- 新建live.c文件,实现URLProtocol中感兴趣的函数,代码如下
class FFLiveProtocol
{
public:
static int live_open(URLContext *h, const char *url, int flags, AVDictionary **options) {
return 0;
}
static int live_read(URLContext *h, unsigned char *buf, int size) {
return 0;
}
static int live_write(URLContext *h, const unsigned char *buf, int size) {
return 0;
}
static int live_close(URLContext *h) {
return 0;
}
};
URLProtocol g_ff_live_protocol = {
.name = "live",
.url_open2 = FFLiveProtocol::live_open,
.url_read = FFLiveProtocol::live_read,
.url_write = FFLiveProtocol::live_write,
.url_close = FFLiveProtocol::live_close,
.priv_data_size = sizeof(FF_LiveContext),
.flags = URL_PROTOCOL_FLAG_NETWORK,
.priv_data_class = &live_class,
};
- 在./configure参数选项中开启live协议 先查看帮助:
# ./configure --help |egrep "enable-protocol"
--enable-protocol=NAME enable protocol NAME
则新增参数如:
--enable-protocol=live
完成这两步后,则可以将原有url修改为
live://1.2.3.4/stream-123.flv
到此,就完成了协议的替换了。后面live协议中的实现就任由你折腾了。