HTTP-FLV 协议简介
HTTP-FLV:通过HTTP协议传输FLV封装格式的流媒体数据。HTTP协议中content-length字段表明客户端需要接收的body长度,在HTTP-FLV中,媒体服务器不设置content-length,客户端会持续接收数据,直到链接断开。
HTTP-FLV相比RTMP协议的优势:
- 防火墙限制:部分防火墙会屏蔽RTMP的1935端口,但是HTTP协议不会被屏蔽。
- 简单:HTTP是最广泛的协议。
- 调度:根据默认的HTTP 302即可实现拉流调度。在SRS中有针对RTMP的302调度,但是这并不是通用的标准协议,对于各家CDN厂商对接存在诸多问题。但是HTTP协议的302是标准协议,各家CDN都支持。
基于nginx-rtmp-module实现HTTP-FLV,关键部分主要是两点:
- 由http request生成rtmp session;
- 对于http-flv的connection,修改收发数据的逻辑,匹配HTTP协议。
因此,HTTP FLV的实现需要同时注册HTTP和RTMP模块。
注册RTMP模块
static ngx_int_t
ngx_rtmp_flv_live_index_postconfiguration(ngx_conf_t *cf)
{
next_play = ngx_rtmp_play;
ngx_rtmp_play = ngx_http_flv_live_play;
next_close_stream = ngx_rtmp_close_stream;
ngx_rtmp_close_stream = ngx_http_flv_live_close_stream;
http_flv_live_next_play = next_play;
http_flv_live_next_close_stream = next_close_stream;
return NGX_OK;
}
HTTP FLV模块只有在play时使用,因此注册的RTMP模块只需将处理函数加入到next_play和next_close_stream函数链表中即可。
注册HTTP模块
生成RTMP Session
- 将ngx_http_flv_live_handler注册到NGX_HTTP_CONTENT_PHASE阶段,ngx_http_flv_live_handler是负责处理http request的回调。
h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_flv_live_handler;
- 判断请求类型是否是GET,判断http version;
- 解析出请求的URI,获取到application和stream name,查找RTMP的configure配置,判断是否匹配;
last = r->uri.data + r->uri.len;
p = ngx_strlchr(r->uri.data + 1, last, '/');
q = ngx_strnstr(r->uri.data, ".flv", r->uri.len);
if(p == NULL || q == NULL) {
return NULL;
}
ctx->app.data = r->uri.data + 1;
ctx->app.len = p - r->uri.data - 1;
ctx->stream.data = p + 1;
ctx->stream.len = q - p - 1;
cmcf = ngx_rtmp_core_main_conf;
if (cmcf == NULL) {
return NULL;
}
appmatch = 0;
cscf = cmcf->servers.elts;
for (m = 0; m < cmcf->servers.nelts; ++m, ++cscf) {
cacf = (*cscf)->applications.elts;
for (n = 0; n < (*cscf)->applications.nelts; ++n, ++cacf) {
if((*cacf)->name.len == ctx->app.len &&
ngx_strncmp((char*)(ctx->app.data),
(char*)((*cacf)->name.data), ctx->app.len) == 0)
{
appmatch = 1;
}
}
}
if(appmatch == 0) {
return NULL;
}
- 获取ngx_rtmp_addr_conf_t addrconf;
ls = ngx_cycle->listening.elts;
for (n = 0; n < ngx_cycle->listening.nelts; ++n, ++ls) {
if (ls->handler == ngx_rtmp_init_connection) {
local_sockaddr = r->connection->local_sockaddr;
sa_family = local_sockaddr->sa_family;
...
...
switch (sa_family) {
...
...
rport = ls->servers;
if (rport->naddrs > 1) {
/**
* listen xxx.xxx.xxx.xxx:port
* listen port
**/
switch (sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
...
addr_conf = &addr6[i].conf;
...
break;
#endif
default:
...
addr_conf = &addr[i].conf;
}
} else {
switch (sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
...
addr_conf = &addr6[0].conf;
...
break;
#endif
default:
...
addr_conf = &addr[0].conf;
...
}
}
}
if (!addr_match) {
addr_match = 1;
continue;
}
break;
}
}
- 根据上一步获取的绑定地址信息和HTTP请求对应的connection,通过ngx_rtmp_init_session()生成rtmp session;
c = r->connection;
data = c->data;
s = ngx_rtmp_init_session(c, addr_conf);
c->data = data;
- 重置connection的发送数据和接收数据的handler,这一步非常重要,因为后面是发送的RTMP数据。
c->write->handler = ngx_rtmp_send;
c->read->handler = ngx_rtmp_recv;
- 调用ngx_rtmp_play,执行next_play函数链表,执行rtmp的play逻辑。
通过以上步骤,我们成功的由一个HTTP Requset生成一个RTMP Session,并将Session加入到rtmp_live_module的保存流信息的结构体中。
HTTP-FLV的数据发送和接收的逻辑
HTTP-FLV不需要处理接收逻辑,对于接收到数据,直接丢弃返回即可。重点是对发送packet的封装。
- 发送HTTP FLV头信息
创建处理HTTP FLV到连接成功后,首先需要发送视频头信息,主要参考HTTP协议和FLV封装格式协议。由于传输的是直播视频数据,不需要设置Content-Length。
HTTP HEADER | Value |
Content-Type | video/x-flv |
Connection | keep-alive |
Expires | -1 |
Transfer-Encoding | chunked |
发送FLV头信息。
/**
* |F|L|V|ver|00000101|header_size|0|0|0|0|, ngx_http_flv_module.c
* for more details, please refer to http://www.adobe.com/devnet/f4v.html
**/
u_char flv_header[] = "FLV\x1\0\0\0\0\x9\0\0\0\0";
- 数据接收
在通过HTTP Request构建RTMP Session时,我们已经设置了Connection读读事件c->read->handler = ngx_rtmp_recv。在ngx_rtmp_recv对HTTP连接接收到的数据不做任何处理。
if(s->signature == NGX_HTTP_MODULE) {
r = c->data;
ctx = ngx_http_get_module_ctx(r, ngx_http_flv_live_module);
s = ctx->s;
}
- 数据发送
由于RTMP和HTTP-FLV的metadata和音视频数据的封装格式不同,所以对于RTMP和HTTP-FLV分别对应不同的数据处理函数。
区域 | 类型 | 说明 |
Reserved | Unsigned Bit[2] | 两位,FMS保留,为0 |
Filter | Unsigned Bit[1] | 一位,通常为0 |
TagType | Unsigned Bit[5] | 五位,tag类型,音频:0x08,视频:0x09,脚本:0x12 |
DataSize | Unsigned Int24 | 3个字节,数据区长度 |
TimeStamp | Unsigned Int24 | 3个字节,毫秒 |
TimeStampExtended | Unsigned Int8 | 1个字节,时间戳扩展高8位 |
StreamsID | Unsigned Int24 | 3个字节,为0 |
TagData | TagType=0x08,Audio头信息,TagType=0x09,Video头信息,TagType=0x12,脚本信息 |
/* type, 5bits */
*pos++ = (u_char) (h->type & 0x1f);
/* data length, 3B */
p = (u_char *) &data_size;
*pos++ = p[2];
*pos++ = p[1];
*pos++ = p[0];
/* timestamp, 3B + ext, 1B */
p = (u_char *) &h->timestamp;
*pos++ = p[2];
*pos++ = p[1];
*pos++ = p[0];
*pos++ = p[3];
/* streamId, 3B, always be 0 */
*pos++ = 0;
*pos++ = 0;
*pos++ = 0;
在ngx_rtmp_send中,对于HTTP FLV的Session,从中获取到连接connection。
if(s->signature == NGX_HTTP_MODULE) {
r = c->data;
ctx = ngx_http_get_module_ctx(r, ngx_http_flv_live_module);
s = ctx->s;
}
- ngx_rtmp_send_shared_packet处理
HTTP-FLV不需要发送RTMP指令类消息。
static ngx_int_t
ngx_rtmp_send_shared_packet(ngx_rtmp_session_t *s, ngx_chain_t *cl)
{
...
/*http flv request*/
if (s->signature == NGX_HTTP_MODULE) {
ngx_rtmp_free_shared_chain(cscf, cl);
return NGX_OK;
}
...
}
play结束
在stream结束播放后,需要释放HTTP Request(ngx_http_free_request)和RTMP Session(ngx_rtmp_finalize_session)。