mirror of
https://github.com/zotanmew/nginx-rtmp-module.git
synced 2024-06-09 17:19:32 +02:00
initial commit
This commit is contained in:
commit
d39b624653
7
TODO
Normal file
7
TODO
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
+1) string allocations inside ngx_chain_t
|
||||||
|
+2) AMF0 creation
|
||||||
|
3) add AMF0 command parsing & replies to RTMP handler
|
||||||
|
4) improve handshake
|
||||||
|
5) add session_buckets option to core srv conf
|
||||||
|
|
||||||
|
Do we need loc confs (=fms apps) ?
|
10
config
Normal file
10
config
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
ngx_addon_name="ngx_rtmp_module"
|
||||||
|
|
||||||
|
NGX_ADDON_SRC="$NGX_ADDON_SRC \
|
||||||
|
$ngx_addon_dir/ngx_rtmp.c \
|
||||||
|
$ngx_addon_dir/ngx_rtmp_handler.c \
|
||||||
|
$ngx_addon_dir/ngx_rtmp_core_module.c \
|
||||||
|
$ngx_addon_dir/ngx_rtmp_amf0.c \
|
||||||
|
$ngx_addon_dir/ngx_rtmp_netconn.c \
|
||||||
|
$ngx_addon_dir/ngx_rtmp_netstream.c \
|
||||||
|
"
|
BIN
doc/rtmp_specification_1.0.pdf
Normal file
BIN
doc/rtmp_specification_1.0.pdf
Normal file
Binary file not shown.
34248
doc/rtmpout.html
Normal file
34248
doc/rtmpout.html
Normal file
File diff suppressed because it is too large
Load diff
529
ngx_rtmp.c
Normal file
529
ngx_rtmp.c
Normal file
|
@ -0,0 +1,529 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Roman Arutyunyan
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <ngx_config.h>
|
||||||
|
#include <ngx_core.h>
|
||||||
|
#include <ngx_event.h>
|
||||||
|
#include "ngx_rtmp.h"
|
||||||
|
|
||||||
|
|
||||||
|
static char *ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
|
||||||
|
static ngx_int_t ngx_rtmp_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
|
||||||
|
ngx_rtmp_listen_t *listen);
|
||||||
|
static char *ngx_rtmp_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports);
|
||||||
|
static ngx_int_t ngx_rtmp_add_addrs(ngx_conf_t *cf, ngx_rtmp_port_t *mport,
|
||||||
|
ngx_rtmp_conf_addr_t *addr);
|
||||||
|
#if (NGX_HAVE_INET6)
|
||||||
|
static ngx_int_t ngx_rtmp_add_addrs6(ngx_conf_t *cf, ngx_rtmp_port_t *mport,
|
||||||
|
ngx_rtmp_conf_addr_t *addr);
|
||||||
|
#endif
|
||||||
|
static ngx_int_t ngx_rtmp_cmp_conf_addrs(const void *one, const void *two);
|
||||||
|
|
||||||
|
|
||||||
|
ngx_uint_t ngx_rtmp_max_module;
|
||||||
|
|
||||||
|
|
||||||
|
static ngx_command_t ngx_rtmp_commands[] = {
|
||||||
|
|
||||||
|
{ ngx_string("rtmp"),
|
||||||
|
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
|
||||||
|
ngx_rtmp_block,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
NULL },
|
||||||
|
|
||||||
|
ngx_null_command
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static ngx_core_module_t ngx_rtmp_module_ctx = {
|
||||||
|
ngx_string("rtmp"),
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
ngx_module_t ngx_rtmp_module = {
|
||||||
|
NGX_MODULE_V1,
|
||||||
|
&ngx_rtmp_module_ctx, /* module context */
|
||||||
|
ngx_rtmp_commands, /* module directives */
|
||||||
|
NGX_CORE_MODULE, /* module type */
|
||||||
|
NULL, /* init master */
|
||||||
|
NULL, /* init module */
|
||||||
|
NULL, /* init process */
|
||||||
|
NULL, /* init thread */
|
||||||
|
NULL, /* exit thread */
|
||||||
|
NULL, /* exit process */
|
||||||
|
NULL, /* exit master */
|
||||||
|
NGX_MODULE_V1_PADDING
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static char *
|
||||||
|
ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||||
|
{
|
||||||
|
char *rv;
|
||||||
|
ngx_uint_t i, m, mi, s;
|
||||||
|
ngx_conf_t pcf;
|
||||||
|
ngx_array_t ports;
|
||||||
|
ngx_rtmp_listen_t *listen;
|
||||||
|
ngx_rtmp_module_t *module;
|
||||||
|
ngx_rtmp_conf_ctx_t *ctx;
|
||||||
|
ngx_rtmp_core_srv_conf_t **cscfp;
|
||||||
|
ngx_rtmp_core_main_conf_t *cmcf;
|
||||||
|
|
||||||
|
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
*(ngx_rtmp_conf_ctx_t **) conf = ctx;
|
||||||
|
|
||||||
|
/* count the number of the rtmp modules and set up their indices */
|
||||||
|
|
||||||
|
ngx_rtmp_max_module = 0;
|
||||||
|
for (m = 0; ngx_modules[m]; m++) {
|
||||||
|
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngx_modules[m]->ctx_index = ngx_rtmp_max_module++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* the rtmp main_conf context, it is the same in the all rtmp contexts */
|
||||||
|
|
||||||
|
ctx->main_conf = ngx_pcalloc(cf->pool,
|
||||||
|
sizeof(void *) * ngx_rtmp_max_module);
|
||||||
|
if (ctx->main_conf == NULL) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* the rtmp null srv_conf context, it is used to merge
|
||||||
|
* the server{}s' srv_conf's
|
||||||
|
*/
|
||||||
|
|
||||||
|
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
|
||||||
|
if (ctx->srv_conf == NULL) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* create the main_conf's, the null srv_conf's, and the null loc_conf's
|
||||||
|
* of the all rtmp modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (m = 0; ngx_modules[m]; m++) {
|
||||||
|
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
module = ngx_modules[m]->ctx;
|
||||||
|
mi = ngx_modules[m]->ctx_index;
|
||||||
|
|
||||||
|
if (module->create_main_conf) {
|
||||||
|
ctx->main_conf[mi] = module->create_main_conf(cf);
|
||||||
|
if (ctx->main_conf[mi] == NULL) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module->create_srv_conf) {
|
||||||
|
ctx->srv_conf[mi] = module->create_srv_conf(cf);
|
||||||
|
if (ctx->srv_conf[mi] == NULL) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* parse inside the rtmp{} block */
|
||||||
|
|
||||||
|
pcf = *cf;
|
||||||
|
cf->ctx = ctx;
|
||||||
|
|
||||||
|
cf->module_type = NGX_RTMP_MODULE;
|
||||||
|
cf->cmd_type = NGX_RTMP_MAIN_CONF;
|
||||||
|
rv = ngx_conf_parse(cf, NULL);
|
||||||
|
|
||||||
|
if (rv != NGX_CONF_OK) {
|
||||||
|
*cf = pcf;
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* init rtmp{} main_conf's, merge the server{}s' srv_conf's */
|
||||||
|
|
||||||
|
cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index];
|
||||||
|
cscfp = cmcf->servers.elts;
|
||||||
|
|
||||||
|
for (m = 0; ngx_modules[m]; m++) {
|
||||||
|
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
module = ngx_modules[m]->ctx;
|
||||||
|
mi = ngx_modules[m]->ctx_index;
|
||||||
|
|
||||||
|
/* init rtmp{} main_conf's */
|
||||||
|
|
||||||
|
cf->ctx = ctx;
|
||||||
|
|
||||||
|
if (module->init_main_conf) {
|
||||||
|
rv = module->init_main_conf(cf, ctx->main_conf[mi]);
|
||||||
|
if (rv != NGX_CONF_OK) {
|
||||||
|
*cf = pcf;
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (s = 0; s < cmcf->servers.nelts; s++) {
|
||||||
|
|
||||||
|
/* merge the server{}s' srv_conf's */
|
||||||
|
|
||||||
|
cf->ctx = cscfp[s]->ctx;
|
||||||
|
|
||||||
|
if (module->merge_srv_conf) {
|
||||||
|
rv = module->merge_srv_conf(cf,
|
||||||
|
ctx->srv_conf[mi],
|
||||||
|
cscfp[s]->ctx->srv_conf[mi]);
|
||||||
|
if (rv != NGX_CONF_OK) {
|
||||||
|
*cf = pcf;
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*cf = pcf;
|
||||||
|
|
||||||
|
|
||||||
|
if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_rtmp_conf_port_t))
|
||||||
|
!= NGX_OK)
|
||||||
|
{
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
listen = cmcf->listen.elts;
|
||||||
|
|
||||||
|
for (i = 0; i < cmcf->listen.nelts; i++) {
|
||||||
|
if (ngx_rtmp_add_ports(cf, &ports, &listen[i]) != NGX_OK) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ngx_rtmp_optimize_servers(cf, &ports);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static ngx_int_t
|
||||||
|
ngx_rtmp_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
|
||||||
|
ngx_rtmp_listen_t *listen)
|
||||||
|
{
|
||||||
|
in_port_t p;
|
||||||
|
ngx_uint_t i;
|
||||||
|
struct sockaddr *sa;
|
||||||
|
struct sockaddr_in *sin;
|
||||||
|
ngx_rtmp_conf_port_t *port;
|
||||||
|
ngx_rtmp_conf_addr_t *addr;
|
||||||
|
#if (NGX_HAVE_INET6)
|
||||||
|
struct sockaddr_in6 *sin6;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
sa = (struct sockaddr *) &listen->sockaddr;
|
||||||
|
|
||||||
|
switch (sa->sa_family) {
|
||||||
|
|
||||||
|
#if (NGX_HAVE_INET6)
|
||||||
|
case AF_INET6:
|
||||||
|
sin6 = (struct sockaddr_in6 *) sa;
|
||||||
|
p = sin6->sin6_port;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default: /* AF_INET */
|
||||||
|
sin = (struct sockaddr_in *) sa;
|
||||||
|
p = sin->sin_port;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
port = ports->elts;
|
||||||
|
for (i = 0; i < ports->nelts; i++) {
|
||||||
|
if (p == port[i].port && sa->sa_family == port[i].family) {
|
||||||
|
|
||||||
|
/* a port is already in the port list */
|
||||||
|
|
||||||
|
port = &port[i];
|
||||||
|
goto found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add a port to the port list */
|
||||||
|
|
||||||
|
port = ngx_array_push(ports);
|
||||||
|
if (port == NULL) {
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
port->family = sa->sa_family;
|
||||||
|
port->port = p;
|
||||||
|
|
||||||
|
if (ngx_array_init(&port->addrs, cf->temp_pool, 2,
|
||||||
|
sizeof(ngx_rtmp_conf_addr_t))
|
||||||
|
!= NGX_OK)
|
||||||
|
{
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
found:
|
||||||
|
|
||||||
|
addr = ngx_array_push(&port->addrs);
|
||||||
|
if (addr == NULL) {
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr->sockaddr = (struct sockaddr *) &listen->sockaddr;
|
||||||
|
addr->socklen = listen->socklen;
|
||||||
|
addr->ctx = listen->ctx;
|
||||||
|
addr->bind = listen->bind;
|
||||||
|
addr->wildcard = listen->wildcard;
|
||||||
|
addr->so_keepalive = listen->so_keepalive;
|
||||||
|
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
|
||||||
|
addr->tcp_keepidle = listen->tcp_keepidle;
|
||||||
|
addr->tcp_keepintvl = listen->tcp_keepintvl;
|
||||||
|
addr->tcp_keepcnt = listen->tcp_keepcnt;
|
||||||
|
#endif
|
||||||
|
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
|
||||||
|
addr->ipv6only = listen->ipv6only;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static char *
|
||||||
|
ngx_rtmp_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)
|
||||||
|
{
|
||||||
|
ngx_uint_t i, p, last, bind_wildcard;
|
||||||
|
ngx_listening_t *ls;
|
||||||
|
ngx_rtmp_port_t *mport;
|
||||||
|
ngx_rtmp_conf_port_t *port;
|
||||||
|
ngx_rtmp_conf_addr_t *addr;
|
||||||
|
|
||||||
|
port = ports->elts;
|
||||||
|
for (p = 0; p < ports->nelts; p++) {
|
||||||
|
|
||||||
|
ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
|
||||||
|
sizeof(ngx_rtmp_conf_addr_t), ngx_rtmp_cmp_conf_addrs);
|
||||||
|
|
||||||
|
addr = port[p].addrs.elts;
|
||||||
|
last = port[p].addrs.nelts;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* if there is the binding to the "*:port" then we need to bind()
|
||||||
|
* to the "*:port" only and ignore the other bindings
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (addr[last - 1].wildcard) {
|
||||||
|
addr[last - 1].bind = 1;
|
||||||
|
bind_wildcard = 1;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
bind_wildcard = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
|
||||||
|
while (i < last) {
|
||||||
|
|
||||||
|
if (bind_wildcard && !addr[i].bind) {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ls = ngx_create_listening(cf, addr[i].sockaddr, addr[i].socklen);
|
||||||
|
if (ls == NULL) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ls->addr_ntop = 1;
|
||||||
|
ls->handler = ngx_rtmp_init_connection;
|
||||||
|
ls->pool_size = 256;
|
||||||
|
|
||||||
|
/* TODO: error_log directive */
|
||||||
|
ls->logp = &cf->cycle->new_log;
|
||||||
|
ls->log.data = &ls->addr_text;
|
||||||
|
ls->log.handler = ngx_accept_log_error;
|
||||||
|
|
||||||
|
ls->keepalive = addr[i].so_keepalive;
|
||||||
|
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
|
||||||
|
ls->keepidle = addr[i].tcp_keepidle;
|
||||||
|
ls->keepintvl = addr[i].tcp_keepintvl;
|
||||||
|
ls->keepcnt = addr[i].tcp_keepcnt;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
|
||||||
|
ls->ipv6only = addr[i].ipv6only;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
mport = ngx_palloc(cf->pool, sizeof(ngx_rtmp_port_t));
|
||||||
|
if (mport == NULL) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ls->servers = mport;
|
||||||
|
|
||||||
|
if (i == last - 1) {
|
||||||
|
mport->naddrs = last;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
mport->naddrs = 1;
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ls->sockaddr->sa_family) {
|
||||||
|
#if (NGX_HAVE_INET6)
|
||||||
|
case AF_INET6:
|
||||||
|
if (ngx_rtmp_add_addrs6(cf, mport, addr) != NGX_OK) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
default: /* AF_INET */
|
||||||
|
if (ngx_rtmp_add_addrs(cf, mport, addr) != NGX_OK) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr++;
|
||||||
|
last--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NGX_CONF_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static ngx_int_t
|
||||||
|
ngx_rtmp_add_addrs(ngx_conf_t *cf, ngx_rtmp_port_t *mport,
|
||||||
|
ngx_rtmp_conf_addr_t *addr)
|
||||||
|
{
|
||||||
|
u_char *p;
|
||||||
|
size_t len;
|
||||||
|
ngx_uint_t i;
|
||||||
|
ngx_rtmp_in_addr_t *addrs;
|
||||||
|
struct sockaddr_in *sin;
|
||||||
|
u_char buf[NGX_SOCKADDR_STRLEN];
|
||||||
|
|
||||||
|
mport->addrs = ngx_pcalloc(cf->pool,
|
||||||
|
mport->naddrs * sizeof(ngx_rtmp_in_addr_t));
|
||||||
|
if (mport->addrs == NULL) {
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs = mport->addrs;
|
||||||
|
|
||||||
|
for (i = 0; i < mport->naddrs; i++) {
|
||||||
|
|
||||||
|
sin = (struct sockaddr_in *) addr[i].sockaddr;
|
||||||
|
addrs[i].addr = sin->sin_addr.s_addr;
|
||||||
|
|
||||||
|
addrs[i].conf.ctx = addr[i].ctx;
|
||||||
|
|
||||||
|
len = ngx_sock_ntop(addr[i].sockaddr, buf, NGX_SOCKADDR_STRLEN, 1);
|
||||||
|
|
||||||
|
p = ngx_pnalloc(cf->pool, len);
|
||||||
|
if (p == NULL) {
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngx_memcpy(p, buf, len);
|
||||||
|
|
||||||
|
addrs[i].conf.addr_text.len = len;
|
||||||
|
addrs[i].conf.addr_text.data = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if (NGX_HAVE_INET6)
|
||||||
|
|
||||||
|
static ngx_int_t
|
||||||
|
ngx_rtmp_add_addrs6(ngx_conf_t *cf, ngx_rtmp_port_t *mport,
|
||||||
|
ngx_rtmp_conf_addr_t *addr)
|
||||||
|
{
|
||||||
|
u_char *p;
|
||||||
|
size_t len;
|
||||||
|
ngx_uint_t i;
|
||||||
|
ngx_rtmp_in6_addr_t *addrs6;
|
||||||
|
struct sockaddr_in6 *sin6;
|
||||||
|
u_char buf[NGX_SOCKADDR_STRLEN];
|
||||||
|
|
||||||
|
mport->addrs = ngx_pcalloc(cf->pool,
|
||||||
|
mport->naddrs * sizeof(ngx_rtmp_in6_addr_t));
|
||||||
|
if (mport->addrs == NULL) {
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs6 = mport->addrs;
|
||||||
|
|
||||||
|
for (i = 0; i < mport->naddrs; i++) {
|
||||||
|
|
||||||
|
sin6 = (struct sockaddr_in6 *) addr[i].sockaddr;
|
||||||
|
addrs6[i].addr6 = sin6->sin6_addr;
|
||||||
|
|
||||||
|
addrs6[i].conf.ctx = addr[i].ctx;
|
||||||
|
|
||||||
|
len = ngx_sock_ntop(addr[i].sockaddr, buf, NGX_SOCKADDR_STRLEN, 1);
|
||||||
|
|
||||||
|
p = ngx_pnalloc(cf->pool, len);
|
||||||
|
if (p == NULL) {
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngx_memcpy(p, buf, len);
|
||||||
|
|
||||||
|
addrs6[i].conf.addr_text.len = len;
|
||||||
|
addrs6[i].conf.addr_text.data = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static ngx_int_t
|
||||||
|
ngx_rtmp_cmp_conf_addrs(const void *one, const void *two)
|
||||||
|
{
|
||||||
|
ngx_rtmp_conf_addr_t *first, *second;
|
||||||
|
|
||||||
|
first = (ngx_rtmp_conf_addr_t *) one;
|
||||||
|
second = (ngx_rtmp_conf_addr_t *) two;
|
||||||
|
|
||||||
|
if (first->wildcard) {
|
||||||
|
/* a wildcard must be the last resort, shift it to the end */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first->bind && !second->bind) {
|
||||||
|
/* shift explicit bind()ed addresses to the start */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!first->bind && second->bind) {
|
||||||
|
/* shift explicit bind()ed addresses to the start */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* do not sort by default */
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
283
ngx_rtmp.h
Normal file
283
ngx_rtmp.h
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Roman Arutyunyan
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef _NGX_RTMP_H_INCLUDED_
|
||||||
|
#define _NGX_RTMP_H_INCLUDED_
|
||||||
|
|
||||||
|
|
||||||
|
#include <ngx_config.h>
|
||||||
|
#include <ngx_core.h>
|
||||||
|
#include <ngx_event.h>
|
||||||
|
#include <ngx_event_connect.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define NGX_RTMP_HANDSHAKE_SIZE 1536
|
||||||
|
|
||||||
|
#define NGX_RTMP_DEFAULT_CHUNK_SIZE 128
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void **main_conf;
|
||||||
|
void **srv_conf;
|
||||||
|
} ngx_rtmp_conf_ctx_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u_char sockaddr[NGX_SOCKADDRLEN];
|
||||||
|
socklen_t socklen;
|
||||||
|
|
||||||
|
/* server ctx */
|
||||||
|
ngx_rtmp_conf_ctx_t *ctx;
|
||||||
|
|
||||||
|
unsigned bind:1;
|
||||||
|
unsigned wildcard:1;
|
||||||
|
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
|
||||||
|
unsigned ipv6only:2;
|
||||||
|
#endif
|
||||||
|
unsigned so_keepalive:2;
|
||||||
|
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
|
||||||
|
int tcp_keepidle;
|
||||||
|
int tcp_keepintvl;
|
||||||
|
int tcp_keepcnt;
|
||||||
|
#endif
|
||||||
|
} ngx_rtmp_listen_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ngx_rtmp_conf_ctx_t *ctx;
|
||||||
|
ngx_str_t addr_text;
|
||||||
|
} ngx_rtmp_addr_conf_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
in_addr_t addr;
|
||||||
|
ngx_rtmp_addr_conf_t conf;
|
||||||
|
} ngx_rtmp_in_addr_t;
|
||||||
|
|
||||||
|
|
||||||
|
#if (NGX_HAVE_INET6)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct in6_addr addr6;
|
||||||
|
ngx_rtmp_addr_conf_t conf;
|
||||||
|
} ngx_rtmp_in6_addr_t;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *addrs;
|
||||||
|
ngx_uint_t naddrs;
|
||||||
|
} ngx_rtmp_port_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int family;
|
||||||
|
in_port_t port;
|
||||||
|
ngx_array_t addrs; /* array of ngx_rtmp_conf_addr_t */
|
||||||
|
} ngx_rtmp_conf_port_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct sockaddr *sockaddr;
|
||||||
|
socklen_t socklen;
|
||||||
|
|
||||||
|
ngx_rtmp_conf_ctx_t *ctx;
|
||||||
|
|
||||||
|
unsigned bind:1;
|
||||||
|
unsigned wildcard:1;
|
||||||
|
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
|
||||||
|
unsigned ipv6only:2;
|
||||||
|
#endif
|
||||||
|
unsigned so_keepalive:2;
|
||||||
|
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
|
||||||
|
int tcp_keepidle;
|
||||||
|
int tcp_keepintvl;
|
||||||
|
int tcp_keepcnt;
|
||||||
|
#endif
|
||||||
|
} ngx_rtmp_conf_addr_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ngx_array_t servers; /* ngx_rtmp_core_srv_conf_t */
|
||||||
|
ngx_array_t listen; /* ngx_rtmp_listen_t */
|
||||||
|
} ngx_rtmp_core_main_conf_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t channel;
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t hsize;
|
||||||
|
uint8_t size;
|
||||||
|
uint32_t timer;
|
||||||
|
uint32_t stream;
|
||||||
|
} ngx_rtmp_packet_hdr_t;
|
||||||
|
|
||||||
|
|
||||||
|
#define NGX_RTMP_PUBLISHER 0x01
|
||||||
|
#define NGX_RTMP_SUBSCRIBER 0x02
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t signature; /* "RTMP" */
|
||||||
|
|
||||||
|
ngx_connection_t *connection;
|
||||||
|
|
||||||
|
void **ctx;
|
||||||
|
void **main_conf;
|
||||||
|
void **srv_conf;
|
||||||
|
|
||||||
|
ngx_uint_t chunk_size;
|
||||||
|
ngx_chain_t *free;
|
||||||
|
|
||||||
|
/* FIXME: there should probably be a better way
|
||||||
|
* to store handshake buffers & states
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* handshake */
|
||||||
|
ngx_buf_t buf;
|
||||||
|
ngx_uint_t hs_stage;
|
||||||
|
|
||||||
|
/* input */
|
||||||
|
ngx_chain_t *in;
|
||||||
|
ngx_rtmp_packet_hdr_t in_hdr;
|
||||||
|
|
||||||
|
/* output */
|
||||||
|
ngx_chain_t *out;
|
||||||
|
ngx_rtmp_packet_hdr_t out_hdr;
|
||||||
|
|
||||||
|
/* broadcast */
|
||||||
|
ngx_str_t *name;
|
||||||
|
ngx_rtmp_session_t *next;
|
||||||
|
ngx_uint_t flags;
|
||||||
|
} ngx_rtmp_session_t;
|
||||||
|
|
||||||
|
|
||||||
|
#define NGX_RTMP_SESSION_HASH_SIZE 16384
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ngx_msec_t timeout;
|
||||||
|
ngx_flag_t so_keepalive;
|
||||||
|
ngx_int_t buffers;
|
||||||
|
ngx_msec_t resolver_timeout;
|
||||||
|
ngx_resolver_t *resolver;
|
||||||
|
ngx_rtmp_conf_ctx_t *ctx;
|
||||||
|
ngx_rtmp_session_t **sessions; /* session hash map: name->session */
|
||||||
|
} ngx_rtmp_core_srv_conf_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ngx_str_t *client;
|
||||||
|
ngx_rtmp_session_t *session;
|
||||||
|
} ngx_rtmp_log_ctx_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *(*create_main_conf)(ngx_conf_t *cf);
|
||||||
|
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
|
||||||
|
|
||||||
|
void *(*create_srv_conf)(ngx_conf_t *cf);
|
||||||
|
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,
|
||||||
|
void *conf);
|
||||||
|
} ngx_rtmp_module_t;
|
||||||
|
|
||||||
|
|
||||||
|
/* RTMP packet types*/
|
||||||
|
#define NGX_RTMP_PACKET_CHUNK_SIZE 0x01
|
||||||
|
#define NGX_RTMP_PACKET_BYTES_READ 0x03
|
||||||
|
#define NGX_RTMP_PACKET_PING 0x04
|
||||||
|
#define NGX_RTMP_PACKET_SERVER_BW 0x05
|
||||||
|
#define NGX_RTMP_PACKET_CLIENT_BW 0x06
|
||||||
|
#define NGX_RTMP_PACKET_AUDIO 0x08
|
||||||
|
#define NGX_RTMP_PACKET_VIDEO 0x09
|
||||||
|
#define NGX_RTMP_PACKET_FLEX 0x0f
|
||||||
|
#define NGX_RTMP_PACKET_FLEX_SO 0x10
|
||||||
|
#define NGX_RTMP_PACKET_FLEX_MSG 0x11
|
||||||
|
#define NGX_RTMP_PACKET_NOTIFY 0x12
|
||||||
|
#define NGX_RTMP_PACKET_SO 0x13
|
||||||
|
#define NGX_RTMP_PACKET_INVOKE 0x14
|
||||||
|
|
||||||
|
/* RMTP ping types */
|
||||||
|
#define NGX_RMTP_PING_CLEAR_STEAM 0
|
||||||
|
#define NGX_RMTP_PING_CLEAR_BUFFER 1
|
||||||
|
#define NGX_RMTP_PING_CLIENT_TIME 3
|
||||||
|
#define NGX_RMTP_PING_RESET_STREAM 4
|
||||||
|
#define NGX_RMTP_PING_PING 6
|
||||||
|
#define NGX_RMTP_PING_PONG 7
|
||||||
|
|
||||||
|
|
||||||
|
#define NGX_RTMP_MODULE 0x504D5452 /* "RTMP" */
|
||||||
|
|
||||||
|
#define NGX_RTMP_MAIN_CONF 0x02000000
|
||||||
|
#define NGX_RTMP_SRV_CONF 0x04000000
|
||||||
|
|
||||||
|
|
||||||
|
#define NGX_RTMP_MAIN_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, main_conf)
|
||||||
|
#define NGX_RTMP_SRV_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, srv_conf)
|
||||||
|
|
||||||
|
|
||||||
|
#define ngx_rtmp_get_module_ctx(s, module) (s)->ctx[module.ctx_index]
|
||||||
|
#define ngx_rtmp_set_ctx(s, c, module) s->ctx[module.ctx_index] = c;
|
||||||
|
#define ngx_rtmp_delete_ctx(s, module) s->ctx[module.ctx_index] = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
#define ngx_rtmp_get_module_main_conf(s, module) \
|
||||||
|
(s)->main_conf[module.ctx_index]
|
||||||
|
#define ngx_rtmp_get_module_srv_conf(s, module) (s)->srv_conf[module.ctx_index]
|
||||||
|
|
||||||
|
#define ngx_rtmp_conf_get_module_main_conf(cf, module) \
|
||||||
|
((ngx_rtmp_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
|
||||||
|
#define ngx_rtmp_conf_get_module_srv_conf(cf, module) \
|
||||||
|
((ngx_rtmp_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
|
||||||
|
|
||||||
|
void ngx_rtmp_init_connection(ngx_connection_t *c);
|
||||||
|
void ngx_rtmp_close_session(ngx_rtmp_session_t *s);
|
||||||
|
u_char * ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len);
|
||||||
|
void ngx_rtmp_send(ngx_event_t *wev);
|
||||||
|
void ngx_rtmp_recv(ngx_event_t *wev);
|
||||||
|
|
||||||
|
void ngx_rtmp_set_chunk_size(ngx_rtmp_session_t *s, uint32_t chunk_size);
|
||||||
|
void ngx_rtmp_set_bytes_read(ngx_rtmp_session_t *s, uint32_t bytes_read);
|
||||||
|
void ngx_rtmp_set_client_buffer_time(ngx_rtmp_session_t *s, int16_t msec);
|
||||||
|
void ngx_rtmp_clear_buffer(ngx_rtmp_sesion_t *s);
|
||||||
|
void ngx_rtmp_set_ping_time(ngx_rtmp_session_t *s, int16_t msec);
|
||||||
|
void ngx_rtmp_set_server_bw(ngx_rtmp_session_t *s, uint32_t bw,
|
||||||
|
uint8_t limit_type);
|
||||||
|
void ngx_rtmp_set_client_bw(ngx_rtmp_session_t *s, uint32_t bw,
|
||||||
|
uint8_t limit_type);
|
||||||
|
|
||||||
|
void ngx_rtmp_join(ngx_rtmp_session_t *s, ngx_str_t *name, ngx_uint_t flags);
|
||||||
|
void ngx_rtmp_leave(ngx_rtmp_session_t *s);
|
||||||
|
|
||||||
|
ngx_int_t ngx_rtmp_receive_packet(ngx_rtmp_session_t *s,
|
||||||
|
ngx_rtmp_packet_hdr_t *h, ngx_chain_t *b);
|
||||||
|
|
||||||
|
void ngx_rtmp_send_packet(ngx_rtmp_session_t *s,
|
||||||
|
ngx_rtmp_packet_hdr_t *h, ngx_chain_t *b);
|
||||||
|
|
||||||
|
|
||||||
|
/* NetConnection methods */
|
||||||
|
ngx_int_t ngx_rtmp_connect(ngx_rtmp_session_t *s, ngx_chain_t **l);
|
||||||
|
ngx_int_t ngx_rtmp_close(ngx_rtmp_session_t *s, ngx_chain_t **l);
|
||||||
|
ngx_int_t ngx_rtmp_createstream(ngx_rtmp_session_t *s, ngx_chain_t **l);
|
||||||
|
|
||||||
|
|
||||||
|
/* NetStream methods */
|
||||||
|
ngx_int_t ngx_rtmp_play(ngx_rtmp_session_t *s, ngx_chain_t **l);
|
||||||
|
ngx_int_t ngx_rtmp_play2(ngx_rtmp_session_t *s, ngx_chain_t **l);
|
||||||
|
ngx_int_t ngx_rtmp_deletestream(ngx_rtmp_session_t *s, ngx_chain_t **l);
|
||||||
|
ngx_int_t ngx_rtmp_closestream(ngx_rtmp_session_t *s, ngx_chain_t **l);
|
||||||
|
ngx_int_t ngx_rtmp_receiveaudio(ngx_rtmp_session_t *s, ngx_chain_t **l);
|
||||||
|
ngx_int_t ngx_rtmp_receivevideo(ngx_rtmp_session_t *s, ngx_chain_t **l);
|
||||||
|
ngx_int_t ngx_rtmp_publish(ngx_rtmp_session_t *s, ngx_chain_t **l);
|
||||||
|
ngx_int_t ngx_rtmp_seek(ngx_rtmp_session_t *s, ngx_chain_t **l);
|
||||||
|
ngx_int_t ngx_rtmp_pause(ngx_rtmp_session_t *s, ngx_chain_t **l);
|
||||||
|
|
||||||
|
|
||||||
|
extern ngx_uint_t ngx_rtmp_max_module;
|
||||||
|
extern ngx_module_t ngx_rtmp_core_module;
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* _NGX_RTMP_H_INCLUDED_ */
|
335
ngx_rtmp_amf0.c
Normal file
335
ngx_rtmp_amf0.c
Normal file
|
@ -0,0 +1,335 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Roman Arutyunyan
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ngx_rtmp_amf0.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static ngx_int_t
|
||||||
|
ngx_rtmp_amf0_get(ngx_chain_t **l, void *p, size_t n)
|
||||||
|
{
|
||||||
|
ngx_buf_t *b;
|
||||||
|
|
||||||
|
if (!n)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for(; *l; l = &l->next) {
|
||||||
|
|
||||||
|
b = (*l)->buf;
|
||||||
|
|
||||||
|
if (b->last > n + b->pos) {
|
||||||
|
if (p)
|
||||||
|
p = ngx_cpymem(p, b->pos, n);
|
||||||
|
b->pos += n;
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p)
|
||||||
|
p = ngx_cpymem(p, b->pos, b->last - b->pos);
|
||||||
|
|
||||||
|
n -= size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static ngx_int_t
|
||||||
|
ngx_rtmp_amf0_put(ngx_chain_t **l, ngx_chain_t **free, void *p, size_t n)
|
||||||
|
{
|
||||||
|
ngx_buf_t *b;
|
||||||
|
|
||||||
|
while(n) {
|
||||||
|
b = (*l) ? (*l)->buf : NULL;
|
||||||
|
|
||||||
|
if (b == NULL || b->last == b->end) {
|
||||||
|
if (*free == NULL)
|
||||||
|
return NGX_ERROR;
|
||||||
|
|
||||||
|
if (*l == NULL) {
|
||||||
|
*l = *free;
|
||||||
|
*free = (*free)->next;
|
||||||
|
} else {
|
||||||
|
(*l)->next = *free;
|
||||||
|
*free = (*free)->next;
|
||||||
|
*l = (*l)->next;
|
||||||
|
}
|
||||||
|
(*l)->next = NULL;
|
||||||
|
b = (*l)->buf;
|
||||||
|
b->pos = b->last = b->start;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b->end - b->last <= n) {
|
||||||
|
b->last = ngx_cpymem(b->last, p, n);
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
b->last = ngx_cpymem(b->last, p, b->end - b->last);
|
||||||
|
p += (b->end - b->last);
|
||||||
|
n -= (b->end - b->last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static ngx_int_t
|
||||||
|
ngx_rtmp_amf0_read_object(ngx_chain_t **l, ngx_rtmp_amf0_objelt_t *elts,
|
||||||
|
size_t nelts)
|
||||||
|
{
|
||||||
|
uint8_t type;
|
||||||
|
uint16_t len;
|
||||||
|
size_t n, namelen, maxlen;
|
||||||
|
ngx_int_t rc;
|
||||||
|
|
||||||
|
maxlen = 0;
|
||||||
|
for(i = 0; i < n; ++i) {
|
||||||
|
namelen = strlen(v[n].name);
|
||||||
|
if (namelen > maxlen)
|
||||||
|
maxlen = namelen;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
|
||||||
|
char name[maxlen + 1];
|
||||||
|
|
||||||
|
/* read key */
|
||||||
|
if (ngx_rtmp_amf0_get(l, &len, sizeof(len)) != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
|
||||||
|
if (!len)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (len <= maxlen) {
|
||||||
|
rc = ngx_rtmp_amf0_get(l, name, len);
|
||||||
|
name[len] = 0;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
rc = ngx_rtmp_amf0_get(l, name, maxlen);
|
||||||
|
if (rc != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
name[maxlen] = 0;
|
||||||
|
rc = ngx_rtmp_amf0_get(l, 0, len - maxlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
|
||||||
|
/* TODO: if we require array to be sorted on name
|
||||||
|
* then we could be able to use binary search */
|
||||||
|
for(n = 0; n < nelts && strcmp(name, elts[n].name); ++n);
|
||||||
|
|
||||||
|
if (ngx_rtmp_amf0_read(s, n < nelts ? &elts[n] : NULL, 1) != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ngx_rtmp_amf0_get(l, type, 1) != NGX_OK
|
||||||
|
|| type != NGX_RTMP_AMF0_END)
|
||||||
|
{
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define NGX_RTMP_AMF0_TILL_END_FLAG (size_t(1) << (sizeof(size_t) * 8 - 1))
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_amf0_read(ngx_chain_t **l, ngx_rtmp_amf0_elt_t *elts, size_t nelts)
|
||||||
|
{
|
||||||
|
void *data;
|
||||||
|
uint8_t type;
|
||||||
|
size_t n, clen;
|
||||||
|
uint16_t len;
|
||||||
|
ngx_int_t rc;
|
||||||
|
int till_end;
|
||||||
|
|
||||||
|
if (nelts & NGX_RTMP_AMF0_TILL_END_FLAG) {
|
||||||
|
till_end = 1;
|
||||||
|
nelts = nelts & ~NGX_RTMP_AMF0_TILL_END_FLAG;
|
||||||
|
} else {
|
||||||
|
till_end = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(n = 0; till_end || n < nelts; ++n) {
|
||||||
|
|
||||||
|
if (ngx_rtmp_amf0_get(l, &type, sizeof(type)) != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
|
||||||
|
data = (n >= nelts || elts == NULL || elts->type != type)
|
||||||
|
? NULL
|
||||||
|
: elts->data;
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case NGX_RTMP_AMF0_NUMBER:
|
||||||
|
if (ngx_rtmp_amf0_get(l, data, 8) != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NGX_RTMP_AMF0_BOOLEAN:
|
||||||
|
if (ngx_rtmp_amf0_get(l, data, 1) != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NGX_RTMP_AMF0_STRING:
|
||||||
|
if (ngx_rtmp_amf0_get(l, &len, sizeof(len)) != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
|
||||||
|
if (data == NULL) {
|
||||||
|
rc = ngx_rtmp_amf0_get(l, data, len);
|
||||||
|
|
||||||
|
} else if (elts->len <= len)
|
||||||
|
rc = ngx_rtmp_amf0_get(l, data, elts->len - 1);
|
||||||
|
if (rc != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
data[elts->len - 1] = 0;
|
||||||
|
rc = ngx_rtmp_amf0_get(l, NULL, len - elts->len + 1);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
rc = ngx_rtmp_amf0_get(l, data, len);
|
||||||
|
data[len] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NGX_RTMP_AMF0_NULL:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NGX_RTMP_AMF0_OBJECT:
|
||||||
|
if (ngx_rtmp_amf0_read_object(l, data,
|
||||||
|
elts ? elts->len / sizeof(ngx_rtmp_amf0_elt_t) : 0
|
||||||
|
) != NGX_OK)
|
||||||
|
{
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NGX_RTMP_AMF0_ARRAY:
|
||||||
|
if (ngx_rtmp_amf0_read(l, data,
|
||||||
|
elts ? (elts->len / sizeof(ngx_rtmp_amf0_elt_t))
|
||||||
|
| NGX_RTMP_AMF0_TILL_END_FLAG : 0
|
||||||
|
) != NGX_OK)
|
||||||
|
{
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NGX_RTMP_AMF0_END:
|
||||||
|
return NGX_OK;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elts)
|
||||||
|
++elts;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static ngx_int_t
|
||||||
|
ngx_rtmp_amf0_write_object(ngx_chain_t **l, ngx_chain_t **free,
|
||||||
|
ngx_rtmp_amf0_elt_t *elts, size_t nelts)
|
||||||
|
{
|
||||||
|
uint16_t len;
|
||||||
|
size_t n;
|
||||||
|
char *name;
|
||||||
|
|
||||||
|
for(n = 0; n < nelts; ++n) {
|
||||||
|
|
||||||
|
name = elts[n].name;
|
||||||
|
len = strlen(name);
|
||||||
|
|
||||||
|
if (ngx_rtmp_amf0_put(l, free, &name, len) != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
|
||||||
|
if (ngx_rtmp_amf0_write(l, free, &elts[n], 1) != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = 0;
|
||||||
|
|
||||||
|
if (ngx_rtmp_amf0_put(l, free, &name, len) != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_amf0_write(ngx_chain_t **l, ngx_chain_t **free,
|
||||||
|
ngx_rtmp_amf0_elt_t *elts, size_t nelts)
|
||||||
|
{
|
||||||
|
size_t n;
|
||||||
|
ngx_int_t rc;
|
||||||
|
uint8_t type;
|
||||||
|
void *data;
|
||||||
|
uint16_t len;
|
||||||
|
|
||||||
|
for(n = 0; n < nelts; ++n) {
|
||||||
|
|
||||||
|
type = elts[n].type;
|
||||||
|
data = elts[n].data;
|
||||||
|
len = elts[n].len;
|
||||||
|
|
||||||
|
if (ngx_rtmp_amf0_put(l, free, &type, sizeof(type)) != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case NGX_RTMP_AMF0_NUMBER:
|
||||||
|
if (ngx_rtmp_amf0_put(l, free, data, 8) != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NGX_RTMP_AMF0_BOOLEAN:
|
||||||
|
if (ngx_rtmp_amf0_put(l, free, data, 1) != NGX_OK)
|
||||||
|
return NGX_ERROR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NGX_RTMP_AMF0_STRING:
|
||||||
|
if (ngx_rtmp_amf0_put(l, free, &len, sizeof(len)) != NGX_OK
|
||||||
|
|| ngx_rtmp_amf0_put(l, free, data, len) != NGX_OK)
|
||||||
|
{
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NGX_RTMP_AMF0_NULL:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NGX_RTMP_AMF0_OBJECT:
|
||||||
|
type = NGX_RTMP_AMF0_END;
|
||||||
|
if (ngx_rtmp_amf0_write_object(l, free, data,
|
||||||
|
elts[n].len / sizeof(ngx_rtmp_amf0_elt_t)) != NGX_OK
|
||||||
|
|| ngx_rtmp_amf0_put(l, free, &type,
|
||||||
|
sizeof(type)) != NGX_OK)
|
||||||
|
{
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NGX_RTMP_AMF0_ARRAY:
|
||||||
|
type = NGX_RTMP_AMF0_END;
|
||||||
|
if (ngx_rtmp_amf0_write(l, free, data,
|
||||||
|
elts[n].len / sizeof(ngx_rtmp_amf0_elt_t)) != NGX_OK
|
||||||
|
|| ngx_rtmp_amf0_put(l, free, &type,
|
||||||
|
sizeof(type)) != NGX_OK)
|
||||||
|
{
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NGX_RTMP_AMF0_END:
|
||||||
|
return NGX_OK;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return NGX_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
79
ngx_rtmp_amf0.h
Normal file
79
ngx_rtmp_amf0.h
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Roman Arutyunyan
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef _NGX_RTMP_AMF0_H_INCLUDED_
|
||||||
|
#define _NGX_RTMP_AMF0_H_INCLUDED_
|
||||||
|
|
||||||
|
#define NGX_RTMP_AMF0_NUMBER 0x00
|
||||||
|
#define NGX_RTMP_AMF0_BOOLEAN 0x01
|
||||||
|
#define NGX_RTMP_AMF0_STRING 0x02
|
||||||
|
|
||||||
|
#define NGX_RTMP_AMF0_OBJECT 0x03
|
||||||
|
#define NGX_RTMP_AMF0_NULL 0x05
|
||||||
|
#define NGX_RTMP_AMF0_ARRAY 0x08
|
||||||
|
#define NGX_RTMP_AMF0_END 0x09
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ngx_int_t type;
|
||||||
|
char *name;
|
||||||
|
void *data;
|
||||||
|
size_t len;
|
||||||
|
} ngx_rtmp_amf0_elt_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
struct {
|
||||||
|
char name[32];
|
||||||
|
double trans_id;
|
||||||
|
char app[32];
|
||||||
|
char flashver[32];
|
||||||
|
char v1[8];
|
||||||
|
int locked;
|
||||||
|
} vals;
|
||||||
|
|
||||||
|
ngx_rtmp_amf0_elt_t props[] = {
|
||||||
|
{ NGX_RTMP_AMF0_STRING, "app", vals.app, sizeof(vals.app) },
|
||||||
|
{ NGX_RTMP_AMF0_STRING, "flashver", vals.flashver, sizeof(vals.flashver) }
|
||||||
|
};
|
||||||
|
|
||||||
|
ngx_rtmp_amf0_elt_t list[] = {
|
||||||
|
{ NGX_RTMP_AMF0_STRING, 0, vals.v1, sizeof(vals.v1) },
|
||||||
|
{ NGX_RTMP_AMF0_BOOLEAN, 0, &vals.locked, sizeof(vals.locked) }
|
||||||
|
};
|
||||||
|
|
||||||
|
ngx_rtmp_amf0_elt elts[] = {
|
||||||
|
{ NGX_RTMP_AMF0_STRING, 0 vals.name, sizeof(vals.name) },
|
||||||
|
{ NGX_RTMP_AMF0_NUMBER, 0 &vals.trans_id, sizeof(vals.trans_id) },
|
||||||
|
{ NGX_RTMP_AMF0_OBJECT, 0, props, sizeof(props) },
|
||||||
|
{ NGX_RTMP_AMF0_ARRAY, 0, list, sizeof(list) },
|
||||||
|
{ NGX_RTMP_AMF0_NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Reading:
|
||||||
|
-------
|
||||||
|
|
||||||
|
memset(&vals, 0, sizeof(vals));
|
||||||
|
ngx_rtmp_amf0_read(l, elts, sizeof(elts));
|
||||||
|
|
||||||
|
|
||||||
|
Writing:
|
||||||
|
-------
|
||||||
|
|
||||||
|
ngx_rtmp_amf0_write(l, free, elts, sizeof(elts));
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* reading AMF0 */
|
||||||
|
ngx_int_t ngx_rtmp_amf0_read(ngx_chain_t **l,
|
||||||
|
ngx_rtmp_amf0_elt_t *elts, size_t nelts);
|
||||||
|
|
||||||
|
/* writing AMF0 */
|
||||||
|
ngx_int_t ngx_rtmp_amf0_write(ngx_chain_t **l, ngx_chain_t **free,
|
||||||
|
ngx_rtmp_amf0_elt_t *elts, size_t nelts);
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* _NGX_RTMP_AMF0_H_INCLUDED_ */
|
||||||
|
|
549
ngx_rtmp_core_module.c
Normal file
549
ngx_rtmp_core_module.c
Normal file
|
@ -0,0 +1,549 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Roman Arutyunyan
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <ngx_config.h>
|
||||||
|
#include <ngx_core.h>
|
||||||
|
#include <ngx_event.h>
|
||||||
|
#include "ngx_rtmp.h"
|
||||||
|
|
||||||
|
|
||||||
|
static void *ngx_rtmp_core_create_main_conf(ngx_conf_t *cf);
|
||||||
|
static void *ngx_rtmp_core_create_srv_conf(ngx_conf_t *cf);
|
||||||
|
static char *ngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent,
|
||||||
|
void *child);
|
||||||
|
static char *ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd,
|
||||||
|
void *conf);
|
||||||
|
static char *ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd,
|
||||||
|
void *conf);
|
||||||
|
static char *ngx_rtmp_core_protocol(ngx_conf_t *cf, ngx_command_t *cmd,
|
||||||
|
void *conf);
|
||||||
|
static char *ngx_rtmp_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd,
|
||||||
|
void *conf);
|
||||||
|
|
||||||
|
|
||||||
|
static ngx_conf_deprecated_t ngx_conf_deprecated_so_keepalive = {
|
||||||
|
ngx_conf_deprecated, "so_keepalive",
|
||||||
|
"so_keepalive\" parameter of the \"listen"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static ngx_command_t ngx_rtmp_core_commands[] = {
|
||||||
|
|
||||||
|
{ ngx_string("server"),
|
||||||
|
NGX_RTMP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
|
||||||
|
ngx_rtmp_core_server,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
NULL },
|
||||||
|
|
||||||
|
{ ngx_string("listen"),
|
||||||
|
NGX_RTMP_SRV_CONF|NGX_CONF_TAKE12,
|
||||||
|
ngx_rtmp_core_listen,
|
||||||
|
NGX_RTMP_SRV_CONF_OFFSET,
|
||||||
|
0,
|
||||||
|
NULL },
|
||||||
|
|
||||||
|
{ ngx_string("so_keepalive"),
|
||||||
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_FLAG,
|
||||||
|
ngx_conf_set_flag_slot,
|
||||||
|
NGX_RTMP_SRV_CONF_OFFSET,
|
||||||
|
offsetof(ngx_rtmp_core_srv_conf_t, so_keepalive),
|
||||||
|
&ngx_conf_deprecated_so_keepalive },
|
||||||
|
|
||||||
|
{ ngx_string("timeout"),
|
||||||
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||||
|
ngx_conf_set_msec_slot,
|
||||||
|
NGX_RTMP_SRV_CONF_OFFSET,
|
||||||
|
offsetof(ngx_rtmp_core_srv_conf_t, timeout),
|
||||||
|
NULL },
|
||||||
|
|
||||||
|
{ ngx_string("buffers"),
|
||||||
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||||
|
ngx_conf_set_num_slot,
|
||||||
|
NGX_RTMP_SRV_CONF_OFFSET,
|
||||||
|
offsetof(ngx_rtmp_core_srv_conf_t, buffers),
|
||||||
|
NULL },
|
||||||
|
|
||||||
|
{ ngx_string("resolver"),
|
||||||
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_1MORE,
|
||||||
|
ngx_rtmp_core_resolver,
|
||||||
|
NGX_RTMP_SRV_CONF_OFFSET,
|
||||||
|
0,
|
||||||
|
NULL },
|
||||||
|
|
||||||
|
{ ngx_string("resolver_timeout"),
|
||||||
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||||
|
ngx_conf_set_msec_slot,
|
||||||
|
NGX_RTMP_SRV_CONF_OFFSET,
|
||||||
|
offsetof(ngx_rtmp_core_srv_conf_t, resolver_timeout),
|
||||||
|
NULL },
|
||||||
|
|
||||||
|
ngx_null_command
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static ngx_rtmp_module_t ngx_rtmp_core_module_ctx = {
|
||||||
|
ngx_rtmp_core_create_main_conf, /* create main configuration */
|
||||||
|
NULL, /* init main configuration */
|
||||||
|
|
||||||
|
ngx_rtmp_core_create_srv_conf, /* create server configuration */
|
||||||
|
ngx_rtmp_core_merge_srv_conf /* merge server configuration */
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
ngx_module_t ngx_rtmp_core_module = {
|
||||||
|
NGX_MODULE_V1,
|
||||||
|
&ngx_rtmp_core_module_ctx, /* module context */
|
||||||
|
ngx_rtmp_core_commands, /* module directives */
|
||||||
|
NGX_RTMP_MODULE, /* module type */
|
||||||
|
NULL, /* init master */
|
||||||
|
NULL, /* init module */
|
||||||
|
NULL, /* init process */
|
||||||
|
NULL, /* init thread */
|
||||||
|
NULL, /* exit thread */
|
||||||
|
NULL, /* exit process */
|
||||||
|
NULL, /* exit master */
|
||||||
|
NGX_MODULE_V1_PADDING
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static void *
|
||||||
|
ngx_rtmp_core_create_main_conf(ngx_conf_t *cf)
|
||||||
|
{
|
||||||
|
ngx_rtmp_core_main_conf_t *cmcf;
|
||||||
|
|
||||||
|
cmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_main_conf_t));
|
||||||
|
if (cmcf == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ngx_array_init(&cmcf->servers, cf->pool, 4,
|
||||||
|
sizeof(ngx_rtmp_core_srv_conf_t *))
|
||||||
|
!= NGX_OK)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ngx_array_init(&cmcf->listen, cf->pool, 4, sizeof(ngx_rtmp_listen_t))
|
||||||
|
!= NGX_OK)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmcf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void *
|
||||||
|
ngx_rtmp_core_create_srv_conf(ngx_conf_t *cf)
|
||||||
|
{
|
||||||
|
ngx_rtmp_core_srv_conf_t *cscf;
|
||||||
|
|
||||||
|
cscf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_srv_conf_t));
|
||||||
|
if (cscf == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* set by ngx_pcalloc():
|
||||||
|
*
|
||||||
|
* cscf->protocol = NULL;
|
||||||
|
*/
|
||||||
|
|
||||||
|
cscf->timeout = NGX_CONF_UNSET_MSEC;
|
||||||
|
cscf->resolver_timeout = NGX_CONF_UNSET_MSEC;
|
||||||
|
cscf->so_keepalive = NGX_CONF_UNSET;
|
||||||
|
cssf->buffers = NGX_CONF_UNSET;
|
||||||
|
|
||||||
|
cscf->resolver = NGX_CONF_UNSET_PTR;
|
||||||
|
|
||||||
|
cscf->file_name = cf->conf_file->file.name.data;
|
||||||
|
cscf->line = cf->conf_file->line;
|
||||||
|
|
||||||
|
return cscf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static char *
|
||||||
|
ngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
|
||||||
|
{
|
||||||
|
ngx_rtmp_core_srv_conf_t *prev = parent;
|
||||||
|
ngx_rtmp_core_srv_conf_t *conf = child;
|
||||||
|
|
||||||
|
ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
|
||||||
|
ngx_conf_merge_msec_value(conf->resolver_timeout, prev->resolver_timeout,
|
||||||
|
30000);
|
||||||
|
|
||||||
|
ngx_conf_merge_value(conf->so_keepalive, prev->so_keepalive, 0);
|
||||||
|
ngx_conf_merge_value(conf->buffers, prev->buffers, 16);
|
||||||
|
|
||||||
|
ngx_conf_merge_ptr_value(conf->resolver, prev->resolver, NULL);
|
||||||
|
|
||||||
|
conf->sessions = ngx_pcalloc(cf->pool,
|
||||||
|
sizeof(ngx_rtmp_session_t*) * NGX_RTMP_SESSION_HASH_SIZE);
|
||||||
|
|
||||||
|
return NGX_CONF_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static char *
|
||||||
|
ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||||
|
{
|
||||||
|
char *rv;
|
||||||
|
void *mconf;
|
||||||
|
ngx_uint_t m;
|
||||||
|
ngx_conf_t pcf;
|
||||||
|
ngx_rtmp_module_t *module;
|
||||||
|
ngx_rtmp_conf_ctx_t *ctx, *rtmp_ctx;
|
||||||
|
ngx_rtmp_core_srv_conf_t *cscf, **cscfp;
|
||||||
|
ngx_rtmp_core_main_conf_t *cmcf;
|
||||||
|
|
||||||
|
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
rtmp_ctx = cf->ctx;
|
||||||
|
ctx->main_conf = rtmp_ctx->main_conf;
|
||||||
|
|
||||||
|
/* the server{}'s srv_conf */
|
||||||
|
|
||||||
|
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
|
||||||
|
if (ctx->srv_conf == NULL) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (m = 0; ngx_modules[m]; m++) {
|
||||||
|
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
module = ngx_modules[m]->ctx;
|
||||||
|
|
||||||
|
if (module->create_srv_conf) {
|
||||||
|
mconf = module->create_srv_conf(cf);
|
||||||
|
if (mconf == NULL) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->srv_conf[ngx_modules[m]->ctx_index] = mconf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the server configuration context */
|
||||||
|
|
||||||
|
cscf = ctx->srv_conf[ngx_rtmp_core_module.ctx_index];
|
||||||
|
cscf->ctx = ctx;
|
||||||
|
|
||||||
|
cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index];
|
||||||
|
|
||||||
|
cscfp = ngx_array_push(&cmcf->servers);
|
||||||
|
if (cscfp == NULL) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
*cscfp = cscf;
|
||||||
|
|
||||||
|
|
||||||
|
/* parse inside server{} */
|
||||||
|
|
||||||
|
pcf = *cf;
|
||||||
|
cf->ctx = ctx;
|
||||||
|
cf->cmd_type = NGX_RTMP_SRV_CONF;
|
||||||
|
|
||||||
|
rv = ngx_conf_parse(cf, NULL);
|
||||||
|
|
||||||
|
*cf = pcf;
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static char *
|
||||||
|
ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||||
|
{
|
||||||
|
ngx_rtmp_core_srv_conf_t *cscf = conf;
|
||||||
|
|
||||||
|
size_t len, off;
|
||||||
|
in_port_t port;
|
||||||
|
ngx_str_t *value;
|
||||||
|
ngx_url_t u;
|
||||||
|
ngx_uint_t i, m;
|
||||||
|
struct sockaddr *sa;
|
||||||
|
ngx_rtmp_listen_t *ls;
|
||||||
|
ngx_rtmp_module_t *module;
|
||||||
|
struct sockaddr_in *sin;
|
||||||
|
ngx_rtmp_core_main_conf_t *cmcf;
|
||||||
|
#if (NGX_HAVE_INET6)
|
||||||
|
struct sockaddr_in6 *sin6;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
value = cf->args->elts;
|
||||||
|
|
||||||
|
ngx_memzero(&u, sizeof(ngx_url_t));
|
||||||
|
|
||||||
|
u.url = value[1];
|
||||||
|
u.listen = 1;
|
||||||
|
|
||||||
|
if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
|
||||||
|
if (u.err) {
|
||||||
|
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||||
|
"%s in \"%V\" of the \"listen\" directive",
|
||||||
|
u.err, &u.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
|
||||||
|
|
||||||
|
ls = cmcf->listen.elts;
|
||||||
|
|
||||||
|
for (i = 0; i < cmcf->listen.nelts; i++) {
|
||||||
|
|
||||||
|
sa = (struct sockaddr *) ls[i].sockaddr;
|
||||||
|
|
||||||
|
if (sa->sa_family != u.family) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (sa->sa_family) {
|
||||||
|
|
||||||
|
#if (NGX_HAVE_INET6)
|
||||||
|
case AF_INET6:
|
||||||
|
off = offsetof(struct sockaddr_in6, sin6_addr);
|
||||||
|
len = 16;
|
||||||
|
sin6 = (struct sockaddr_in6 *) sa;
|
||||||
|
port = sin6->sin6_port;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default: /* AF_INET */
|
||||||
|
off = offsetof(struct sockaddr_in, sin_addr);
|
||||||
|
len = 4;
|
||||||
|
sin = (struct sockaddr_in *) sa;
|
||||||
|
port = sin->sin_port;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ngx_memcmp(ls[i].sockaddr + off, u.sockaddr + off, len) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port != u.port) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||||
|
"duplicate \"%V\" address and port pair", &u.url);
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ls = ngx_array_push(&cmcf->listen);
|
||||||
|
if (ls == NULL) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngx_memzero(ls, sizeof(ngx_rtmp_listen_t));
|
||||||
|
|
||||||
|
ngx_memcpy(ls->sockaddr, u.sockaddr, u.socklen);
|
||||||
|
|
||||||
|
ls->socklen = u.socklen;
|
||||||
|
ls->wildcard = u.wildcard;
|
||||||
|
ls->ctx = cf->ctx;
|
||||||
|
|
||||||
|
for (m = 0; ngx_modules[m]; m++) {
|
||||||
|
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
module = ngx_modules[m]->ctx;
|
||||||
|
|
||||||
|
if (module->protocol == NULL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; module->protocol->port[i]; i++) {
|
||||||
|
if (module->protocol->port[i] == u.port) {
|
||||||
|
cscf->protocol = module->protocol;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 2; i < cf->args->nelts; i++) {
|
||||||
|
|
||||||
|
if (ngx_strcmp(value[i].data, "bind") == 0) {
|
||||||
|
ls->bind = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) {
|
||||||
|
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
|
||||||
|
struct sockaddr *sa;
|
||||||
|
u_char buf[NGX_SOCKADDR_STRLEN];
|
||||||
|
|
||||||
|
sa = (struct sockaddr *) ls->sockaddr;
|
||||||
|
|
||||||
|
if (sa->sa_family == AF_INET6) {
|
||||||
|
|
||||||
|
if (ngx_strcmp(&value[i].data[10], "n") == 0) {
|
||||||
|
ls->ipv6only = 1;
|
||||||
|
|
||||||
|
} else if (ngx_strcmp(&value[i].data[10], "ff") == 0) {
|
||||||
|
ls->ipv6only = 2;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||||
|
"invalid ipv6only flags \"%s\"",
|
||||||
|
&value[i].data[9]);
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ls->bind = 1;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
len = ngx_sock_ntop(sa, buf, NGX_SOCKADDR_STRLEN, 1);
|
||||||
|
|
||||||
|
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||||
|
"ipv6only is not supported "
|
||||||
|
"on addr \"%*s\", ignored", len, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
#else
|
||||||
|
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||||
|
"bind ipv6only is not supported "
|
||||||
|
"on this platform");
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) {
|
||||||
|
|
||||||
|
if (ngx_strcmp(&value[i].data[13], "on") == 0) {
|
||||||
|
ls->so_keepalive = 1;
|
||||||
|
|
||||||
|
} else if (ngx_strcmp(&value[i].data[13], "off") == 0) {
|
||||||
|
ls->so_keepalive = 2;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
|
||||||
|
u_char *p, *end;
|
||||||
|
ngx_str_t s;
|
||||||
|
|
||||||
|
end = value[i].data + value[i].len;
|
||||||
|
s.data = value[i].data + 13;
|
||||||
|
|
||||||
|
p = ngx_strlchr(s.data, end, ':');
|
||||||
|
if (p == NULL) {
|
||||||
|
p = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p > s.data) {
|
||||||
|
s.len = p - s.data;
|
||||||
|
|
||||||
|
ls->tcp_keepidle = ngx_parse_time(&s, 1);
|
||||||
|
if (ls->tcp_keepidle == (time_t) NGX_ERROR) {
|
||||||
|
goto invalid_so_keepalive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.data = (p < end) ? (p + 1) : end;
|
||||||
|
|
||||||
|
p = ngx_strlchr(s.data, end, ':');
|
||||||
|
if (p == NULL) {
|
||||||
|
p = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p > s.data) {
|
||||||
|
s.len = p - s.data;
|
||||||
|
|
||||||
|
ls->tcp_keepintvl = ngx_parse_time(&s, 1);
|
||||||
|
if (ls->tcp_keepintvl == (time_t) NGX_ERROR) {
|
||||||
|
goto invalid_so_keepalive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.data = (p < end) ? (p + 1) : end;
|
||||||
|
|
||||||
|
if (s.data < end) {
|
||||||
|
s.len = end - s.data;
|
||||||
|
|
||||||
|
ls->tcp_keepcnt = ngx_atoi(s.data, s.len);
|
||||||
|
if (ls->tcp_keepcnt == NGX_ERROR) {
|
||||||
|
goto invalid_so_keepalive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ls->tcp_keepidle == 0 && ls->tcp_keepintvl == 0
|
||||||
|
&& ls->tcp_keepcnt == 0)
|
||||||
|
{
|
||||||
|
goto invalid_so_keepalive;
|
||||||
|
}
|
||||||
|
|
||||||
|
ls->so_keepalive = 1;
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||||
|
"the \"so_keepalive\" parameter accepts "
|
||||||
|
"only \"on\" or \"off\" on this platform");
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
ls->bind = 1;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
|
||||||
|
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
|
||||||
|
invalid_so_keepalive:
|
||||||
|
|
||||||
|
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||||
|
"invalid so_keepalive value: \"%s\"",
|
||||||
|
&value[i].data[13]);
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||||
|
"the invalid \"%V\" parameter", &value[i]);
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NGX_CONF_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static char *
|
||||||
|
ngx_rtmp_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||||
|
{
|
||||||
|
ngx_rtmp_core_srv_conf_t *cscf = conf;
|
||||||
|
|
||||||
|
ngx_str_t *value;
|
||||||
|
|
||||||
|
value = cf->args->elts;
|
||||||
|
|
||||||
|
if (cscf->resolver != NGX_CONF_UNSET_PTR) {
|
||||||
|
return "is duplicate";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ngx_strcmp(value[1].data, "off") == 0) {
|
||||||
|
cscf->resolver = NULL;
|
||||||
|
return NGX_CONF_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
cscf->resolver = ngx_resolver_create(cf, &value[1], cf->args->nelts - 1);
|
||||||
|
if (cscf->resolver == NULL) {
|
||||||
|
return NGX_CONF_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NGX_CONF_OK;
|
||||||
|
}
|
||||||
|
|
1015
ngx_rtmp_handler.c
Normal file
1015
ngx_rtmp_handler.c
Normal file
File diff suppressed because it is too large
Load diff
24
ngx_rtmp_netconn.c
Normal file
24
ngx_rtmp_netconn.c
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Roman Arutyunyan
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ngx_rtmp.h"
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_connect(ngx_rtmp_session_t *s, ngx_chain_t **l)
|
||||||
|
{
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_close(ngx_rtmp_session_t *s, ngx_chain_t **l)
|
||||||
|
{
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_createstream(ngx_rtmp_session_t *s, ngx_chain_t **l)
|
||||||
|
{
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
67
ngx_rtmp_netstream.c
Normal file
67
ngx_rtmp_netstream.c
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Roman Arutyunyan
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ngx_rtmp.h"
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_play(ngx_rtmp_session_t *s, ngx_chain_t **l)
|
||||||
|
{
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_play2(ngx_rtmp_session_t *s, ngx_chain_t **l)
|
||||||
|
{
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_deletestream(ngx_rtmp_session_t *s, ngx_chain_t **l)
|
||||||
|
{
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_closestream(ngx_rtmp_session_t *s, ngx_chain_t **l)
|
||||||
|
{
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_receiveaudio(ngx_rtmp_session_t *s, ngx_chain_t **l)
|
||||||
|
{
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_receivevideo(ngx_rtmp_session_t *s, ngx_chain_t **l)
|
||||||
|
{
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_publish(ngx_rtmp_session_t *s, ngx_chain_t **l)
|
||||||
|
{
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_seek(ngx_rtmp_session_t *s, ngx_chain_t **l)
|
||||||
|
{
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ngx_int_t
|
||||||
|
ngx_rtmp_pause(ngx_rtmp_session_t *s, ngx_chain_t **l)
|
||||||
|
{
|
||||||
|
return NGX_OK;
|
||||||
|
}
|
Loading…
Reference in a new issue