HTTP-FLV:通过HTTP协议传输FLV封装格式的流媒体数据。HTTP协议中content-length字段表明客户端需要接收的body长度,在HTTP-FLV中,媒体服务器不设置content-length,客户端会持续接收数据,直到链接断开。
HTTP-FLV相比RTMP协议的优势:
基于nginx-rtmp-module实现HTTP-FLV,关键部分主要是两点:
因此,HTTP FLV的实现需要同时注册HTTP和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函数链表中即可。
h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_flv_live_handler;
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;
}
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;
}
}
c = r->connection;
data = c->data;
s = ngx_rtmp_init_session(c, addr_conf);
c->data = data;
c->write->handler = ngx_rtmp_send;
c->read->handler = ngx_rtmp_recv;
通过以上步骤,我们成功的由一个HTTP Requset生成一个RTMP Session,并将Session加入到rtmp_live_module的保存流信息的结构体中。
HTTP-FLV不需要处理接收逻辑,对于接收到数据,直接丢弃返回即可。重点是对发送packet的封装。
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;
}
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;
}
...
}
在stream结束播放后,需要释放HTTP Request(ngx_http_free_request)和RTMP Session(ngx_rtmp_finalize_session)。