luci-app-nginx-pingos: add package

This commit is contained in:
CN_SZTL 2020-07-05 20:42:06 +08:00
parent 9b74564a2a
commit 76a09141eb
No known key found for this signature in database
GPG Key ID: 6850B6345C862176
193 changed files with 70408 additions and 0 deletions

View File

@ -0,0 +1,98 @@
# Copyright (C) 2020 Lienol <lawlienol@gmail.com>
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-nginx-pingos
PKG_VERSION:=1.19.0
PKG_RELEASE:=3
PKG_SOURCE:=nginx-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://nginx.org/download/
PKG_HASH:=44a616171fcd7d7ad7c6af3e6f3ad0879b54db5a5d21be874cd458b5691e36c8
PKG_DIR:=$(BUILD_DIR)/$(PKG_NAME)
PKG_BUILD_DIR:=$(PKG_DIR)/nginx-$(PKG_VERSION)
PKG_FIXUP:=autoreconf
PKG_BUILD_PARALLEL:=1
PKG_INSTALL:=1
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
CATEGORY:=LuCI
SUBMENU:=3. Applications
TITLE:=PingOS server
PKGARCH:=all
URL:=https://pingos.io/
DEPENDS:=+libpcre +libopenssl +zlib +libpthread
endef
define Package/$(PKG_NAME)/conffiles
endef
define Build/Prepare
rm -r $(PKG_BUILD_DIR)
mkdir -p $(PKG_BUILD_DIR)
tar -zxvf $(DL_DIR)/$(PKG_SOURCE) -C $(PKG_DIR)
$(CP) -pR ./modules $(PKG_BUILD_DIR)/modules
$(call Build/Prepare/Default,)
endef
ADDITIONAL_MODULES:= --with-http_ssl_module \
--add-module=./modules/nginx-rtmp-module \
--add-module=./modules/nginx-client-module \
--add-module=./modules/nginx-multiport-module \
--add-module=./modules/nginx-toolkit-module
TARGET_CFLAGS += -fvisibility=hidden -ffunction-sections -fdata-sections -DNGX_LUA_NO_BY_LUA_BLOCK
TARGET_LDFLAGS += -Wl,--gc-sections
CONFIGURE_ARGS += \
--crossbuild=Linux::$(ARCH) \
--prefix=/usr \
--conf-path=/usr/share/pingos/conf/nginx.conf \
$(ADDITIONAL_MODULES) \
--error-log-path=/var/etc/pingos/error.log \
--pid-path=/var/etc/pingos/pingos.pid \
--lock-path=/var/etc/pingos/pingos.lock \
--http-log-path=/var/etc/pingos/access.log \
--http-client-body-temp-path=/var/etc/pingos/lib/body \
--http-proxy-temp-path=/var/etc/pingos/lib/proxy \
--with-cc="$(TARGET_CC)" \
--with-cc-opt="$(TARGET_CPPFLAGS) $(TARGET_CFLAGS)" \
--with-ld-opt="$(TARGET_LDFLAGS)" \
--without-http_upstream_zone_module
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/etc
$(INSTALL_CONF) ./root/etc/pingos.template $(1)/etc/pingos.template
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./root/etc/config/pingos $(1)/etc/config/pingos
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./root/etc/init.d/pingos $(1)/etc/init.d/pingos
$(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_CONF) ./root/etc/uci-defaults/* $(1)/etc/uci-defaults
$(INSTALL_DIR) $(1)/usr/share/pingos
cp -pR $(PKG_INSTALL_DIR)/usr/share/pingos/conf $(1)/usr/share/pingos
$(INSTALL_DATA) ./root/resource/conf-template/nginx.conf $(1)/usr/share/pingos/conf/nginx.conf
cp -pR $(PKG_INSTALL_DIR)/usr/html $(1)/usr/share/pingos/html
$(INSTALL_DATA) ./root/resource/crossdomain.xml $(1)/usr/share/pingos/html/crossdomain.xml
$(INSTALL_DATA) ./root/resource/stat.xsl $(1)/usr/share/pingos/html/stat.xsl
$(INSTALL_DIR) $(1)/usr/lib/lua/luci
cp -pR ./luasrc/* $(1)/usr/lib/lua/luci/
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/i18n
po2lmo ./po/zh-cn/pingos.po $(1)/usr/lib/lua/luci/i18n/pingos.zh-cn.lmo
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/nginx $(1)/usr/sbin/pingos
endef
$(eval $(call BuildPackage,$(PKG_NAME)))

View File

@ -0,0 +1,17 @@
-- Copyright 2020 Lienol <lawlienol@gmail.com>
module("luci.controller.pingos", package.seeall)
function index()
if not nixio.fs.access("/etc/config/pingos") then return end
entry({"admin", "nas"}, firstchild(), "NAS", 44).dependent = false
entry({"admin", "nas", "pingos"}, cbi("pingos"), _("PingOS"), 3).dependent = true
entry({"admin", "nas", "pingos", "status"}, call("act_status")).leaf = true
end
function act_status()
local e = {}
e.status = luci.sys.call("ps -w | grep pingos | grep nginx | grep -v grep > /dev/null") == 0
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end

View File

@ -0,0 +1,65 @@
m = Map("pingos", translate("PingOS"))
m:append(Template("pingos/status"))
s = m:section(TypedSection, "global")
s.anonymous = true
s.addremove = false
s:tab("global", translate("Global Settings"))
s:tab("template", translate("Edit Template"))
nginx = s:taboption("template", Value, "_nginx", translatef("Edit the template that is used for generating the %s configuration.", "nginx"),
translatef("This is the content of the file '%s'", "/etc/pingos.template") .. "<br />" ..
translatef("Values enclosed by pipe symbols ('|') should not be changed. They get their values from the '%s' tab.", translate("Global Settings")))
nginx.template = "cbi/tvalue"
nginx.rows = 30
function nginx.cfgvalue(self, section)
return nixio.fs.readfile("/etc/pingos.template")
end
function nginx.write(self, section, value)
value = value:gsub("\r\n?", "\n")
nixio.fs.writefile("/etc/pingos.template", value)
end
o = s:taboption("global", Flag, "enable", translate("Enable"))
o.rmempty = false
o = s:taboption("global", Value, "http_port", "HTTP(S)" ..translate("Port"))
o.datatype = "port"
o.default = 8082
o.rmempty = false
o = s:taboption("global", Flag, "https", translate("HTTPS"))
o.rmempty = false
o = s:taboption("global", FileUpload, "certificate", translate("certificate"))
o:depends("https", 1)
o = s:taboption("global", FileUpload, "key", translate("key"))
o:depends("https", 1)
o = s:taboption("global", Value, "rtmp_port", "RTMP" ..translate("Port"))
o.datatype = "port"
o.default = 1935
o.rmempty = false
o = s:taboption("global", Flag, "hls", translate("HLS"))
o.rmempty = false
o = s:taboption("global", Flag, "hls2", translate("HLS2"))
o.rmempty = false
o = s:taboption("global", Flag, "ts_record", "TS " .. translate("Record"))
o.rmempty = false
o = s:taboption("global", Flag, "flv_record", "FLV " .. translate("Record"))
o.rmempty = false
o = s:taboption("global", Value, "record_path", translate("Record") .. translate("Path"))
o.default = "/tmp/record"
o:depends("ts_record", 1)
o:depends("flv_record", 1)
return m

View File

@ -0,0 +1,52 @@
<fieldset class="cbi-section">
<legend><%:Running Status%></legend>
<fieldset class="cbi-section">
<div class="cbi-value">
<label class="cbi-value-title">Nginx <%:Status%></label>
<div class="cbi-value-field" id="_nginx_status"><%:Collecting data...%></div>
</div>
<div class="cbi-value">
<label class="cbi-value-title"></label>
<div class="cbi-value-field">
<font color="red" id="tips"></font>
<ul id="tips2">
<li>rtmp rtmp://ip/live/<%:Stream name%></li>
<li>http(s)-flv http(s)://ip/flv/<%:Stream name%></li>
<li>hls http(s)://ip/hls/<%:Stream name%>.m3u8</li>
<li>hls+ http(s)://ip/hls2/<%:Stream name%>.m3u8</li>
<li>http(s)-ts http(s)://ip/ts/<%:Stream name%></li>
</ul>
</div>
</div>
</fieldset>
</fieldset>
<script type="text/javascript">//<![CDATA[
var nginx_status = document.getElementById('_nginx_status');
XHR.poll(3, '<%=url([[admin]], [[nas]], [[pingos]], [[status]])%>', null,
function(x, json) {
if (x && x.status == 200) {
if (nginx_status) {
var str = "";
if (json.status) {
document.getElementById("tips").innerHTML = '<%:If you need external network access, please open the port by yourself.%>';
str = '<font color="green"><%:RUNNING%> ✓</font><input type="button" class="cbi-button cbi-input-apply" value="<%:Enter interface%>" onclick="open_web()" />'
} else {
document.getElementById("tips").innerHTML = '';
str = '<font color="red"><%:NOT RUNNING%> X</font>';
}
nginx_status.innerHTML = str;
}
}
}
);
function open_web(){
var port = '<%=luci.sys.exec("uci -q get pingos.@global[0].http_port"):gsub("^%s*(.-)%s*$", "%1")%>';
var ishttps = '<%=luci.sys.exec("uci -q get pingos.@global[0].https"):gsub("^%s*(.-)%s*$", "%1")%>';
var protocol = (ishttps == "1") ? "https://" : "http://";
var hostname = location.hostname;
var url = protocol + hostname + ":" + port;
window.open(url, 'target', '');
}
//]]></script>

View File

@ -0,0 +1,24 @@
Copyright (C) 2016-2020, by Jie Wu "AlexWoo" <wj19840501@gmail.com>.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

View File

@ -0,0 +1,19 @@
ngx_addon_name=ngx_client_module
CORE_MODULES="$CORE_MODULES \
ngx_client_module \
ngx_http_client_module \
"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
$ngx_addon_dir/ngx_client.c \
$ngx_addon_dir/ngx_http_client.c \
"
NGX_ADDON_DEPS="$NGX_ADDON_DEPS \
$ngx_addon_dir/ngx_client.h \
$ngx_addon_dir/ngx_http_client.h \
"
CFLAGS="$CFLAGS -I $ngx_addon_dir"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,165 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#ifndef _NGX_CLIENT_H_INCLUDED_
#define _NGX_CLIENT_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_http.h>
typedef struct ngx_client_session_s ngx_client_session_t;
typedef void (* ngx_client_connect_pt)(ngx_client_session_t *s);
typedef void (* ngx_client_recv_pt)(ngx_client_session_t *s);
typedef void (* ngx_client_send_pt)(ngx_client_session_t *s);
typedef void (* ngx_client_closed_pt)(ngx_client_session_t *s);
struct ngx_client_session_s {
ngx_peer_connection_t peer;
ngx_str_t server; /* server original address */
in_port_t port; /* server port */
ngx_connection_t *connection;
ngx_pool_t *pool;
ngx_log_t log;
void *data; /* save ctx for callback */
ngx_chain_t *out; /* save data unsend */
/* configured part */
/* timer for connecting to server */
ngx_msec_t connect_timeout;
/* timer for sending buffer full */
ngx_msec_t send_timeout;
/*
* data will be postponed until nginx has at least
* postpone_output bytes of data to send
*/
size_t postpone_output;
/* use dynamic resolver mechanism for resolving domain */
unsigned dynamic_resolver:1;
unsigned tcp_nodelay:1; /* TCP_NODELAY */
unsigned tcp_nopush:1; /* TCP_CORK */
/* runtime part */
size_t recv; /* client recv bytes */
unsigned connected:1; /* client connected to server */
unsigned closed:1; /* client has been closed */
ngx_event_t close; /* for async close */
/* callback */
ngx_client_connect_pt client_connected; /* connect successd */
ngx_client_recv_pt client_recv; /* recv msg from peer */
ngx_client_send_pt client_send; /* send msg to peer */
ngx_client_closed_pt client_closed; /* finalize connection */
};
/*
* create a client session
*
* return value:
* return client session for successd, return NULL for failed
* paras:
* peer: server address and port, address could be domain or ip
* local: set if need to bind local address, or set NULL
* udp: set 1, use udp, set 0, use tcp
* log: for logging error when create client session failed
*/
ngx_client_session_t *ngx_client_create(ngx_str_t *peer, ngx_str_t *local,
ngx_flag_t udp, ngx_log_t *log);
/*
* connect to client server, should use client session created by
* ngx_client_create. before connect to server, user can set paras in
* configured part.
*
* return value:
* void
* paras:
* s: client session created by ngx_client_create
*/
void ngx_client_connect(ngx_client_session_t *s);
/*
* send data to server
*
* return value:
* NGX_ERROR: write error, client session will be closed
* NGX_AGAIN: data not sent completely, it will save in client session out
* NGX_OK: data sent completely
* paras:
* s: client session
* out: data for sending
*/
ngx_int_t ngx_client_write(ngx_client_session_t *s, ngx_chain_t *out);
/*
* read data from server
*
* return value:
* NGX_ERROR: read error, client session will be closed
* NGX_DECLINED: buf for receiving data is full
* NGX_AGAIN: no data for reading
* 0: server closed
* >0: bytes read into buffer
* paras:
* s: client session
* b: buffer for receiving data
*/
ngx_int_t ngx_client_read(ngx_client_session_t *s, ngx_buf_t *b);
/*
* keepalive client connection, and destroy session
* if use client connect the same ip:port,
* new client session will reuse the connection
*
* return value:
* void
* paras:
* s: client session
*/
void ngx_client_set_keepalive(ngx_client_session_t *s);
/*
* close client session
*
* return value:
* void
* paras:
* s: client session
*/
void ngx_client_close(ngx_client_session_t *s);
/*
* paras:
* r: http request to query status of client
*/
ngx_chain_t *ngx_client_state(ngx_http_request_t *r, unsigned detail);
#endif

View File

@ -0,0 +1,323 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#ifndef _NGX_HTTP_CLIENT_H_INCLUDE_
#define _NGX_HTTP_CLIENT_H_INCLUDE_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include "ngx_client.h"
#include "ngx_toolkit_misc.h"
// http client method
#define NGX_HTTP_CLIENT_GET 0
#define NGX_HTTP_CLIENT_HEAD 1
#define NGX_HTTP_CLIENT_POST 2
#define NGX_HTTP_CLIENT_PUT 3
#define NGX_HTTP_CLIENT_DELETE 4
#define NGX_HTTP_CLIENT_MKCOL 5
#define NGX_HTTP_CLIENT_COPY 6
#define NGX_HTTP_CLIENT_MOVE 7
#define NGX_HTTP_CLIENT_OPTIONS 8
#define NGX_HTTP_CLIENT_PROPFIND 9
#define NGX_HTTP_CLIENT_PROPPATCH 10
#define NGX_HTTP_CLIENT_LOCK 11
#define NGX_HTTP_CLIENT_UNLOCK 12
#define NGX_HTTP_CLIENT_PATCH 13
#define NGX_HTTP_CLIENT_TRACE 14
// http client version
#define NGX_HTTP_CLIENT_VERSION_9 0
#define NGX_HTTP_CLIENT_VERSION_10 1
#define NGX_HTTP_CLIENT_VERSION_11 2
#define NGX_HTTP_CLIENT_VERSION_20 3
// http client opt
#define NGX_HTTP_CLIENT_OPT_CONNECT_TIMEOUT 0
#define NGX_HTTP_CLIENT_OPT_SEND_TIMEOUT 1
#define NGX_HTTP_CLIENT_OPT_POSTPONE_OUTPUT 2
#define NGX_HTTP_CLIENT_OPT_DYNAMIC_RESOLVER 3
#define NGX_HTTP_CLIENT_OPT_TCP_NODELAY 4
#define NGX_HTTP_CLIENT_OPT_TCP_NOPUSH 5
#define NGX_HTTP_CLIENT_OPT_HEADER_TIMEOUT 6
typedef void (* ngx_http_client_handler_pt)(void *r, ngx_http_request_t *hcr);
/* create and set http request */
/*
* create a http request for sending to server
*
* return value:
* return http request for successd, return NULL for failed
*
* paras:
* log: error in create will use this log
* method: http client method
* url: full request url like "http://test.com/index.html?hello=world"
* headers: http request header for sending
* send_body: callback for sending body
* request: who send http request
*/
ngx_http_request_t *ngx_http_client_create(ngx_log_t *log,
ngx_uint_t method, ngx_str_t *url, ngx_keyval_t *headers,
ngx_http_client_handler_pt send_body, void *request);
/*
* add cleanup as ngx_http_cleanup_add
*/
ngx_http_cleanup_t *ngx_http_client_cleanup_add(ngx_http_request_t *r,
size_t size);
/*
* set read handler for http client, should set before send request,
* otherwise body from server will discard
*
* return value:
* void
*
* paras:
* r: http client request
* read_handler: handler for setting
*/
void ngx_http_client_set_read_handler(ngx_http_request_t *r,
ngx_http_client_handler_pt read_handler);
/*
* set http headers
*
* return value:
* NGX_OK for successd, NGX_ERROR for failed
*
* paras:
* r: http client request
* headers: headers set into r
* if value is not null, will set or modify the header
* if value is null string, will delete the header
*/
ngx_int_t ngx_http_client_set_headers(ngx_http_request_t *r,
ngx_keyval_t *headers);
/*
* set write handler for http client, if set,
* will use this handler for sending body,
*
* return value:
* void
*
* paras:
* r: http client request
* write_handler: handler for setting
*/
void ngx_http_client_set_write_handler(ngx_http_request_t *r,
ngx_http_client_handler_pt write_handler);
/*
* set write handler for http client, if set,
* will use this handler for sending body,
*
* return value:
* void
*
* paras:
* r: http client request
* write_handler: handler for setting
*/
void ngx_http_client_set_version(ngx_http_request_t *r, ngx_uint_t version);
/*
* set http client option
*
* return value:
* void
*
* paras:
* r: http client request
* opt: http client opt
* NGX_HTTP_CLIENT_OPT_CONNECT_TIMEOUT:
* connect server timer
* NGX_HTTP_CLIENT_OPT_SEND_TIMEOUT:
* send data timer when buffer full
* NGX_HTTP_CLIENT_OPT_POSTPONE_OUTPUT:
* size threshold to send
* NGX_HTTP_CLIENT_OPT_DYNAMIC_RESOLVER:
* whether use dynamic resolver to resolv domain
* NGX_HTTP_CLIENT_OPT_TCP_NODELAY:
* whether set TCP_NODELAY
* NGX_HTTP_CLIENT_OPT_TCP_NOPUSH:
* whether set TCP_CORK
* NGX_HTTP_CLIENT_OPT_HEADER_TIMEOUT:
* timer for waiting response header from server
* value: http client opt value want to set
*/
void ngx_http_client_setopt(ngx_http_request_t *r, unsigned opt,
ngx_uint_t value);
/* send http request */
/*
* send http request
*
* return value:
* NGX_OK for successd, NGX_ERROR for failed
*
* paras:
* r: http request for seding, create by ngx_http_client_create
*/
ngx_int_t ngx_http_client_send(ngx_http_request_t *r);
/*
* create and send http GET request to server
*
* return value:
* return http request for successd, return NULL for failed
*
* paras:
* log: error in create will use this log
* url: full request url like "http://test.com/index.html?hello=world"
* headers: http request header for sending
* request: who send http request
*/
ngx_http_request_t *ngx_http_client_get(ngx_log_t *log, ngx_str_t *url,
ngx_keyval_t *headers, void *request);
/*
* create and send http HEAD request to server
*
* return value:
* return http request for successd, return NULL for failed
*
* paras:
* log: error in create will use this log
* url: full request url like "http://test.com/index.html?hello=world"
* headers: http request header for sending
* request: who send http request
*/
ngx_http_request_t *ngx_http_client_head(ngx_log_t *log, ngx_str_t *url,
ngx_keyval_t *headers, void *request);
/*
* create and send http POST request to server
*
* return value:
* return http request for successd, return NULL for failed
*
* paras:
* log: error in create will use this log
* url: full request url like "http://test.com/index.html?hello=world"
* headers: http request header for sending
* send_body: callback for sending body
* request: who send http request
*/
ngx_http_request_t *ngx_http_client_post(ngx_log_t *log, ngx_str_t *url,
ngx_keyval_t *headers, ngx_http_client_handler_pt send_body, void *request);
/* get response */
/*
* get http response version
*
* return value:
* http response version
*
* paras:
* r: http client request
*/
ngx_uint_t ngx_http_client_http_version(ngx_http_request_t *r);
/*
* get http response status code
*
* return value:
* http response status code like 200, 500
*
* paras:
* r: http client request
*/
ngx_uint_t ngx_http_client_status_code(ngx_http_request_t *r);
/*
* get http response header's value
*
* return value:
* http response header's value
*
* paras:
* r: http client request
* key: http header like "Host", "Content-Type"
*/
ngx_str_t *ngx_http_client_header_in(ngx_http_request_t *r, ngx_str_t *key);
/*
* read http response body
*
* return value:
* NGX_AGAIN: read part of data
* 0: tcp connection disconnect, need finalize request with 1
* NGX_ERROR: tcp connection error disconnect, need finalize request with 1
* NGX_DONE: response body has been read, could finalize request with 0
*
* paras:
* r: http client request
* in: where read data put
*/
ngx_int_t ngx_http_client_read_body(ngx_http_request_t *r, ngx_chain_t **in);
/*
* get receive bytes
*
* return value:
* bytes receive from server
*
* paras:
* r: http client request
*/
off_t ngx_http_client_rbytes(ngx_http_request_t *r);
/*
* get send bytes
*
* return value:
* bytes send to server
*
* paras:
* r: http client request
*/
off_t ngx_http_client_wbytes(ngx_http_request_t *r);
/* end request */
/*
* detach http client request with it's creator,
* all read and write handler will not be triggered
*
* return value:
* bytes send to server
*
* paras:
* r: http client request
*/
void ngx_http_client_detach(ngx_http_request_t *r);
/*
* finalize http client request
*
* return value:
* void
*
* paras:
* r: http client request
* closed: set to 1, will close connection to server
* set to 0, will keep connection to server alive
*/
void ngx_http_client_finalize_request(ngx_http_request_t *r, ngx_flag_t closed);
#endif

View File

@ -0,0 +1,13 @@
ngx_addon_name=ngx_client_test_module
HTTP_MODULES="$HTTP_MODULES \
ngx_client_test_module \
ngx_http_client_test_module \
ngx_client_stat_module \
"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS
$ngx_addon_dir/ngx_client_test_module.c \
$ngx_addon_dir/ngx_http_client_test_module.c \
$ngx_addon_dir/ngx_client_stat_module.c \
"

View File

@ -0,0 +1,57 @@
user root;
worker_processes 4;
#error_log logs/error.log;
#error_log logs/error.log notice;
error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
resolver 192.168.84.254;
dynamic_refresh_interval 5m;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
location /client_test {
client_test;
}
location /http_client_test {
http_client_test;
}
location /client_stat {
client_stat;
}
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_http.h>
#include "ngx_client.h"
#include "ngx_rbuf.h"
#include "ngx_poold.h"
#include "ngx_timerd.h"
#include "ngx_event_timer_module.h"
#include "ngx_event_resolver.h"
#include "ngx_dynamic_resolver.h"
static char *ngx_client_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_command_t ngx_client_stat_commands[] = {
{ ngx_string("client_stat"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_client_stat,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_client_stat_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
ngx_module_t ngx_client_stat_module = {
NGX_MODULE_V1,
&ngx_client_stat_module_ctx, /* module context */
ngx_client_stat_commands, /* module directives */
NGX_HTTP_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 ngx_int_t
ngx_client_stat_handler(ngx_http_request_t *r)
{
ngx_chain_t **ll, *out;
ngx_buf_t *b;
size_t len;
r->headers_out.status = NGX_HTTP_OK;
ngx_http_send_header(r);
ll = &out;
len = sizeof("--------------------------------------------------\n") - 1
+ sizeof("ngx_worker: ngx_process_slot: pid: \n") - 1
+ 3 * NGX_OFF_T_LEN;
*ll = ngx_alloc_chain_link(r->pool);
if (*ll == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
(*ll)->next = NULL;
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
(*ll)->buf = b;
b->last = ngx_snprintf(b->last, len,
"--------------------------------------------------\n"
"ngx_worker: %i ngx_process_slot: %i pid: %i\n",
ngx_worker, ngx_process_slot, ngx_pid);
if (*ll) {
ll = &(*ll)->next;
}
*ll = ngx_rbuf_state(r, 1);
if (*ll) {
ll = &(*ll)->next;
}
*ll = ngx_event_timer_state(r);
if (*ll) {
ll = &(*ll)->next;
}
*ll = ngx_event_resolver_state(r);
if (*ll) {
ll = &(*ll)->next;
}
*ll = ngx_dynamic_resolver_state(r);
if (*ll) {
ll = &(*ll)->next;
}
*ll = ngx_poold_state(r, 1);
if (*ll) {
ll = &(*ll)->next;
}
*ll = ngx_timerd_state(r, 1);
if (*ll) {
ll = &(*ll)->next;
}
*ll = ngx_client_state(r, 1);
(*ll)->buf->last_buf = 1;
return ngx_http_output_filter(r, out);
}
static char *
ngx_client_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_client_stat_handler;
return NGX_CONF_OK;
}

View File

@ -0,0 +1,188 @@
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include "ngx_client.h"
static char *ngx_client_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_command_t ngx_client_test_commands[] = {
{ ngx_string("client_test"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_client_test,
0,
0,
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_client_test_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
ngx_module_t ngx_client_test_module = {
NGX_MODULE_V1,
&ngx_client_test_module_ctx, /* module context */
ngx_client_test_commands, /* module directives */
NGX_HTTP_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_client_test_connected(ngx_client_session_t *s)
{
ngx_buf_t *b;
size_t len;
ngx_chain_t out;
ngx_http_request_t *r;
ngx_event_t *wev;
ngx_log_error(NGX_LOG_ERR, &s->log, 0, "client connected");
r = s->data;
wev = s->peer.connection->write;
len = sizeof("nginx client test\n") - 1;
b = ngx_create_temp_buf(s->pool, len);
if (b == NULL) {
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
b->last = ngx_copy(b->last, "nginx client test\n", len);
b->last_buf = 1;
out.buf = b;
out.next = NULL;
ngx_client_write(s, &out);
ngx_handle_write_event(wev, 0);
}
static void
ngx_client_test_recv(ngx_client_session_t *s)
{
ngx_buf_t *b;
ngx_int_t n;
ngx_connection_t *c;
ngx_str_t recv;
ngx_http_request_t *r;
c = s->peer.connection;
r = s->data;
b = ngx_create_temp_buf(s->pool, 4096);
if (b == NULL) {
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
n = c->recv(c, b->pos, b->end - b->last);
if (n == NGX_AGAIN) {
ngx_log_error(NGX_LOG_ERR, &s->log, 0, "client recv NGX_AGAIN");
return;
}
if (n == NGX_ERROR || n == 0) {
ngx_log_error(NGX_LOG_ERR, &s->log, 0, "client recv NGX_ERROR");
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
ngx_client_close(s);
return;
}
b->last += n;
recv.data = b->pos;
recv.len = b->last - b->pos;
ngx_log_error(NGX_LOG_ERR, &s->log, 0, "client recv %d: %V, %z",
n, &recv, recv.len);
ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN);
ngx_client_set_keepalive(s);
return;
}
static void
ngx_client_test_send(ngx_client_session_t *s)
{
ngx_log_error(NGX_LOG_ERR, &s->log, 0, "client send");
}
static void
ngx_client_test_closed(ngx_client_session_t *s)
{
ngx_log_error(NGX_LOG_ERR, &s->log, 0, "client closed");
}
static ngx_int_t
ngx_client_test_handler(ngx_http_request_t *r)
{
ngx_client_session_t *s;
ngx_str_t echo;
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "client test handler");
if (ngx_http_arg(r, (u_char *) "echo", sizeof("echo") - 1, &echo)
!= NGX_OK)
{
return NGX_HTTP_BAD_REQUEST;
}
s = ngx_client_create(&echo, NULL, 0, r->connection->log);
if (s == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
//ci->dynamic_resolver = 0;
//ci->recvbuf = 4096;
s->client_connected = ngx_client_test_connected;
s->client_recv = ngx_client_test_recv;
s->client_send = ngx_client_test_send;
s->client_closed = ngx_client_test_closed;
s->data = r;
ngx_client_connect(s);
++r->count;
return NGX_DONE;
}
static char *
ngx_client_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_client_test_handler;
return NGX_CONF_OK;
}

View File

@ -0,0 +1,203 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include "ngx_http_client.h"
#include "ngx_rbuf.h"
static char *ngx_http_client_test(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static ngx_command_t ngx_http_client_test_commands[] = {
{ ngx_string("http_client_test"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_client_test,
0,
0,
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_http_client_test_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
ngx_module_t ngx_http_client_test_module = {
NGX_MODULE_V1,
&ngx_http_client_test_module_ctx, /* module context */
ngx_http_client_test_commands, /* module directives */
NGX_HTTP_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_http_client_test_recv_body(void *request, ngx_http_request_t *hcr)
{
ngx_http_request_t *r;
ngx_chain_t *cl = NULL;
ngx_chain_t **ll;
ngx_int_t rc;
r = request;
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"http client test recv body");
rc = ngx_http_client_read_body(hcr, &cl);
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"http client test recv body, rc %i %i, %O",
rc, ngx_errno, ngx_http_client_rbytes(hcr));
if (rc == 0) {
goto done;
}
if (rc == NGX_ERROR) {
goto done;
}
if (rc == NGX_DONE) {
for (ll = &cl; (*ll)->next; ll = &(*ll)->next);
(*ll)->buf->last_buf = 1;
}
ngx_http_output_filter(r, cl);
ngx_http_run_posted_requests(r->connection);
if (rc == NGX_AGAIN) {
return;
}
if (rc == NGX_DONE) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"all body has been read");
ngx_http_client_finalize_request(hcr, 0);
}
done:
ngx_http_finalize_request(r, NGX_OK);
}
static void
ngx_http_client_test_recv(void *request, ngx_http_request_t *hcr)
{
ngx_http_request_t *r;
static ngx_str_t content_type = ngx_string("Content-Type");
static ngx_str_t connection = ngx_string("Connection");
static ngx_str_t unknown = ngx_string("Unknown");
ngx_str_t *ct, *con;
r = request;
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"http client test recv, connection: %p", hcr->connection);
r->headers_out.status = 200;
ngx_http_client_set_read_handler(hcr, ngx_http_client_test_recv_body);
ngx_http_send_header(r);
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"status_code: %ui http_version: %ui",
ngx_http_client_status_code(hcr),
ngx_http_client_http_version(hcr));
ct = ngx_http_client_header_in(hcr, &content_type);
con = ngx_http_client_header_in(hcr, &connection);
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "Content-Type: %V", ct);
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "Connection: %V", con);
if (ngx_http_client_header_in(hcr, &unknown) == NULL) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "no header Unknown");
}
ngx_http_client_test_recv_body(request, hcr);
}
static ngx_int_t
ngx_http_client_test_handler(ngx_http_request_t *r)
{
ngx_http_request_t *hcr;
static ngx_str_t request_url = ngx_string("http://101.200.241.232/");
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"http client test handler");
// Default header Host, User-Agent, Connection(below HTTP/1.1), Accept, Date
hcr = ngx_http_client_create(r->connection->log, NGX_HTTP_CLIENT_GET,
&request_url, NULL, NULL, r);
// add Connection, delete Date, Modify Host, add new header
ngx_str_t value;
value.data = (u_char *) "World";
value.len = sizeof("World") - 1;
ngx_keyval_t headers[] = {
{ ngx_string("Host"), ngx_string("www.test.com") },
{ ngx_string("Connection"), ngx_string("upgrade") },
{ ngx_string("Date"), ngx_null_string },
{ ngx_string("Hello"), value },
{ ngx_null_string, ngx_null_string } // must end with null str
};
ngx_http_client_set_headers(hcr, headers);
ngx_http_client_set_read_handler(hcr, ngx_http_client_test_recv);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"http client test before send");
ngx_http_client_send(hcr);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"http client test after send");
// ngx_http_client_detach(hcr);
// return NGX_HTTP_FORBIDDEN;
++r->count;
return NGX_DONE;
}
static char *
ngx_http_client_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_http_client_test_handler;
return NGX_CONF_OK;
}

View File

@ -0,0 +1,41 @@
package main
import (
"fmt"
"net"
)
func handleConnection(c net.Conn) {
b := make([]byte, 4096)
for {
n, err := c.Read(b)
if err != nil {
fmt.Print("Read Error ", err)
c.Close()
return
}
fmt.Print("recv ", n, " data:", string(b))
c.Write(b[0:n])
}
}
func main() {
ln, err := net.Listen("tcp", ":10000")
if err != nil {
fmt.Print("Listen Error ", err)
return
}
for {
conn, err := ln.Accept()
if err != nil {
fmt.Print("Accept Error ", err)
continue
}
go handleConnection(conn)
}
}

View File

@ -0,0 +1,24 @@
Copyright (C) 2016-2020, by Jie Wu "AlexWoo" <wj19840501@gmail.com>.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

View File

@ -0,0 +1,181 @@
# Module nginx-multiport-module
---
## Instructions
Every worker process can bind own port, user can visit specific worker process by using the port.
- [ngx-stream-zone-module](doc/ngx-stream-zone-module.md)
Record stream's owner worker process slot
- [ngx-http-broadcast-module](doc/ngx-http-broadcast-module.md)
Broadcast HTTP request to all worker processes when receive HTTP request
## Directives
### multi\_listen
Syntax : multi_listen multiport relationport;
Default : None;
Context : events
multiport can configured as below:
address:port
port
unix:path
when configured with IPv4 or IPv6 port, worker process listen port plus with worker process's slot. For Example, we start four workers, add configured multiport with 9000. worker 0 will listen 9000, worker 1 will listen 9001, worker 2 will listen 9002, worker 3 will listen 9003
when configured with unix path, worker will listen path plus with suffix of worker process's slot. For Example, we start four workers, add configured multiport with unix:/tmp/http. worker 0 will listen /tmp/http.0, worker 1 will listen /tmp/http.1, worker 2 will listen /tmp/http.2, worker 3 will listen /tmp/http.3
relationport must configured same as listen directives in http server, rtmp server, stream server or other server
### inner\_proxy
Syntax : inner_proxy multiport uri;
Default : None;
Context : http, server, location
- multiport: configured in multi_listen
- uri: uri for inner_proxy, configured as below
location /multiport_test/ {
inner_proxy unix:/tmp/http.sock.80 /inner_proxy;
multiport_test;
}
location /inner_proxy/ {
rewrite ^/inner_proxy/(.*):/(.*) /$2 break;
proxy_pass http://$1:;
}
As example above, if send subrequest to process whose workerid is 0, the uri will change to /inner_proxy/unix:/tmp/http.sock.80.0:/multiport_test/xxx
proxy_pass will send current request to process 0 as inner proxy request.
## API
- ngx\_multiport\_get\_port
ngx_int_t ngx_event_multiport_get_port(ngx_pool_t *pool, ngx_str_t *port, ngx_str_t *multiport, ngx_int_t pslot);
- para:
pool: pool for port memory alloc
port: process real listen port while process\_slot is pslot
multiport: port configure for processes, format as below:
port only: port
IPv4: host:port host must be ipaddr of IPv4 or *
IPv6: [host]:port host must be ipaddr of IPv6
Unix: unix:/path
pslot: process\_slot, process\_slot of other worker process can get through ngx\_process\_slot\_get\_slot
- return value:
NGX\_OK for successd, NGX\_ERROR for failed
- ngx\_multiport\_get\_slot
ngx_int_t ngx_multiport_get_slot(ngx_uint_t wpid);
- para:
wpid: worker process id, 0 to ccf->worker_processes - 1
- return value:
ngx_process_slot for successd, NGX_ERROR for failed
- ngx\_http\_inner\_proxy\_request
ngx_int_t ngx_http_inner_proxy_request(ngx_http_request_t *r, ngx_int_t pslot);
send a inner proxy request to specific process, must use with directives inner\_proxy
- paras:
- r: http request for send inner request to sibling worker
- pslot: sibling worker ngx_process_slot
- return values:
- NGX_OK: for successd
- NGX_ERROR: for failed
- NGX_DECLINED: for not configured or send inner proxy to self
## Build
cd to NGINX source directory & run this:
./configure --add-module=/path/to/nginx-multiport-module/
make && make install
## Example
See t/ngx\_http\_process\_slot\_test\_module.c as reference
**Build**:
./configure --with-debug --with-ipv6 --add-module=/path/to/nginx-multiport-module/t/ --add-module=/path/to/nginx-multiport-module/ --add-module=/path/to/echo-nginx-module/
make && make install
**Configure**:
worker_processes 4;
events {
...
multi_listen 9000 80;
multi_listen unix:/tmp/http.sock.80 80;
}
http {
...
server {
...
location /multiport_test/ {
inner_proxy unix:/tmp/http.sock.80 /inner_proxy;
multiport_test;
}
location /inner_proxy/ {
rewrite ^/inner_proxy/(.*):/(.*) /$2 break;
proxy_pass http://$1:;
}
}
}
**Test for API**:
$ curl http://192.168.84.254/multiport_test/123
TEST cases 19, 19 pass
If request send to worker1 to worker3, the request will proxy to worker 0. will get log as below:
2017/10/14 20:45:44 [error] 20065#0: *6 multiport test handler, client: 192.168.84.1, server: localhost, request: "GET /multiport_test/123 HTTP/1.1", host: "192.168.84.254:9003"
2017/10/14 20:45:44 [error] 20065#0: *6 inner proxy return 0, client: 192.168.84.1, server: localhost, request: "GET /multiport_test/123 HTTP/1.1", host: "192.168.84.254:9003"
2017/10/14 20:45:44 [error] 20062#0: *8 multiport test handler, client: unix:, server: localhost, request: "GET //multiport_test/123 HTTP/1.0", host: "localhost"
**Test for multiport**:
curl -v http://127.0.0.1/
curl -v http://127.0.0.1:9000/
curl -v http://127.0.0.1:9001/
curl -v http://127.0.0.1:9002/
curl -v http://127.0.0.1:9003/
curl -v --unix-socket /tmp/http.sock.80.0 http:/
curl -v --unix-socket /tmp/http.sock.80.1 http:/
curl -v --unix-socket /tmp/http.sock.80.2 http:/
curl -v --unix-socket /tmp/http.sock.80.3 http:/
Tests will get the same result, for port 9000 will always send to worker process 0, 9001 to worker process 1 and so on

View File

@ -0,0 +1,31 @@
ngx_addon_name=ngx_multiport_module
EVENT_MODULES="$EVENT_MODULES \
ngx_event_multiport_module \
"
CORE_MODULES="$CORE_MODULES \
ngx_process_slot_module \
ngx_stream_zone_module \
"
HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES \
ngx_http_broadcast_module \
ngx_http_inner_proxy_module \
"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
$ngx_addon_dir/ngx_multiport_misc.c \
$ngx_addon_dir/ngx_event_multiport_module.c \
$ngx_addon_dir/ngx_process_slot_module.c \
$ngx_addon_dir/ngx_stream_zone_module.c \
$ngx_addon_dir/ngx_http_broadcast_module.c \
$ngx_addon_dir/ngx_http_inner_proxy_module.c \
"
NGX_ADDON_DEPS="$NGX_ADDON_DEPS \
$ngx_addon_dir/ngx_multiport.h \
$ngx_addon_dir/ngx_stream_zone_module.h \
"
CFLAGS="$CFLAGS -I $ngx_addon_dir"

View File

@ -0,0 +1,77 @@
# ngx-http-broadcast-module
---
## Instructions
Broadcast HTTP request to all worker processes when receive HTTP request
## Directives
### broadcast
Syntax : broadcast multiport uri;
Default : None;
Context : location
- multiport is multi_listen port configured in event
- uri is http proxy_pass uri configured as below
location /auth_proxy/ {
rewrite ^/auth_proxy/(.*) /auth break;
proxy_pass http://$1:;
}
## Build
cd to NGINX source directory & run this:
./configure --add-module=/path/to/nginx-multiport-module/
make && make install
## Example
**Build**:
./configure --with-debug --with-ipv6 --add-module=/path/to/nginx-multiport-module/t/ --add-module=/path/to/nginx-multiport-module/ --add-module=/path/to/echo-nginx-module/
make && make install
**Configure**:
events {
...
multi_listen unix:/tmp/http.sock.80 80;
}
http {
...
server {
listen 80;
server_name localhost;
...
location / {
broadcast unix:/tmp/http.sock.80 /auth_proxy;
}
location /auth_proxy/ {
rewrite ^/auth_proxy/(.*) /auth break;
proxy_pass http://$1:;
}
location /auth {
# return 403;
echo "auth";
echo $scheme://$host$uri?$args;
}
}
}
**Test**:
curl -v 'http://192.168.84.254/aa?a=b&c=d'
curl will get all response content if worker not return non 200 response

View File

@ -0,0 +1,92 @@
# ngx-stream-zone-module
---
## Instructions
Record stream's owner worker process slot
## Directives
### stream\_zone
Syntax : stream_zone buckets=$nbuckets streams=$nstreams;
Default : None;
Context : main
nbuckets is hash buckect number, nstreams is max streams system can store
nbuckets is recommended use a prime number
## API
**header file**
For using this API, You should include the header file as below:
#include "ngx_stream_zone_module.h"
**ngx\_stream\_zone\_insert\_stream**
ngx_int_t ngx_stream_zone_insert_stream(ngx_str_t *name);
- para:
name: stream name
- return value:
process\_slot for owner of stream, NGX\_ERROR for error
**ngx\_stream\_zone\_delete\_stream**
void ngx_stream_zone_delete_stream(ngx_str_t *name);
- para:
name: stream name
**ngx\_stream\_zone\_state**
ngx_chain_t *ngx_stream_zone_state(ngx_http_request_t *r, ngx_flag_t detail);
- para:
- r: http request to query status of rbuf
- detail: print stream detail in log
- return value:
chain of stream zone state for returning to http client
## Build
cd to NGINX source directory & run this:
./configure --add-module=/path/to/nginx-multiport-module/
make && make install
## Example
See t/ngx\_stream\_zone\_test\_module.c as reference
**Build**:
./configure --with-debug --with-ipv6 --add-module=/path/to/nginx-multiport-module/t/ --add-module=/path/to/nginx-multiport-module/
make && make install
**Configure**:
stream_zone buckets=10007 streams=10000;
**Test**:
curl -XPOST -v "http://127.0.0.1:9001/stream_zone_test/ab?stream=test"
curl -XPOST -v "http://127.0.0.1:9002/stream_zone_test/ab?stream=test1"
curl -XPOST -v "http://127.0.0.1:9003/stream_zone_test/ab?stream=test2"
curl -XPOST -v "http://127.0.0.1:9003/stream_zone_test/ab?stream=test"
curl -XDELETE -v "http://127.0.0.1:9000/stream_zone_test/ab?stream=test3"
curl -XDELETE -v "http://127.0.0.1:9002/stream_zone_test/ab?stream=test1"
curl -XDELETE -v "http://127.0.0.1:9001/stream_zone_test/ab?stream=test2"
curl -XGET -v "http://127.0.0.1:9001/stream_zone_test/ab"

View File

@ -0,0 +1,263 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include "ngx_multiport.h"
typedef struct {
ngx_str_t multiport;
ngx_str_t uri;
} ngx_http_broadcast_conf_t;
typedef struct {
ngx_int_t workerid;
ngx_http_request_t *sr;
} ngx_http_broadcast_ctx_t;
static ngx_int_t ngx_http_broadcast_filter_init(ngx_conf_t *cf);
static void *ngx_http_broadcast_create_conf(ngx_conf_t *cf);
static char *ngx_http_broadcast_merge_conf(ngx_conf_t *cf,
void *parent, void *child);
static char *ngx_http_broadcast(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static ngx_command_t ngx_http_broadcast_commands[] = {
{ ngx_string("broadcast"),
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
ngx_http_broadcast,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_http_broadcast_module_ctx = {
NULL, /* preconfiguration */
ngx_http_broadcast_filter_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_broadcast_create_conf, /* create location configuration */
ngx_http_broadcast_merge_conf /* merge location configuration */
};
ngx_module_t ngx_http_broadcast_module = {
NGX_MODULE_V1,
&ngx_http_broadcast_module_ctx, /* module context */
ngx_http_broadcast_commands, /* module directives */
NGX_HTTP_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 ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
static void *
ngx_http_broadcast_create_conf(ngx_conf_t *cf)
{
ngx_http_broadcast_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_broadcast_conf_t));
if (conf == NULL) {
return NULL;
}
return conf;
}
static char *
ngx_http_broadcast_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_broadcast_conf_t *prev = parent;
ngx_http_broadcast_conf_t *conf = child;
ngx_conf_merge_str_value(conf->multiport, prev->multiport, "");
ngx_conf_merge_str_value(conf->uri, prev->uri, "");
return NGX_CONF_OK;
}
static char *
ngx_http_broadcast(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_broadcast_conf_t *hbcf;
ngx_str_t *value;
hbcf = conf;
if (hbcf->multiport.data != NULL) {
return "is duplicate";
}
value = cf->args->elts;
hbcf->multiport = value[1];
hbcf->uri = value[2];
return NGX_CONF_OK;
}
static ngx_int_t
ngx_http_broadcast_header_filter(ngx_http_request_t *r)
{
ngx_http_broadcast_conf_t *hbcf;
hbcf = ngx_http_get_module_loc_conf(r, ngx_http_broadcast_module);
if (hbcf == NULL || hbcf->multiport.len == 0) { /* not configured */
goto next;
}
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"broadcast header filter, r:%p r->main:%p, %ui, %O",
r, r->main, r->headers_out.status, r->headers_out.content_length_n);
r->headers_out.status = NGX_HTTP_OK;
ngx_http_clear_content_length(r);
ngx_http_clear_accept_ranges(r);
next:
return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_broadcast_send_subrequest(ngx_http_request_t *r, ngx_int_t pslot)
{
ngx_http_broadcast_conf_t *hbcf;
ngx_str_t uri;
ngx_str_t port;
ngx_http_request_t *sr;
ngx_int_t rc;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"broadcast send subrequest to %i", pslot);
hbcf = ngx_http_get_module_loc_conf(r, ngx_http_broadcast_module);
if (ngx_multiport_get_port(r->pool, &port, &hbcf->multiport, pslot)
== NGX_ERROR)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"broadcast get port error, %V %i", &hbcf->multiport, pslot);
return NGX_ERROR;
}
uri.len = hbcf->uri.len + 1 + port.len;
uri.data = ngx_pcalloc(r->pool, uri.len);
ngx_snprintf(uri.data, uri.len, "%V/%V", &hbcf->uri, &port);
rc = ngx_http_subrequest(r, &uri, &r->args, &sr, NULL, 0);
sr->method = r->method;
sr->method_name = r->method_name;
return rc;
}
static ngx_int_t
ngx_http_broadcast_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_http_broadcast_conf_t *hbcf;
ngx_http_broadcast_ctx_t *ctx;
ngx_core_conf_t *ccf;
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t cl;
hbcf = ngx_http_get_module_loc_conf(r->main, ngx_http_broadcast_module);
if (hbcf == NULL || hbcf->multiport.len == 0) { /* not configured */
return ngx_http_next_body_filter(r, in);
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"broadcast body filter, r:%p r->main:%p", r, r->main);
if (r != r->main) { /* send subrequest */
if (r->headers_out.status != NGX_HTTP_OK) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"broadcast subrequest send non 200 response: %i",
r->headers_out.status);
return NGX_OK;
}
return ngx_http_next_body_filter(r, in);
}
ctx = ngx_http_get_module_ctx(r, ngx_http_broadcast_module);
if (ctx == NULL) {
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_broadcast_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_http_set_ctx(r, ctx, ngx_http_broadcast_module);
}
/* send to all process */
ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_core_module);
while (ctx->workerid < ccf->worker_processes) {
rc = ngx_http_broadcast_send_subrequest(r,
ngx_multiport_get_slot(ctx->workerid));
++ctx->workerid;
return rc;
}
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b->last_buf = 1;
cl.buf = b;
cl.next = NULL;
return ngx_http_next_body_filter(r, &cl);
}
static ngx_int_t
ngx_http_broadcast_filter_init(ngx_conf_t *cf)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_broadcast_header_filter;
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_broadcast_body_filter;
return NGX_OK;
}

View File

@ -0,0 +1,254 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include "ngx_multiport.h"
typedef struct {
ngx_str_t multiport;
ngx_str_t uri;
} ngx_http_inner_proxy_conf_t;
typedef struct {
ngx_str_t port;
ngx_flag_t last;
} ngx_http_inner_proxy_ctx_t;
static ngx_int_t ngx_http_inner_proxy_filter_init(ngx_conf_t *cf);
static void *ngx_http_inner_proxy_create_conf(ngx_conf_t *cf);
static char *ngx_http_inner_proxy_merge_conf(ngx_conf_t *cf,
void *parent, void *child);
static char *ngx_http_inner_proxy(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static ngx_command_t ngx_http_inner_proxy_commands[] = {
{ ngx_string("inner_proxy"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
ngx_http_inner_proxy,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_http_inner_proxy_module_ctx = {
NULL, /* preconfiguration */
ngx_http_inner_proxy_filter_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_inner_proxy_create_conf, /* create location configuration */
ngx_http_inner_proxy_merge_conf /* merge location configuration */
};
ngx_module_t ngx_http_inner_proxy_module = {
NGX_MODULE_V1,
&ngx_http_inner_proxy_module_ctx, /* module context */
ngx_http_inner_proxy_commands, /* module directives */
NGX_HTTP_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 ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
static void *
ngx_http_inner_proxy_create_conf(ngx_conf_t *cf)
{
ngx_http_inner_proxy_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_inner_proxy_conf_t));
if (conf == NULL) {
return NULL;
}
return conf;
}
static char *
ngx_http_inner_proxy_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_inner_proxy_conf_t *prev = parent;
ngx_http_inner_proxy_conf_t *conf = child;
ngx_conf_merge_str_value(conf->multiport, prev->multiport, "");
ngx_conf_merge_str_value(conf->uri, prev->uri, "");
return NGX_CONF_OK;
}
static char *
ngx_http_inner_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_inner_proxy_conf_t *hipcf;
ngx_str_t *value;
hipcf = conf;
if (hipcf->multiport.data != NULL) {
return "is duplicate";
}
value = cf->args->elts;
hipcf->multiport = value[1];
hipcf->uri = value[2];
return NGX_CONF_OK;
}
static ngx_int_t
ngx_http_inner_proxy_header_filter(ngx_http_request_t *r)
{
ngx_http_inner_proxy_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r->main, ngx_http_inner_proxy_module);
if (ctx == NULL) { /* not configured */
return ngx_http_next_header_filter(r);
}
if (r == r->main) {
return NGX_OK;
}
r->main->headers_out = r->headers_out;
return ngx_http_next_header_filter(r->main);
}
static ngx_int_t
ngx_http_inner_proxy_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_http_inner_proxy_ctx_t *ctx;
ngx_chain_t *cl, l;
ngx_buf_t *b;
ctx = ngx_http_get_module_ctx(r->main, ngx_http_inner_proxy_module);
if (ctx == NULL) { /* not configured */
return ngx_http_next_body_filter(r, in);
}
if (r == r->main) {
if (ctx->last == 0) {
return NGX_OK;
}
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b->last_buf = 1;
l.buf = b;
l.next = NULL;
return ngx_http_next_body_filter(r, &l);
}
for (cl = in; cl; cl = cl->next) {
if (cl->buf->last_in_chain) {
ctx->last = 1;
}
}
return ngx_http_next_body_filter(r, in);
}
static ngx_int_t
ngx_http_inner_proxy_filter_init(ngx_conf_t *cf)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_inner_proxy_header_filter;
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_inner_proxy_body_filter;
return NGX_OK;
}
ngx_int_t
ngx_http_inner_proxy_request(ngx_http_request_t *r, ngx_int_t pslot)
{
ngx_http_inner_proxy_conf_t *hipcf;
ngx_http_inner_proxy_ctx_t *ctx;
ngx_http_request_t *sr;
ngx_str_t uri;
ngx_int_t rc;
hipcf = ngx_http_get_module_loc_conf(r, ngx_http_inner_proxy_module);
if (hipcf == NULL || hipcf->multiport.len == 0) { /* not configured */
return NGX_DECLINED;
}
if (pslot == ngx_process_slot) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"inner proxy send request to self: %i", ngx_process_slot);
return NGX_DECLINED;
}
ctx = ngx_http_get_module_ctx(r, ngx_http_inner_proxy_module);
if (ctx) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"inner proxy has been called in this request");
return NGX_ERROR;
}
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_inner_proxy_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_http_set_ctx(r, ctx, ngx_http_inner_proxy_module);
if (ngx_multiport_get_port(r->pool, &ctx->port, &hipcf->multiport, pslot)
== NGX_ERROR)
{
return NGX_ERROR;
}
uri.len = hipcf->uri.len + 1 + ctx->port.len + 2 + r->uri.len;
uri.data = ngx_pcalloc(r->pool, uri.len);
ngx_snprintf(uri.data, uri.len, "%V/%V:/%V",
&hipcf->uri, &ctx->port, &r->uri);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"inner proxy send request to %V", &ctx->port);
rc = ngx_http_subrequest(r, &uri, &r->args, &sr, NULL, 0);
sr->method = r->method;
sr->method_name = r->method_name;
return rc;
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#ifndef _NGX_MULTIPORT_H_INCLUDED_
#define _NGX_MULTIPORT_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
/*
* return value:
* NGX_OK for success, NGX_ERROR for failed
* paras:
* pool: pool for port memory alloc
* port: process real listen port while process_slot is pslot
* multiport: port configure for processes, format as below:
* port only: port
* IPv4: host:port host must be ipaddr of IPv4 or *
* IPv6: [host]:port host must be ipaddr of IPv6
* Unix: unix:/path
* pslot: process_slot
*/
ngx_int_t ngx_multiport_get_port(ngx_pool_t *pool, ngx_str_t *port,
ngx_str_t *multiport, ngx_int_t pslot);
/*
* return value:
* ngx_process_slot for successd, NGX_ERROR for failed
* paras:
* wpid: worker process id, 0 to ccf->worker_processes - 1
*/
ngx_int_t ngx_multiport_get_slot(ngx_uint_t wpid);
/*
* return value:
* NGX_OK : for successd
* NGX_ERROR : for failed
* NGX_DECLINED: for not configured or send inner proxy to self
* paras:
* r : http request for send inner request to sibling worker
* pslot: sibling worker ngx_process_slot
*/
ngx_int_t ngx_http_inner_proxy_request(ngx_http_request_t *r, ngx_int_t pslot);
#endif

View File

@ -0,0 +1,165 @@
#include <ngx_config.h>
#include <ngx_core.h>
static ngx_int_t
ngx_multiport_get_port_unix(ngx_pool_t *pool, ngx_str_t *port,
ngx_str_t *multiport, ngx_int_t pslot)
{
#if (NGX_HAVE_UNIX_DOMAIN)
u_char *p;
size_t len;
len = multiport->len + 5; /* unix:/path -> unix:/path.127\0 */
port->data = ngx_pcalloc(pool, len);
if (port->data == NULL) {
return NGX_ERROR;
}
p = ngx_snprintf(port->data, len, "%V.%i", multiport, pslot);
*p = 0;
port->len = p - port->data;
return NGX_OK;
#else
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"the unix domain sockets not support");
return NGX_ERROR;
#endif
}
static ngx_int_t
ngx_multiport_get_port_inet6(ngx_pool_t *pool, ngx_str_t *port,
ngx_str_t *multiport, ngx_int_t pslot)
{
#if (NGX_HAVE_INET6)
u_char *p, *last;
ngx_str_t addr;
size_t len;
ngx_int_t n;
last = multiport->data + multiport->len;
p = ngx_strlchr(multiport->data, last, ']');
if (p == NULL) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "invalid INET6 host");
return NGX_ERROR;
}
++p;
if (p == last || *p != ':') {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "no INET6 port");
return NGX_ERROR;
}
++p;
addr.data = multiport->data;
addr.len = p - multiport->data;
len = last - p;
n = ngx_atoi(p, len);
/* 65408 + 127 = 65535, pslot in [0, 127] */
if (n < 1 || n + pslot > 65408) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "invalid INET6 port");
return NGX_ERROR;
}
n += pslot;
len = multiport->len + 3; /* [::]:1 -> [::]:128\0 */
port->data = ngx_pcalloc(pool, len);
if (port->data == NULL) {
return NGX_ERROR;
}
p = port->data;
p = ngx_snprintf(p, len, "%V%i", &addr, n);
port->len = p - port->data;
return NGX_OK;
#else
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"the INET6 sockets not support");
return NGX_ERROR;
#endif
}
static ngx_int_t
ngx_multiport_get_port_inet(ngx_pool_t *pool, ngx_str_t *port,
ngx_str_t *multiport, ngx_int_t pslot)
{
u_char *p, *last;
ngx_str_t addr;
size_t len;
ngx_int_t n;
last = multiport->data + multiport->len;
p = ngx_strlchr(multiport->data, last, ':');
if (p == NULL) { /* port */
p = multiport->data;
addr.len = 0;
} else { /* host:port */
++p;
if (p == last) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "no port");
return NGX_ERROR;
}
addr.data = multiport->data;
addr.len = p - multiport->data;
}
len = last - p;
n = ngx_atoi(p, len);
/* 65408 + 127 = 65535, pslot in [0, 127] */
if (n < 1 || n + pslot > 65408) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "invalid port");
return NGX_ERROR;
}
n += pslot;
len = multiport->len + 3; /* 127.0.0.1:1 -> 127.0.0.1:128\0 */
port->data = ngx_pcalloc(pool, len);
if (port->data == NULL) {
return NGX_ERROR;
}
p = port->data;
if (addr.len == 0) {
p = ngx_snprintf(p, len, "%i", n);
} else {
p = ngx_snprintf(p, len, "%V%i", &addr, n);
}
port->len = p - port->data;
return NGX_OK;
}
ngx_int_t
ngx_multiport_get_port(ngx_pool_t *pool, ngx_str_t *port,
ngx_str_t *multiport, ngx_int_t pslot)
{
u_char *p;
size_t len;
p = multiport->data;
len = multiport->len;
if (pslot < 0 || pslot > 127) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "invalid pslot: %i",
pslot);
return NGX_ERROR;
}
if (len >= 5 && ngx_strncasecmp(p, (u_char *) "unix:", 5) == 0) {
return ngx_multiport_get_port_unix(pool, port, multiport, pslot);
}
if (len && p[0] == '[') {
return ngx_multiport_get_port_inet6(pool, port, multiport, pslot);
}
return ngx_multiport_get_port_inet(pool, port, multiport, pslot);
}

View File

@ -0,0 +1,167 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include <ngx_config.h>
#include <ngx_core.h>
static ngx_int_t ngx_process_slot_module_init(ngx_cycle_t *cycle);
static ngx_int_t ngx_process_slot_process_init(ngx_cycle_t *cycle);
static void ngx_process_slot_process_exit(ngx_cycle_t *cycle);
static void *ngx_process_slot_module_create_conf(ngx_cycle_t *cycle);
static char *ngx_process_slot_module_init_conf(ngx_cycle_t *cycle, void *conf);
#define MAX_PROCESSES 128
typedef struct {
ngx_atomic_int_t process_slot[MAX_PROCESSES];
} ngx_process_slot_ctx_t;
typedef struct {
ngx_process_slot_ctx_t *ctx;
} ngx_process_slot_conf_t;
static ngx_command_t ngx_process_slot_commands[] = {
ngx_null_command
};
static ngx_core_module_t ngx_process_slot_module_ctx = {
ngx_string("process_slot"),
ngx_process_slot_module_create_conf,
ngx_process_slot_module_init_conf
};
ngx_module_t ngx_process_slot_module = {
NGX_MODULE_V1,
&ngx_process_slot_module_ctx, /* module context */
ngx_process_slot_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
ngx_process_slot_module_init, /* init module */
ngx_process_slot_process_init, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
ngx_process_slot_process_exit, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static void *
ngx_process_slot_module_create_conf(ngx_cycle_t *cycle)
{
ngx_process_slot_conf_t *pscf;
pscf = ngx_palloc(cycle->pool, sizeof(ngx_process_slot_conf_t));
if (pscf == NULL) {
return NULL;
}
return pscf;
}
static char *
ngx_process_slot_module_init_conf(ngx_cycle_t *cycle, void *conf)
{
return NGX_CONF_OK;
}
static ngx_int_t
ngx_process_slot_module_init(ngx_cycle_t *cycle)
{
ngx_process_slot_conf_t *pscf;
ngx_shm_t shm;
ngx_uint_t i;
pscf = (ngx_process_slot_conf_t *) ngx_get_conf(cycle->conf_ctx,
ngx_process_slot_module);
shm.size = sizeof(ngx_process_slot_ctx_t);
shm.name.len = sizeof("process_slot_zone") - 1;
shm.name.data = (u_char *) "process_slot_zone";
shm.log = cycle->log;
if (ngx_shm_alloc(&shm) != NGX_OK) {
return NGX_ERROR;
}
pscf->ctx = (ngx_process_slot_ctx_t *) shm.addr;
for (i = 0; i < MAX_PROCESSES; ++i) {
pscf->ctx->process_slot[i] = -1;
}
return NGX_OK;
}
static ngx_int_t
ngx_process_slot_process_init(ngx_cycle_t *cycle)
{
ngx_process_slot_conf_t *pscf;
ngx_process_slot_ctx_t *ctx;
if (ngx_process != NGX_PROCESS_WORKER) {
return NGX_OK;
}
pscf = (ngx_process_slot_conf_t *) ngx_get_conf(cycle->conf_ctx,
ngx_process_slot_module);
ctx = pscf->ctx;
for (;;) {
if (ngx_atomic_cmp_set((ngx_atomic_t *) &ctx->process_slot[ngx_worker],
(ngx_atomic_uint_t)ctx->process_slot[ngx_worker], ngx_process_slot))
{
break;
}
}
return NGX_OK;
}
static void
ngx_process_slot_process_exit(ngx_cycle_t *cycle)
{
ngx_process_slot_conf_t *pscf;
ngx_process_slot_ctx_t *ctx;
if (ngx_process != NGX_PROCESS_WORKER) {
return;
}
pscf = (ngx_process_slot_conf_t *) ngx_get_conf(cycle->conf_ctx,
ngx_process_slot_module);
ctx = pscf->ctx;
ngx_atomic_cmp_set((ngx_atomic_t *) &ctx->process_slot[ngx_worker],
(ngx_atomic_uint_t)ngx_process_slot, -1);
}
ngx_int_t
ngx_multiport_get_slot(ngx_uint_t wpid)
{
ngx_process_slot_conf_t *pscf;
ngx_process_slot_ctx_t *ctx;
ngx_core_conf_t *ccf;
ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_core_module);
if (wpid >= (ngx_uint_t)ccf->worker_processes) {
return -1;
}
pscf = (ngx_process_slot_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_process_slot_module);
ctx = pscf->ctx;
return ctx->process_slot[wpid];
}

View File

@ -0,0 +1,551 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct ngx_stream_zone_hash_s ngx_stream_zone_hash_t;
typedef struct ngx_stream_zone_node_s ngx_stream_zone_node_t;
typedef struct ngx_stream_zone_conf_s ngx_stream_zone_conf_t;
static ngx_int_t
ngx_stream_zone_init_process(ngx_cycle_t *cycle);
static void
ngx_stream_zone_exit_process(ngx_cycle_t *cycle);
static void *
ngx_stream_zone_create_conf(ngx_cycle_t *cf);
static char *
ngx_stream_zone_init_conf(ngx_cycle_t *cycle, void *conf);
static char *
ngx_stream_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *
ngx_stream_zone_shm_init(ngx_shm_t *shm, ngx_stream_zone_conf_t *szcf,
ngx_cycle_t *cycle);
#define NAME_LEN 1024
static ngx_str_t stream_zone_key = ngx_string("stream_zone");
struct ngx_stream_zone_node_s {
u_char name[NAME_LEN];
ngx_int_t slot; /* process slot */
ngx_int_t idx;
ngx_int_t next; /* idx of stream node */
};
struct ngx_stream_zone_hash_s {
ngx_shmtx_t mutex;
ngx_shmtx_sh_t lock;
ngx_int_t node; /* idx of stream node */
};
struct ngx_stream_zone_conf_s {
ngx_int_t nbuckets;
ngx_int_t nstreams;
ngx_pool_t *pool;
ngx_shmtx_t *mutex;
ngx_shmtx_sh_t *lock;
ngx_stream_zone_hash_t *hash; /* hash in shm */
ngx_stream_zone_node_t *stream_node;/* node in shm */
ngx_int_t *free_node; /* free node chain */
ngx_int_t *alloc; /* node number in use*/
};
static ngx_command_t ngx_stream_zone_commands[] = {
{ ngx_string("stream_zone"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE2,
ngx_stream_zone,
0,
0,
NULL },
ngx_null_command
};
static ngx_core_module_t ngx_stream_zone_module_ctx = {
ngx_string("rtmp_stream_zone"),
ngx_stream_zone_create_conf, /* create conf */
ngx_stream_zone_init_conf /* init conf */
};
ngx_module_t ngx_stream_zone_module = {
NGX_MODULE_V1,
&ngx_stream_zone_module_ctx, /* module context */
ngx_stream_zone_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
ngx_stream_zone_init_process, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
ngx_stream_zone_exit_process, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_stream_zone_node_t *
ngx_stream_zone_get_node(ngx_str_t *name, ngx_int_t pslot)
{
ngx_stream_zone_conf_t *szcf;
ngx_stream_zone_node_t *node;
szcf = (ngx_stream_zone_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_stream_zone_module);
ngx_shmtx_lock(szcf->mutex);
if (*szcf->free_node == -1) {
ngx_shmtx_unlock(szcf->mutex);
return NULL;
}
node = &szcf->stream_node[*szcf->free_node];
*szcf->free_node = node->next;
*ngx_copy(node->name, name->data, ngx_min(NAME_LEN - 1, name->len)) = '\0';
node->slot = pslot;
node->next = -1;
++*szcf->alloc;
ngx_shmtx_unlock(szcf->mutex);
return node;
}
static void
ngx_stream_zone_put_node(ngx_int_t idx)
{
ngx_stream_zone_conf_t *szcf;
ngx_stream_zone_node_t *node;
szcf = (ngx_stream_zone_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_stream_zone_module);
ngx_shmtx_lock(szcf->mutex);
node = &szcf->stream_node[idx];
node->next = *szcf->free_node;
*szcf->free_node = idx;
--*szcf->alloc;
ngx_shmtx_unlock(szcf->mutex);
}
static void *
ngx_stream_zone_create_conf(ngx_cycle_t *cf)
{
ngx_stream_zone_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_zone_conf_t));
if (conf == NULL) {
return NULL;
}
conf->nbuckets = NGX_CONF_UNSET;
conf->nstreams = NGX_CONF_UNSET;
conf->pool = ngx_create_pool(4096, cf->log);
return conf;
}
static char *
ngx_stream_zone_init_conf(ngx_cycle_t *cycle, void *conf)
{
size_t len;
ngx_shm_t shm;
ngx_stream_zone_conf_t *szcf = conf;
ngx_conf_init_value(szcf->nbuckets, 512);
ngx_conf_init_value(szcf->nstreams, 40960);
/* create shm zone */
len = sizeof(ngx_shmtx_t) + sizeof(ngx_shmtx_sh_t)
+ sizeof(ngx_stream_zone_hash_t) * szcf->nbuckets
+ sizeof(ngx_stream_zone_node_t) * szcf->nstreams
+ sizeof(ngx_int_t) + sizeof(ngx_int_t);
shm.size = len;
shm.name = stream_zone_key;
shm.log = cycle->log;
if (ngx_shm_alloc(&shm) != NGX_OK) {
return NGX_CONF_ERROR;
}
return ngx_stream_zone_shm_init(&shm, szcf, cycle);
}
static void
ngx_stream_zone_clear(ngx_cycle_t *cycle)
{
ngx_stream_zone_conf_t *szcf;
volatile ngx_int_t idx, cur, next;
szcf = (ngx_stream_zone_conf_t *) ngx_get_conf(cycle->conf_ctx,
ngx_stream_zone_module);
if (szcf->nbuckets <= 0 || szcf->nstreams <= 0) {
return;
}
for (idx = 0; idx < szcf->nbuckets; ++idx) {
ngx_shmtx_lock(&szcf->hash[idx].mutex);
cur = -1;
while (1) {
if (cur == -1) {
next = szcf->hash[idx].node;
} else {
next = szcf->stream_node[cur].next;
}
if (next == -1) {
break;
}
if (szcf->stream_node[next].slot == ngx_process_slot) {
if (cur == -1) {
szcf->hash[idx].node = szcf->stream_node[next].next;
} else {
szcf->stream_node[cur].next = szcf->stream_node[next].next;
}
ngx_stream_zone_put_node(next);
continue;
}
cur = next;
}
ngx_shmtx_unlock(&szcf->hash[idx].mutex);
}
}
static ngx_int_t
ngx_stream_zone_init_process(ngx_cycle_t *cycle)
{
ngx_stream_zone_clear(cycle);
return NGX_OK;
}
static void
ngx_stream_zone_exit_process(ngx_cycle_t *cycle)
{
ngx_stream_zone_clear(cycle);
}
static char *
ngx_stream_zone_shm_init(ngx_shm_t *shm, ngx_stream_zone_conf_t *szcf,
ngx_cycle_t *cycle)
{
u_char *p;
ngx_int_t i, next;
p = shm->addr;
szcf->mutex = (ngx_shmtx_t *) p;
p += sizeof(ngx_shmtx_t);
szcf->lock = (ngx_shmtx_sh_t *) p;
p += sizeof(ngx_shmtx_sh_t);
szcf->hash = (ngx_stream_zone_hash_t *) p;
p += sizeof(ngx_stream_zone_hash_t) * szcf->nbuckets;
szcf->stream_node = (ngx_stream_zone_node_t *) p;
p += sizeof(ngx_stream_zone_node_t) * szcf->nstreams;
szcf->free_node = (ngx_int_t *) p;
p += sizeof(ngx_int_t);
szcf->alloc = (ngx_int_t *) p;
/* init shm zone */
#if (NGX_HAVE_ATOMIC_OPS)
p = NULL;
#else
p = ngx_pnalloc(szcf->pool, cycle->lock_file.len
+ stream_zone_key.len);
if (p == NULL) {
return NGX_CONF_ERROR;
}
*ngx_sprintf(p, "%V%V", &cycle->lock_file, &stream_zone_key) = 0;
#endif
if (ngx_shmtx_create(szcf->mutex, szcf->lock, p) != NGX_OK) {
return NGX_CONF_ERROR;
}
for (i = 0; i < szcf->nbuckets; ++i) {
#if (NGX_HAVE_ATOMIC_OPS)
p = NULL;
#else
p = ngx_pnalloc(szcf->pool, cycle->lock_file.len + stream_zone_key.len
+ NGX_INT32_LEN);
if (p == NULL) {
return NGX_CONF_ERROR;
}
*ngx_sprintf(p, "%V%V%d", &cycle->lock_file,
&stream_zone_key, i) = 0;
#endif
if (ngx_shmtx_create(&szcf->hash[i].mutex, &szcf->hash[i].lock, p)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
szcf->hash[i].node = -1;
}
next = -1;
i = szcf->nstreams;
do {
--i;
szcf->stream_node[i].slot = -1;
szcf->stream_node[i].idx = i;
szcf->stream_node[i].next = next;
next = i;
} while (i);
*szcf->free_node = i;
*szcf->alloc = 0;
return NGX_CONF_OK;
}
static char *
ngx_stream_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_uint_t i;
ngx_str_t *value;
ngx_stream_zone_conf_t *szcf = conf;
value = cf->args->elts;
for (i = 1; i < cf->args->nelts; ++i) {
if (ngx_strncmp(value[i].data, "buckets=", 8) == 0) {
szcf->nbuckets = ngx_atoi(value[i].data + 8, value[i].len - 8);
if (szcf->nbuckets <= 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid buckets \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
continue;
}
if (ngx_strncmp(value[i].data, "streams=", 8) == 0) {
szcf->nstreams = ngx_atoi(value[i].data + 8, value[i].len - 8);
if (szcf->nstreams <= 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid streams \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
continue;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
ngx_int_t
ngx_stream_zone_insert_stream(ngx_str_t *name)
{
ngx_stream_zone_conf_t *szcf;
volatile ngx_uint_t idx;
volatile ngx_int_t i, pslot;
ngx_stream_zone_node_t *node;
szcf = (ngx_stream_zone_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_stream_zone_module);
if (szcf->nbuckets <= 0 || szcf->nstreams <= 0) {
return NGX_ERROR;
}
if (name->len >= NAME_LEN) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"stream name(%V) too long", name);
return NGX_ERROR;
}
idx = ngx_hash_key(name->data, name->len) % szcf->nbuckets;
ngx_shmtx_lock(&szcf->hash[idx].mutex);
i = szcf->hash[idx].node;
pslot = -1;
while (i != -1) {
if (ngx_strlen(szcf->stream_node[i].name) == name->len
&& ngx_memcmp(szcf->stream_node[i].name, name->data, name->len)
== 0)
{
pslot = szcf->stream_node[i].slot;
break;
}
i = szcf->stream_node[i].next;
}
if (i == -1) { /* stream not in hash */
node = ngx_stream_zone_get_node(name, ngx_process_slot);
if (node == NULL) {
ngx_shmtx_unlock(&szcf->hash[idx].mutex);
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"stream zone get node failed");
return NGX_ERROR;
}
node->slot = ngx_process_slot;
node->next = szcf->hash[idx].node;
szcf->hash[idx].node = node->idx;
pslot = ngx_process_slot;
}
ngx_shmtx_unlock(&szcf->hash[idx].mutex);
return pslot;
}
void
ngx_stream_zone_delete_stream(ngx_str_t *name)
{
ngx_stream_zone_conf_t *szcf;
volatile ngx_uint_t idx;
volatile ngx_int_t cur, next;
szcf = (ngx_stream_zone_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_stream_zone_module);
if (szcf->nbuckets <= 0 || szcf->nstreams <= 0) {
return;
}
idx = ngx_hash_key(name->data, name->len) % szcf->nbuckets;
ngx_shmtx_lock(&szcf->hash[idx].mutex);
cur = -1;
next = szcf->hash[idx].node;
while (next != -1) {
if (ngx_strlen(szcf->stream_node[next].name) == name->len
&& ngx_memcmp(szcf->stream_node[next].name, name->data, name->len)
== 0)
{
if (szcf->stream_node[next].slot != ngx_process_slot) {
break;
}
if (cur == -1) { /* link header */
szcf->hash[idx].node = szcf->stream_node[next].next;
} else {
szcf->stream_node[cur].next = szcf->stream_node[next].next;
}
ngx_stream_zone_put_node(next);
break;
}
cur = next;
next = szcf->stream_node[next].next;
}
ngx_shmtx_unlock(&szcf->hash[idx].mutex);
}
ngx_chain_t *
ngx_stream_zone_state(ngx_http_request_t *r, ngx_flag_t detail)
{
ngx_stream_zone_conf_t *szcf;
ngx_chain_t *cl;
ngx_buf_t *b;
size_t len;
volatile ngx_int_t idx, next;
szcf = (ngx_stream_zone_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_stream_zone_module);
if (szcf->nbuckets <= 0 || szcf->nstreams <= 0) {
return NULL;
}
len = sizeof("##########stream zone state##########\n") - 1
+ sizeof("ngx_stream_zone buckets: \n") - 1 + NGX_OFF_T_LEN
+ sizeof("ngx_stream_zone streams: \n") - 1 + NGX_OFF_T_LEN
+ sizeof("ngx_stream_zone alloc: \n") - 1 + NGX_OFF_T_LEN;
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NULL;
}
cl->next = NULL;
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NULL;
}
cl->buf = b;
b->last = ngx_snprintf(b->last, len,
"##########stream zone state##########\n"
"ngx_stream_zone buckets: %i\nngx_stream_zone streams: %i\n"
"ngx_stream_zone alloc: %i\n",
szcf->nbuckets, szcf->nstreams, *szcf->alloc);
if (detail) {
for (idx = 0; idx < szcf->nbuckets; ++idx) {
ngx_shmtx_lock(&szcf->hash[idx].mutex);
next = szcf->hash[idx].node;
if (next == -1) {
ngx_shmtx_unlock(&szcf->hash[idx].mutex);
continue;
}
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "slot: %i", idx);
while (next != -1) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"\t\tname:%s, slot:%i, idx:%i, next:%i",
szcf->stream_node[next].name,
szcf->stream_node[next].slot,
szcf->stream_node[next].idx,
szcf->stream_node[next].next);
next = szcf->stream_node[next].next;
}
ngx_shmtx_unlock(&szcf->hash[idx].mutex);
}
}
return cl;
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#ifndef _NGX_STREAM_ZONE_MODULE_H_INCLUDED_
#define _NGX_STREAM_ZONE_MODULE_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
/*
* return value:
* process_slot for owner of stream, NGX_ERROR for error
* name:
* stream name
*/
ngx_int_t ngx_stream_zone_insert_stream(ngx_str_t *name);
/*
* name:
* stream name
*/
void ngx_stream_zone_delete_stream(ngx_str_t *name);
/*
* return value:
* chain of stream zone state for returning to http client
* paras:
* r: http request to query status of rbuf
* detail: print stream detail in log
*/
ngx_chain_t *ngx_stream_zone_state(ngx_http_request_t *r, ngx_flag_t detail);
#endif

View File

@ -0,0 +1,12 @@
ngx_addon_name=ngx_multiport_test_module
HTTP_MODULES="$HTTP_MODULES \
ngx_stream_zone_test_module \
ngx_multiport_test_module \
"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS
$ngx_addon_dir/ngx_stream_zone_test_module.c \
$ngx_addon_dir/ngx_multiport_test_module.c \
"

View File

@ -0,0 +1,71 @@
user root;
worker_processes 4;
#error_log logs/error.log;
#error_log logs/error.log notice;
error_log logs/error.log info;
#pid logs/nginx.pid;
stream_zone buckets=10007 streams=10000;
events {
worker_connections 1024;
multi_listen 9000 80;
multi_listen unix:/tmp/http.sock.80 80;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
location /stream_zone_test/ {
stream_zone_test;
}
location /multiport_test/ {
inner_proxy unix:/tmp/http.sock.80 /inner_proxy;
multiport_test;
}
location /inner_proxy/ {
rewrite ^/inner_proxy/(.*):/(.*) /$2 break;
proxy_pass http://$1:;
}
location / {
broadcast unix:/tmp/http.sock.80 /auth_proxy;
}
location /auth_proxy/ {
rewrite ^/auth_proxy/(.*) /auth break;
proxy_pass http://$1:;
}
location /auth {
# return 403;
echo "auth";
echo $scheme://$host$uri?$args;
}
}
}

View File

@ -0,0 +1,168 @@
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include "ngx_multiport.h"
#include "ngx_test_macro.h"
static char *ngx_multiport_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_command_t ngx_multiport_test_commands[] = {
{ ngx_string("multiport_test"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_multiport_test,
0,
0,
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_multiport_test_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
ngx_module_t ngx_multiport_test_module = {
NGX_MODULE_V1,
&ngx_multiport_test_module_ctx, /* module context */
ngx_multiport_test_commands, /* module directives */
NGX_HTTP_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
};
ngx_int_t
ngx_multiport_test_get_port(ngx_http_request_t *r, char *multiport,
ngx_int_t pslot, char *expect)
{
ngx_str_t port;
ngx_int_t rc;
ngx_str_t mp;
mp.data = (u_char *) multiport;
mp.len = ngx_strlen(multiport);
ngx_memzero(&port, sizeof(ngx_str_t));
rc = ngx_multiport_get_port(r->pool, &port, &mp, pslot);
if (port.len) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "port: %V, %s, %d",
&port, port.data, port.len);
}
if (expect == NULL && rc == NGX_ERROR) {
return 1;
} else {
return ngx_test_str(&port, expect);
}
return 0;
}
static ngx_int_t
ngx_multiport_test_handler(ngx_http_request_t *r)
{
ngx_buf_t *b;
ngx_chain_t cl;
size_t len;
ngx_int_t rc;
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"multiport test handler");
rc = ngx_http_inner_proxy_request(r, ngx_multiport_get_slot(0));
if (rc != NGX_DECLINED) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"inner proxy return %i", rc);
return rc;
}
NGX_TEST_INIT
/* test ngx_multiport_get_port */
/* normal format */
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "10", 127, "137"));
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "127.0.0.1:55635", 4,
"127.0.0.1:55639"));
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "[::127.0.0.1]:1024", 0,
"[::127.0.0.1]:1024"));
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "unix:/tmp.test", 7,
"unix:/tmp.test.7"));
/* inet6 format error */
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "[::127.0.0.1:1024", 0, NULL));
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "[::127.0.0.1]:abcd",
0, NULL));
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "[::127.0.0.1]:65409",
0, NULL));
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "[::127.0.0.1]:", 0, NULL));
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "[::127.0.0.1]", 0, NULL));
/* inet format error */
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "127.0.0.1:", 4, NULL));
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "abcd", 4, NULL));
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "65410", 4, NULL));
/* pslot error */
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "65410", -1, NULL));
NGX_TEST_ISOK(ngx_multiport_test_get_port(r, "65410", 128, NULL));
/* test ngx_multiport_get_slot */
NGX_TEST_ISOK(ngx_multiport_get_slot(4) == -1);
NGX_TEST_ISOK(ngx_multiport_get_slot(0) == 0);
NGX_TEST_ISOK(ngx_multiport_get_slot(1) == 1);
NGX_TEST_ISOK(ngx_multiport_get_slot(2) == 2);
NGX_TEST_ISOK(ngx_multiport_get_slot(3) == 3);
r->headers_out.status = NGX_HTTP_OK;
ngx_http_send_header(r);
len = sizeof("pslot: %i TEST cases 4294967296, 4294967296 pass\n") - 1;
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b->last = ngx_snprintf(b->last, len, "pslot: %i TEST cases %d, %d pass\n",
ngx_process_slot, count, pass);
b->last_buf = 1;
b->last_in_chain = 1;
cl.buf = b;
cl.next = NULL;
return ngx_http_output_filter(r, &cl);
}
static char *
ngx_multiport_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_multiport_test_handler;
return NGX_CONF_OK;
}

View File

@ -0,0 +1,134 @@
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include "ngx_stream_zone_module.h"
static char *ngx_stream_zone_test(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static ngx_command_t ngx_stream_zone_test_commands[] = {
{ ngx_string("stream_zone_test"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_stream_zone_test,
0,
0,
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_stream_zone_test_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
ngx_module_t ngx_stream_zone_test_module = {
NGX_MODULE_V1,
&ngx_stream_zone_test_module_ctx, /* module context */
ngx_stream_zone_test_commands, /* module directives */
NGX_HTTP_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 ngx_int_t
ngx_stream_zone_test_handler(ngx_http_request_t *r)
{
ngx_chain_t cl, *out;
ngx_buf_t *b;
size_t len;
ngx_str_t stream;
ngx_int_t rc;
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "stream zone test handler");
rc = -1;
if (r->method == NGX_HTTP_GET) {
out = ngx_stream_zone_state(r, 1);
out->buf->last_buf = 1;
out->buf->last_in_chain = 1;
r->headers_out.status = NGX_HTTP_OK;
ngx_http_send_header(r);
return ngx_http_output_filter(r, out);
} else if (r->method == NGX_HTTP_DELETE) {
if (ngx_http_arg(r, (u_char *) "stream", 6, &stream) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"stream zone test, no stream in http args");
return NGX_HTTP_BAD_REQUEST;
}
len = sizeof("delete stream=\n") - 1 + stream.len;
ngx_stream_zone_delete_stream(&stream);
} else if (r->method == NGX_HTTP_POST) {
if (ngx_http_arg(r, (u_char *) "stream", 6, &stream) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"stream zone test, no stream in http args");
return NGX_HTTP_BAD_REQUEST;
}
len = sizeof("stream= in process:4294967296\n") - 1 + stream.len;
rc = ngx_stream_zone_insert_stream(&stream);
} else {
return NGX_HTTP_BAD_REQUEST;
}
r->headers_out.status = NGX_HTTP_OK;
ngx_http_send_header(r);
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (r->method == NGX_HTTP_DELETE) {
b->last = ngx_snprintf(b->last, len, "delete stream=%V\n", &stream);
} else {
b->last = ngx_snprintf(b->last, len,
"stream=%V in process:%i\n", &stream, rc);
}
b->last_buf = 1;
b->last_in_chain = 1;
cl.buf = b;
cl.next = NULL;
return ngx_http_output_filter(r, &cl);
}
static char *
ngx_stream_zone_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_stream_zone_test_handler;
return NGX_CONF_OK;
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#ifndef _NGX_TEST_MACRO_H_INCLUDE_
#define _NGX_TEST_MACRO_H_INCLUDE_
#include <ngx_config.h>
#include <ngx_core.h>
static ngx_int_t count = 0;
static ngx_int_t pass = 0;
#define NGX_TEST_INIT count = 0, pass = 0;
#define NGX_TEST_ISOK(testcase) \
{ \
ngx_int_t __ret = testcase; \
++count; \
if (__ret) ++pass; \
ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, " TEST "#testcase"%s",\
(__ret ? " ...OK" : " ...ERROR")); \
}
#define NGX_TEST_INT(di, si) \
(di == si)
static ngx_inline ngx_int_t
ngx_test_str(ngx_str_t *nstr, char *cstr)
{
size_t len;
len = ngx_strlen(cstr);
return (nstr->len == len && ngx_memcmp(nstr->data, cstr, len) == 0);
}
#endif

View File

@ -0,0 +1,9 @@
Project author:
Roman Arutyunyan
Moscow, Russia, Pingo
Contacts:
arut@qip.ru
arutyunyan.roman@gmail.com
cczjp89@gmail.com

View File

@ -0,0 +1,22 @@
Copyright (c) 2012-2014, Roman Arutyunyan
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,169 @@
ngx_addon_name="ngx_rtmp_module"
RTMP_CORE_MODULES=" \
ngx_live_module \
ngx_rtmp_module \
ngx_rtmp_dynamic_module \
ngx_rtmp_dynamic_core_module \
ngx_rtmp_core_module \
ngx_rtmp_cmd_module \
ngx_rtmp_codec_module \
ngx_rtmp_access_module \
ngx_rtmp_live_module \
ngx_live_record_module \
ngx_live_relay_module \
ngx_live_relay_rtmp_module \
ngx_live_relay_static_module \
ngx_live_relay_simple_module \
ngx_rtmp_exec_module \
ngx_rtmp_notify_module \
ngx_live_relay_inner_module \
ngx_rtmp_log_module \
ngx_rtmp_limit_module \
ngx_rtmp_hls_module \
ngx_rtmp_dash_module \
ngx_rtmp_shared_module \
ngx_rtmp_record_module \
ngx_rtmp_gop_module \
ngx_rtmp_monitor_module \
ngx_mpegts_live_module \
ngx_mpegts_gop_module \
ngx_hls_live_module \
"
RTMP_HTTP_MODULES=" \
ngx_rtmp_stat_module \
ngx_rtmp_sys_stat_module \
ngx_rtmp_control_module \
ngx_http_flv_live_module \
ngx_hls_http_module \
ngx_mpegts_http_module \
"
RTMP_DEPS=" \
$ngx_addon_dir/ngx_netcall.h \
$ngx_addon_dir/ngx_rtmp_amf.h \
$ngx_addon_dir/ngx_rtmp_bandwidth.h \
$ngx_addon_dir/ngx_rtmp_cmd_module.h \
$ngx_addon_dir/ngx_rtmp_codec_module.h \
$ngx_addon_dir/ngx_rtmp_eval.h \
$ngx_addon_dir/ngx_rtmp.h \
$ngx_addon_dir/ngx_rtmp_version.h \
$ngx_addon_dir/ngx_rtmp_live_module.h \
$ngx_addon_dir/ngx_rtmp_notify_module.h \
$ngx_addon_dir/ngx_rtmp_streams.h \
$ngx_addon_dir/ngx_rtmp_bitop.h \
$ngx_addon_dir/ngx_rtmp_proxy_protocol.h \
$ngx_addon_dir/ngx_rtmp_monitor_module.h \
$ngx_addon_dir/hls/ngx_rtmp_mpegts.h \
$ngx_addon_dir/dash/ngx_rtmp_mp4.h \
$ngx_addon_dir/http/ngx_http_set_header.h \
$ngx_addon_dir/ngx_live.h \
$ngx_addon_dir/ngx_live_relay.h \
$ngx_addon_dir/ngx_live_record.h \
$ngx_addon_dir/ngx_rtmp_dynamic.h \
$ngx_addon_dir/ngx_rtmp_variables.h \
$ngx_addon_dir/ngx_rtmp_record_module.h \
$ngx_addon_dir/mpegts/ngx_mpegts_live_module.h \
$ngx_addon_dir/mpegts/ngx_hls_live_module.h \
$ngx_addon_dir/mpegts/ngx_mpegts_gop_module.h \
"
RTMP_CORE_SRCS=" \
$ngx_addon_dir/ngx_netcall.c \
$ngx_addon_dir/ngx_rtmp.c \
$ngx_addon_dir/ngx_rtmp_init.c \
$ngx_addon_dir/ngx_rtmp_handshake.c \
$ngx_addon_dir/ngx_rtmp_handler.c \
$ngx_addon_dir/ngx_rtmp_amf.c \
$ngx_addon_dir/ngx_rtmp_send.c \
$ngx_addon_dir/ngx_rtmp_eval.c \
$ngx_addon_dir/ngx_rtmp_receive.c \
$ngx_addon_dir/ngx_rtmp_core_module.c \
$ngx_addon_dir/ngx_rtmp_cmd_module.c \
$ngx_addon_dir/ngx_rtmp_codec_module.c \
$ngx_addon_dir/ngx_rtmp_access_module.c \
$ngx_addon_dir/ngx_rtmp_live_module.c \
$ngx_addon_dir/ngx_rtmp_bandwidth.c \
$ngx_addon_dir/ngx_rtmp_exec_module.c \
$ngx_addon_dir/ngx_rtmp_notify_module.c \
$ngx_addon_dir/ngx_rtmp_log_module.c \
$ngx_addon_dir/ngx_rtmp_limit_module.c \
$ngx_addon_dir/ngx_rtmp_bitop.c \
$ngx_addon_dir/ngx_rtmp_proxy_protocol.c \
$ngx_addon_dir/hls/ngx_rtmp_hls_module.c \
$ngx_addon_dir/dash/ngx_rtmp_dash_module.c \
$ngx_addon_dir/hls/ngx_rtmp_mpegts.c \
$ngx_addon_dir/dash/ngx_rtmp_mp4.c \
$ngx_addon_dir/ngx_live.c \
$ngx_addon_dir/ngx_live_relay.c \
$ngx_addon_dir/ngx_live_relay_httpflv.c \
$ngx_addon_dir/ngx_live_relay_rtmp.c \
$ngx_addon_dir/ngx_live_relay_inner.c \
$ngx_addon_dir/ngx_live_relay_simple.c \
$ngx_addon_dir/ngx_live_relay_static.c \
$ngx_addon_dir/ngx_live_record.c \
$ngx_addon_dir/ngx_rtmp_shared_module.c \
$ngx_addon_dir/ngx_rtmp_gop_module.c \
$ngx_addon_dir/ngx_rtmp_monitor_module.c \
$ngx_addon_dir/ngx_rtmp_dynamic.c \
$ngx_addon_dir/ngx_rtmp_variables.c \
$ngx_addon_dir/ngx_rtmp_record_module.c \
$ngx_addon_dir/mpegts/ngx_mpegts_live_module.c \
$ngx_addon_dir/mpegts/ngx_hls_live_module.c \
$ngx_addon_dir/mpegts/ngx_mpegts_gop_module.c \
"
RTMP_HTTP_SRCS=" \
$ngx_addon_dir/ngx_rtmp_sys_stat_module.c \
$ngx_addon_dir/ngx_rtmp_stat_module.c \
$ngx_addon_dir/ngx_rtmp_control_module.c \
$ngx_addon_dir/http/ngx_http_flv_live_module.c \
$ngx_addon_dir/http/ngx_http_set_header.c \
$ngx_addon_dir/mpegts/ngx_hls_http_module.c \
$ngx_addon_dir/mpegts/ngx_mpegts_http_module.c \
"
if [ -f auto/module ] ; then
ngx_module_incs=$ngx_addon_dir
ngx_module_deps=$RTMP_DEPS
if [ $ngx_module_link = DYNAMIC ] ; then
ngx_module_name="$RTMP_CORE_MODULES $RTMP_HTTP_MODULES"
ngx_module_srcs="$RTMP_CORE_SRCS $RTMP_HTTP_SRCS"
. auto/module
else
ngx_module_type=CORE
ngx_module_name=$RTMP_CORE_MODULES
ngx_module_srcs=$RTMP_CORE_SRCS
. auto/module
ngx_module_type=HTTP
ngx_module_name=$RTMP_HTTP_MODULES
ngx_module_incs="$ngx_addon_dir $ngx_addon_dir/http $ngx_addon_dir/hls $ngx_addon_dir/mpegts"
ngx_module_deps=
ngx_module_srcs=$RTMP_HTTP_SRCS
. auto/module
fi
else
CORE_MODULES="$CORE_MODULES $RTMP_CORE_MODULES"
HTTP_MODULES="$HTTP_MODULES $RTMP_HTTP_MODULES"
NGX_ADDON_DEPS="$NGX_ADDON_DEPS $RTMP_DEPS"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $RTMP_CORE_SRCS $RTMP_HTTP_SRCS"
CFLAGS="$CFLAGS -I$ngx_addon_dir -I$ngx_addon_dir/http -I$ngx_addon_dir/hls -I$ngx_addon_dir/mpegts"
fi
USE_OPENSSL=YES

View File

@ -0,0 +1,52 @@
#ifndef _NGX_RTMP_MP4_H_INCLUDED_
#define _NGX_RTMP_MP4_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_rtmp.h>
#define NGX_RTMP_MP4_SAMPLE_SIZE 0x01
#define NGX_RTMP_MP4_SAMPLE_DURATION 0x02
#define NGX_RTMP_MP4_SAMPLE_DELAY 0x04
#define NGX_RTMP_MP4_SAMPLE_KEY 0x08
typedef struct {
uint32_t size;
uint32_t duration;
uint32_t delay;
uint32_t timestamp;
unsigned key:1;
} ngx_rtmp_mp4_sample_t;
typedef enum {
NGX_RTMP_MP4_FILETYPE_INIT,
NGX_RTMP_MP4_FILETYPE_SEG
} ngx_rtmp_mp4_file_type_t;
typedef enum {
NGX_RTMP_MP4_VIDEO_TRACK,
NGX_RTMP_MP4_AUDIO_TRACK
} ngx_rtmp_mp4_track_type_t;
ngx_int_t ngx_rtmp_mp4_write_ftyp(ngx_buf_t *b);
ngx_int_t ngx_rtmp_mp4_write_styp(ngx_buf_t *b);
ngx_int_t ngx_rtmp_mp4_write_moov(ngx_rtmp_session_t *s, ngx_buf_t *b,
ngx_rtmp_mp4_track_type_t ttype);
ngx_int_t ngx_rtmp_mp4_write_moof(ngx_buf_t *b, uint32_t earliest_pres_time,
uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples,
ngx_uint_t sample_mask, uint32_t index);
ngx_int_t ngx_rtmp_mp4_write_sidx(ngx_buf_t *b,
ngx_uint_t reference_size, uint32_t earliest_pres_time,
uint32_t latest_pres_time);
ngx_uint_t ngx_rtmp_mp4_write_mdat(ngx_buf_t *b, ngx_uint_t size);
#endif /* _NGX_RTMP_MP4_H_INCLUDED_ */

View File

@ -0,0 +1,558 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
#include "ngx_rtmp_mpegts.h"
u_char ngx_rtmp_mpegts_pat[] = {
/* TS */
0x47, 0x40, 0x00, 0x10, 0x00,
/* PSI */
0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00,
/* PAT */
0x00, 0x01, 0xf0, 0x01,
/* CRC */
0x2e, 0x70, 0x19, 0x05,
/* stuffing 167 bytes */
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
static u_char ngx_mpegts_ts_header[] = {
/* TS */
0x47, 0x50, 0x01, 0x10, 0x00
};
static u_char ngx_mpegts_pmt_header[] = {
/* PSI */
0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00,
/* PMT */
0xe1, 0x00,
0xf0, 0x00
};
enum {
NGX_RTMP_MPEGTS_PID_H264 = 0,
NGX_RTMP_MPEGTS_PID_H265,
NGX_RTMP_MPEGTS_PID_AAC,
NGX_RTMP_MPEGTS_PID_MP3
};
static u_char ngx_mpegts_pid[4][5] = {
{0x1b, 0xe1, 0x00, 0xf0, 0x00}, /* h264 */
{0x24, 0xe1, 0x00, 0xf0, 0x00}, /* h265 */
{0x0f, 0xe1, 0x01, 0xf0, 0x00}, /* aac */
{0x03, 0xe1, 0x01, 0xf0, 0x00} /* mp3 */
};
/* 700 ms PCR delay */
#define NGX_RTMP_HLS_DELAY 63000
static uint32_t crc32table[256] = {
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7,
0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3,
0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef,
0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb,
0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4,
0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08,
0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc,
0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050,
0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1,
0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5,
0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9,
0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd,
0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2,
0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e,
0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a,
0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676,
0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
};
uint32_t
ngx_rtmp_mpegts_crc32(u_char *crc_buf, const u_char *data, int len)
{
int i;
uint32_t crc = 0xFFFFFFFF;
for(i = 0; i < len; i++) {
crc = (crc << 8) ^ crc32table[((crc >> 24) ^ *data++) & 0xFF];
}
crc_buf[0] = (crc & 0xff000000) >> 24;
crc_buf[1] = (crc & 0x00ff0000) >> 16;
crc_buf[2] = (crc & 0x0000ff00) >> 8;
crc_buf[3] = crc & 0x000000ff;
return crc;
}
ngx_int_t
ngx_rtmp_mpegts_gen_pmt(ngx_int_t vcodec, ngx_int_t acodec,
ngx_log_t *log, u_char *pmt)
{
u_char *p, crc_buf[4], *pmt_pos;
ngx_int_t vpid, apid;
u_char section_length;
vpid = -1;
apid = -1;
if (vcodec == acodec && vcodec == 0) {
return NGX_ERROR;
}
p = pmt;
p = ngx_cpymem(p, ngx_mpegts_ts_header, sizeof(ngx_mpegts_ts_header));
pmt_pos = p;
p = ngx_cpymem(p, ngx_mpegts_pmt_header, sizeof(ngx_mpegts_pmt_header));
if (vcodec == 0) {
// ignore
} else if (vcodec == NGX_RTMP_VIDEO_H264) {
vpid = NGX_RTMP_MPEGTS_PID_H264;
} else if (vcodec == NGX_RTMP_HEVC_CODEC_ID) {
vpid = NGX_RTMP_MPEGTS_PID_H265;
} else if (log) {
ngx_log_error(NGX_LOG_ERR, log, 0,
"rtmp: gen_pmt| unknown video codec (%d)", vcodec);
}
switch (acodec) {
case 0: //ignore
break;
case NGX_RTMP_AUDIO_MP3:
apid = NGX_RTMP_MPEGTS_PID_MP3;
break;
case NGX_RTMP_AUDIO_AAC:
apid = NGX_RTMP_MPEGTS_PID_AAC;
break;
default:
if (log) {
ngx_log_error(NGX_LOG_ERR, log, 0,
"rtmp: gen_pmt| unknown video codec (%d)", vcodec);
}
}
section_length = 13;
if (vpid != -1) {
p = ngx_cpymem(p, ngx_mpegts_pid[vpid], 5);
section_length += 5;
}
if (apid != -1) {
p = ngx_cpymem(p, ngx_mpegts_pid[apid], 5);
section_length += 5;
}
pmt_pos[2] = section_length;
ngx_rtmp_mpegts_crc32(crc_buf, pmt_pos, p - pmt_pos);
p = ngx_cpymem(p, crc_buf, 4);
ngx_memset(p, 0xff, 188 - (p - pmt));
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mpegts_write_file(ngx_rtmp_mpegts_file_t *file, u_char *in,
size_t in_size)
{
u_char *out;
size_t out_size, n;
ssize_t rc;
static u_char buf[1024];
if (!file->encrypt) {
ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
"mpegts: write %uz bytes", in_size);
if (file->whandle == NULL) {
rc = ngx_write_fd(file->fd, in, in_size);
if (rc < 0) {
return NGX_ERROR;
}
file->file_size += rc;
} else {
rc = file->whandle(file, in, in_size);
if (rc < 0) {
return NGX_ERROR;
}
}
return NGX_OK;
}
/* encrypt */
ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
"mpegts: write %uz encrypted bytes", in_size);
out = buf;
out_size = sizeof(buf);
if (file->size > 0 && file->size + in_size >= 16) {
ngx_memcpy(file->buf + file->size, in, 16 - file->size);
in += 16 - file->size;
in_size -= 16 - file->size;
AES_cbc_encrypt(file->buf, out, 16, &file->key, file->iv, AES_ENCRYPT);
out += 16;
out_size -= 16;
file->size = 0;
}
for ( ;; ) {
n = in_size & ~0x0f;
if (n > 0) {
if (n > out_size) {
n = out_size;
}
AES_cbc_encrypt(in, out, n, &file->key, file->iv, AES_ENCRYPT);
in += n;
in_size -= n;
} else if (out == buf) {
break;
}
rc = ngx_write_fd(file->fd, buf, out - buf + n);
if (rc < 0) {
return NGX_ERROR;
}
out = buf;
out_size = sizeof(buf);
}
if (in_size) {
ngx_memcpy(file->buf + file->size, in, in_size);
file->size += in_size;
}
return NGX_OK;
}
ngx_int_t
ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file)
{
ngx_int_t ret;
u_char pmt[188];
ret = ngx_rtmp_mpegts_write_file(file, ngx_rtmp_mpegts_pat,
sizeof(ngx_rtmp_mpegts_pat));
if (ret != NGX_OK) {
return ret;
}
if (ngx_rtmp_mpegts_gen_pmt(file->vcodec,
file->acodec, file->log, pmt) != NGX_OK)
{
return NGX_ERROR;
}
return ngx_rtmp_mpegts_write_file(file, pmt, sizeof(pmt));
}
static u_char *
ngx_rtmp_mpegts_write_pcr(u_char *p, uint64_t pcr)
{
*p++ = (u_char) (pcr >> 25);
*p++ = (u_char) (pcr >> 17);
*p++ = (u_char) (pcr >> 9);
*p++ = (u_char) (pcr >> 1);
*p++ = (u_char) (pcr << 7 | 0x7e);
*p++ = 0;
return p;
}
static u_char *
ngx_rtmp_mpegts_write_pts(u_char *p, ngx_uint_t fb, uint64_t pts)
{
ngx_uint_t val;
val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1;
*p++ = (u_char) val;
val = (((pts >> 15) & 0x7fff) << 1) | 1;
*p++ = (u_char) (val >> 8);
*p++ = (u_char) val;
val = (((pts) & 0x7fff) << 1) | 1;
*p++ = (u_char) (val >> 8);
*p++ = (u_char) val;
return p;
}
ngx_int_t
ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file,
ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b)
{
ngx_uint_t pes_size, header_size, body_size, in_size, stuff_size, flags;
u_char packet[188], *p, *base;
ngx_int_t first, rc;
ngx_log_debug6(NGX_LOG_DEBUG_CORE, file->log, 0,
"mpegts: pid=%ui, sid=%ui, pts=%uL, "
"dts=%uL, key=%ui, size=%ui",
f->pid, f->sid, f->pts, f->dts,
(ngx_uint_t) f->key, (size_t) (b->last - b->pos));
first = 1;
while (b->pos < b->last) {
p = packet;
f->cc++;
*p++ = 0x47;
*p++ = (u_char) (f->pid >> 8);
if (first) {
p[-1] |= 0x40;
}
*p++ = (u_char) f->pid;
*p++ = 0x10 | (f->cc & 0x0f); /* payload */
if (first) {
if (f->key) {
packet[3] |= 0x20; /* adaptation */
*p++ = 7; /* size */
*p++ = 0x50; /* random access + PCR */
p = ngx_rtmp_mpegts_write_pcr(p, f->dts - NGX_RTMP_HLS_DELAY);
}
/* PES header */
*p++ = 0x00;
*p++ = 0x00;
*p++ = 0x01;
*p++ = (u_char) f->sid;
header_size = 5;
flags = 0x80; /* PTS */
if (f->dts != f->pts) {
header_size += 5;
flags |= 0x40; /* DTS */
}
pes_size = (b->last - b->pos) + header_size + 3;
if (pes_size > 0xffff) {
pes_size = 0;
}
*p++ = (u_char) (pes_size >> 8);
*p++ = (u_char) pes_size;
*p++ = 0x80; /* H222 */
*p++ = (u_char) flags;
*p++ = (u_char) header_size;
p = ngx_rtmp_mpegts_write_pts(p, flags >> 6, f->pts +
NGX_RTMP_HLS_DELAY);
if (f->dts != f->pts) {
p = ngx_rtmp_mpegts_write_pts(p, 1, f->dts +
NGX_RTMP_HLS_DELAY);
}
first = 0;
}
body_size = (ngx_uint_t) (packet + sizeof(packet) - p);
in_size = (ngx_uint_t) (b->last - b->pos);
if (body_size <= in_size) {
ngx_memcpy(p, b->pos, body_size);
b->pos += body_size;
} else {
stuff_size = (body_size - in_size);
if (packet[3] & 0x20) {
/* has adaptation */
base = &packet[5] + packet[4];
p = ngx_movemem(base + stuff_size, base, p - base);
ngx_memset(base, 0xff, stuff_size);
packet[4] += (u_char) stuff_size;
} else {
/* no adaptation */
packet[3] |= 0x20;
p = ngx_movemem(&packet[4] + stuff_size, &packet[4],
p - &packet[4]);
packet[4] = (u_char) (stuff_size - 1);
if (stuff_size >= 2) {
packet[5] = 0;
ngx_memset(&packet[6], 0xff, stuff_size - 2);
}
}
ngx_memcpy(p, b->pos, in_size);
b->pos = b->last;
}
rc = ngx_rtmp_mpegts_write_file(file, packet, sizeof(packet));
if (rc != NGX_OK) {
return rc;
}
}
return NGX_OK;
}
ngx_int_t
ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file,
u_char *key, size_t key_len, uint64_t iv)
{
if (AES_set_encrypt_key(key, key_len * 8, &file->key)) {
return NGX_ERROR;
}
ngx_memzero(file->iv, 8);
file->iv[8] = (u_char) (iv >> 56);
file->iv[9] = (u_char) (iv >> 48);
file->iv[10] = (u_char) (iv >> 40);
file->iv[11] = (u_char) (iv >> 32);
file->iv[12] = (u_char) (iv >> 24);
file->iv[13] = (u_char) (iv >> 16);
file->iv[14] = (u_char) (iv >> 8);
file->iv[15] = (u_char) (iv);
file->encrypt = 1;
return NGX_OK;
}
ngx_int_t
ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path,
ngx_log_t *log)
{
file->log = log;
file->fd = ngx_open_file(path, NGX_FILE_WRONLY, NGX_FILE_TRUNCATE,
NGX_FILE_DEFAULT_ACCESS);
if (file->fd == NGX_INVALID_FILE) {
ngx_log_error(NGX_LOG_ERR, log, ngx_errno,
"hls: error creating fragment file");
return NGX_ERROR;
}
file->size = 0;
if (ngx_rtmp_mpegts_write_header(file) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, log, ngx_errno,
"hls: error writing fragment header");
ngx_close_file(file->fd);
return NGX_ERROR;
}
return NGX_OK;
}
ngx_int_t
ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file)
{
u_char buf[16];
ssize_t rc;
if (file->encrypt) {
ngx_memset(file->buf + file->size, 16 - file->size, 16 - file->size);
AES_cbc_encrypt(file->buf, buf, 16, &file->key, file->iv, AES_ENCRYPT);
rc = ngx_write_fd(file->fd, buf, 16);
if (rc < 0) {
return NGX_ERROR;
}
}
ngx_close_file(file->fd);
return NGX_OK;
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#ifndef _NGX_RTMP_MPEGTS_H_INCLUDED_
#define _NGX_RTMP_MPEGTS_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <openssl/aes.h>
typedef struct ngx_rtmp_mpegts_file_s ngx_rtmp_mpegts_file_t;
typedef ssize_t (*ngx_rtmp_mpegts_write_pt) (ngx_rtmp_mpegts_file_t *file,
u_char *in, size_t in_size);
struct ngx_rtmp_mpegts_file_s {
ngx_fd_t fd;
ngx_log_t *log;
off_t file_size;
unsigned encrypt:1;
unsigned size:4;
u_char buf[16];
u_char iv[16];
AES_KEY key;
ngx_int_t acodec;
ngx_int_t vcodec;
ngx_buf_t wbuf;
ngx_rtmp_mpegts_write_pt whandle;
};
typedef struct {
uint64_t pts;
uint64_t dts;
ngx_uint_t pid;
ngx_uint_t sid;
ngx_uint_t cc;
unsigned key:1;
} ngx_rtmp_mpegts_frame_t;
ngx_int_t ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file,
u_char *key, size_t key_len, uint64_t iv);
ngx_int_t ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path,
ngx_log_t *log);
ngx_int_t ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file);
ngx_int_t ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file);
ngx_int_t ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file,
ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b);
#endif /* _NGX_RTMP_MPEGTS_H_INCLUDED_ */

View File

@ -0,0 +1,733 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_http.h>
#include "ngx_rtmp.h"
#include "ngx_rtmp_cmd_module.h"
#include "ngx_rbuf.h"
#include "ngx_http_set_header.h"
#include "ngx_rtmp_monitor_module.h"
static char *ngx_http_flv_live(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *ngx_http_flv_live_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_flv_live_merge_loc_conf(ngx_conf_t *cf, void *parent,
void *child);
static u_char ngx_flv_live_audio_header[] = "FLV\x1\x1\0\0\0\x9\0\0\0\0";
static u_char ngx_flv_live_video_header[] = "FLV\x1\x4\0\0\0\x9\0\0\0\0";
static u_char ngx_flv_live_av_header[] = "FLV\x1\x5\0\0\0\x9\0\0\0\0";
static ngx_keyval_t ngx_http_flv_live_headers[] = {
{ ngx_string("Cache-Control"), ngx_string("no-cache") },
{ ngx_string("Content-Type"), ngx_string("video/x-flv") },
{ ngx_null_string, ngx_null_string }
};
#define NGX_FLV_TAG_SIZE 11
#define NGX_FLV_PTS_SIZE 4
typedef struct {
ngx_rtmp_session_t *session;
} ngx_http_flv_live_ctx_t;
typedef struct {
ngx_str_t app;
ngx_str_t flashver;
ngx_str_t swf_url;
ngx_str_t tc_url;
ngx_str_t page_url;
ngx_uint_t audio;
ngx_uint_t video;
ngx_rtmp_addr_conf_t *addr_conf;
} ngx_http_flv_live_loc_conf_t;
static ngx_command_t ngx_http_flv_live_commands[] = {
{ ngx_string("flv_live"),
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
ngx_http_flv_live,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_http_flv_live_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_flv_live_create_loc_conf, /* create location configuration */
ngx_http_flv_live_merge_loc_conf /* merge location configuration */
};
ngx_module_t ngx_http_flv_live_module = {
NGX_MODULE_V1,
&ngx_http_flv_live_module_ctx, /* module context */
ngx_http_flv_live_commands, /* module directives */
NGX_HTTP_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 ngx_int_t
ngx_http_flv_live_send_header(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_keyval_t *h;
ngx_buf_t *b;
ngx_chain_t out;
ngx_http_flv_live_loc_conf_t *hflcf;
if (r->header_sent) {
return NGX_OK;
}
hflcf = ngx_http_get_module_loc_conf(r, ngx_http_flv_live_module);
r->headers_out.status = NGX_HTTP_OK;
r->keepalive = 0; /* set Connection to closed */
h = ngx_http_flv_live_headers;
while (h->key.len) {
rc = ngx_http_set_header_out(r, &h->key, &h->value);
if (rc != NGX_OK) {
return rc;
}
++h;
}
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK) {
return rc;
}
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
switch (hflcf->audio | (hflcf->video << 1)) {
case 1: // audio only
b->start = b->pos = ngx_flv_live_audio_header;
b->end = b->last = ngx_flv_live_audio_header +
sizeof(ngx_flv_live_audio_header) - 1;
break;
case 2: // video only
b->start = b->pos = ngx_flv_live_video_header;
b->end = b->last = ngx_flv_live_video_header +
sizeof(ngx_flv_live_video_header) - 1;
break;
case 3: // audio and video
b->start = b->pos = ngx_flv_live_av_header;
b->end = b->last = ngx_flv_live_av_header +
sizeof(ngx_flv_live_av_header) - 1;
break;
default:
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"flv-live: send_header| av header config error.");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b->memory = 1;
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(r, &out);
}
static ngx_chain_t *
ngx_http_flv_live_prepare_out_chain(ngx_rtmp_session_t *s)
{
ngx_rtmp_frame_t *frame;
ngx_chain_t *head, **ll, *cl;
u_char *p;
size_t datasize, prev_tag_size;
frame = NULL;
head = NULL;
datasize = 0;
while (s->out_pos != s->out_last) {
frame = s->out[s->out_pos];
if (frame->hdr.type != NGX_RTMP_MSG_VIDEO
&& frame->hdr.type != NGX_RTMP_MSG_AUDIO
&& frame->hdr.type != NGX_RTMP_MSG_AMF_META
&& frame->hdr.type != NGX_RTMP_MSG_AMF3_META)
{
ngx_rtmp_shared_free_frame(frame);
++s->out_pos;
s->out_pos %= s->out_queue;
frame = NULL;
continue;
}
break;
}
/* no frame to send */
if (frame == NULL) {
return NULL;
}
for (ll = &head; *ll; ll = &(*ll)->next);
for (cl = frame->chain; cl; cl = cl->next) {
datasize += (cl->buf->last - cl->buf->pos);
}
prev_tag_size = datasize + NGX_FLV_TAG_SIZE;
/* flv tag header */
*ll = ngx_get_chainbuf(NGX_FLV_TAG_SIZE, 1);
if (*ll == NULL) {
goto falied;
}
p = (*ll)->buf->pos;
/* TagType 1 byte */
*p++ = frame->hdr.type;
/* DataSize 3 bytes */
*p++ = ((u_char *) &datasize)[2];
*p++ = ((u_char *) &datasize)[1];
*p++ = ((u_char *) &datasize)[0];
/* Timestamp 4 bytes */
*p++ = ((u_char *) &frame->hdr.timestamp)[2];
*p++ = ((u_char *) &frame->hdr.timestamp)[1];
*p++ = ((u_char *) &frame->hdr.timestamp)[0];
*p++ = ((u_char *) &frame->hdr.timestamp)[3];
/* StreamID 4 bytes, always set to 0 */
*p++ = 0;
*p++ = 0;
*p++ = 0;
(*ll)->buf->last = p;
ll = &(*ll)->next;
/* flv payload */
for (cl = frame->chain; cl; cl = cl->next) {
(*ll) = ngx_get_chainbuf(0, 0);
if (*ll == NULL) {
goto falied;
}
(*ll)->buf->pos = cl->buf->pos;
(*ll)->buf->last = cl->buf->last;
ll = &(*ll)->next;
}
/* flv previous tag size */
*ll = ngx_get_chainbuf(NGX_FLV_PTS_SIZE, 1);
if (*ll == NULL) {
goto falied;
}
p = (*ll)->buf->pos;
*p++ = ((u_char *) &prev_tag_size)[3];
*p++ = ((u_char *) &prev_tag_size)[2];
*p++ = ((u_char *) &prev_tag_size)[1];
*p++ = ((u_char *) &prev_tag_size)[0];
(*ll)->buf->last = p;
(*ll)->buf->flush = 1;
ngx_rtmp_monitor_frame(s, &frame->hdr, NULL, frame->av_header, 0);
return head;
falied:
ngx_put_chainbufs(head);
ngx_rtmp_finalize_session(s);
return NULL;
}
static void
ngx_http_flv_live_write_handler(ngx_http_request_t *r)
{
ngx_http_flv_live_ctx_t *ctx;
ngx_rtmp_session_t *s;
ngx_event_t *wev;
size_t present, sent;
ngx_int_t rc;
ngx_chain_t *cl;
wev = r->connection->write;
if (r->connection->destroyed) {
return;
}
ctx = ngx_http_get_module_ctx(r, ngx_http_flv_live_module);
s = ctx->session;
if (wev->timedout) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, NGX_ETIMEDOUT,
"http flv live, client timed out");
r->connection->timedout = 1;
s->finalize_reason = NGX_LIVE_FLV_SEND_TIMEOUT;
if (r->header_sent) {
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
} else {
r->error_page = 1;
ngx_http_finalize_request(r, NGX_HTTP_SERVICE_UNAVAILABLE);
}
return;
}
if (wev->timer_set) {
ngx_del_timer(wev);
}
if (ngx_rtmp_core_main_conf->fast_reload && (ngx_exiting || ngx_terminate)) {
r->error_page = 1;
ngx_http_finalize_request(r, NGX_HTTP_SERVICE_UNAVAILABLE);
return;
}
if (ngx_rtmp_prepare_merge_frame(s) == NGX_ERROR) {
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
if (s->out_chain) {
rc = ngx_http_flv_live_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK) {
s->finalize_reason = NGX_LIVE_FLV_SEND_ERR;
ngx_http_finalize_request(r, rc);
return;
}
}
while (s->out_chain) {
present = r->connection->sent;
if (r->connection->buffered) {
rc = ngx_http_output_filter(r, NULL);
} else {
rc = ngx_http_output_filter(r, s->out_chain);
}
sent = r->connection->sent - present;
ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_out, sent);
if (rc == NGX_AGAIN) {
ngx_add_timer(wev, s->timeout);
if (ngx_handle_write_event(wev, 0) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
"http flv live, handle write event failed");
ngx_http_finalize_request(r, NGX_ERROR);
}
return;
}
if (rc == NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
"http flv live, send error");
s->finalize_reason = NGX_LIVE_FLV_SEND_ERR;
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
/* NGX_OK */
for (cl = s->out_chain; cl;) {
s->out_chain = cl->next;
ngx_free_chain(s->pool, cl);
cl = s->out_chain;
}
if (ngx_rtmp_prepare_merge_frame(s) == NGX_ERROR) {
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
}
if (wev->active) {
ngx_del_event(wev, NGX_WRITE_EVENT, 0);
}
}
static void
ngx_http_flv_live_parse_url(ngx_http_request_t *r, ngx_str_t *app,
ngx_str_t *name)
{
u_char *p, *end, *pos;
p = r->uri.data + 1; /* skip '/' */
end = r->uri.data + r->uri.len;
app->data = p;
pos = ngx_strnstr(p, ".flv", end - p);
if (pos) {
end = pos;
}
p = (u_char *) ngx_strnstr(p, "/", end - p);
while (p) {
name->data = p;
p = (u_char *) ngx_strnstr(p + 1, "/", end - p);
}
if (name->data == NULL) {
return;
}
app->len = name->data - app->data;
++name->data;
name->len = end - name->data;
}
static ngx_int_t
ngx_http_flv_live_parse(ngx_http_request_t *r, ngx_rtmp_session_t *s,
ngx_rtmp_play_t *v)
{
ngx_http_flv_live_loc_conf_t *hflcf;
ngx_str_t app, stream;
size_t tcurl_len;
u_char *p;
hflcf = ngx_http_get_module_loc_conf(r, ngx_http_flv_live_module);
ngx_memzero(&app, sizeof(ngx_str_t));
ngx_memzero(&stream, sizeof(ngx_str_t));
ngx_http_flv_live_parse_url(r, &app, &stream);
if (app.len == 0 || stream.len == 0 || stream.len > NGX_RTMP_MAX_NAME) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"http flv live, url error: %V", &r->uri);
return NGX_HTTP_BAD_REQUEST;
}
if (hflcf->app.len) {
app = hflcf->app;
}
if (ngx_http_arg(r, (u_char *) "flashver", 8, &s->flashver) != NGX_OK) {
s->flashver = hflcf->flashver;
}
ngx_http_arg(r, (u_char *) "app", 3, &app);
s->app = app;
/* tc_url */
#if (NGX_HTTP_SSL)
if (r->connection->ssl) {
tcurl_len = sizeof("https://") - 1;
} else
#endif
{
tcurl_len = sizeof("http://") - 1;
}
tcurl_len += r->headers_in.server.len + 1 + app.len;
s->tc_url.len = tcurl_len;
s->tc_url.data = ngx_pcalloc(r->pool, tcurl_len);
if (s->tc_url.data == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
p = s->tc_url.data;
#if (NGX_HTTP_SSL)
if (r->connection->ssl) {
p = ngx_cpymem(p, "https://", sizeof("https://") - 1);
} else
#endif
{
p = ngx_cpymem(p, "http://", sizeof("http://") - 1);
}
p = ngx_cpymem(p, r->headers_in.server.data, r->headers_in.server.len);
*p++ = '/';
p = ngx_cpymem(p, app.data, app.len);
/* page_url */
if (r->headers_in.referer) {
s->page_url = r->headers_in.referer->value;
} else {
s->page_url = hflcf->page_url;
}
s->acodecs = 0x0DF7;
s->vcodecs = 0xFC;
ngx_memcpy(v->name, stream.data, stream.len);
if (r->args.len) {
ngx_memcpy(v->args, r->args.data,
ngx_min(r->args.len, NGX_RTMP_MAX_ARGS));
}
ngx_rtmp_cmd_middleware_init(s);
return NGX_OK;
}
static void
ngx_http_flv_live_cleanup(void *data)
{
ngx_http_request_t *r;
ngx_http_flv_live_ctx_t *ctx;
r = data;
ctx = ngx_http_get_module_ctx(r, ngx_http_flv_live_module);
if (ctx == NULL) {
return;
}
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"http flv live, cleanup");
if (ctx->session) {
ctx->session->request = NULL;
if (ctx->session->finalize_reason == 0) {
ctx->session->finalize_reason = r->connection->read->error?
NGX_LIVE_FLV_RECV_ERR:
NGX_LIVE_NORMAL_CLOSE;
}
ngx_rtmp_finalize_fake_session(ctx->session);
}
}
static ngx_int_t
ngx_http_flv_live_handler(ngx_http_request_t *r)
{
ngx_http_flv_live_loc_conf_t *hflcf;
ngx_http_flv_live_ctx_t *ctx;
ngx_rtmp_session_t *s;
ngx_rtmp_play_t v;
ngx_int_t rc;
ngx_uint_t n;
ngx_rtmp_core_srv_conf_t *cscf;
ngx_rtmp_core_app_conf_t **cacfp;
ngx_http_cleanup_t *cln;
ngx_rtmp_core_main_conf_t *cmcf;
rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_flv_live_ctx_t));
if (ctx == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_http_set_ctx(r, ctx, ngx_http_flv_live_module);
/* cleanup handler */
cln = ngx_http_cleanup_add(r, 0);
if (cln == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
cln->handler = ngx_http_flv_live_cleanup;
cln->data = r;
hflcf = ngx_http_get_module_loc_conf(r, ngx_http_flv_live_module);
/* create fake session */
s = ngx_rtmp_create_session(hflcf->addr_conf);
if (s == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
s->connection = r->connection;
s->number = r->connection->number;
s->remote_addr_text.data = ngx_pcalloc(s->pool, r->connection->addr_text.len);
s->remote_addr_text.len = r->connection->addr_text.len;
ngx_memcpy(s->remote_addr_text.data,
r->connection->addr_text.data, r->connection->addr_text.len);
ngx_rtmp_set_combined_log(s, r->connection->log->data,
r->connection->log->handler);
s->log->connection = r->connection->number;
ctx->session = s;
/* get host, app, stream name */
ngx_memzero(&v, sizeof(ngx_rtmp_play_t));
rc = ngx_http_flv_live_parse(r, s, &v);
if (rc != NGX_OK) {
return rc;
}
if (ngx_rtmp_set_virtual_server(s, &s->domain)) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
s->live_type = NGX_HTTP_FLV_LIVE;
s->live_server = ngx_live_create_server(&s->serverid);
s->request = r;
v.silent = 1;
cacfp = cscf->applications.elts;
for (n = 0; n < cscf->applications.nelts; ++n, ++cacfp) {
if ((*cacfp)->name.len == s->app.len &&
ngx_strncmp((*cacfp)->name.data, s->app.data, s->app.len) == 0)
{
/* found app! */
s->app_conf = (*cacfp)->app_conf;
break;
}
}
if (s->app_conf == NULL) {
if (cscf->default_app == NULL || cscf->default_app->app_conf == NULL) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"http flv live, application not found '%V'", &s->app);
return NGX_HTTP_NOT_FOUND;
}
s->app_conf = cscf->default_app->app_conf;
}
s->prepare_handler = ngx_http_flv_live_prepare_out_chain;
s->stage = NGX_LIVE_PLAY;
s->ptime = ngx_current_msec;
cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);
s->variables = ngx_pcalloc(s->pool, cmcf->variables.nelts
* sizeof(ngx_http_variable_value_t));
if (s->variables == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (ngx_rtmp_play_filter(s, &v) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_add_timer(r->connection->write, s->timeout);
r->read_event_handler = ngx_http_test_reading;
r->write_event_handler = ngx_http_flv_live_write_handler;
++r->count;
return NGX_DONE;
}
static void *
ngx_http_flv_live_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_flv_live_loc_conf_t *hflcf;
hflcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_flv_live_loc_conf_t));
if (hflcf == NULL) {
return NULL;
}
return hflcf;
}
static char *
ngx_http_flv_live_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_flv_live_loc_conf_t *prev = parent;
ngx_http_flv_live_loc_conf_t *conf = child;
ngx_conf_merge_str_value(conf->app, prev->app, "");
ngx_conf_merge_str_value(conf->flashver, prev->flashver, "");
ngx_conf_merge_str_value(conf->swf_url, prev->swf_url, "");
ngx_conf_merge_str_value(conf->tc_url, prev->tc_url, "");
ngx_conf_merge_str_value(conf->page_url, prev->page_url, "");
return NGX_CONF_OK;
}
static char *
ngx_http_flv_live(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
ngx_http_flv_live_loc_conf_t *hflcf;
ngx_str_t *value, v;
ngx_uint_t i;
ngx_uint_t audio, video;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_http_flv_live_handler;
hflcf = conf;
value = cf->args->elts;
hflcf->addr_conf = ngx_rtmp_find_related_addr_conf(cf->cycle, &value[1]);
if (hflcf->addr_conf == NULL) {
return NGX_CONF_ERROR;
}
audio = NGX_CONF_UNSET_UINT;
video = NGX_CONF_UNSET_UINT;
for (i = 2; i < cf->args->nelts; ++i) {
if (ngx_strncmp(value[i].data, "app=", 4) == 0) {
v.data = value[i].data + 4;
v.len = value[i].len - 4;
hflcf->app = v;
} else if (ngx_strncmp(value[i].data, "audio=", 6) == 0) {
v.data = value[i].data + 6;
v.len = value[i].len - 6;
audio = ngx_atoi(v.data, v.len);
} else if (ngx_strncmp(value[i].data, "video=", 6) == 0) {
v.data = value[i].data + 6;
v.len = value[i].len - 6;
video = ngx_atoi(v.data, v.len);
} else {
return NGX_CONF_ERROR;
}
}
if (audio == NGX_CONF_UNSET_UINT) {
audio = 1;
}
if (video == NGX_CONF_UNSET_UINT) {
video = 1;
}
hflcf->audio = audio;
hflcf->video = video;
return NGX_CONF_OK;
}

View File

@ -0,0 +1,282 @@
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct ngx_http_header_val_s ngx_http_header_val_t;
typedef ngx_int_t (*ngx_http_set_header_pt)(ngx_http_request_t *r,
ngx_http_header_val_t *hv, ngx_str_t *value);
struct ngx_http_header_val_s {
ngx_http_complex_value_t value;
ngx_uint_t hash;
ngx_str_t key;
ngx_http_set_header_pt handler;
ngx_uint_t offset;
};
typedef struct {
ngx_str_t name;
ngx_uint_t offset;
ngx_http_set_header_pt handler;
} ngx_http_set_header_t;
//TODO need fill all header set in future
/* for header has no quick link in ngx_http_headers_out_t */
static ngx_int_t ngx_http_set_header_out_other(ngx_http_request_t *r,
ngx_http_header_val_t *hv, ngx_str_t *value);
/* for header has quick link like ngx_table_elt_t* in ngx_http_headers_out_t */
static ngx_int_t ngx_http_set_header_out_builtin(ngx_http_request_t *r,
ngx_http_header_val_t *hv, ngx_str_t *value);
/* for header has quick link like ngx_array_t in ngx_http_headers_out_t */
static ngx_int_t ngx_http_set_header_out_builtin_multi(ngx_http_request_t *r,
ngx_http_header_val_t *hv, ngx_str_t *value);
/* for header has quick link like ngx_table_elt_t* and
* other attribute in ngx_http_headers_out_t defined below */
static ngx_int_t ngx_http_set_header_out_content_type(ngx_http_request_t *r,
ngx_http_header_val_t *hv, ngx_str_t *value);
static ngx_http_set_header_t ngx_http_set_header_out_handlers[] = {
{ ngx_string("Server"),
offsetof(ngx_http_headers_out_t, server),
ngx_http_set_header_out_builtin },
{ ngx_string("Date"),
offsetof(ngx_http_headers_out_t, date),
ngx_http_set_header_out_builtin },
{ ngx_string("Content-Type"),
offsetof(ngx_http_headers_out_t, content_type),
ngx_http_set_header_out_content_type },
{ ngx_string("Cache-Control"),
offsetof(ngx_http_headers_out_t, cache_control),
ngx_http_set_header_out_builtin_multi },
{ ngx_null_string, 0, ngx_http_set_header_out_other }
};
static ngx_int_t
ngx_http_set_header_out_helper(ngx_http_request_t *r,
ngx_http_header_val_t *hv, ngx_str_t *value, unsigned no_create)
{
ngx_table_elt_t *h;
ngx_list_part_t *part;
ngx_uint_t i;
part = &r->headers_out.headers.part;
h = part->elts;
for (i = 0; /* void */; ++i) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
h = part->elts;
i = 0;
}
if (h[i].hash != 0
&& h[i].key.len == hv->key.len
&& ngx_strncasecmp(hv->key.data, h[i].key.data, h[i].key.len) == 0)
/* header has been set */
{
h[i].value = *value;
if (value->len == 0) { /* if value is empty, remove header */
h[i].hash = 0;
} else {
h[i].hash = hv->hash;
}
return NGX_OK;
}
}
if (no_create && value->len == 0) { /* set header to empty but header cannot found */
return NGX_OK;
}
/* header has not been set, create it */
h = ngx_list_push(&r->headers_out.headers);
if (h == NULL) {
return NGX_ERROR;
}
h->hash = hv->hash;
h->key = hv->key;
h->value = *value;
h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
if (h->lowcase_key == NULL) {
return NGX_ERROR;
}
ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
return NGX_OK;
}
static ngx_int_t
ngx_http_set_header_out_other(ngx_http_request_t * r,
ngx_http_header_val_t * hv,ngx_str_t * value)
{
return ngx_http_set_header_out_helper(r, hv, value, 0);
}
static ngx_int_t
ngx_http_set_header_out_builtin(ngx_http_request_t *r,
ngx_http_header_val_t *hv, ngx_str_t *value)
{
ngx_table_elt_t *h, **old;
if (hv->offset) {
old = (ngx_table_elt_t **) ((char *) &r->headers_out + hv->offset);
} else {
old = NULL;
}
if (old == NULL || *old == NULL) {
/* user should use ngx_http_set_header_out_other but use this func to set header */
//TODO
return ngx_http_set_header_out_helper(r, hv, value, 0);
}
h = *old;
h->value = *value;
if (value->len == 0) { /* if value is empty, remove header */
h->hash = 0;
return NGX_OK;
}
h->hash = hv->hash;
h->key = hv->key;
return NGX_OK;
}
static ngx_int_t
ngx_http_set_header_out_builtin_multi(ngx_http_request_t *r,
ngx_http_header_val_t *hv, ngx_str_t *value)
{
ngx_array_t *pa;
ngx_table_elt_t *ho, **ph;
ngx_uint_t i;
pa = (ngx_array_t *) ((char *) &r->headers_out + hv->offset);
if (pa->elts == NULL) {
if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *))
!= NGX_OK)
{
return NGX_ERROR;
}
}
if (pa->nelts > 0) {
ph = pa->elts;
for (i = 1; i < pa->nelts; i++) { /* clear old value */
ph[i]->hash = 0;
ph[i]->value.len = 0;
}
ph[0]->value = *value;
if (value->len == 0) {
ph[0]->hash = 0;
} else {
ph[0]->hash = hv->hash;
}
return NGX_OK;
}
/* header does not set */
ph = ngx_array_push(pa);
if (ph == NULL) {
return NGX_ERROR;
}
ho = ngx_list_push(&r->headers_out.headers);
if (ho == NULL) {
return NGX_ERROR;
}
ho->value = *value;
ho->hash = hv->hash;
ho->key = hv->key;
*ph = ho;
return NGX_OK;
}
static ngx_int_t
ngx_http_set_header_out_content_type(ngx_http_request_t *r,
ngx_http_header_val_t *hv, ngx_str_t *value)
{
ngx_uint_t i;
r->headers_out.content_type_len = value->len;
for (i = 0; i < value->len; i++) {
if (value->data[i] == ';') {
r->headers_out.content_type_len = i;
break;
}
}
r->headers_out.content_type = *value;
r->headers_out.content_type_hash = hv->hash;
r->headers_out.content_type_lowcase = NULL;
value->len = 0;
return ngx_http_set_header_out_helper(r, hv, value, 1);
}
ngx_int_t
ngx_http_set_header_out(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value)
{
ngx_http_header_val_t hv;
ngx_http_set_header_t *handlers = ngx_http_set_header_out_handlers;
ngx_uint_t i;
ngx_str_t v;
hv.hash = ngx_hash_key_lc(key->data, key->len);
hv.key = *key;
hv.offset = 0;
hv.handler = NULL;
for (i = 0; handlers[i].name.len; ++i) {
if (hv.key.len != handlers[i].name.len
|| ngx_strncasecmp(hv.key.data, handlers[i].name.data,
handlers[i].name.len) != 0) {
continue;
}
/* match handler */
hv.offset = handlers[i].offset;
hv.handler = handlers[i].handler;
break;
}
if (handlers[i].name.len == 0 && handlers[i].handler) { /* if not matched, use ngx_http_set_header as default*/
hv.offset = handlers[i].offset;
hv.handler = handlers[i].handler;
}
v = *value;
return hv.handler(r, &hv, &v);
}

View File

@ -0,0 +1,11 @@
#ifndef _NGX_RTMP_HTTP_HEADER_OUT_H_INCLUDED_
#define _NGX_RTMP_HTTP_HEADER_OUT_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
ngx_int_t ngx_http_set_header_out(ngx_http_request_t *r,
ngx_str_t *key, ngx_str_t *value);
#endif

View File

@ -0,0 +1,95 @@
/*
* Copyright (C) Pingo (cczjp89@gmail.com)
*/
#ifndef _NGX_HLS_LIVE_MODULE_H_INCLUDE_
#define _NGX_HLS_LIVE_MODULE_H_INCLUDE_
#define ngx_hls_live_next(s, pos) ((pos + 1) % s->out_queue)
#define ngx_hls_live_prev(s, pos) (pos == 0 ? s->out_queue - 1 : pos - 1)
typedef struct ngx_hls_live_frag_s ngx_hls_live_frag_t;
typedef struct ngx_hls_live_play_s ngx_hls_live_play_t;
struct ngx_hls_live_play_s {
ngx_str_t name;
/* connection parameters */
ngx_rtmp_addr_conf_t *addr_conf;
ngx_str_t app;
ngx_str_t stream;
ngx_str_t args;
ngx_str_t flashver;
ngx_str_t swf_url;
ngx_str_t tc_url;
uint32_t acodecs;
uint32_t vcodecs;
ngx_str_t page_url;
ngx_str_t domain;
ngx_str_t serverid;
ngx_log_t *log;
};
struct ngx_hls_live_frag_s {
ngx_uint_t ref;
ngx_hls_live_frag_t *next;
time_t last_modified_time;
uint64_t id;
uint64_t key_id;
double duration;
unsigned active:1;
unsigned discont:1; /* before */
ngx_uint_t length;
ngx_chain_t *out;
ngx_uint_t content_last;
ngx_uint_t content_pos;
ngx_mpegts_frame_t *content[0];
};
struct ngx_hls_live_ctx_s {
unsigned opened:1;
unsigned playing:1;
ngx_buf_t *patpmt;
ngx_rtmp_session_t *session;
ngx_str_t sid;
ngx_live_stream_t *stream;
ngx_str_t name;
uint64_t nfrag;
uint64_t frag_ts;
uint64_t key_id;
ngx_uint_t nfrags;
ngx_hls_live_frag_t **frags; /* circular 2 * winfrags + 1 */
ngx_hls_live_frag_t *frag;
ngx_uint_t audio_cc;
ngx_uint_t video_cc;
ngx_uint_t key_frags;
uint64_t aframe_base;
uint64_t aframe_num;
ngx_buf_t *aframe;
uint64_t aframe_pts;
ngx_event_t ev;
ngx_msec_t timeout;
ngx_msec_t last_time;
time_t playlist_modified_time;
ngx_buf_t *playlist;
ngx_hls_live_ctx_t *next;
};
ngx_int_t ngx_hls_live_write_playlist(ngx_rtmp_session_t *s, ngx_buf_t *out,
time_t *last_modified_time);
ngx_hls_live_frag_t* ngx_hls_live_find_frag(ngx_rtmp_session_t *s,
ngx_str_t *name);
ngx_chain_t* ngx_hls_live_prepare_frag(ngx_rtmp_session_t *s,
ngx_hls_live_frag_t *frag);
void ngx_hls_live_free_frag(ngx_hls_live_frag_t *frag);
ngx_rtmp_session_t* ngx_hls_live_fetch_session(ngx_str_t *server,
ngx_str_t *stream, ngx_str_t *session);
void ngx_rtmp_shared_acquire_frag(ngx_hls_live_frag_t *frag);
#endif

View File

@ -0,0 +1,731 @@
/*
* Copyright (C) Pingo (cczjp89@gmail.com)
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
#include "ngx_rtmp_codec_module.h"
#include "ngx_rtmp_live_module.h"
#include "ngx_mpegts_live_module.h"
static ngx_rtmp_close_stream_pt next_close_stream;
static ngx_mpegts_video_pt next_mpegts_video;
static ngx_mpegts_audio_pt next_mpegts_audio;
static void *ngx_mpegts_gop_create_app_conf(ngx_conf_t *cf);
static char *ngx_mpegts_gop_merge_app_conf(ngx_conf_t *cf, void *parent,
void *child);
static ngx_int_t ngx_mpegts_gop_postconfiguration(ngx_conf_t *cf);
#define ngx_mpegts_gop_next(s, pos) ((pos + 1) % s->out_queue)
#define ngx_mpegts_gop_prev(s, pos) (pos == 0 ? s->out_queue - 1 : pos - 1)
typedef struct {
/* publisher: head of cache
* player: cache send position of publisher's out
*/
size_t gop_pos;
/* tail of cache */
size_t gop_last;
/* 0 for not send, 1 for sending, 2 for sent */
ngx_flag_t send_gop;
ngx_mpegts_frame_t *keyframe;
ngx_uint_t meta_version;
uint64_t first_timestamp;
uint64_t current_timestamp;
ngx_uint_t base_type;
/* only for publisher, must at last of ngx_mpegts_gop_ctx_t */
ngx_mpegts_frame_t *cache[];
} ngx_mpegts_gop_ctx_t;
typedef struct {
ngx_msec_t cache_time;
ngx_msec_t roll_back;
ngx_msec_t one_off_send;
ngx_flag_t low_latency;
ngx_flag_t send_all;
ngx_msec_t fix_timestamp;
ngx_flag_t zero_start;
} ngx_mpegts_gop_app_conf_t;
static ngx_command_t ngx_mpegts_gop_commands[] = {
{ ngx_string("mpegts_cache_time"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_mpegts_gop_app_conf_t, cache_time),
NULL },
{ ngx_string("mpegts_roll_back"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_mpegts_gop_app_conf_t, roll_back),
NULL },
{ ngx_string("mpegts_one_off_send"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_mpegts_gop_app_conf_t, one_off_send),
NULL },
{ ngx_string("mpegts_low_latency"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_mpegts_gop_app_conf_t, low_latency),
NULL },
{ ngx_string("mpegts_send_all"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_mpegts_gop_app_conf_t, send_all),
NULL },
{ ngx_string("mpegts_fix_timestamp"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_mpegts_gop_app_conf_t, fix_timestamp),
NULL },
{ ngx_string("mpegts_zero_start"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_mpegts_gop_app_conf_t, zero_start),
NULL },
ngx_null_command
};
static ngx_rtmp_module_t ngx_mpegts_gop_module_ctx = {
NULL, /* preconfiguration */
ngx_mpegts_gop_postconfiguration, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_mpegts_gop_create_app_conf, /* create app configuration */
ngx_mpegts_gop_merge_app_conf /* merge app configuration */
};
ngx_module_t ngx_mpegts_gop_module = {
NGX_MODULE_V1,
&ngx_mpegts_gop_module_ctx, /* module context */
ngx_mpegts_gop_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_mpegts_gop_create_app_conf(ngx_conf_t *cf)
{
ngx_mpegts_gop_app_conf_t *gacf;
gacf = ngx_pcalloc(cf->pool, sizeof(ngx_mpegts_gop_app_conf_t));
if (gacf == NULL) {
return NULL;
}
gacf->cache_time = NGX_CONF_UNSET_MSEC;
gacf->roll_back = NGX_CONF_UNSET_MSEC;
gacf->one_off_send = NGX_CONF_UNSET_MSEC;
gacf->low_latency = NGX_CONF_UNSET;
gacf->send_all = NGX_CONF_UNSET;
gacf->fix_timestamp = NGX_CONF_UNSET_MSEC;
gacf->zero_start = NGX_CONF_UNSET;
return gacf;
}
static char *
ngx_mpegts_gop_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_mpegts_gop_app_conf_t *prev = parent;
ngx_mpegts_gop_app_conf_t *conf = child;
ngx_conf_merge_msec_value(conf->cache_time, prev->cache_time, 0);
ngx_conf_merge_msec_value(conf->roll_back, prev->roll_back, conf->cache_time);
ngx_conf_merge_msec_value(conf->one_off_send, prev->one_off_send, 3000);
ngx_conf_merge_value(conf->low_latency, prev->low_latency, 0);
ngx_conf_merge_value(conf->send_all, prev->send_all, 0);
ngx_conf_merge_msec_value(conf->fix_timestamp, prev->fix_timestamp, 10000);
ngx_conf_merge_value(conf->zero_start, prev->zero_start, 0);
return NGX_CONF_OK;
}
static ngx_int_t
ngx_mpegts_gop_link_frame(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame)
{
ngx_uint_t nmsg;
if (frame == NULL) {
return NGX_OK;
}
nmsg = (s->out_last - s->out_pos) % s->out_queue + 1;
if (nmsg >= s->out_queue) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"link frame nmsg(%ui) >= out_queue(%O)", nmsg, s->out_queue);
return NGX_AGAIN;
}
#if 0
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
"link frame pos[%O] last[%O], pts[%ud]",
s->out_pos, s->out_last, frame->pts);
#endif
s->mpegts_out[s->out_last] = frame;
s->out_last = ngx_mpegts_gop_next(s, s->out_last);
ngx_rtmp_shared_acquire_mpegts_frame(frame);
return NGX_OK;
}
static void
ngx_mpegts_gop_reset_gop(ngx_rtmp_session_t *s, ngx_mpegts_gop_ctx_t *ctx,
ngx_mpegts_frame_t *frame)
{
ngx_mpegts_gop_app_conf_t *gacf;
ngx_mpegts_frame_t *f, *next_keyframe;
size_t pos;
ngx_uint_t nmsg;
ngx_msec_t cache_time;
pos = ctx->gop_pos;
f = ctx->cache[pos];
if (f == NULL) {
return;
}
gacf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_gop_module);
cache_time = gacf->cache_time > gacf->roll_back ?
gacf->cache_time : gacf->roll_back;
/* only audio in cache */
if (ctx->keyframe == NULL) {
if (frame->pts - ctx->cache[ctx->gop_pos]->pts > cache_time * 90) {
ngx_rtmp_shared_free_mpegts_frame(f);
ctx->cache[ctx->gop_pos] = NULL;
ctx->gop_pos = ngx_mpegts_gop_next(s, ctx->gop_pos);
}
return;
}
/* only video of video + audio */
next_keyframe = ctx->keyframe->next;
/* only one gop in cache */
if (next_keyframe == NULL) {
return;
}
nmsg = (ctx->gop_last - ctx->gop_pos) % s->out_queue + 2;
if (nmsg >= s->out_queue) {
goto reset;
}
if (frame->type == NGX_MPEGTS_MSG_AUDIO) {
return;
}
if (frame->type == NGX_MPEGTS_MSG_VIDEO && frame->pts
- next_keyframe->pts < cache_time * 90)
{
return;
}
reset:
for (pos = ctx->gop_pos; ctx->cache[pos] != next_keyframe;
pos = ngx_mpegts_gop_next(s, pos))
{
f = ctx->cache[pos];
ngx_rtmp_shared_free_mpegts_frame(f);
ctx->cache[pos] = NULL;
}
ctx->keyframe = next_keyframe;
ctx->gop_pos = pos;
}
static void
ngx_mpegts_gop_print_cache(ngx_rtmp_session_t *s, ngx_mpegts_gop_ctx_t *ctx)
{
#if (NGX_DEBUG)
ngx_mpegts_frame_t *frame;
u_char content[10240], *p;
size_t pos;
ngx_memzero(content, sizeof(content));
p = content;
for (pos = ctx->gop_pos; pos != ctx->gop_last;
pos = ngx_mpegts_gop_next(s, pos))
{
frame = ctx->cache[pos];
switch (frame->type) {
case NGX_MPEGTS_MSG_AUDIO:
*p++ = 'A';
break;
case NGX_MPEGTS_MSG_VIDEO:
*p++ = 'V';
break;
default:
*p++ = 'O';
break;
}
if (frame->key) {
*p++ = 'I';
}
*p++ = ' ';
}
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
"[%z %z] %s", ctx->gop_pos, ctx->gop_last, content);
#endif
}
ngx_int_t
ngx_mpegts_gop_cache(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame)
{
ngx_mpegts_gop_app_conf_t *gacf;
ngx_mpegts_gop_ctx_t *ctx;
ngx_mpegts_frame_t **keyframe;
ngx_uint_t nmsg;
gacf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_gop_module);
if (gacf->cache_time == 0) {
return NGX_OK;
}
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_gop_module);
if (ctx == NULL) {
ctx = ngx_pcalloc(s->pool, sizeof(ngx_mpegts_gop_ctx_t)
+ s->out_queue * sizeof(ngx_mpegts_frame_t *));
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_rtmp_set_ctx(s, ctx, ngx_mpegts_gop_module);
}
nmsg = (ctx->gop_last - ctx->gop_pos) % s->out_queue + 1;
if (nmsg >= s->out_queue) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"cache frame nmsg(%ui) >= out_queue(%z)", nmsg, s->out_queue);
return NGX_AGAIN;
}
#if 0
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
"cache frame: %ud[%d], %ud, %ud",
frame->type, frame->key, frame->pts, frame->length);
#endif
/* first video frame is not intra_frame */
if (ctx->keyframe == NULL && frame->type == NGX_MPEGTS_MSG_VIDEO
&& !frame->key)
{
return NGX_OK;
}
/* video intra_frame */
if (frame->key) {
for (keyframe = &ctx->keyframe; *keyframe;
keyframe = &((*keyframe)->next));
*keyframe = frame;
}
frame->pos = ctx->gop_last;
ctx->current_timestamp = frame->pts;
ctx->cache[ctx->gop_last] = frame;
ctx->gop_last = ngx_mpegts_gop_next(s, ctx->gop_last);
ngx_rtmp_shared_acquire_mpegts_frame(frame);
ngx_mpegts_gop_reset_gop(s, ctx, frame);
ngx_mpegts_gop_print_cache(s, ctx);
return NGX_OK;
}
static ngx_int_t
ngx_mpegts_gop_send_gop(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss)
{
ngx_mpegts_gop_app_conf_t *gacf;
ngx_mpegts_gop_ctx_t *sctx, *ssctx;
ngx_mpegts_frame_t *frame, *keyframe;
size_t pos;
gacf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_gop_module);
sctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_gop_module);
ssctx = ngx_rtmp_get_module_ctx(ss, ngx_mpegts_gop_module);
/* already send gop */
if (ssctx->send_gop == 2) {
return NGX_OK;
}
//*****************************************************************
//
// TODO: send pat pmt
//
//*****************************************************************
/* link frame in s to ss */
if (ssctx->send_gop == 0) {
ssctx->gop_pos = sctx->gop_pos;
if (sctx->cache[ssctx->gop_pos] == NULL) {
return NGX_AGAIN;
}
ssctx->send_gop = 1;
ssctx->first_timestamp = sctx->cache[ssctx->gop_pos]->pts;
ssctx->base_type = sctx->cache[ssctx->gop_pos]->type;
} else {
if (sctx->cache[ssctx->gop_pos] == NULL) {
ssctx->gop_pos = sctx->gop_pos;
}
}
frame = NULL;
keyframe = sctx->keyframe;
while (ss->roll_back && keyframe &&
((sctx->current_timestamp - keyframe->pts) > ss->roll_back * 90))
{
ngx_log_error(NGX_LOG_INFO, s->log, 0,
"rtmp-gop: send_gop| curr %D - k %D, %D",
sctx->current_timestamp, keyframe->pts, ss->roll_back * 90);
frame = keyframe;
keyframe = keyframe->next;
}
if (frame == NULL) {
pos = ssctx->gop_pos;
frame = sctx->cache[pos];
} else {
pos = frame->pos;
}
while (frame) {
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
"send gop link %D, type %d, curr %D",
frame->pts/90, frame->type, sctx->current_timestamp/90);
if (ngx_mpegts_gop_link_frame(ss, frame) == NGX_AGAIN) {
break;
}
if (!gacf->send_all && frame->type == ssctx->base_type &&
frame->pts - ssctx->first_timestamp >= gacf->one_off_send * 90)
{
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
"gone %D, type %d, first %D, curr %D, send %D",
frame->pts/90, frame->type, ssctx->first_timestamp/90,
sctx->current_timestamp/90, gacf->one_off_send);
ssctx->send_gop = 2;
pos = ngx_mpegts_gop_next(s, pos);
break;
}
pos = ngx_mpegts_gop_next(s, pos);
frame = sctx->cache[pos];
}
if (frame == NULL) { /* send all frame in cache */
ssctx->send_gop = 2;
}
ssctx->gop_pos = pos;
ngx_rtmp_send_message(ss, NULL, 0);
return NGX_AGAIN;
}
ngx_int_t
ngx_mpegts_gop_send(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss)
{
ngx_mpegts_gop_app_conf_t *gacf;
ngx_mpegts_gop_ctx_t *sctx, *ssctx;
ngx_mpegts_frame_t *frame;
gacf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_gop_module);
if (gacf->cache_time == 0) {
return NGX_DECLINED;
}
sctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_gop_module);
if (sctx == NULL) { /* publisher doesn't publish av frame */
return NGX_DECLINED;
}
ssctx = ngx_rtmp_get_module_ctx(ss, ngx_mpegts_gop_module);
if (ssctx == NULL) {
ssctx = ngx_pcalloc(ss->pool, sizeof(ngx_mpegts_gop_ctx_t));
if (ssctx == NULL) {
return NGX_ERROR;
}
ngx_rtmp_set_ctx(ss, ssctx, ngx_mpegts_gop_module);
}
if (ngx_mpegts_gop_send_gop(s, ss) == NGX_AGAIN) {
return NGX_OK;
}
if (sctx->cache[ssctx->gop_pos] == NULL) {
ngx_log_error(NGX_LOG_ERR, ss->log, 0,
"mpegts-gop: gop_send| current gop pos is NULL, "
"skip to new postion [pos %d last %d] %d",
sctx->gop_pos, sctx->gop_last, ssctx->gop_pos);
ssctx->gop_pos = sctx->gop_pos;
}
frame = sctx->cache[ssctx->gop_pos];
if (ngx_mpegts_gop_link_frame(ss, frame) == NGX_AGAIN) {
return NGX_AGAIN;
}
ssctx->gop_pos = ngx_mpegts_gop_next(s, ssctx->gop_pos);
ngx_rtmp_send_message(ss, NULL, 0);
return NGX_OK;
}
static ngx_int_t
ngx_mpegts_gop_offset_frames(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss,
ngx_msec_t time_offset, ngx_msec_t duration)
{
ngx_mpegts_gop_ctx_t *sctx, *ssctx;
ngx_mpegts_frame_t *frame, *keyframe;
size_t pos;
sctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_gop_module);
ssctx = ngx_rtmp_get_module_ctx(ss, ngx_mpegts_gop_module);
/* already send gop */
if (ssctx->send_gop == 2) {
return NGX_OK;
}
//*****************************************************************
//
// TODO: send pat pmt
//
//*****************************************************************
/* link frame in s to ss */
if (ssctx->send_gop == 0) {
ssctx->gop_pos = sctx->gop_pos;
if (sctx->cache[ssctx->gop_pos] == NULL) {
return NGX_AGAIN;
}
ssctx->send_gop = 1;
ssctx->first_timestamp = sctx->cache[ssctx->gop_pos]->pts;
ssctx->base_type = sctx->cache[ssctx->gop_pos]->type;
} else {
if (sctx->cache[ssctx->gop_pos] == NULL) {
ssctx->gop_pos = sctx->gop_pos;
}
}
frame = NULL;
keyframe = sctx->keyframe;
while (time_offset && keyframe &&
((sctx->current_timestamp - keyframe->pts) > time_offset * 90))
{
ngx_log_error(NGX_LOG_DEBUG, ss->log, 0,
"mpegts-gop: offset_frames| curr %D - k %D = %D, %D",
sctx->current_timestamp, keyframe->pts,
(sctx->current_timestamp - keyframe->pts) / 90, time_offset);
frame = keyframe;
keyframe = keyframe->next;
ssctx->first_timestamp = frame->pts;
ssctx->base_type = frame->type;
}
if (frame == NULL) {
pos = ssctx->gop_pos;
frame = sctx->cache[pos];
} else {
pos = frame->pos;
}
while (frame) {
if (ngx_mpegts_gop_link_frame(ss, frame) == NGX_AGAIN) {
break;
}
if (frame->type == ssctx->base_type &&
frame->pts - ssctx->first_timestamp >= duration * 90)
{
ngx_log_error(NGX_LOG_INFO, ss->log, 0, "gone %D, first %D, curr %D",
frame->pts, ssctx->first_timestamp, sctx->current_timestamp);
ssctx->send_gop = 2;
pos = ngx_mpegts_gop_next(s, pos);
break;
}
pos = ngx_mpegts_gop_next(s, pos);
frame = sctx->cache[pos];
}
if (frame == NULL) { /* send all frame in cache */
ssctx->send_gop = 2;
}
ssctx->gop_pos = pos;
return NGX_AGAIN;
}
ngx_int_t
ngx_mpegts_gop_link(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss,
ngx_msec_t time_offset, ngx_msec_t duration)
{
ngx_mpegts_gop_app_conf_t *gacf;
ngx_mpegts_gop_ctx_t *sctx, *ssctx;
ngx_mpegts_frame_t *frame;
gacf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_gop_module);
if (gacf->cache_time == 0) {
return NGX_DECLINED;
}
sctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_gop_module);
if (sctx == NULL) { /* publisher doesn't publish av frame */
return NGX_DECLINED;
}
ssctx = ngx_rtmp_get_module_ctx(ss, ngx_mpegts_gop_module);
if (ssctx == NULL) {
ssctx = ngx_pcalloc(ss->pool, sizeof(ngx_mpegts_gop_ctx_t));
if (ssctx == NULL) {
return NGX_ERROR;
}
ngx_rtmp_set_ctx(ss, ssctx, ngx_mpegts_gop_module);
}
if (ngx_mpegts_gop_offset_frames(s, ss, time_offset, duration) == NGX_AGAIN)
{
return NGX_OK;
}
/* new frame is video key frame */
if (sctx->cache[ssctx->gop_pos] == NULL) {
ngx_log_error(NGX_LOG_ERR, ss->log, 0,
"mpegts-gop: link| current gop pos is NULL, "
"skip to new postion [pos %d last %d] %d",
sctx->gop_pos, sctx->gop_last, ssctx->gop_pos);
ssctx->gop_pos = sctx->gop_pos;
}
frame = sctx->cache[ssctx->gop_pos];
if (ngx_mpegts_gop_link_frame(ss, frame) == NGX_AGAIN) {
return NGX_AGAIN;
}
ssctx->gop_pos = ngx_mpegts_gop_next(s, ssctx->gop_pos);
return NGX_OK;
}
static ngx_int_t
ngx_mpegts_gop_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
{
ngx_mpegts_gop_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_gop_module);
if (ctx == NULL) {
goto next;
}
if (!s->published) {
goto next;
}
/* free cache in publisher */
while (ctx->gop_pos != ctx->gop_last) {
ngx_rtmp_shared_free_mpegts_frame(ctx->cache[ctx->gop_pos]);
ctx->gop_pos = ngx_mpegts_gop_next(s, ctx->gop_pos);
}
next:
return next_close_stream(s, v);
}
static ngx_int_t
ngx_mpegts_gop_av(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame)
{
if (frame->type == NGX_MPEGTS_MSG_VIDEO) {
ngx_mpegts_gop_cache(s, frame);
return next_mpegts_video(s, frame);
} else if (frame->type == NGX_MPEGTS_MSG_AUDIO) {
ngx_mpegts_gop_cache(s, frame);
return next_mpegts_audio(s, frame);
}
return NGX_ERROR;
}
static ngx_int_t
ngx_mpegts_gop_postconfiguration(ngx_conf_t *cf)
{
next_close_stream = ngx_rtmp_close_stream;
ngx_rtmp_close_stream = ngx_mpegts_gop_close_stream;
next_mpegts_video = ngx_mpegts_video;
ngx_mpegts_video = ngx_mpegts_gop_av;
next_mpegts_audio = ngx_mpegts_audio;
ngx_mpegts_audio = ngx_mpegts_gop_av;
return NGX_OK;
}

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) Pingo (cczjp89@gmail.com)
*/
#ifndef _NGX_MPEGTS_GOP_MODULE_H_INCLUDE_
#define _NGX_MPEGTS_GOP_MODULE_H_INCLUDE_
#include "ngx_rtmp.h"
ngx_int_t
ngx_mpegts_gop_cache(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame);
ngx_int_t
ngx_mpegts_gop_link(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss,
ngx_msec_t time_offset, ngx_msec_t duration);
ngx_int_t
ngx_mpegts_gop_send(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss);
#endif

View File

@ -0,0 +1,611 @@
/*
* Copyright (C) Pingo (cczjp89@gmail.com)
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_http.h>
#include "ngx_rtmp.h"
#include "ngx_rtmp_cmd_module.h"
#include "ngx_rbuf.h"
#include "ngx_http_set_header.h"
#include "ngx_rtmp_monitor_module.h"
#include "ngx_mpegts_gop_module.h"
#include "ngx_mpegts_live_module.h"
static char *ngx_mpegts_http(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *ngx_mpegts_http_create_loc_conf(ngx_conf_t *cf);
static char *ngx_mpegts_http_merge_loc_conf(ngx_conf_t *cf, void *parent,
void *child);
static ngx_keyval_t ngx_mpegts_http_headers[] = {
{ ngx_string("Cache-Control"), ngx_string("no-cache") },
// { ngx_string("Content-Type"), ngx_string("video/x-ts") },
{ ngx_null_string, ngx_null_string }
};
typedef struct {
ngx_rtmp_session_t *session;
} ngx_mpegts_http_ctx_t;
typedef struct {
ngx_str_t app;
ngx_str_t flashver;
ngx_str_t swf_url;
ngx_str_t tc_url;
ngx_str_t page_url;
ngx_rtmp_addr_conf_t *addr_conf;
} ngx_mpegts_http_loc_conf_t;
static ngx_command_t ngx_mpegts_http_commands[] = {
{ ngx_string("ts_live"),
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
ngx_mpegts_http,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_mpegts_http_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_mpegts_http_create_loc_conf, /* create location configuration */
ngx_mpegts_http_merge_loc_conf /* merge location configuration */
};
ngx_module_t ngx_mpegts_http_module = {
NGX_MODULE_V1,
&ngx_mpegts_http_module_ctx, /* module context */
ngx_mpegts_http_commands, /* module directives */
NGX_HTTP_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 ngx_int_t
ngx_mpegts_http_send_header(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_keyval_t *h;
ngx_chain_t out;
ngx_mpegts_http_ctx_t *ctx;
ngx_rtmp_session_t *s, *ps;
if (r->header_sent) {
return NGX_OK;
}
ctx = ngx_http_get_module_ctx(r, ngx_mpegts_http_module);
s = ctx->session;
ps = s->live_stream->publish_ctx->session;
r->headers_out.status = NGX_HTTP_OK;
r->keepalive = 0; /* set Connection to closed */
h = ngx_mpegts_http_headers;
while (h->key.len) {
rc = ngx_http_set_header_out(r, &h->key, &h->value);
if (rc != NGX_OK) {
return rc;
}
++h;
}
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK) {
return rc;
}
ngx_memzero(&out, sizeof(out));
out.buf = ngx_create_temp_buf(s->pool, 376);
out.buf->last = ngx_cpymem(out.buf->pos,
ngx_rtmp_mpegts_pat, 188);
ngx_rtmp_mpegts_gen_pmt(ps->vcodec, ps->acodec, s->log, out.buf->last);
out.buf->last += 188;
out.buf->flush = 1;
out.buf->memory = 1;
return ngx_http_output_filter(r, &out);
}
static void
ngx_mpegts_http_write_handler(ngx_http_request_t *r)
{
ngx_mpegts_http_ctx_t *ctx;
ngx_rtmp_session_t *s;
ngx_event_t *wev;
size_t present, sent;
ngx_int_t rc;
ngx_chain_t *cl;
wev = r->connection->write;
if (r->connection->destroyed) {
return;
}
ctx = ngx_http_get_module_ctx(r, ngx_mpegts_http_module);
s = ctx->session;
if (wev->timedout) {
ngx_log_error(NGX_LOG_INFO, s->log, NGX_ETIMEDOUT,
"mpegts-http: write_handler| client timed out");
r->connection->timedout = 1;
s->finalize_reason = NGX_LIVE_FLV_SEND_TIMEOUT;
if (r->header_sent) {
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
} else {
r->error_page = 1;
ngx_http_finalize_request(r, NGX_HTTP_SERVICE_UNAVAILABLE);
}
return;
}
if (wev->timer_set) {
ngx_del_timer(wev);
}
if (ngx_rtmp_core_main_conf->fast_reload && (ngx_exiting || ngx_terminate)) {
r->error_page = 1;
ngx_http_finalize_request(r, NGX_HTTP_SERVICE_UNAVAILABLE);
return;
}
if (ngx_rtmp_prepare_merge_frame(s) == NGX_ERROR) {
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
if (s->out_chain) {
rc = ngx_mpegts_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK) {
s->finalize_reason = NGX_LIVE_FLV_SEND_ERR;
ngx_http_finalize_request(r, rc);
return;
}
}
while (s->out_chain) {
present = r->connection->sent;
if (r->connection->buffered) {
rc = ngx_http_output_filter(r, NULL);
} else {
rc = ngx_http_output_filter(r, s->out_chain);
}
sent = r->connection->sent - present;
ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_out, sent);
if (rc == NGX_AGAIN) {
ngx_add_timer(wev, s->timeout);
if (ngx_handle_write_event(wev, 0) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
"mpegts-http: write_handler| write event failed");
ngx_http_finalize_request(r, NGX_ERROR);
}
return;
}
if (rc == NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
"mpegts-http: write_handler| send error");
s->finalize_reason = NGX_LIVE_FLV_SEND_ERR;
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
/* NGX_OK */
for (cl = s->out_chain; cl;) {
s->out_chain = cl->next;
ngx_free_chain(s->pool, cl);
cl = s->out_chain;
}
if (ngx_rtmp_prepare_merge_frame(s) == NGX_ERROR) {
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
}
if (wev->active) {
ngx_del_event(wev, NGX_WRITE_EVENT, 0);
}
}
static void
ngx_mpegts_http_parse_url(ngx_http_request_t *r, ngx_str_t *app,
ngx_str_t *name)
{
u_char *p, *end, *pos;
p = r->uri.data + 1; /* skip '/' */
end = r->uri.data + r->uri.len;
app->data = p;
pos = (u_char *) ngx_strnstr(p, ".ts", end - p);
if (pos) {
end = pos;
}
p = (u_char *) ngx_strnstr(p, "/", end - p);
while (p) {
name->data = p;
p = (u_char *) ngx_strnstr(p + 1, "/", end - p);
}
if (name->data == NULL) {
return;
}
app->len = name->data - app->data;
++name->data;
name->len = end - name->data;
}
static ngx_int_t
ngx_mpegts_http_parse(ngx_http_request_t *r, ngx_rtmp_session_t *s,
ngx_rtmp_play_t *v)
{
ngx_mpegts_http_loc_conf_t *hflcf;
ngx_str_t app, stream;
size_t tcurl_len;
u_char *p;
hflcf = ngx_http_get_module_loc_conf(r, ngx_mpegts_http_module);
ngx_memzero(&app, sizeof(ngx_str_t));
ngx_memzero(&stream, sizeof(ngx_str_t));
ngx_mpegts_http_parse_url(r, &app, &stream);
if (app.len == 0 || stream.len == 0 || stream.len > NGX_RTMP_MAX_NAME) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"mpegts-http: http_parse| url error: %V", &r->uri);
return NGX_HTTP_BAD_REQUEST;
}
if (hflcf->app.len) {
app = hflcf->app;
}
ngx_http_arg(r, (u_char *) "app", 3, &app);
s->app = app;
if (ngx_http_arg(r, (u_char *) "flashver", 8, &s->flashver) != NGX_OK) {
s->flashver = hflcf->flashver;
}
/* tc_url */
#if (NGX_HTTP_SSL)
if (r->connection->ssl) {
tcurl_len = sizeof("https://") - 1;
} else
#endif
{
tcurl_len = sizeof("http://") - 1;
}
tcurl_len += r->headers_in.server.len + 1 + app.len;
s->tc_url.len = tcurl_len;
s->tc_url.data = ngx_pcalloc(r->pool, tcurl_len);
if (s->tc_url.data == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
p = s->tc_url.data;
#if (NGX_HTTP_SSL)
if (r->connection->ssl) {
p = ngx_cpymem(p, "https://", sizeof("https://") - 1);
} else
#endif
{
p = ngx_cpymem(p, "http://", sizeof("http://") - 1);
}
p = ngx_cpymem(p, r->headers_in.server.data, r->headers_in.server.len);
*p++ = '/';
p = ngx_cpymem(p, app.data, app.len);
/* page_url */
if (r->headers_in.referer) {
s->page_url = r->headers_in.referer->value;
} else {
s->page_url = hflcf->page_url;
}
s->acodecs = 0x0DF7;
s->vcodecs = 0xFC;
ngx_memcpy(v->name, stream.data, stream.len);
if (r->args.len) {
ngx_memcpy(v->args, r->args.data,
ngx_min(r->args.len, NGX_RTMP_MAX_ARGS));
}
ngx_rtmp_cmd_middleware_init(s);
return NGX_OK;
}
static void
ngx_mpegts_http_cleanup(void *data)
{
ngx_http_request_t *r;
ngx_mpegts_http_ctx_t *ctx;
r = data;
ctx = ngx_http_get_module_ctx(r, ngx_mpegts_http_module);
if (ctx == NULL) {
return;
}
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"mpegts-http: cleanup| cleanup");
if (ctx->session) {
ctx->session->request = NULL;
if (ctx->session->finalize_reason == 0) {
ctx->session->finalize_reason = r->connection->read->error?
NGX_LIVE_FLV_RECV_ERR : NGX_LIVE_NORMAL_CLOSE;
}
ngx_rtmp_finalize_fake_session(ctx->session);
}
}
static ngx_chain_t *
ngx_mpegts_http_prepare_out_chain(ngx_rtmp_session_t *s)
{
ngx_mpegts_frame_t *frame;
ngx_chain_t *head, **ll, *cl;
frame = NULL;
head = NULL;
if (s->out_pos != s->out_last) {
frame = s->mpegts_out[s->out_pos];
}
/* no frame to send */
if (frame == NULL) {
return NULL;
}
ll = &head;
for (cl = frame->chain; cl; cl = cl->next) {
(*ll) = ngx_get_chainbuf(0, 0);
if (*ll == NULL) {
goto falied;
}
(*ll)->buf->pos = cl->buf->pos;
(*ll)->buf->last = cl->buf->last;
if (!(*ll)->next) {
(*ll)->buf->flush = 1;
}
ll = &(*ll)->next;
}
return head;
falied:
ngx_put_chainbufs(head);
ngx_rtmp_finalize_session(s);
return NULL;
}
static ngx_int_t
ngx_mpegts_http_handler(ngx_http_request_t *r)
{
ngx_mpegts_http_loc_conf_t *hflcf;
ngx_mpegts_http_ctx_t *ctx;
ngx_rtmp_session_t *s;
ngx_rtmp_play_t v;
ngx_int_t rc;
ngx_uint_t n;
ngx_rtmp_core_srv_conf_t *cscf;
ngx_rtmp_core_app_conf_t **cacfp;
ngx_http_cleanup_t *cln;
ngx_rtmp_core_main_conf_t *cmcf;
ctx = ngx_pcalloc(r->pool, sizeof(ngx_mpegts_http_ctx_t));
if (ctx == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_http_set_ctx(r, ctx, ngx_mpegts_http_module);
/* cleanup handler */
cln = ngx_http_cleanup_add(r, 0);
if (cln == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
cln->handler = ngx_mpegts_http_cleanup;
cln->data = r;
hflcf = ngx_http_get_module_loc_conf(r, ngx_mpegts_http_module);
/* create fake session */
s = ngx_rtmp_create_session(hflcf->addr_conf);
if (s == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ctx->session = s;
s->connection = r->connection;
ngx_rtmp_set_combined_log(s, r->connection->log->data,
r->connection->log->handler);
s->log->connection = r->connection->number;
s->number = r->connection->number;
s->remote_addr_text.data = ngx_pcalloc(s->pool, r->connection->addr_text.len);
s->remote_addr_text.len = r->connection->addr_text.len;
ngx_memcpy(s->remote_addr_text.data,
r->connection->addr_text.data, r->connection->addr_text.len);
/* get host, app, stream name */
ngx_memzero(&v, sizeof(ngx_rtmp_play_t));
rc = ngx_mpegts_http_parse(r, s, &v);
if (rc != NGX_OK) {
return rc;
}
if (ngx_rtmp_set_virtual_server(s, &s->domain)) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
s->live_type = NGX_MPEGTS_LIVE;
s->live_server = ngx_live_create_server(&s->serverid);
s->request = r;
v.silent = 1;
cacfp = cscf->applications.elts;
for (n = 0; n < cscf->applications.nelts; ++n, ++cacfp) {
if ((*cacfp)->name.len == s->app.len &&
ngx_strncmp((*cacfp)->name.data, s->app.data, s->app.len) == 0)
{
/* found app! */
s->app_conf = (*cacfp)->app_conf;
break;
}
}
if (s->app_conf == NULL) {
if (cscf->default_app == NULL || cscf->default_app->app_conf == NULL) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"mpegts-http: http_handler| "
"application not found '%V'", &s->app);
return NGX_HTTP_NOT_FOUND;
}
s->app_conf = cscf->default_app->app_conf;
}
s->prepare_handler = ngx_mpegts_http_prepare_out_chain;
s->stage = NGX_LIVE_PLAY;
s->ptime = ngx_current_msec;
cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);
s->variables = ngx_pcalloc(s->pool, cmcf->variables.nelts
* sizeof(ngx_http_variable_value_t));
if (s->variables == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (ngx_rtmp_play_filter(s, &v) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_add_timer(r->connection->write, s->timeout);
r->read_event_handler = ngx_http_test_reading;
r->write_event_handler = ngx_mpegts_http_write_handler;
++r->count;
return NGX_DONE;
}
static void *
ngx_mpegts_http_create_loc_conf(ngx_conf_t *cf)
{
ngx_mpegts_http_loc_conf_t *hflcf;
hflcf = ngx_pcalloc(cf->pool, sizeof(ngx_mpegts_http_loc_conf_t));
if (hflcf == NULL) {
return NULL;
}
return hflcf;
}
static char *
ngx_mpegts_http_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_mpegts_http_loc_conf_t *prev = parent;
ngx_mpegts_http_loc_conf_t *conf = child;
ngx_conf_merge_str_value(conf->app, prev->app, "");
ngx_conf_merge_str_value(conf->flashver, prev->flashver, "");
ngx_conf_merge_str_value(conf->swf_url, prev->swf_url, "");
ngx_conf_merge_str_value(conf->tc_url, prev->tc_url, "");
ngx_conf_merge_str_value(conf->page_url, prev->page_url, "");
return NGX_CONF_OK;
}
static char *
ngx_mpegts_http(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
ngx_mpegts_http_loc_conf_t *hflcf;
ngx_str_t *value, v;
ngx_uint_t i;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_mpegts_http_handler;
hflcf = conf;
value = cf->args->elts;
hflcf->addr_conf = ngx_rtmp_find_related_addr_conf(cf->cycle, &value[1]);
if (hflcf->addr_conf == NULL) {
return NGX_CONF_ERROR;
}
for (i = 2; i < cf->args->nelts; ++i) {
if (ngx_strncmp(value[i].data, "app=", 4) == 0) {
v.data = value[i].data + 4;
v.len = value[i].len - 4;
hflcf->app = v;
} else {
return NGX_CONF_ERROR;
}
}
return NGX_CONF_OK;
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) Pingo (cczjp89@gmail.com)
*/
#ifndef _NGX_RTMP_MPEGTS_MODULE_H
#define _NGX_RTMP_MPEGTS_MODULE_H
#include "ngx_rtmp.h"
typedef ngx_int_t (*ngx_mpegts_video_pt)(ngx_rtmp_session_t *s,
ngx_mpegts_frame_t *frame);
typedef ngx_int_t (*ngx_mpegts_audio_pt)(ngx_rtmp_session_t *s,
ngx_mpegts_frame_t *frame);
extern ngx_mpegts_video_pt ngx_mpegts_video;
extern ngx_mpegts_audio_pt ngx_mpegts_audio;
ngx_int_t
ngx_rtmp_mpegts_gen_pmt(ngx_int_t vcodec, ngx_int_t acodec,
ngx_log_t *log, u_char *pmt);
ngx_int_t
ngx_mpegts_live_video_filter(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame);
ngx_int_t
ngx_mpegts_live_audio_filter(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame);
extern u_char ngx_rtmp_mpegts_pat[];
#endif

View File

@ -0,0 +1,449 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include "ngx_live.h"
static void *ngx_live_create_conf(ngx_cycle_t *cf);
static char *ngx_live_init_conf(ngx_cycle_t *cycle, void *conf);
static ngx_command_t ngx_live_commands[] = {
{ ngx_string("stream_buckets"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
0,
offsetof(ngx_live_conf_t, stream_buckets),
NULL },
{ ngx_string("server_buckets"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
0,
offsetof(ngx_live_conf_t, server_buckets),
NULL },
ngx_null_command
};
static ngx_core_module_t ngx_live_module_ctx = {
ngx_string("live"),
ngx_live_create_conf, /* create conf */
ngx_live_init_conf /* init conf */
};
ngx_module_t ngx_live_module = {
NGX_MODULE_V1,
&ngx_live_module_ctx, /* module context */
ngx_live_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 void *
ngx_live_create_conf(ngx_cycle_t *cycle)
{
ngx_live_conf_t *lcf;
lcf = ngx_pcalloc(cycle->pool, sizeof(ngx_live_conf_t));
if (lcf == NULL) {
return NULL;
}
lcf->stream_buckets = NGX_CONF_UNSET_SIZE;
lcf->server_buckets = NGX_CONF_UNSET_SIZE;
return lcf;
}
static char *
ngx_live_init_conf(ngx_cycle_t *cycle, void *conf)
{
ngx_live_conf_t *lcf = conf;
lcf->pool = ngx_create_pool(4096, cycle->log);
if (lcf->pool == NULL) {
return NGX_CONF_ERROR;
}
ngx_conf_init_size_value(lcf->stream_buckets, 10007);
ngx_conf_init_size_value(lcf->server_buckets, 1031);
lcf->servers = ngx_pcalloc(lcf->pool,
sizeof(ngx_live_server_t *) * lcf->server_buckets);
if (lcf->servers == NULL) {
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
static ngx_live_server_t **
ngx_live_find_server(ngx_str_t *serverid)
{
ngx_live_conf_t *lcf;
ngx_live_server_t **psrv;
lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_live_module);
psrv = &lcf->servers[ngx_hash_key(serverid->data, serverid->len)
% lcf->server_buckets];
for (; *psrv; psrv = &(*psrv)->next) {
if (ngx_strlen((*psrv)->serverid) == serverid->len &&
ngx_memcmp((*psrv)->serverid, serverid->data, serverid->len) == 0)
{
break;
}
}
return psrv;
}
static ngx_live_server_t *
ngx_live_get_server(ngx_str_t *serverid)
{
ngx_live_conf_t *lcf;
ngx_live_server_t *srv;
if (serverid->len > NGX_LIVE_SERVERID_LEN - 1) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"serverid too long: %ui", serverid->len);
return NULL;
}
lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_live_module);
srv = lcf->free_server;
if (srv == NULL) {
srv = ngx_pcalloc(lcf->pool, sizeof(ngx_live_server_t));
if (srv == NULL) {
return NULL;
}
srv->streams = ngx_pcalloc(lcf->pool,
sizeof(ngx_live_stream_t *) * lcf->stream_buckets);
if (srv->streams == NULL) {
return NULL;
}
++lcf->alloc_server_count;
} else {
lcf->free_server = srv->next;
--lcf->free_server_count;
}
*ngx_cpymem(srv->serverid, serverid->data, serverid->len) = 0;
srv->deleted = 0;
srv->n_stream = 0;
return srv;
}
static void
ngx_live_put_server(ngx_live_server_t *server)
{
ngx_live_conf_t *lcf;
lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_live_module);
server->next = lcf->free_server;
lcf->free_server = server;
++lcf->free_server_count;
}
static ngx_live_stream_t **
ngx_live_find_stream(ngx_live_server_t *server, ngx_str_t *stream)
{
ngx_live_conf_t *lcf;
ngx_live_stream_t **pst;
lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_live_module);
pst = &server->streams[ngx_hash_key(stream->data, stream->len)
% lcf->stream_buckets];
for (; *pst; pst = &(*pst)->next) {
if (ngx_strlen((*pst)->name) == stream->len &&
ngx_memcmp((*pst)->name, stream->data, stream->len) == 0)
{
break;
}
}
return pst;
}
static ngx_live_stream_t *
ngx_live_get_stream(ngx_str_t *stream)
{
ngx_live_conf_t *lcf;
ngx_live_stream_t *st;
if (stream->len > NGX_LIVE_STREAM_LEN - 1) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"stream too long: %ui", stream->len);
return NULL;
}
lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_live_module);
st = lcf->free_stream;
if (st == NULL) {
st = ngx_pcalloc(lcf->pool, sizeof(ngx_live_stream_t));
++lcf->alloc_stream_count;
} else {
lcf->free_stream = st->next;
--lcf->free_stream_count;
ngx_memzero(st, sizeof(ngx_live_stream_t));
}
*ngx_cpymem(st->name, stream->data, stream->len) = 0;
st->pslot = -1;
st->epoch = ngx_current_msec;
ngx_map_init(&st->pubctx, ngx_map_hash_int, ngx_cmp_int);
return st;
}
static void
ngx_live_put_stream(ngx_live_stream_t *st)
{
ngx_live_conf_t *lcf;
lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_live_module);
st->next = lcf->free_stream;
lcf->free_stream = st;
++lcf->free_stream_count;
}
ngx_live_server_t *
ngx_live_create_server(ngx_str_t *serverid)
{
ngx_live_server_t **psrv;
psrv = ngx_live_find_server(serverid);
if (*psrv) {
(*psrv)->deleted = 0;
return *psrv;
}
*psrv = ngx_live_get_server(serverid);
return *psrv;
}
ngx_live_server_t *
ngx_live_fetch_server(ngx_str_t *serverid)
{
ngx_live_server_t **psrv;
psrv = ngx_live_find_server(serverid);
return *psrv;
}
void
ngx_live_delete_server(ngx_str_t *serverid)
{
ngx_live_server_t **psrv, *srv;
psrv = ngx_live_find_server(serverid);
if (*psrv == NULL) {
return;
}
if ((*psrv)->n_stream != 0) {
(*psrv)->deleted = 1;
}
if ((*psrv)->n_stream == 0) {
srv = *psrv;
*psrv = srv->next;
ngx_live_put_server(srv);
}
}
ngx_live_stream_t *
ngx_live_create_stream(ngx_str_t *serverid, ngx_str_t *stream)
{
ngx_live_server_t **psrv;
ngx_live_stream_t **pst;
psrv = ngx_live_find_server(serverid);
if (*psrv == NULL) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"server %V does not exist when create stream", serverid);
return NULL;
}
pst = ngx_live_find_stream(*psrv, stream);
if (*pst) {
return *pst;
}
*pst = ngx_live_get_stream(stream);
++(*psrv)->n_stream;
return *pst;
}
ngx_live_stream_t *
ngx_live_fetch_stream(ngx_str_t *serverid, ngx_str_t *stream)
{
ngx_live_server_t **psrv;
ngx_live_stream_t **pst;
psrv = ngx_live_find_server(serverid);
if (*psrv == NULL) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"server %V does not exist when fetch stream", serverid);
return NULL;
}
pst = ngx_live_find_stream(*psrv, stream);
return *pst;
}
void
ngx_live_delete_stream(ngx_str_t *serverid, ngx_str_t *stream)
{
ngx_live_server_t **psrv;
ngx_live_stream_t **pst, *st;
psrv = ngx_live_find_server(serverid);
if (*psrv == NULL) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"server %V does not exist when delete stream", serverid);
return;
}
pst = ngx_live_find_stream(*psrv, stream);
if (*pst == NULL) {
return;
}
st = *pst;
*pst = st->next;
ngx_live_put_stream(st);
--(*psrv)->n_stream;
if ((*psrv)->deleted && (*psrv)->n_stream == 0) {
ngx_live_delete_server(serverid);
}
}
void
ngx_live_create_ctx(ngx_rtmp_session_t *s, unsigned publishing)
{
ngx_rtmp_core_ctx_t *ctx, **pctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_core_module);
if (ctx == NULL) {
ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_core_ctx_t));
if (ctx == NULL) {
return;
}
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_core_module);
}
ctx->publishing = publishing;
ctx->session = s;
if (publishing) {
pctx = &s->live_stream->publish_ctx;
} else {
pctx = &s->live_stream->play_ctx;
}
ctx->next = (*pctx);
*pctx = ctx;
}
void
ngx_live_delete_ctx(ngx_rtmp_session_t *s)
{
ngx_rtmp_core_ctx_t *ctx, **pctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_core_module);
if (ctx == NULL) {
return;
}
if (ctx->publishing) {
pctx = &s->live_stream->publish_ctx;
} else {
pctx = &s->live_stream->play_ctx;
}
for (/* void */; *pctx; pctx = &(*pctx)->next) {
if (*pctx == ctx) {
*pctx = ctx->next;
return;
}
}
}
ngx_chain_t *
ngx_live_state(ngx_http_request_t *r)
{
ngx_live_conf_t *lcf;
ngx_chain_t *cl;
ngx_buf_t *b;
size_t len;
lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_live_module);
len = sizeof("##########ngx live state##########\n") - 1
+ sizeof("ngx_live nalloc server: \n") - 1 + NGX_OFF_T_LEN
+ sizeof("ngx_live nfree server: \n") - 1 + NGX_OFF_T_LEN
+ sizeof("ngx_live nalloc stream: \n") - 1 + NGX_OFF_T_LEN
+ sizeof("ngx_live nfree stream: \n") - 1 + NGX_OFF_T_LEN;
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NULL;
}
cl->next = NULL;
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NULL;
}
cl->buf = b;
b->last = ngx_snprintf(b->last, len,
"##########ngx live state##########\n"
"ngx_live nalloc server: %ui\nngx_live nfree server: %ui\n"
"ngx_live nalloc stream: %ui\nngx_live nfree stream: %ui\n",
lcf->alloc_server_count, lcf->free_server_count,
lcf->alloc_stream_count, lcf->free_stream_count);
return cl;
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#ifndef _NGX_LIVE_H_INCLUDED_
#define _NGX_LIVE_H_INCLUDED_
#include "ngx_rtmp.h"
typedef struct {
size_t stream_buckets;
size_t server_buckets;
ngx_live_server_t **servers;
ngx_live_server_t *free_server;
ngx_live_stream_t *free_stream;
ngx_uint_t alloc_server_count;
ngx_uint_t free_server_count;
ngx_uint_t alloc_stream_count;
ngx_uint_t free_stream_count;
ngx_pool_t *pool;
} ngx_live_conf_t;
extern ngx_module_t ngx_live_module;
/*
* paras:
* r: http request to query status of rbuf
*/
ngx_chain_t *ngx_live_state(ngx_http_request_t *r);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#ifndef _NGX_LIVE_RECORD_H_INCLUDED_
#define _NGX_LIVE_RECORD_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
#include "hls/ngx_rtmp_mpegts.h"
typedef struct {
unsigned open; /* 0 close, 1 open, 2 wait for key */
time_t last_time;
ngx_file_t index;
ngx_rtmp_mpegts_file_t ts;
ngx_file_t file;
ngx_rtmp_publish_t pubv;
ngx_uint_t audio_cc;
ngx_uint_t video_cc;
ngx_msec_t begintime;
ngx_msec_t starttime;
ngx_msec_t endtime;
off_t startsize;
off_t endsize;
ngx_msec_t publish_epoch;
ngx_msec_t basetime;
} ngx_live_record_ctx_t;
typedef ngx_int_t (*ngx_live_record_start_pt)(ngx_rtmp_session_t *s);
typedef ngx_int_t (*ngx_live_record_update_pt)(ngx_rtmp_session_t *s);
typedef ngx_int_t (*ngx_live_record_done_pt)(ngx_rtmp_session_t *s);
extern ngx_live_record_start_pt ngx_live_record_start;
extern ngx_live_record_update_pt ngx_live_record_update;
extern ngx_live_record_done_pt ngx_live_record_done;
extern ngx_module_t ngx_live_record_module;
const char *ngx_live_record_open(ngx_rtmp_session_t *s);
const char *ngx_live_record_close(ngx_rtmp_session_t *s);
#endif

View File

@ -0,0 +1,418 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include "ngx_live_relay.h"
#include "ngx_rtmp_cmd_module.h"
#include "ngx_toolkit_misc.h"
static ngx_rtmp_publish_pt next_publish;
static ngx_rtmp_play_pt next_play;
static ngx_rtmp_close_stream_pt next_close_stream;
static ngx_int_t ngx_live_relay_postconfiguration(ngx_conf_t *cf);
static void *ngx_live_relay_create_app_conf(ngx_conf_t *cf);
static char *ngx_live_relay_merge_app_conf(ngx_conf_t *cf, void *parent,
void *child);
ngx_live_push_pt ngx_live_push;
ngx_live_pull_pt ngx_live_pull;
ngx_live_push_close_pt ngx_live_push_close;
ngx_live_pull_close_pt ngx_live_pull_close;
typedef ngx_int_t (* relay_create_pt)(ngx_rtmp_session_t *rs,
ngx_live_relay_t *relay,
ngx_live_relay_url_t *url);
static relay_create_pt create_relay[] = {
ngx_live_relay_create_httpflv,
ngx_live_relay_create_rtmp
};
static const char *relay_protocol[] = {
"httpflv",
"rtmp"
};
static const char *relay_type[] = {
"push",
"pull"
};
static ngx_command_t ngx_live_relay_commands[] = {
{ ngx_string("failed_reconnect"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_live_relay_app_conf_t, failed_reconnect),
NULL },
{ ngx_string("relay_reconnect"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_live_relay_app_conf_t, relay_reconnect),
NULL },
ngx_null_command
};
static ngx_rtmp_module_t ngx_live_relay_module_ctx = {
NULL, /* preconfiguration */
ngx_live_relay_postconfiguration, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_live_relay_create_app_conf, /* create app configuration */
ngx_live_relay_merge_app_conf /* merge app configuration */
};
ngx_module_t ngx_live_relay_module = {
NGX_MODULE_V1,
&ngx_live_relay_module_ctx, /* module context */
ngx_live_relay_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_live_relay_create_app_conf(ngx_conf_t *cf)
{
ngx_live_relay_app_conf_t *racf;
racf = ngx_pcalloc(cf->pool, sizeof(ngx_live_relay_app_conf_t));
if (racf == NULL) {
return NULL;
}
racf->failed_reconnect = NGX_CONF_UNSET_MSEC;
racf->relay_reconnect = NGX_CONF_UNSET_MSEC;
return racf;
}
static char *
ngx_live_relay_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_live_relay_app_conf_t *prev = parent;
ngx_live_relay_app_conf_t *conf = child;
ngx_conf_merge_msec_value(conf->failed_reconnect, prev->failed_reconnect,
50);
ngx_conf_merge_msec_value(conf->relay_reconnect, prev->relay_reconnect,
3000);
return NGX_CONF_OK;
}
ngx_int_t
ngx_live_relay_create(ngx_rtmp_session_t *rs, ngx_live_relay_t *relay)
{
ngx_live_relay_ctx_t *ctx;
ngx_live_relay_url_t *url;
relay_create_pt create;
ctx = ngx_rtmp_get_module_ctx(rs, ngx_live_relay_module);
if (ctx->idx >= relay->urls.nelts) {
ctx->idx = 0;
}
ctx->failed_delay = 0;
url = relay->urls.elts;
url += ctx->idx;
if (url->relay_type >= NGX_LIVE_RELAY_MAXTYPE) {
ngx_log_error(NGX_LOG_ERR, rs->log, 0, "unsupported relay type %ui",
url->relay_type);
return NGX_ERROR;
}
create = create_relay[url->relay_type];
ngx_log_error(NGX_LOG_INFO, rs->log, 0,
"create %s relay %s to %V:%d, domain='%V' app='%V' name='%V' "
"pargs='%V' referer='%V' user_agent='%V'",
relay_protocol[url->relay_type], relay_type[rs->publishing],
&url->url.host, url->port, &relay->domain, &relay->app,
&relay->name, &relay->pargs, &relay->referer, &relay->user_agent);
++ctx->idx;
return create(rs, relay, url);
}
ngx_int_t
ngx_live_relay_play_local(ngx_rtmp_session_t *rs)
{
ngx_rtmp_play_t v;
ngx_memzero(&v, sizeof(ngx_rtmp_play_t));
v.silent = 1;
*(ngx_cpymem(v.name, rs->name.data, ngx_min(sizeof(v.name) - 1,
rs->name.len))) = 0;
if (rs->pargs.len) {
*(ngx_cpymem(v.args, rs->pargs.data, ngx_min(sizeof(v.args) - 1,
rs->pargs.len))) = 0;
}
return ngx_rtmp_play_filter(rs, &v);
}
ngx_int_t
ngx_live_relay_publish_local(ngx_rtmp_session_t *rs)
{
ngx_rtmp_publish_t v;
ngx_memzero(&v, sizeof(ngx_rtmp_publish_t));
v.silent = 1;
*(ngx_cpymem(v.name, rs->name.data, ngx_min(sizeof(v.name) - 1,
rs->name.len))) = 0;
if (rs->pargs.len) {
*(ngx_cpymem(v.args, rs->pargs.data, ngx_min(sizeof(v.args) - 1,
rs->pargs.len))) = 0;
}
return ngx_rtmp_publish_filter(rs, &v);
}
static ngx_int_t
ngx_live_relay_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
{
ngx_live_relay_ctx_t *ctx;
// second publish will not trigger push
if (s->live_stream->publish_ctx->next != NULL) {
goto next;
}
if (ngx_live_push(s) != NGX_OK) {
return NGX_ERROR;
}
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (ctx == NULL) {
goto next;
}
if (ctx->reconnect.timer_set) {
ngx_del_timer(&ctx->reconnect);
}
if (ctx->reconnect.posted) {
ngx_delete_posted_event(&ctx->reconnect);
}
ctx->successd = 1;
ctx->failed_reconnect = 0;
ctx->idx = 0;
next:
return next_publish(s, v);
}
static ngx_int_t
ngx_live_relay_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
{
ngx_live_relay_ctx_t *ctx;
if (ngx_live_pull(s) != NGX_OK) {
return NGX_ERROR;
}
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (ctx == NULL) {
goto next;
}
if (ctx->reconnect.timer_set) {
ngx_del_timer(&ctx->reconnect);
}
if (ctx->reconnect.posted) {
ngx_delete_posted_event(&ctx->reconnect);
}
ctx->successd = 1;
ctx->failed_reconnect = 0;
ctx->idx = 0;
next:
return next_play(s, v);
}
static ngx_int_t
ngx_live_relay_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
{
ngx_rtmp_core_ctx_t *ctx;
ngx_live_relay_ctx_t *rctx;
if (s->live_stream == NULL) {
goto next;
}
if (s->publishing) {
/*
* normal publisher close or relay puller close
* need to trigger ngx_live_pull_close
*/
ngx_live_pull_close(s);
// all publishers close, close push (play + relay)
if (s->live_stream->publish_ctx == NULL) {
for (ctx = s->live_stream->play_ctx; ctx; ctx = ctx->next) {
if (ctx->session->relay) {
ctx->session->finalize_reason = NGX_LIVE_RELAY_CLOSE;
ngx_rtmp_finalize_session(ctx->session);
}
}
}
} else {
rctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (rctx) {
// push close will trigger relay push reconnect in relay modules
ngx_live_push_close(s);
}
// all players close, close pull (publish + relay)
if (s->live_stream->play_ctx == NULL) {
for (ctx = s->live_stream->publish_ctx; ctx; ctx = ctx->next) {
if (ctx->session->relay) {
ctx->session->finalize_reason = NGX_LIVE_RELAY_CLOSE;
ngx_rtmp_finalize_session(ctx->session);
}
}
}
}
next:
return next_close_stream(s, v);
}
static ngx_int_t
ngx_live_relay_push(ngx_rtmp_session_t *s)
{
return NGX_OK;
}
static ngx_int_t
ngx_live_relay_pull(ngx_rtmp_session_t *s)
{
return NGX_OK;
}
static ngx_int_t
ngx_live_relay_push_close(ngx_rtmp_session_t *s)
{
ngx_live_relay_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (ctx && ctx->reconnect.timer_set) {
ngx_del_timer(&ctx->reconnect);
}
if (ctx && ctx->reconnect.posted) {
ngx_delete_posted_event(&ctx->reconnect);
}
return NGX_OK;
}
/*
* pull reconnect need to trigger ngx_live_pull chain
*/
static ngx_int_t
ngx_live_relay_pull_close(ngx_rtmp_session_t *s)
{
ngx_live_relay_ctx_t *ctx;
ngx_flag_t has_player;
ngx_rtmp_core_ctx_t *cctx;;
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
has_player = 0;
if (s->static_pull == 0) {
for (cctx = s->live_stream->play_ctx; cctx; cctx = cctx->next) {
if (cctx->session->relay == 0) { // has pure player, not relay push
has_player = 1;
break;
}
}
}
if (has_player || s->static_pull) { // has player in stream
// ctx is NULL, s is a normal publisher
// ctx is not NULL, s is a puller, if giveup flag set
// no need to create pull reconnect
if (ctx == NULL || !ctx->giveup) {
ngx_live_pull(s);
}
}
if (ctx && ctx->reconnect.timer_set) {
ngx_del_timer(&ctx->reconnect);
}
if (ctx && ctx->reconnect.posted) {
ngx_delete_posted_event(&ctx->reconnect);
}
return NGX_OK;
}
static ngx_int_t
ngx_live_relay_postconfiguration(ngx_conf_t *cf)
{
next_publish = ngx_rtmp_publish;
ngx_rtmp_publish = ngx_live_relay_publish;
next_play = ngx_rtmp_play;
ngx_rtmp_play = ngx_live_relay_play;
next_close_stream = ngx_rtmp_close_stream;
ngx_rtmp_close_stream = ngx_live_relay_close_stream;
ngx_live_pull = ngx_live_relay_pull;
ngx_live_push = ngx_live_relay_push;
ngx_live_pull_close = ngx_live_relay_pull_close;
ngx_live_push_close = ngx_live_relay_push_close;
return NGX_OK;
}

View File

@ -0,0 +1,99 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#ifndef _NGX_LIVE_RELAY_H_INCLUDE_
#define _NGX_LIVE_RELAY_H_INCLUDE_
#include <ngx_core.h>
#include "ngx_rtmp.h"
#define NGX_LIVE_RELAY_HTTPFLV 0
#define NGX_LIVE_RELAY_RTMP 1
#define NGX_LIVE_RELAY_MAXTYPE 2
typedef struct {
// reconnect
ngx_event_t reconnect;
ngx_flag_t successd; // connect successd
/*
* connect failed for first time, reconnect immediately
* then reconnect after failed_reconect
* then reconnect after 2 * failed_reconect
* then reconnect after 2 * 2 * failed_reconect
* ...
* when failed_reconnect is bigger than relay_reconnect
* then use relay_reconnect as failed_reconect
*
* connect failed not include connect timeout
*/
ngx_msec_t failed_reconnect; // reconnect timeout
ngx_flag_t failed_delay;
ngx_flag_t giveup; // no need to reconnect
// base para
ngx_str_t domain;
ngx_str_t app;
ngx_str_t args; // rtmp app args
ngx_str_t name;
ngx_str_t pargs;
// other para
ngx_str_t referer; // rtmp page_url
ngx_str_t user_agent; // rtmp flashver
ngx_str_t swf_url; // rtmp swf_url
uint32_t acodecs;
uint32_t vcodecs;
void *tag;
ngx_uint_t idx;
} ngx_live_relay_ctx_t;
typedef struct {
ngx_request_url_t url;
in_port_t port;
ngx_uint_t relay_type;
} ngx_live_relay_url_t;
typedef struct {
ngx_msec_t failed_reconnect;
ngx_msec_t relay_reconnect;
} ngx_live_relay_app_conf_t;
extern ngx_module_t ngx_live_relay_module;
ngx_int_t ngx_live_relay_create_httpflv(ngx_rtmp_session_t *rs,
ngx_live_relay_t *relay, ngx_live_relay_url_t *url);
ngx_int_t ngx_live_relay_create_rtmp(ngx_rtmp_session_t *rs,
ngx_live_relay_t *relay, ngx_live_relay_url_t *url);
ngx_int_t ngx_live_relay_create(ngx_rtmp_session_t *rs,
ngx_live_relay_t *relay);
ngx_int_t ngx_live_relay_play_local(ngx_rtmp_session_t *rs);
ngx_int_t ngx_live_relay_publish_local(ngx_rtmp_session_t *rs);
typedef ngx_int_t (*ngx_live_pull_pt)(ngx_rtmp_session_t *s);
typedef ngx_int_t (*ngx_live_push_pt)(ngx_rtmp_session_t *s);
typedef ngx_int_t (*ngx_live_pull_close_pt)(ngx_rtmp_session_t *s);
typedef ngx_int_t (*ngx_live_push_close_pt)(ngx_rtmp_session_t *s);
extern ngx_live_push_pt ngx_live_push;
extern ngx_live_pull_pt ngx_live_pull;
extern ngx_live_push_close_pt ngx_live_push_close;
extern ngx_live_pull_close_pt ngx_live_pull_close;
#endif

View File

@ -0,0 +1,637 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include "ngx_live_relay.h"
#include "ngx_http_client.h"
#include "ngx_toolkit_misc.h"
#include "ngx_dynamic_resolver.h"
#include "ngx_rbuf.h"
typedef struct {
ngx_uint_t status;
char *code;
char *level;
char *desc;
} ngx_http_status_code_t;
static ngx_http_status_code_t ngx_http_relay_status_code[] = {
{ 400, "NetStream.Play.BadName", "error", "Bad Request" },
{ 403, "NetStream.Play.Forbidden", "error", "Forbidden" },
{ 404, "NetStream.Play.StreamNotFound", "error", "No such stream" },
{ 503, "NetStream.Play.ServiceUnavailable", "error", "Service Unavailable"},
{ 0, "NetStream.Play.StreamError", "error", "Stream Error" }
};
static ngx_int_t
ngx_live_relay_httpflv_parse(ngx_rtmp_session_t *s, ngx_buf_t *b)
{
u_char ch, *p, *pc;
ngx_rtmp_stream_t *st;
ngx_rtmp_header_t *h;
ngx_chain_t **ll;
size_t len;
ngx_rtmp_core_srv_conf_t *cscf;
ngx_int_t rc = NGX_AGAIN;
enum {
flv_header_F = 0,
flv_header_FL,
flv_header_FLV,
flv_header_Version,
flv_header_Flags,
flv_header_DataOffset0,
flv_header_DataOffset1,
flv_header_DataOffset2,
flv_header_DataOffset3,
flv_tagsize0,
flv_tagsize1,
flv_tagsize2,
flv_tagsize3,
flv_tagtype,
flv_datasize0,
flv_datasize1,
flv_datasize2,
flv_timestamp0,
flv_timestamp1,
flv_timestamp2,
flv_timestamp_extended,
flv_streamid0,
flv_streamid1,
flv_streamid2,
flv_data
} state;
state = s->flv_state;
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
for (p = b->pos; p < b->last; ++p) {
ch = *p;
switch (state) {
case flv_header_F:
switch (ch) {
case 'F':
state = flv_header_FL;
break;
default:
rc = NGX_ERROR;
goto done;
}
break;
case flv_header_FL:
switch (ch) {
case 'L':
state = flv_header_FLV;
break;
default:
rc = NGX_ERROR;
goto done;
}
break;
case flv_header_FLV:
switch (ch) {
case 'V':
state = flv_header_Version;
break;
default:
rc = NGX_ERROR;
goto done;
}
break;
case flv_header_Version:
s->flv_version = ch;
if (s->flv_version != 1) {
rc = NGX_ERROR;
goto done;
}
state = flv_header_Flags;
break;
case flv_header_Flags:
s->flv_flags = ch;
state = flv_header_DataOffset0;
break;
case flv_header_DataOffset0:
pc = (u_char *) &s->flv_data_offset;
pc[3] = ch;
state = flv_header_DataOffset1;
break;
case flv_header_DataOffset1:
pc = (u_char *) &s->flv_data_offset;
pc[2] = ch;
state = flv_header_DataOffset2;
break;
case flv_header_DataOffset2:
pc = (u_char *) &s->flv_data_offset;
pc[1] = ch;
state = flv_header_DataOffset3;
break;
case flv_header_DataOffset3:
pc = (u_char *) &s->flv_data_offset;
pc[0] = ch;
state = flv_tagsize0;
break;
case flv_tagsize0:
s->flv_tagsize = 0;
pc = (u_char *) &s->flv_tagsize;
pc[3] = ch;
state = flv_tagsize1;
break;
case flv_tagsize1:
pc = (u_char *) &s->flv_tagsize;
pc[2] = ch;
state = flv_tagsize2;
break;
case flv_tagsize2:
pc = (u_char *) &s->flv_tagsize;
pc[1] = ch;
state = flv_tagsize3;
break;
case flv_tagsize3:
pc = (u_char *) &s->flv_tagsize;
pc[0] = ch;
st = &s->in_streams[0];
h = &st->hdr;
if (h->mlen == 0 && s->flv_first_pts == 0) {
s->flv_first_pts = 1;
if (s->flv_tagsize != 0) {
rc = NGX_ERROR;
goto done;
}
} else {
if (h->mlen + 11 != s->flv_tagsize) {
rc = NGX_ERROR;
goto done;
}
}
state = flv_tagtype;
break;
case flv_tagtype:
if (ch != NGX_RTMP_MSG_AMF_META && ch != NGX_RTMP_MSG_AUDIO
&& ch != NGX_RTMP_MSG_VIDEO)
{
rc = NGX_ERROR;
goto done;
}
st = &s->in_streams[0];
h = &st->hdr;
h->type = ch;
state = flv_datasize0;
break;
case flv_datasize0:
st = &s->in_streams[0];
h = &st->hdr;
h->mlen = 0;
pc = (u_char *) &h->mlen;
pc[2] = ch;
state = flv_datasize1;
break;
case flv_datasize1:
st = &s->in_streams[0];
h = &st->hdr;
pc = (u_char *) &h->mlen;
pc[1] = ch;
state = flv_datasize2;
break;
case flv_datasize2:
st = &s->in_streams[0];
h = &st->hdr;
pc = (u_char *) &h->mlen;
pc[0] = ch;
state = flv_timestamp0;
st->len = h->mlen;
break;
case flv_timestamp0:
st = &s->in_streams[0];
h = &st->hdr;
pc = (u_char *) &h->timestamp;
pc[2] = ch;
state = flv_timestamp1;
break;
case flv_timestamp1:
st = &s->in_streams[0];
h = &st->hdr;
pc = (u_char *) &h->timestamp;
pc[1] = ch;
state = flv_timestamp2;
break;
case flv_timestamp2:
st = &s->in_streams[0];
h = &st->hdr;
pc = (u_char *) &h->timestamp;
pc[0] = ch;
state = flv_timestamp_extended;
break;
case flv_timestamp_extended:
st = &s->in_streams[0];
h = &st->hdr;
pc = (u_char *) &h->timestamp;
pc[3] = ch;
state = flv_streamid0;
break;
case flv_streamid0:
st = &s->in_streams[0];
h = &st->hdr;
h->msid = 0;
pc = (u_char *) &h->msid;
pc[2] = ch;
state = flv_streamid1;
break;
case flv_streamid1:
st = &s->in_streams[0];
h = &st->hdr;
pc = (u_char *) &h->msid;
pc[1] = ch;
state = flv_streamid2;
break;
case flv_streamid2:
st = &s->in_streams[0];
h = &st->hdr;
pc = (u_char *) &h->msid;
pc[0] = ch;
state = flv_data;
break;
case flv_data:
st = &s->in_streams[0];
for (ll = &st->in; (*ll) && (*ll)->buf->last == (*ll)->buf->end;
ll = &(*ll)->next);
for (;;) {
if (*ll == NULL) {
*ll = ngx_get_chainbuf(cscf->chunk_size, 1);
}
len = ngx_min(st->len, b->last - p);
if ((*ll)->buf->end - (*ll)->buf->last >= (long) len) {
(*ll)->buf->last = ngx_cpymem((*ll)->buf->last, p, len);
p += len;
st->len -= len;
break;
}
len = (*ll)->buf->end - (*ll)->buf->last;
(*ll)->buf->last = ngx_cpymem((*ll)->buf->last, p, len);
p += len;
st->len -= len;
ll = &(*ll)->next;
}
if (st->len != 0) {
rc = NGX_AGAIN;
goto done;
}
state = flv_tagsize0;
rc = NGX_OK;
goto done;
}
}
done:
b->pos = p;
s->flv_state = state;
return rc;
}
static void
ngx_live_relay_httpflv_recv_body(void *request, ngx_http_request_t *hcr)
{
ngx_int_t n;
ngx_rtmp_session_t *s;
ngx_chain_t *cl, *l, *in;
ngx_rtmp_header_t *h;
ngx_rtmp_stream_t *st = NULL;
s = request;
n = ngx_http_client_read_body(hcr, &cl);
if (n == 0 || n == NGX_ERROR) {
s->finalize_reason = n == 0? NGX_LIVE_NORMAL_CLOSE:
NGX_LIVE_FLV_RECV_ERR;
ngx_log_error(NGX_LOG_INFO, s->log, ngx_errno,
"http relay, recv body error");
ngx_rtmp_finalize_session(s);
return;
}
l = cl;
for (;;) {
if (l && l->buf->pos == l->buf->last) {
l = l->next;
}
if (l == NULL) {
return;
}
n = ngx_live_relay_httpflv_parse(s, l->buf);
if (n == NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"http relay, parse flv frame failed in state %d",
s->flv_state);
ngx_http_client_finalize_request(hcr, 1);
return;
}
if (n == NGX_AGAIN) {
continue;
}
/* NGX_OK */
st = &s->in_streams[0];
h = &st->hdr;
in = st->in;
if (ngx_rtmp_receive_message(s, h, in) != NGX_OK) {
ngx_rtmp_finalize_session(s);
return;
}
ngx_put_chainbufs(st->in);
st->in = NULL;
}
}
static void
ngx_live_relay_httpflv_cleanup(void *data)
{
ngx_rtmp_session_t *s;
s = data;
ngx_log_error(NGX_LOG_INFO, s->log, 0, "http flv client, cleanup");
if (s) {
if (s->close.posted) {
ngx_delete_posted_event(&s->close);
}
if (s->finalize_reason == 0) {
s->finalize_reason = NGX_LIVE_FLV_RECV_ERR;
}
ngx_rtmp_finalize_fake_session(s);
}
}
static void
ngx_live_relay_httpflv_error(ngx_rtmp_session_t *s, ngx_uint_t status)
{
ngx_live_stream_t *st;
ngx_rtmp_core_ctx_t *cctx;
char *code, *level, *desc;
size_t i;
for (i = 0; ngx_http_relay_status_code[i].status; ++i) {
if (status != ngx_http_relay_status_code[i].status) {
continue;
}
break;
}
code = ngx_http_relay_status_code[i].code;
level = ngx_http_relay_status_code[i].level;
desc = ngx_http_relay_status_code[i].desc;
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"http relay transit, %d: level='%s' code='%s' description='%s'",
status, level, code, desc);
st = ngx_live_create_stream(&s->serverid, &s->stream);
cctx = st->play_ctx;
for (; cctx; cctx = cctx->next) {
cctx->session->status = status;
ngx_rtmp_send_status(cctx->session, code, level, desc);
if (ngx_strcmp(level, "error") == 0 && !cctx->session->static_pull) {
cctx->session->finalize_reason = NGX_LIVE_RELAY_TRANSIT;
ngx_rtmp_finalize_session(cctx->session);
}
}
}
static void
ngx_live_relay_httpflv_recv(void *request, ngx_http_request_t *hcr)
{
ngx_rtmp_session_t *s;
ngx_uint_t status_code;
s = request;
status_code = ngx_http_client_status_code(hcr);
s->stage = NGX_LIVE_PLAY;
s->ptime = ngx_current_msec;
s->connection = hcr->connection;
ngx_rtmp_set_combined_log(s, hcr->connection->log->data,
hcr->connection->log->handler);
s->log->connection = s->connection->number;
if (ngx_rtmp_core_main_conf->fast_reload && (ngx_exiting || ngx_terminate)) {
ngx_live_relay_httpflv_error(s, NGX_LIVE_PROCESS_EXIT);
s->finalize_reason = NGX_LIVE_PROCESS_EXIT;
ngx_http_client_finalize_request(hcr, 1);
return;
}
if (status_code != NGX_HTTP_OK) {
ngx_live_relay_httpflv_error(s, status_code);
s->finalize_reason = NGX_LIVE_FLV_RECV_ERR;
ngx_http_client_finalize_request(hcr, 1);
return;
}
ngx_live_relay_publish_local(s);
ngx_http_client_set_read_handler(hcr, ngx_live_relay_httpflv_recv_body);
ngx_live_relay_httpflv_recv_body(request, hcr);
}
static ngx_int_t
ngx_live_relay_httpflv_send_request(ngx_rtmp_session_t *s,
ngx_live_relay_url_t *url)
{
ngx_http_request_t *hcr;
ngx_str_t request_url;
size_t len;
ngx_live_relay_ctx_t *ctx;
ngx_http_cleanup_t *cln;
u_char *p;
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
// request url
// http:// + url + ':' + port + '/' + app + '/' + name + ['?' + pargs]
len = 7 + url->url.host.len + 1 + sizeof("65535") - 1
+ 1 + ctx->app.len + 1 + ctx->name.len;
if (ctx->pargs.len) {
len = len + 1 + ctx->pargs.len;
}
request_url.data = ngx_pcalloc(s->pool, len);
if (request_url.data == NULL) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"alloc mem for request url failed");
return NGX_ERROR;
}
if (ctx->pargs.len) {
p = ngx_snprintf(request_url.data, len, "http://%V:%d/%V/%V?%V",
&url->url.host, url->port, &ctx->app, &ctx->name, &ctx->pargs);
} else {
p = ngx_snprintf(request_url.data, len, "http://%V:%d/%V/%V",
&url->url.host, url->port, &ctx->app, &ctx->name);
}
request_url.len = p - request_url.data;
// request headers
ngx_keyval_t headers[] = {
{ ngx_string("Host"), ctx->domain },
{ ngx_string("Referer"), ctx->referer },
{ ngx_string("User-Agent"), ctx->user_agent },
{ ngx_null_string, ngx_null_string }
};
s->stage = NGX_LIVE_CONNECT;
s->connect_time = ngx_current_msec;
hcr = ngx_http_client_get(s->log, &request_url, headers, s);
ngx_http_client_set_read_handler(hcr, ngx_live_relay_httpflv_recv);
cln = ngx_http_client_cleanup_add(hcr, 0);
if (cln == NULL) {
ngx_log_error(NGX_LOG_ERR, s->log, 0, "http client add cleanup failed");
ngx_live_relay_httpflv_error(s, NGX_HTTP_INTERNAL_SERVER_ERROR);
ngx_http_client_finalize_request(hcr, 1);
return NGX_ERROR;
}
cln->handler = ngx_live_relay_httpflv_cleanup;
cln->data = s;
s->request = hcr;
s->live_type = NGX_HTTP_FLV_LIVE;
return NGX_OK;
}
ngx_int_t
ngx_live_relay_create_httpflv(ngx_rtmp_session_t *s, ngx_live_relay_t *relay,
ngx_live_relay_url_t *url)
{
ngx_live_relay_ctx_t *rctx;
// must use ngx_sockaddr_t, because sizeof(struct sockaddr)
// is not long enouph, content will be covered by other var
ngx_sockaddr_t nsa;
struct sockaddr *sa;
socklen_t len;
rctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (rctx == NULL) {
return NGX_ERROR;
}
#define NGX_LIVE_RELAY_CTX(para) \
if (ngx_copy_str(s->pool, &rctx->para, &relay->para) != NGX_OK) { \
goto destroy; \
}
NGX_LIVE_RELAY_CTX(domain);
NGX_LIVE_RELAY_CTX(app);
NGX_LIVE_RELAY_CTX(name);
NGX_LIVE_RELAY_CTX(pargs);
NGX_LIVE_RELAY_CTX(referer);
NGX_LIVE_RELAY_CTX(user_agent);
#undef NGX_LIVE_RELAY_CTX
rctx->tag = relay->tag;
// get address, host in url must be resolv sync
sa = (struct sockaddr *) &nsa;
len = ngx_dynamic_resolver_gethostbyname(&url->url.host, sa);
if (len == 0) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"relay httpflv: gethostbyname failed %V", &url->url.host);
goto destroy;
}
// send http request
if (ngx_live_relay_httpflv_send_request(s, url) != NGX_OK) {
goto destroy;
}
return NGX_OK;
destroy:
ngx_rtmp_finalize_session(s);
return NGX_ERROR;
}

View File

@ -0,0 +1,399 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_live_relay.h"
#include "ngx_stream_zone_module.h"
#include "ngx_multiport.h"
static ngx_live_push_pt next_push;
static ngx_live_pull_pt next_pull;
static ngx_live_push_close_pt next_push_close;
static ngx_live_pull_close_pt next_pull_close;
static void *ngx_live_relay_inner_create_app_conf(ngx_conf_t *cf);
static char *ngx_live_relay_inner_merge_app_conf(ngx_conf_t *cf,
void *parent, void *child);
static ngx_int_t ngx_live_relay_inner_postconfiguration(ngx_conf_t *cf);
typedef struct {
ngx_flag_t inner_pull;
ngx_str_t inner_pull_port;
} ngx_live_relay_inner_app_conf_t;
static ngx_command_t ngx_live_relay_inner_commands[] = {
{ ngx_string("rtmp_auto_pull"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_live_relay_inner_app_conf_t, inner_pull),
NULL },
{ ngx_string("rtmp_auto_pull_port"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_live_relay_inner_app_conf_t, inner_pull_port),
NULL },
ngx_null_command
};
static ngx_rtmp_module_t ngx_live_relay_inner_module_ctx = {
NULL, /* preconfiguration */
ngx_live_relay_inner_postconfiguration, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_live_relay_inner_create_app_conf, /* create app configuration */
ngx_live_relay_inner_merge_app_conf /* merge app configuration */
};
ngx_module_t ngx_live_relay_inner_module = {
NGX_MODULE_V1,
&ngx_live_relay_inner_module_ctx, /* module context */
ngx_live_relay_inner_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_live_relay_inner_create_app_conf(ngx_conf_t *cf)
{
ngx_live_relay_inner_app_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_live_relay_inner_app_conf_t));
if (conf == NULL) {
return NULL;
}
conf->inner_pull = NGX_CONF_UNSET;
return conf;
}
static char *
ngx_live_relay_inner_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_live_relay_inner_app_conf_t *prev = parent;
ngx_live_relay_inner_app_conf_t *conf = child;
ngx_conf_merge_value(conf->inner_pull, prev->inner_pull, 1);
ngx_conf_merge_str_value(conf->inner_pull_port, prev->inner_pull_port,
"unix:/tmp/inner.sock");
return NGX_CONF_OK;
}
static ngx_int_t
ngx_live_relay_inner_create_relay(ngx_rtmp_session_t *rs,
ngx_live_relay_t *relay, ngx_int_t pslot)
{
ngx_live_relay_inner_app_conf_t *riacf;
ngx_live_relay_url_t *url;
ngx_str_t port;
riacf = ngx_rtmp_get_module_app_conf(rs, ngx_live_relay_inner_module);
ngx_memzero(relay, sizeof(ngx_live_relay_t));
if (ngx_array_init(&relay->urls, rs->pool, 1, sizeof(ngx_live_relay_url_t))
!= NGX_OK)
{
ngx_log_error(NGX_LOG_ERR, rs->log, 0,
"inner relay, relay init error: %V", &riacf->inner_pull_port);
return NGX_ERROR;
}
url = ngx_array_push(&relay->urls);
if (url == NULL) {
ngx_log_error(NGX_LOG_ERR, rs->log, 0,
"inner relay, get url failed: %V", &riacf->inner_pull_port);
return NGX_ERROR;
}
relay->tag = &ngx_live_relay_inner_module;
ngx_memzero(url, sizeof(ngx_live_relay_url_t));
ngx_memzero(&port, sizeof(ngx_str_t));
if (ngx_multiport_get_port(rs->pool, &port,
&riacf->inner_pull_port, pslot) == NGX_ERROR)
{
ngx_log_error(NGX_LOG_ERR, rs->log, 0,
"inner relay, get mulitport error: %V",
&riacf->inner_pull_port);
return NGX_ERROR;
}
url->url.host = port;
url->url.host_with_port = port;
url->relay_type = NGX_LIVE_RELAY_RTMP;
return NGX_OK;
}
static void
ngx_live_relay_inner_handler(ngx_event_t *ev)
{
ngx_rtmp_session_t *s;
ngx_live_relay_app_conf_t *lracf;
ngx_live_relay_ctx_t *ctx;
ngx_live_relay_t relay;
s = ev->data;
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (!ctx->failed_delay && ev->timedout) { // connect timeout
ngx_log_error(NGX_LOG_ERR, s->log, NGX_ETIMEDOUT,
"inner relay, relay timeout");
s->finalize_reason = NGX_LIVE_RELAY_TIMEOUT;
ngx_rtmp_finalize_session(s);
return;
}
// relay pull, no player or relay push no publisher
if ((s->publishing && s->live_stream->play_ctx == NULL)
|| (!s->publishing && s->live_stream->publish_ctx == NULL))
{
return;
}
lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_module);
ngx_add_timer(&ctx->reconnect, lracf->relay_reconnect);
if (ngx_live_relay_inner_create_relay(s, &relay, s->live_stream->pslot)
!= NGX_OK)
{
return;
}
ngx_live_relay_create(s, &relay);
}
// if stream's need to continue pull or push chain, otherwise return NGX_OK
static ngx_int_t
ngx_live_relay_inner_relay(ngx_rtmp_session_t *s, unsigned publishing)
{
ngx_rtmp_session_t *rs;
ngx_live_relay_ctx_t *ctx, *pctx;
ngx_live_relay_app_conf_t *lracf;
ngx_int_t pslot;
pslot = ngx_stream_zone_insert_stream(&s->stream);
if (pslot == NGX_ERROR) { // stream zone not configured or configured error
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"inner relay, insert stream %V failed", &s->stream);
return NGX_DECLINED;
}
ngx_log_error(NGX_LOG_INFO, s->log, 0,
"inner relay, stream %V not in current process, "
"pslot:%i ngx_process_slot:%i",
&s->stream, pslot, ngx_process_slot);
s->live_stream->pslot = pslot;
if (pslot == ngx_process_slot) { // current process become stream owner
return NGX_DECLINED;
}
rs = ngx_rtmp_create_relay_session(s, &ngx_live_relay_inner_module);
if (rs == NULL) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"inner relay, create relay session failed");
return NGX_DECLINED;
}
rs->publishing = publishing;
rs->live_stream = s->live_stream;
ngx_live_create_ctx(rs, publishing);
ctx = ngx_rtmp_get_module_ctx(rs, ngx_live_relay_module);
ctx->reconnect.log = rs->log;
ctx->reconnect.data = rs;
ctx->reconnect.handler = ngx_live_relay_inner_handler;
lracf = ngx_rtmp_get_module_app_conf(rs, ngx_live_relay_module);
// play trigger pull or publish trigger push
if (s->publishing != rs->publishing) {
ngx_post_event(&ctx->reconnect, &ngx_posted_events);
return NGX_OK;
}
// normal publisher close, need to trigger pull
if (s->publishing && !s->relay) {
ngx_post_event(&ctx->reconnect, &ngx_posted_events);
return NGX_OK;
}
// reconnect
pctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (pctx->successd) { // prev relay susccessd
ngx_post_event(&ctx->reconnect, &ngx_posted_events);
return NGX_OK;
}
if (!pctx->reconnect.timer_set) { // prev relay timeout
ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2,
lracf->relay_reconnect);
ngx_post_event(&ctx->reconnect, &ngx_posted_events);
return NGX_OK;
}
if (pctx->failed_reconnect) {
ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2,
lracf->relay_reconnect);
} else {
ctx->failed_reconnect = lracf->failed_reconnect;
}
ctx->failed_delay = 1;
ngx_add_timer(&ctx->reconnect, ctx->failed_reconnect);
return NGX_OK;
}
static ngx_int_t
ngx_live_relay_inner_push(ngx_rtmp_session_t *s)
{
ngx_live_relay_inner_app_conf_t *riacf;
riacf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_inner_module);
if (!riacf->inner_pull) {
goto next;
}
if (s->relay) {
goto next;
}
if (ngx_live_relay_inner_relay(s, 0) == NGX_OK) {
return NGX_OK;
}
next:
return next_push(s);
}
static ngx_int_t
ngx_live_relay_inner_pull(ngx_rtmp_session_t *s)
{
ngx_live_relay_inner_app_conf_t *riacf;
riacf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_inner_module);
if (!riacf->inner_pull) {
goto next;
}
if (!s->publishing && s->relay) { // relay push
goto next;
}
if (s->live_stream->pslot != -1) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
"inner relay, stream %V already in current process",
&s->stream);
if (s->live_stream->pslot == ngx_process_slot) {
// stream leader is in current process, continue pull or push chain
goto next;
}
return NGX_OK;
}
if (ngx_live_relay_inner_relay(s, 1) == NGX_OK) {
return NGX_OK;
}
next:
return next_pull(s);
}
static ngx_int_t
ngx_live_relay_inner_push_close(ngx_rtmp_session_t *s)
{
ngx_live_relay_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (ctx->tag != &ngx_live_relay_inner_module) {
goto next;
}
// relay push and has publisher in stream
if (!ctx->giveup && s->live_stream->publish_ctx) {
ngx_live_relay_inner_relay(s, 0);
}
next:
return next_push_close(s);
}
static ngx_int_t
ngx_live_relay_inner_pull_close(ngx_rtmp_session_t *s)
{
ngx_live_relay_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (ctx == NULL) {
goto next;
}
if (ctx->tag != &ngx_live_relay_inner_module) {
goto next;
}
// inner relay close
s->live_stream->pslot = -1;
next:
return next_pull_close(s);
}
static ngx_int_t
ngx_live_relay_inner_postconfiguration(ngx_conf_t *cf)
{
/* chain handlers */
next_push = ngx_live_push;
ngx_live_push = ngx_live_relay_inner_push;
next_pull = ngx_live_pull;
ngx_live_pull = ngx_live_relay_inner_pull;
next_push_close = ngx_live_push_close;
ngx_live_push_close = ngx_live_relay_inner_push_close;
next_pull_close = ngx_live_pull_close;
ngx_live_pull_close = ngx_live_relay_inner_pull_close;
return NGX_OK;
}

View File

@ -0,0 +1,860 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include "ngx_live_relay.h"
#include "ngx_poold.h"
#include "ngx_toolkit_misc.h"
#include "ngx_dynamic_resolver.h"
static ngx_int_t ngx_live_relay_rtmp_postconfiguration(ngx_conf_t *cf);
static void *ngx_live_relay_rtmp_create_app_conf(ngx_conf_t *cf);
static char *ngx_live_relay_rtmp_merge_app_conf(ngx_conf_t *cf, void *parent,
void *child);
typedef struct {
ngx_msec_t buflen;
} ngx_live_relay_rtmp_app_conf_t;
typedef struct {
char *code;
ngx_uint_t status;
ngx_flag_t finalize;
} ngx_rtmp_status_code_t;
static ngx_rtmp_status_code_t ngx_rtmp_relay_status_error_code[] = {
{ "NetStream.Publish.BadName", 400, 1 },
{ "NetStream.Stream.Forbidden", 403, 1 },
{ "NetStream.Play.StreamNotFound", 404, 1 },
{ "NetStream.Relay.ServerError", 500, 1 },
{ NULL, 0, 0 }
};
#define NGX_RTMP_RELAY_CONNECT_TRANS 1
#define NGX_RTMP_RELAY_CREATE_STREAM_TRANS 2
#define NGX_RTMP_RELAY_CSID_AMF_INI 3
#define NGX_RTMP_RELAY_CSID_AMF 5
#define NGX_RTMP_RELAY_MSID 1
static ngx_command_t ngx_live_relay_rtmp_commands[] = {
{ ngx_string("relay_buffer"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_live_relay_rtmp_app_conf_t, buflen),
NULL },
ngx_null_command
};
static ngx_rtmp_module_t ngx_live_relay_rtmp_module_ctx = {
NULL, /* preconfiguration */
ngx_live_relay_rtmp_postconfiguration, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_live_relay_rtmp_create_app_conf, /* create app configuration */
ngx_live_relay_rtmp_merge_app_conf /* merge app configuration */
};
ngx_module_t ngx_live_relay_rtmp_module = {
NGX_MODULE_V1,
&ngx_live_relay_rtmp_module_ctx, /* module context */
ngx_live_relay_rtmp_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_live_relay_rtmp_create_app_conf(ngx_conf_t *cf)
{
ngx_live_relay_rtmp_app_conf_t *racf;
racf = ngx_pcalloc(cf->pool, sizeof(ngx_live_relay_rtmp_app_conf_t));
if (racf == NULL) {
return NULL;
}
racf->buflen = NGX_CONF_UNSET_MSEC;
return racf;
}
static char *
ngx_live_relay_rtmp_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_live_relay_rtmp_app_conf_t *prev = parent;
ngx_live_relay_rtmp_app_conf_t *conf = child;
ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 5000);
return NGX_CONF_OK;
}
static ngx_int_t
ngx_live_relay_rtmp_send_connect(ngx_rtmp_session_t *s)
{
ngx_str_t app, tcurl;
double acodecs = 3575, vcodecs = 252;
static double trans = NGX_RTMP_RELAY_CONNECT_TRANS;
static ngx_rtmp_amf_elt_t out_cmd[] = {
{ NGX_RTMP_AMF_STRING,
ngx_string("app"),
NULL, 0 }, /* <-- fill */
{ NGX_RTMP_AMF_STRING,
ngx_string("tcUrl"),
NULL, 0 }, /* <-- fill */
{ NGX_RTMP_AMF_STRING,
ngx_string("pageUrl"),
NULL, 0 }, /* <-- fill */
{ NGX_RTMP_AMF_STRING,
ngx_string("swfUrl"),
NULL, 0 }, /* <-- fill */
{ NGX_RTMP_AMF_STRING,
ngx_string("flashVer"),
NULL, 0 }, /* <-- fill */
{ NGX_RTMP_AMF_NUMBER,
ngx_string("audioCodecs"),
NULL, 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_string("videoCodecs"),
NULL, 0 }
};
static ngx_rtmp_amf_elt_t out_elts[] = {
{ NGX_RTMP_AMF_STRING,
ngx_null_string,
"connect", 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_null_string,
&trans, 0 },
{ NGX_RTMP_AMF_OBJECT,
ngx_null_string,
out_cmd, sizeof(out_cmd) }
};
ngx_rtmp_core_app_conf_t *cacf;
ngx_rtmp_core_srv_conf_t *cscf;
ngx_live_relay_ctx_t *ctx;
ngx_rtmp_header_t h;
cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module);
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (cacf == NULL || ctx == NULL) {
return NGX_ERROR;
}
/* app */
if (ctx->args.len) {
app.len = ctx->app.len + 1 + ctx->args.len;
app.data = ngx_pcalloc(s->pool, app.len);
if (app.data == NULL) {
return NGX_ERROR;
}
ngx_snprintf(app.data, app.len, "%V?%V", &ctx->app, &ctx->args);
} else {
app = ctx->app;
}
out_cmd[0].data = app.data;
out_cmd[0].len = app.len;
/* tcUrl */
tcurl.len = sizeof("rtmp://") - 1 + ctx->domain.len + 1 + ctx->app.len;
tcurl.data = ngx_palloc(s->pool, tcurl.len);
if (tcurl.data == NULL) {
return NGX_ERROR;
}
ngx_snprintf(tcurl.data, tcurl.len, "rtmp://%V/%V",
&ctx->domain, &ctx->app);
out_cmd[1].data = tcurl.data;
out_cmd[1].len = tcurl.len;
/* pageUrl */
out_cmd[2].data = ctx->referer.data;
out_cmd[2].len = ctx->referer.len;
/* swfUrl */
out_cmd[3].data = ctx->swf_url.data;
out_cmd[3].len = ctx->swf_url.len;
/* flashVer */
out_cmd[4].data = ctx->user_agent.data;
out_cmd[4].len = ctx->user_agent.len;
if (ctx->acodecs != 0) {
acodecs = (double) ctx->acodecs;
}
out_cmd[5].data = &acodecs;
if (ctx->vcodecs != 0) {
vcodecs = (double) ctx->vcodecs;
}
out_cmd[6].data = &vcodecs;
ngx_memzero(&h, sizeof(h));
h.csid = NGX_RTMP_RELAY_CSID_AMF_INI;
h.type = NGX_RTMP_MSG_AMF_CMD;
s->status = NGX_LIVE_CONNECT;
s->connect_time = ngx_current_msec;
return ngx_rtmp_send_chunk_size(s, cscf->chunk_size) != NGX_OK
|| ngx_rtmp_send_ack_size(s, cscf->ack_window) != NGX_OK
|| ngx_rtmp_send_amf(s, &h, out_elts,
sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK
? NGX_ERROR
: NGX_OK;
}
static ngx_int_t
ngx_live_relay_rtmp_send_create_stream(ngx_rtmp_session_t *s)
{
static double trans = NGX_RTMP_RELAY_CREATE_STREAM_TRANS;
static ngx_rtmp_amf_elt_t out_elts[] = {
{ NGX_RTMP_AMF_STRING,
ngx_null_string,
"createStream", 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_null_string,
&trans, 0 },
{ NGX_RTMP_AMF_NULL,
ngx_null_string,
NULL, 0 }
};
ngx_rtmp_header_t h;
ngx_memzero(&h, sizeof(h));
h.csid = NGX_RTMP_RELAY_CSID_AMF_INI;
h.type = NGX_RTMP_MSG_AMF_CMD;
s->stage = NGX_LIVE_CREATE_STREAM;
s->create_stream_time = ngx_current_msec;
return ngx_rtmp_send_amf(s, &h, out_elts,
sizeof(out_elts) / sizeof(out_elts[0]));
}
static ngx_int_t
ngx_live_relay_rtmp_send_publish(ngx_rtmp_session_t *s)
{
ngx_str_t name;
static double trans;
static ngx_rtmp_amf_elt_t out_elts[] = {
{ NGX_RTMP_AMF_STRING,
ngx_null_string,
"publish", 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_null_string,
&trans, 0 },
{ NGX_RTMP_AMF_NULL,
ngx_null_string,
NULL, 0 },
{ NGX_RTMP_AMF_STRING,
ngx_null_string,
NULL, 0 }, /* <- to fill */
{ NGX_RTMP_AMF_STRING,
ngx_null_string,
"live", 0 }
};
ngx_rtmp_header_t h;
ngx_live_relay_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (ctx == NULL) {
return NGX_ERROR;
}
if (ctx->pargs.len) {
name.len = ctx->name.len + 1 + ctx->pargs.len;
name.data = ngx_pcalloc(s->pool, name.len);
if (name.data == NULL) {
return NGX_ERROR;
}
ngx_snprintf(name.data, name.len, "%V?%V", &ctx->name, &ctx->pargs);
} else {
name = ctx->name;
}
out_elts[3].data = name.data;
out_elts[3].len = name.len;
ngx_memzero(&h, sizeof(h));
h.csid = NGX_RTMP_RELAY_CSID_AMF;
h.msid = NGX_RTMP_RELAY_MSID;
h.type = NGX_RTMP_MSG_AMF_CMD;
s->stage = NGX_LIVE_PUBLISH;
s->ptime = ngx_current_msec;
return ngx_rtmp_send_amf(s, &h, out_elts,
sizeof(out_elts) / sizeof(out_elts[0]));
}
static ngx_int_t
ngx_live_relay_rtmp_send_play(ngx_rtmp_session_t *s)
{
ngx_str_t name;
static double trans;
static double start, duration;
static ngx_rtmp_amf_elt_t out_elts[] = {
{ NGX_RTMP_AMF_STRING,
ngx_null_string,
"play", 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_null_string,
&trans, 0 },
{ NGX_RTMP_AMF_NULL,
ngx_null_string,
NULL, 0 },
{ NGX_RTMP_AMF_STRING,
ngx_null_string,
NULL, 0 }, /* <- fill */
{ NGX_RTMP_AMF_NUMBER,
ngx_null_string,
&start, 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_null_string,
&duration, 0 },
};
ngx_rtmp_header_t h;
ngx_live_relay_ctx_t *ctx;
ngx_live_relay_rtmp_app_conf_t *racf;
racf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_rtmp_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (racf == NULL) {
return NGX_ERROR;
}
if (ctx->pargs.len) {
name.len = ctx->name.len + 1 + ctx->pargs.len;
name.data = ngx_pcalloc(s->pool, name.len);
if (name.data == NULL) {
return NGX_ERROR;
}
ngx_snprintf(name.data, name.len, "%V?%V", &ctx->name, &ctx->pargs);
} else {
name = ctx->name;
}
out_elts[3].data = name.data;
out_elts[3].len = name.len;
start = -1000;
duration = -1000;
ngx_memzero(&h, sizeof(h));
h.csid = NGX_RTMP_RELAY_CSID_AMF;
h.msid = NGX_RTMP_RELAY_MSID;
h.type = NGX_RTMP_MSG_AMF_CMD;
s->stage = NGX_LIVE_PLAY;
s->ptime = ngx_current_msec;
return ngx_rtmp_send_amf(s, &h, out_elts,
sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK
|| ngx_rtmp_send_set_buflen(s, NGX_RTMP_RELAY_MSID,
racf->buflen) != NGX_OK
? NGX_ERROR
: NGX_OK;
}
static ngx_int_t
ngx_live_relay_rtmp_status_error(ngx_rtmp_session_t *s, char *type, char *code,
char *level, char *desc)
{
ngx_rtmp_core_ctx_t *cctx;
size_t i;
ngx_flag_t status = 0;
if (ngx_strcmp(type, "onStatus") == 0) {
status = 1;
}
for (i = 0; ngx_rtmp_relay_status_error_code[i].code; ++i) {
if (ngx_strcmp(ngx_rtmp_relay_status_error_code[i].code, code)
!= 0)
{
continue;
}
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"relay transit, %s: level='%s' code='%s' description='%s'",
type, level, code, desc);
if (s->publishing) {
cctx = s->live_stream->play_ctx;
} else {
cctx = s->live_stream->publish_ctx;
}
for (; cctx; cctx = cctx->next) {
cctx->session->status = ngx_rtmp_relay_status_error_code[i].status;
status ? ngx_rtmp_send_status(cctx->session, code, level, desc)
: ngx_rtmp_send_error(cctx->session, code, level, desc);
if (ngx_rtmp_relay_status_error_code[i].finalize
&& !cctx->session->static_pull)
{
cctx->session->finalize_reason = NGX_LIVE_RELAY_TRANSIT;
ngx_rtmp_finalize_session(cctx->session);
}
}
}
return NGX_OK;
}
static ngx_int_t
ngx_live_relay_rtmp_on_result(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
ngx_live_relay_ctx_t *ctx;
static struct {
double trans;
u_char level[32];
u_char code[128];
u_char desc[1024];
} v;
static ngx_rtmp_amf_elt_t in_inf[] = {
{ NGX_RTMP_AMF_STRING,
ngx_string("level"),
&v.level, sizeof(v.level) },
{ NGX_RTMP_AMF_STRING,
ngx_string("code"),
&v.code, sizeof(v.code) },
{ NGX_RTMP_AMF_STRING,
ngx_string("description"),
&v.desc, sizeof(v.desc) },
};
static ngx_rtmp_amf_elt_t in_elts[] = {
{ NGX_RTMP_AMF_NUMBER,
ngx_null_string,
&v.trans, 0 },
{ NGX_RTMP_AMF_NULL,
ngx_null_string,
NULL, 0 },
{ NGX_RTMP_AMF_OBJECT,
ngx_null_string,
in_inf, sizeof(in_inf) },
};
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (ctx == NULL) {
return NGX_OK;
}
ngx_memzero(&v, sizeof(v));
if (ngx_rtmp_receive_amf(s, in, in_elts,
sizeof(in_elts) / sizeof(in_elts[0])))
{
return NGX_ERROR;
}
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0,
"relay: _result: level='%s' code='%s' description='%s'",
v.level, v.code, v.desc);
switch ((ngx_int_t)v.trans) {
case NGX_RTMP_RELAY_CONNECT_TRANS:
return ngx_live_relay_rtmp_send_create_stream(s);
case NGX_RTMP_RELAY_CREATE_STREAM_TRANS:
if (s->publishing == 0) {
if (ngx_live_relay_rtmp_send_publish(s) != NGX_OK) {
return NGX_ERROR;
}
return ngx_live_relay_play_local(s);
} else {
if (ngx_live_relay_rtmp_send_play(s) != NGX_OK) {
return NGX_ERROR;
}
return ngx_live_relay_publish_local(s);
}
default:
return NGX_OK;
}
}
static ngx_int_t
ngx_live_relay_rtmp_on_error(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
ngx_live_relay_ctx_t *ctx;
static struct {
double trans;
u_char level[32];
u_char code[128];
u_char desc[1024];
} v;
static ngx_rtmp_amf_elt_t in_inf[] = {
{ NGX_RTMP_AMF_STRING,
ngx_string("level"),
&v.level, sizeof(v.level) },
{ NGX_RTMP_AMF_STRING,
ngx_string("code"),
&v.code, sizeof(v.code) },
{ NGX_RTMP_AMF_STRING,
ngx_string("description"),
&v.desc, sizeof(v.desc) },
};
static ngx_rtmp_amf_elt_t in_elts[] = {
{ NGX_RTMP_AMF_NUMBER,
ngx_null_string,
&v.trans, 0 },
{ NGX_RTMP_AMF_NULL,
ngx_null_string,
NULL, 0 },
{ NGX_RTMP_AMF_OBJECT,
ngx_null_string,
in_inf, sizeof(in_inf) },
};
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (ctx == NULL) {
return NGX_OK;
}
ngx_memzero(&v, sizeof(v));
if (ngx_rtmp_receive_amf(s, in, in_elts,
sizeof(in_elts) / sizeof(in_elts[0])))
{
return NGX_ERROR;
}
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0,
"relay: _error: level='%s' code='%s' description='%s'",
v.level, v.code, v.desc);
ngx_live_relay_rtmp_status_error(s, "_error", (char *) v.code,
(char *) v.level, (char *) v.desc);
return NGX_OK;
}
static ngx_int_t
ngx_live_relay_rtmp_on_status(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
ngx_live_relay_ctx_t *ctx;
static struct {
double trans;
u_char level[32];
u_char code[128];
u_char desc[1024];
} v;
static ngx_rtmp_amf_elt_t in_inf[] = {
{ NGX_RTMP_AMF_STRING,
ngx_string("level"),
&v.level, sizeof(v.level) },
{ NGX_RTMP_AMF_STRING,
ngx_string("code"),
&v.code, sizeof(v.code) },
{ NGX_RTMP_AMF_STRING,
ngx_string("description"),
&v.desc, sizeof(v.desc) },
};
static ngx_rtmp_amf_elt_t in_elts[] = {
{ NGX_RTMP_AMF_NUMBER,
ngx_null_string,
&v.trans, 0 },
{ NGX_RTMP_AMF_NULL,
ngx_null_string,
NULL, 0 },
{ NGX_RTMP_AMF_OBJECT,
ngx_null_string,
in_inf, sizeof(in_inf) },
};
static ngx_rtmp_amf_elt_t in_elts_meta[] = {
{ NGX_RTMP_AMF_OBJECT,
ngx_null_string,
in_inf, sizeof(in_inf) },
};
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (ctx == NULL) {
return NGX_OK;
}
ngx_memzero(&v, sizeof(v));
if (h->type == NGX_RTMP_MSG_AMF_META) {
ngx_rtmp_receive_amf(s, in, in_elts_meta,
sizeof(in_elts_meta) / sizeof(in_elts_meta[0]));
} else {
ngx_rtmp_receive_amf(s, in, in_elts,
sizeof(in_elts) / sizeof(in_elts[0]));
}
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0,
"relay: onStatus: level='%s' code='%s' description='%s'",
v.level, v.code, v.desc);
ngx_live_relay_rtmp_status_error(s, "onStatus", (char *) v.code,
(char *) v.level, (char *) v.desc);
return NGX_OK;
}
static ngx_int_t
ngx_live_relay_rtmp_handshake_done(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
ngx_live_relay_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (ctx == NULL) {
return NGX_OK;
}
return ngx_live_relay_rtmp_send_connect(s);
}
static ngx_int_t
ngx_live_relay_rtmp_get_peer(ngx_peer_connection_t *pc, void *data)
{
return NGX_OK;
}
static void
ngx_live_relay_rtmp_free_peer(ngx_peer_connection_t *pc, void *data,
ngx_uint_t state)
{
}
ngx_int_t
ngx_live_relay_create_rtmp(ngx_rtmp_session_t *s, ngx_live_relay_t *relay,
ngx_live_relay_url_t *url)
{
ngx_live_relay_ctx_t *rctx;
ngx_pool_t *pool;
ngx_peer_connection_t *pc;
ngx_connection_t *c;
ngx_int_t rc;
ngx_str_t name;
u_char text[NGX_SOCKADDRLEN];
// must use ngx_sockaddr_t, because sizeof(struct sockaddr)
// is not long enouph, content will be covered by other var
ngx_sockaddr_t nsa;
struct sockaddr *sa;
socklen_t len;
rctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (rctx == NULL) {
return NGX_ERROR;
}
pool = ngx_create_pool(4096, ngx_cycle->log);
if (pool == NULL) {
goto destroy;
}
#define NGX_LIVE_RELAY_CTX(para) \
if (ngx_copy_str(s->pool, &rctx->para, &relay->para) != NGX_OK) { \
goto destroy; \
}
NGX_LIVE_RELAY_CTX(domain);
NGX_LIVE_RELAY_CTX(app);
NGX_LIVE_RELAY_CTX(name);
NGX_LIVE_RELAY_CTX(pargs);
NGX_LIVE_RELAY_CTX(referer);
NGX_LIVE_RELAY_CTX(user_agent);
#undef NGX_LIVE_RELAY_CTX
rctx->tag = relay->tag;
// connect server
pc = ngx_pcalloc(s->pool, sizeof(ngx_peer_connection_t));
if (pc == NULL) {
goto destroy;
}
pc->log = s->log;
// get address
sa = (struct sockaddr *) &nsa;
len = ngx_dynamic_resolver_gethostbyname(&url->url.host, sa);
if (len == 0) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"relay rtmp: gethostbyname failed %V", &url->url.host);
goto destroy;
}
if (url->port != 0) {
ngx_inet_set_port(sa, url->port);
}
ngx_memzero(text, sizeof(text));
name.len = ngx_sock_ntop(sa, len, text, NGX_SOCKADDRLEN, 1);
name.data = text;
/* copy log to keep shared log unchanged */
pc->get = ngx_live_relay_rtmp_get_peer;
pc->free = ngx_live_relay_rtmp_free_peer;
pc->name = &name;
pc->socklen = len;
pc->sockaddr = sa;
rc = ngx_event_connect_peer(pc);
if (rc != NGX_OK && rc != NGX_AGAIN ) {
ngx_log_error(NGX_LOG_ERR, s->log, 0, "relay: connection failed");
goto destroy;
}
c = pc->connection;
if (ngx_copy_str(pool, &c->addr_text, &url->url.host_with_port) != NGX_OK) {
goto destroy;
}
ngx_rtmp_init_session(s, c);
c->pool = pool;
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, 1);
#endif
ngx_rtmp_client_handshake(s, 1);
return NGX_OK;
destroy:
if (pool) {
ngx_destroy_pool(pool);
}
ngx_rtmp_finalize_session(s);
return NGX_ERROR;
}
static ngx_int_t
ngx_live_relay_rtmp_postconfiguration(ngx_conf_t *cf)
{
ngx_rtmp_core_main_conf_t *cmcf;
ngx_rtmp_handler_pt *h;
ngx_rtmp_amf_handler_t *ch;
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
h = ngx_array_push(&cmcf->events[NGX_RTMP_HANDSHAKE_DONE]);
*h = ngx_live_relay_rtmp_handshake_done;
ch = ngx_array_push(&cmcf->amf);
ngx_str_set(&ch->name, "_result");
ch->handler = ngx_live_relay_rtmp_on_result;
ch = ngx_array_push(&cmcf->amf);
ngx_str_set(&ch->name, "_error");
ch->handler = ngx_live_relay_rtmp_on_error;
ch = ngx_array_push(&cmcf->amf);
ngx_str_set(&ch->name, "onStatus");
ch->handler = ngx_live_relay_rtmp_on_status;
return NGX_OK;
}

View File

@ -0,0 +1,460 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_live_relay.h"
#include "ngx_rtmp_dynamic.h"
#include "ngx_dynamic_resolver.h"
static ngx_live_push_pt next_push;
static ngx_live_pull_pt next_pull;
static ngx_live_push_close_pt next_push_close;
static void *ngx_live_relay_simple_create_app_conf(ngx_conf_t *cf);
static char *ngx_live_relay_simple_merge_app_conf(ngx_conf_t *cf,
void *parent, void *child);
static ngx_int_t ngx_live_relay_simple_postconfiguration(ngx_conf_t *cf);
static char *ngx_live_relay_push(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_live_relay_pull(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
typedef struct {
ngx_live_relay_t pull;
ngx_array_t pushes; /* ngx_live_relay_t */
} ngx_live_relay_simple_app_conf_t;
typedef struct {
ngx_live_relay_t *relay;
} ngx_live_relay_simple_ctx_t;
static ngx_command_t ngx_live_relay_simple_commands[] = {
{ ngx_string("push"),
NGX_RTMP_APP_CONF|NGX_CONF_1MORE,
ngx_live_relay_push,
NGX_RTMP_APP_CONF_OFFSET,
0,
NULL },
{ ngx_string("pull"),
NGX_RTMP_APP_CONF|NGX_CONF_1MORE,
ngx_live_relay_pull,
NGX_RTMP_APP_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
static ngx_rtmp_module_t ngx_live_relay_simple_module_ctx = {
NULL, /* preconfiguration */
ngx_live_relay_simple_postconfiguration,/* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_live_relay_simple_create_app_conf, /* create app configuration */
ngx_live_relay_simple_merge_app_conf /* merge app configuration */
};
ngx_module_t ngx_live_relay_simple_module = {
NGX_MODULE_V1,
&ngx_live_relay_simple_module_ctx, /* module context */
ngx_live_relay_simple_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_live_relay_simple_create_app_conf(ngx_conf_t *cf)
{
ngx_live_relay_simple_app_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_live_relay_simple_app_conf_t));
if (conf == NULL) {
return NULL;
}
if (ngx_array_init(&conf->pushes, cf->pool, 8, sizeof(ngx_live_relay_t))
!= NGX_OK)
{
return NULL;
}
return conf;
}
static char *
ngx_live_relay_simple_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
{
return NGX_CONF_OK;
}
static void
ngx_live_relay_simple_handler(ngx_event_t *ev)
{
ngx_rtmp_session_t *s;
ngx_live_relay_app_conf_t *lracf;
ngx_live_relay_ctx_t *ctx;
ngx_live_relay_simple_ctx_t *sctx;
ngx_live_relay_t *relay;
s = ev->data;
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (!ctx->failed_delay && ev->timedout) { // connect timeout
ngx_log_error(NGX_LOG_ERR, s->log, NGX_ETIMEDOUT,
"simple relay, relay timeout");
s->finalize_reason = NGX_LIVE_RELAY_TIMEOUT;
ngx_rtmp_finalize_session(s);
return;
}
// relay pull, no player or relay push no publisher
if ((s->publishing && s->live_stream->play_ctx == NULL)
|| (!s->publishing && s->live_stream->publish_ctx == NULL))
{
return;
}
lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_module);
ngx_add_timer(&ctx->reconnect, lracf->relay_reconnect);
sctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_simple_module);
relay = sctx->relay;
ngx_live_relay_create(s, relay);
}
static ngx_int_t
ngx_live_relay_simple_relay(ngx_rtmp_session_t *s, ngx_live_relay_t *relay,
unsigned publishing)
{
ngx_rtmp_session_t *rs;
ngx_live_relay_ctx_t *ctx, *pctx;
ngx_live_relay_app_conf_t *lracf;
ngx_live_relay_simple_ctx_t *sctx;
rs = ngx_rtmp_create_relay_session(s, &ngx_live_relay_simple_module);
if (rs == NULL) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"relay simple, create relay session failed");
return NGX_DECLINED;
}
rs->publishing = publishing;
rs->live_stream = s->live_stream;
ngx_live_create_ctx(rs, publishing);
sctx = ngx_pcalloc(rs->pool, sizeof(ngx_live_relay_simple_ctx_t));
if (sctx == NULL) {
ngx_log_error(NGX_LOG_ERR, rs->log, 0,
"relay simple, create simple relay ctx failed");
ngx_rtmp_finalize_session(rs);
return NGX_OK;
}
ngx_rtmp_set_ctx(rs, sctx, ngx_live_relay_simple_module);
sctx->relay = relay;
ctx = ngx_rtmp_get_module_ctx(rs, ngx_live_relay_module);
ctx->reconnect.log = rs->log;
ctx->reconnect.data = rs;
ctx->reconnect.handler = ngx_live_relay_simple_handler;
if (s->publishing != rs->publishing) {
ngx_post_event(&ctx->reconnect, &ngx_posted_events);
return NGX_OK;
}
// normal publisher close, need to trigger pull
if (s->publishing && !s->relay) {
ngx_post_event(&ctx->reconnect, &ngx_posted_events);
return NGX_OK;
}
// reconnect
pctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (pctx->successd) { // prev relay successd
ngx_post_event(&ctx->reconnect, &ngx_posted_events);
return NGX_OK;
}
ctx->idx = pctx->idx;
ctx->failed_reconnect = pctx->failed_reconnect;
if (ctx->idx < relay->urls.nelts) { // retry backup url immediately
ngx_post_event(&ctx->reconnect, &ngx_posted_events);
return NGX_OK;
}
lracf = ngx_rtmp_get_module_app_conf(rs, ngx_live_relay_module);
if (!pctx->reconnect.timer_set) { // prev relay timeout
ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2,
lracf->relay_reconnect);
ngx_post_event(&ctx->reconnect, &ngx_posted_events);
return NGX_OK;
}
if (pctx->failed_reconnect) {
ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2,
lracf->relay_reconnect);
} else {
ctx->failed_reconnect = lracf->failed_reconnect;
}
ctx->failed_delay = 1;
ngx_add_timer(&ctx->reconnect, ctx->failed_reconnect);
return NGX_OK;
}
static ngx_int_t
ngx_live_relay_simple_push(ngx_rtmp_session_t *s)
{
ngx_live_relay_simple_app_conf_t *rsacf;
ngx_live_relay_t *relay;
ngx_uint_t i;
rsacf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_simple_module);
if (rsacf->pushes.nelts == 0) { // not configured
goto next;
}
if (s->relay && s->static_pull == 0) {
goto next;
}
relay = rsacf->pushes.elts;
for (i = 0; i < rsacf->pushes.nelts; ++i, ++relay) {
ngx_live_relay_simple_relay(s, relay, 0);
}
next:
return next_push(s);
}
static ngx_int_t
ngx_live_relay_simple_pull(ngx_rtmp_session_t *s)
{
ngx_live_relay_simple_app_conf_t *rsacf;
ngx_live_relay_t *relay;
rsacf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_simple_module);
if (rsacf->pull.urls.nelts == 0) { // not configured
goto next;
}
if (!s->publishing && s->relay) { // relay push
goto next;
}
if (s->live_stream->publish_ctx) { // already has publisher
goto next;
}
relay = &rsacf->pull;
if (ngx_live_relay_simple_relay(s, relay, 1) == NGX_OK) {
return NGX_OK;
}
next:
return next_pull(s);
}
static ngx_int_t
ngx_live_relay_simple_push_close(ngx_rtmp_session_t *s)
{
ngx_live_relay_ctx_t *ctx;
ngx_live_relay_simple_ctx_t *sctx;
ngx_live_relay_t *relay;
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (ctx->tag != &ngx_live_relay_simple_module) {
goto next;
}
// relay push and has publisher in stream
if (!ctx->giveup && s->live_stream->publish_ctx) {
sctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_simple_module);
relay = sctx->relay;
ngx_live_relay_simple_relay(s, relay, 0);
}
next:
return next_push_close(s);
}
static char *
ngx_live_relay_push_pull(ngx_conf_t *cf, ngx_live_relay_t *relay)
{
ngx_live_relay_url_t *url;
ngx_str_t *value, n, v;
ngx_uint_t i;
u_char *p;
relay->tag = &ngx_live_relay_simple_module;
if (ngx_array_init(&relay->urls, cf->pool, 8, sizeof(ngx_live_relay_url_t))
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
value = cf->args->elts;
++value;
for (i = 1; i < cf->args->nelts; ++i, ++value) {
if (ngx_strncasecmp(value->data, (u_char *) "rtmp://", 7) == 0
|| ngx_strncasecmp(value->data, (u_char *) "http://", 7) == 0)
{
url = ngx_array_push(&relay->urls);
if (url == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(url, sizeof(ngx_live_relay_url_t));
if (value->data[0] == 'h') {
url->relay_type = NGX_LIVE_RELAY_HTTPFLV;
} else {
url->relay_type = NGX_LIVE_RELAY_RTMP;
}
if (ngx_parse_request_url(&url->url, value) != NGX_OK) {
return NGX_CONF_ERROR;
}
url->port = ngx_request_port(&url->url.scheme, &url->url.port);
if (url->port == 0) {
return "invalid port";
}
ngx_dynamic_resolver_add_domain(&url->url.host, cf->cycle);
continue;
}
p = ngx_strlchr(value->data, value->data + value->len, '=');
if (p == NULL) {
return "unsupported parameter format";
} else {
n.data = value->data;
n.len = p - value->data;
v.data = p + 1;
v.len = value->data + value->len - v.data;
}
#define NGX_LIVE_RELAY_STR_PAR(name, var) \
if (n.len == sizeof(name) - 1 \
&& ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0) \
{ \
relay->var = v; \
continue; \
}
NGX_LIVE_RELAY_STR_PAR("domain", domain);
NGX_LIVE_RELAY_STR_PAR("app", app);
NGX_LIVE_RELAY_STR_PAR("name", name);
NGX_LIVE_RELAY_STR_PAR("pargs", pargs);
NGX_LIVE_RELAY_STR_PAR("referer", referer);
NGX_LIVE_RELAY_STR_PAR("user_agent", user_agent);
#undef NGX_LIVE_RELAY_STR_PAR
return "unsupported parameter";
}
return NGX_CONF_OK;
}
static char *
ngx_live_relay_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_live_relay_simple_app_conf_t *rsacf;
ngx_live_relay_t *relay;
char *rc;
rsacf = conf;
relay = ngx_array_push(&rsacf->pushes);
if (relay == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(relay, sizeof(ngx_live_relay_t));
rc = ngx_live_relay_push_pull(cf, relay);
if (rc != NGX_CONF_OK) {
return rc;
}
return NGX_CONF_OK;
}
static char *
ngx_live_relay_pull(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_live_relay_simple_app_conf_t *rsacf;
rsacf = conf;
if (rsacf->pull.urls.nelts) {
return "is duplicate";
}
return ngx_live_relay_push_pull(cf, &rsacf->pull);
}
static ngx_int_t
ngx_live_relay_simple_postconfiguration(ngx_conf_t *cf)
{
/* chain handlers */
next_push = ngx_live_push;
ngx_live_push = ngx_live_relay_simple_push;
next_pull = ngx_live_pull;
ngx_live_pull = ngx_live_relay_simple_pull;
next_push_close = ngx_live_push_close;
ngx_live_push_close = ngx_live_relay_simple_push_close;
return NGX_OK;
}

View File

@ -0,0 +1,656 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
#include "ngx_live_relay.h"
#include "ngx_map.h"
#include "ngx_dynamic_conf.h"
#include "ngx_rtmp_dynamic.h"
#include "ngx_dynamic_resolver.h"
static ngx_live_pull_pt next_pull;
static void *ngx_live_relay_static_create_main_conf(ngx_conf_t *cf);
static char *ngx_live_relay_static_init_main_conf(ngx_conf_t *cf, void *conf);
static void *ngx_live_relay_static_create_main_dconf(ngx_conf_t *cf);
static char *ngx_live_relay_static_init_main_dconf(ngx_conf_t *cf, void *conf);
static char *ngx_live_relay_pull(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static ngx_int_t ngx_live_relay_static_postconfiguration(ngx_conf_t *cf);
typedef struct ngx_live_relay_static_relay_s ngx_live_relay_static_relay_t;
struct ngx_live_relay_static_relay_s {
ngx_map_node_t node;
ngx_live_relay_t *relay;
ngx_rtmp_session_t *session;
ngx_live_relay_static_relay_t *next;
};
typedef struct {
/* ngx_live_relay_static_relay_t */
ngx_map_t pulls[2];
/* 0 and 1 for index of conf */
unsigned used;
ngx_str_t pull_port;
ngx_live_relay_static_relay_t *free;
unsigned nalloc;
unsigned nfree;
} ngx_live_relay_static_main_conf_t;
typedef struct {
ngx_array_t pulls; /* ngx_live_relay_t */
} ngx_live_relay_static_main_dconf_t;
typedef struct {
ngx_live_relay_static_relay_t *relay;
} ngx_live_relay_static_ctx_t;
static ngx_command_t ngx_live_relay_static_dcommands[] = {
{ ngx_string("static_pull"),
NGX_RTMP_MAIN_CONF|NGX_CONF_1MORE,
ngx_live_relay_pull,
NGX_RTMP_MAIN_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
static ngx_rtmp_dynamic_module_t ngx_live_relay_static_module_dctx = {
ngx_live_relay_static_create_main_dconf,/* create main configuration */
ngx_live_relay_static_init_main_dconf, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create app configuration */
NULL /* merge app configuration */
};
static ngx_command_t ngx_live_relay_static_commands[] = {
{ ngx_string("static_pull_port"),
NGX_RTMP_MAIN_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_RTMP_MAIN_CONF_OFFSET,
offsetof(ngx_live_relay_static_main_conf_t, pull_port),
NULL },
ngx_null_command
};
static ngx_rtmp_module_t ngx_live_relay_static_module_ctx = {
NULL, /* preconfiguration */
ngx_live_relay_static_postconfiguration,/* postconfiguration */
ngx_live_relay_static_create_main_conf, /* create main configuration */
ngx_live_relay_static_init_main_conf, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create app configuration */
NULL /* merge app configuration */
};
ngx_module_t ngx_live_relay_static_module = {
NGX_MODULE_V1,
&ngx_live_relay_static_module_ctx, /* module context */
ngx_live_relay_static_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 */
(uintptr_t) &ngx_live_relay_static_module_dctx, /* module dynamic context */
(uintptr_t) ngx_live_relay_static_dcommands, /* module dynamic directives */
NGX_MODULE_V1_DYNAMIC_PADDING
};
static void
ngx_live_relay_static_handler(ngx_event_t *ev)
{
ngx_rtmp_session_t *s;
ngx_live_relay_app_conf_t *lracf;
ngx_live_relay_ctx_t *ctx;
ngx_live_relay_static_ctx_t *sctx;
ngx_live_relay_t *relay;
s = ev->data;
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (!ctx->failed_delay && ev->timedout) { // connect timeout
ngx_log_error(NGX_LOG_ERR, s->log, NGX_ETIMEDOUT,
"static relay, relay timeout");
s->finalize_reason = NGX_LIVE_RELAY_TIMEOUT;
ngx_rtmp_finalize_session(s);
return;
}
lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_module);
ngx_add_timer(&ctx->reconnect, lracf->relay_reconnect);
sctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_static_module);
relay = sctx->relay->relay;
ngx_live_relay_create(s, relay);
}
static ngx_int_t
ngx_live_relay_static_relay(ngx_rtmp_session_t *s,
ngx_live_relay_static_relay_t *r)
{
ngx_rtmp_session_t *rs;
ngx_live_relay_ctx_t *ctx, *pctx;
ngx_live_relay_app_conf_t *lracf;
ngx_live_relay_static_main_conf_t *rsmcf;
ngx_live_relay_static_ctx_t *sctx;
ngx_live_relay_t *relay;
ngx_rtmp_addr_conf_t *addr_conf;
relay = r->relay;
rsmcf = ngx_rtmp_cycle_get_module_main_conf(ngx_cycle,
ngx_live_relay_static_module);
addr_conf = ngx_rtmp_find_related_addr_conf((ngx_cycle_t *) ngx_cycle,
&rsmcf->pull_port);
if (addr_conf == NULL) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"relay static, find related add_conf for %V failed",
&rsmcf->pull_port);
return NGX_DECLINED;
}
rs = ngx_rtmp_create_static_session(relay, addr_conf,
&ngx_live_relay_static_module);
if (rs == NULL) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"relay static, create relay session %V failed", &relay->stream);
return NGX_DECLINED;
}
r->session = rs;
rs->publishing = 1;
rs->live_stream = ngx_live_create_stream(&rs->domain, &rs->stream);
ngx_live_create_ctx(rs, 1);
sctx = ngx_pcalloc(rs->pool, sizeof(ngx_live_relay_static_ctx_t));
if (sctx == NULL) {
ngx_log_error(NGX_LOG_ERR, rs->log, 0,
"relay static, create static relay ctx failed");
ngx_rtmp_finalize_session(rs);
return NGX_OK;
}
ngx_rtmp_set_ctx(rs, sctx, ngx_live_relay_static_module);
sctx->relay = r;
ctx = ngx_rtmp_get_module_ctx(rs, ngx_live_relay_module);
ctx->reconnect.log = rs->log;
ctx->reconnect.data = rs;
ctx->reconnect.handler = ngx_live_relay_static_handler;
if (s == NULL) {
ngx_post_event(&ctx->reconnect, &ngx_posted_events);
return NGX_OK;
}
// reconnect
pctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (pctx->successd) { // prev relay successd
ngx_post_event(&ctx->reconnect, &ngx_posted_events);
return NGX_OK;
}
ctx->idx = pctx->idx;
ctx->failed_reconnect = pctx->failed_reconnect;
if (ctx->idx < relay->urls.nelts) { // retry backup url immediately
ngx_post_event(&ctx->reconnect, &ngx_posted_events);
return NGX_OK;
}
lracf = ngx_rtmp_get_module_app_conf(rs, ngx_live_relay_module);
if (!pctx->reconnect.timer_set) { // prev relay timeout
ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2,
lracf->relay_reconnect);
ngx_post_event(&ctx->reconnect, &ngx_posted_events);
return NGX_OK;
}
if (pctx->failed_reconnect) {
ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2,
lracf->relay_reconnect);
} else {
ctx->failed_reconnect = lracf->failed_reconnect;
}
ctx->failed_delay = 1;
ngx_add_timer(&ctx->reconnect, ctx->failed_reconnect);
return NGX_OK;
}
// only be called when reconnect
static ngx_int_t
ngx_live_relay_static_pull(ngx_rtmp_session_t *s)
{
ngx_live_relay_static_ctx_t *ctx;
ngx_live_relay_ctx_t *rctx;
rctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
if (rctx == NULL || rctx->tag != &ngx_live_relay_static_module) {
goto next;
}
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_static_module);
if (ngx_live_relay_static_relay(s, ctx->relay) == NGX_OK) {
return NGX_OK;
}
next:
return next_pull(s);
}
static ngx_live_relay_static_relay_t *
ngx_live_relay_get_static_relay(ngx_live_relay_static_main_conf_t *rsmcf)
{
ngx_live_relay_static_relay_t *r;
r = rsmcf->free;
if (r) {
rsmcf->free = r->next;
r->session = NULL;
r->next = NULL;
--rsmcf->nfree;
} else {
r = ngx_pcalloc(ngx_cycle->pool, sizeof(ngx_live_relay_static_relay_t));
if (r == NULL) {
return NULL;
}
++rsmcf->nalloc;
}
return r;
}
static void
ngx_live_relay_put_static_relay(ngx_live_relay_static_main_conf_t *rsmcf,
ngx_live_relay_static_relay_t *r)
{
r->next = rsmcf->free;
rsmcf->free = r;
++rsmcf->nfree;
}
static void *
ngx_live_relay_static_create_main_conf(ngx_conf_t *cf)
{
ngx_live_relay_static_main_conf_t *rsmcf;
rsmcf = ngx_pcalloc(cf->pool, sizeof(ngx_live_relay_static_main_conf_t));
if (rsmcf == NULL) {
return NULL;
}
rsmcf->used = 1;
ngx_map_init(&rsmcf->pulls[0], ngx_map_hash_str, ngx_cmp_str);
ngx_map_init(&rsmcf->pulls[1], ngx_map_hash_str, ngx_cmp_str);
return rsmcf;
}
static char *
ngx_live_relay_static_init_main_conf(ngx_conf_t *cf, void *conf)
{
ngx_live_relay_static_main_conf_t *rsmcf;
rsmcf = conf;
if (rsmcf->pull_port.len == 0) {
rsmcf->pull_port.data = (u_char *) "1935";
rsmcf->pull_port.len = sizeof("1935") - 1;
}
return NGX_CONF_OK;
}
static void *
ngx_live_relay_static_create_main_dconf(ngx_conf_t *cf)
{
ngx_live_relay_static_main_dconf_t *rsmdcf;
rsmdcf = ngx_pcalloc(cf->pool, sizeof(ngx_live_relay_static_main_dconf_t));
if (rsmdcf == NULL) {
return NULL;
}
if (ngx_array_init(&rsmdcf->pulls, cf->pool, 64, sizeof(ngx_live_relay_t))
!= NGX_OK)
{
return NULL;
}
return rsmdcf;
}
// merge all static pull into ngx_live_relay_static_main_conf_t;
static char *
ngx_live_relay_static_init_main_dconf(ngx_conf_t *cf, void *conf)
{
ngx_live_relay_static_main_conf_t *rsmcf;
ngx_live_relay_static_main_dconf_t *rsmdcf;
ngx_core_conf_t *ccf;
ngx_live_relay_t *relay;
ngx_live_relay_static_relay_t *srelay, *old, *sl, *sln, **sll;
ngx_live_relay_static_ctx_t *ctx;
ngx_live_relay_ctx_t *rctx;
ngx_map_node_t *node;
unsigned used;
char *rc;
ngx_uint_t i, hash;
rsmdcf = conf;
rsmcf = ngx_rtmp_cycle_get_module_main_conf(ngx_cycle,
ngx_live_relay_static_module);
ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_core_module);
used = rsmcf->used? 0: 1;
sl = NULL;
sll = &sl;
relay = rsmdcf->pulls.elts;
for (i = 0; i < rsmdcf->pulls.nelts; ++i, ++relay) {
// should static pull in current process?
if (ngx_process == NGX_PROCESS_WORKER) {
hash = ngx_hash_key_lc(relay->stream.data, relay->stream.len);
if (hash % ccf->worker_processes != ngx_worker) {
continue;
}
}
// check static pull duplicate
node = ngx_map_find(&rsmcf->pulls[used], (intptr_t) &relay->stream);
if (node) {
rc = "duplicate static pull";
goto error;
}
srelay = ngx_live_relay_get_static_relay(rsmcf);
if (srelay == NULL) {
rc = "get static relay failed";
goto error;
}
srelay->relay = relay;
srelay->node.raw_key = (intptr_t) &relay->stream;
ngx_map_insert(&rsmcf->pulls[used], &srelay->node, 0);
// check static pull is exist
node = ngx_map_find(&rsmcf->pulls[rsmcf->used],
(intptr_t) &relay->stream);
if (node) {
old = (ngx_live_relay_static_relay_t *) node;
srelay->session = old->session;
// link swap static pull
*sll = old;
sll = &(*sll)->next;
}
}
// delete swap static pull from old
while (sl) {
sln = sl;
sl = sl->next;
ngx_map_delete(&rsmcf->pulls[rsmcf->used],
(intptr_t) &sln->relay->stream);
ngx_live_relay_put_static_relay(rsmcf, sln);
}
// stop deleted static pull
node = ngx_map_begin(&rsmcf->pulls[rsmcf->used]);
while (node) {
srelay = (ngx_live_relay_static_relay_t *) node;
node = ngx_map_next(node);
ngx_live_relay_put_static_relay(rsmcf, srelay);
rctx = ngx_rtmp_get_module_ctx(srelay->session, ngx_live_relay_module);
rctx->giveup = 1;
srelay->session->finalize_reason = NGX_LIVE_NORMAL_CLOSE;
ngx_rtmp_finalize_session(srelay->session);
ngx_map_delete(&rsmcf->pulls[rsmcf->used],
(intptr_t) &srelay->relay->stream);
}
// new static relay
node = ngx_map_begin(&rsmcf->pulls[used]);
for (; node; node = ngx_map_next(node)) {
srelay = (ngx_live_relay_static_relay_t *) node;
if (srelay->session == NULL) {
ngx_live_relay_static_relay(NULL, srelay);
} else {
ctx = ngx_rtmp_get_module_ctx(srelay->session,
ngx_live_relay_static_module);
ctx->relay = srelay;
}
}
rsmcf->used = used;
return NGX_CONF_OK;
error:
// recycle static relay resource
node = ngx_map_begin(&rsmcf->pulls[used]);
while (node) {
srelay = (ngx_live_relay_static_relay_t *) node;
node = ngx_map_next(node);
ngx_live_relay_put_static_relay(rsmcf, srelay);
ngx_map_delete(&rsmcf->pulls[used],
(intptr_t) &srelay->relay->stream);
}
return rc;
}
static char *
ngx_live_relay_pull(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_live_relay_static_main_dconf_t *rsmdcf;
ngx_live_relay_t *relay;
ngx_live_relay_url_t *url;
ngx_str_t *value, n, v;
ngx_uint_t i;
u_char *p;
rsmdcf = conf;
relay = ngx_array_push(&rsmdcf->pulls);
if (relay == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(relay, sizeof(ngx_live_relay_t));
relay->tag = &ngx_live_relay_static_module;
if (ngx_array_init(&relay->urls, cf->pool, 8, sizeof(ngx_live_relay_url_t))
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
value = cf->args->elts;
++value;
for (i = 1; i < cf->args->nelts; ++i, ++value) {
if (ngx_strncasecmp(value->data, (u_char *) "rtmp://", 7) == 0
|| ngx_strncasecmp(value->data, (u_char *) "http://", 7) == 0)
{
url = ngx_array_push(&relay->urls);
if (url == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(url, sizeof(ngx_live_relay_url_t));
if (value->data[0] == 'h') {
url->relay_type = NGX_LIVE_RELAY_HTTPFLV;
} else {
url->relay_type = NGX_LIVE_RELAY_RTMP;
}
if (ngx_parse_request_url(&url->url, value) != NGX_OK) {
return NGX_CONF_ERROR;
}
url->port = ngx_request_port(&url->url.scheme, &url->url.port);
if (url->port == 0) {
return "invalid port";
}
ngx_dynamic_resolver_add_domain(&url->url.host, cf->cycle);
continue;
}
p = ngx_strlchr(value->data, value->data + value->len, '=');
if (p == NULL) {
return "unsupported parameter format";
} else {
n.data = value->data;
n.len = p - value->data;
v.data = p + 1;
v.len = value->data + value->len - v.data;
}
#define NGX_LIVE_RELAY_STR_PAR(name, var) \
if (n.len == sizeof(name) - 1 \
&& ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0) \
{ \
relay->var = v; \
continue; \
}
NGX_LIVE_RELAY_STR_PAR("domain", domain);
NGX_LIVE_RELAY_STR_PAR("app", app);
NGX_LIVE_RELAY_STR_PAR("name", name);
NGX_LIVE_RELAY_STR_PAR("pargs", pargs);
NGX_LIVE_RELAY_STR_PAR("referer", referer);
NGX_LIVE_RELAY_STR_PAR("user_agent", user_agent);
#undef NGX_LIVE_RELAY_STR_PAR
return "unsupported parameter";
}
if (relay->domain.len == 0) {
return "domain not configured";
}
if (relay->app.len == 0) {
return "app not configured";
}
if (relay->name.len == 0) {
return "name not configured";
}
// domain/app/name
relay->stream.len = relay->domain.len + 1 + relay->app.len + 1
+ relay->name.len;
relay->stream.data = ngx_pcalloc(cf->pool, relay->stream.len);
if (relay->stream.data == NULL) {
return NGX_CONF_ERROR;
}
ngx_snprintf(relay->stream.data, relay->stream.len, "%V/%V/%V",
&relay->domain, &relay->app, &relay->name);
return NGX_CONF_OK;
}
static ngx_int_t
ngx_live_relay_static_postconfiguration(ngx_conf_t *cf)
{
next_pull = ngx_live_pull;
ngx_live_pull = ngx_live_relay_static_pull;
return NGX_OK;
}
ngx_chain_t *
ngx_live_relay_static_state(ngx_http_request_t *r)
{
ngx_live_relay_static_main_conf_t *rsmcf;
ngx_chain_t *cl;
ngx_buf_t *b;
size_t len;
rsmcf = ngx_rtmp_cycle_get_module_main_conf(ngx_cycle,
ngx_live_relay_static_module);
len = sizeof("##########rtmp live relay static##########\n") - 1
+ sizeof("relay_static alloc frame: \n") - 1 + NGX_OFF_T_LEN
+ sizeof("relay_static free frame: \n") - 1 + NGX_OFF_T_LEN;
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NULL;
}
cl->next = NULL;
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NULL;
}
cl->buf = b;
b->last = ngx_snprintf(b->last, len,
"##########rtmp live relay static##########\n"
"relay_static alloc frame: %ui\n"
"relay_static free frame: %ui\n",
rsmcf->nalloc, rsmcf->nfree);
return cl;
}

View File

@ -0,0 +1,205 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include "ngx_netcall.h"
#include "ngx_poold.h"
// cleanup only be called when connect failed(exclusive timeout)
static void
ngx_netcall_cleanup(void *data)
{
ngx_netcall_ctx_t *nctx;
nctx = data;
if (nctx->ev.timer_set) {
ngx_del_timer(&nctx->ev);
}
if (nctx->ev.posted) {
ngx_delete_posted_event(&nctx->ev);
}
if (nctx->hcr) {
ngx_http_client_detach(nctx->hcr);
nctx->handler(nctx, NGX_ERROR);
nctx->hcr = NULL;
}
}
// netcall timeout
static void
ngx_netcall_timeout(ngx_event_t *ev)
{
ngx_netcall_ctx_t *nctx;
nctx = ev->data;
if (nctx->ev.timer_set) {
ngx_del_timer(&nctx->ev);
}
if (nctx->ev.posted) {
ngx_delete_posted_event(&nctx->ev);
}
if (nctx->hcr) {
ngx_http_client_detach(nctx->hcr);
nctx->handler(nctx, NGX_ERROR);
nctx->hcr = NULL;
}
}
static void
ngx_netcall_handler(void *data, ngx_http_request_t *hcr)
{
ngx_netcall_ctx_t *nctx;
ngx_int_t code;
nctx = data;
if (nctx->ev.timer_set) {
ngx_del_timer(&nctx->ev);
}
if (nctx->ev.posted) {
ngx_delete_posted_event(&nctx->ev);
}
code = ngx_http_client_status_code(hcr);
if (nctx->hcr) {
ngx_http_client_detach(nctx->hcr);
nctx->handler(nctx, code);
nctx->hcr = NULL;
}
}
static void
ngx_netcall_destroy_handler(ngx_event_t *ev)
{
ngx_netcall_ctx_t *nctx;
nctx = ev->data;
NGX_DESTROY_POOL(nctx->pool);
}
ngx_netcall_ctx_t *
ngx_netcall_create_ctx(ngx_uint_t type, ngx_str_t *groupid, ngx_uint_t stage,
ngx_msec_t timeout, ngx_msec_t update, ngx_uint_t idx)
{
ngx_netcall_ctx_t *ctx;
ngx_pool_t *pool;
pool = NGX_CREATE_POOL(4096, ngx_cycle->log);
if (pool == NULL) {
return NULL;
}
ctx = ngx_pcalloc(pool, sizeof(ngx_netcall_ctx_t));
if (ctx == NULL) {
NGX_DESTROY_POOL(pool);
return NULL;
}
ctx->url.data = ngx_pcalloc(pool, NGX_NETCALL_MAX_URL_LEN);
if (ctx->url.data == NULL) {
NGX_DESTROY_POOL(pool);
return NULL;
}
ctx->pool = pool;
ctx->idx = idx;
ctx->type = type;
ctx->groupid.len = groupid->len;
ctx->groupid.data = ngx_pcalloc(pool, ctx->groupid.len);
if (ctx->groupid.data == NULL) {
NGX_DESTROY_POOL(pool);
return NULL;
}
ngx_memcpy(ctx->groupid.data, groupid->data, groupid->len);
ctx->ev.log = ngx_cycle->log;
ctx->ev.data = ctx;
ctx->stage = stage;
ctx->timeout = timeout;
ctx->update = update;
return ctx;
}
void
ngx_netcall_create(ngx_netcall_ctx_t *nctx, ngx_log_t *log)
{
ngx_http_request_t *hcr;
ngx_http_cleanup_t *cln;
hcr = ngx_http_client_get(log, &nctx->url, NULL, nctx);
if (hcr == NULL) {
return;
}
ngx_http_client_set_read_handler(hcr, ngx_netcall_handler);
cln = ngx_http_client_cleanup_add(hcr, 0);
if (cln == NULL) {
ngx_log_error(NGX_LOG_ERR, log, 0,
"netcall create add cleanup failed");
return;
}
cln->handler = ngx_netcall_cleanup;
cln->data = nctx;
// detach old http client request
if (nctx->hcr) {
ngx_http_client_detach(nctx->hcr);
}
nctx->hcr = hcr;
nctx->ev.log = log;
nctx->ev.handler = ngx_netcall_timeout;
ngx_add_timer(&nctx->ev, nctx->timeout);
}
void
ngx_netcall_destroy(ngx_netcall_ctx_t *nctx)
{
if (nctx->ev.timer_set) {
ngx_del_timer(&nctx->ev);
}
if (nctx->ev.posted) {
ngx_delete_posted_event(&nctx->ev);
}
if (nctx->hcr) { // use detach will keep client connection alive
ngx_http_client_detach(nctx->hcr);
nctx->hcr = NULL;
}
// destroy may called in nctx->handler
// destroy pool may cause memory error
// so we destroy nctx pool asynchronous
nctx->ev.handler = ngx_netcall_destroy_handler;
// reset ev log, use rtmp session log may be destroy
nctx->ev.log = ngx_cycle->log;
ngx_post_event(&nctx->ev, &ngx_posted_events);
}
ngx_str_t *
ngx_netcall_header(ngx_netcall_ctx_t *nctx, ngx_str_t *key)
{
ngx_http_request_t *hcr;
hcr = nctx->hcr;
return ngx_http_client_header_in(hcr, key);
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#ifndef _NGX_NETCALL_H_INCLUDE_
#define _NGX_NETCALL_H_INCLUDE_
#include <ngx_core.h>
#include "ngx_http_client.h"
#define NGX_NETCALL_MAX_URL_LEN 2048
typedef struct ngx_netcall_ctx_s ngx_netcall_ctx_t;
typedef void (* ngx_netcall_handler_pt)(ngx_netcall_ctx_t *ctx, ngx_int_t code);
struct ngx_netcall_ctx_s {
ngx_pool_t *pool;
ngx_str_t url;
ngx_uint_t idx;
ngx_uint_t type;
ngx_str_t groupid;
ngx_uint_t stage;
ngx_msec_t timeout;
ngx_msec_t update;
ngx_event_t ev;
ngx_http_request_t *hcr;
ngx_netcall_handler_pt handler;
void *data;
};
ngx_netcall_ctx_t *ngx_netcall_create_ctx(ngx_uint_t type, ngx_str_t *groupid,
ngx_uint_t stage, ngx_msec_t timeout, ngx_msec_t update, ngx_uint_t idx);
void ngx_netcall_create(ngx_netcall_ctx_t *nctx, ngx_log_t *log);
void ngx_netcall_destroy(ngx_netcall_ctx_t *nctx);
ngx_str_t *ngx_netcall_header(ngx_netcall_ctx_t *nctx, ngx_str_t *key);
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,482 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
#include "ngx_rtmp_cmd_module.h"
static ngx_rtmp_publish_pt next_publish;
static ngx_rtmp_play_pt next_play;
#define NGX_RTMP_ACCESS_PUBLISH 0x01
#define NGX_RTMP_ACCESS_PLAY 0x02
static char * ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static ngx_int_t ngx_rtmp_access_postconfiguration(ngx_conf_t *cf);
static void * ngx_rtmp_access_create_app_conf(ngx_conf_t *cf);
static char * ngx_rtmp_access_merge_app_conf(ngx_conf_t *cf,
void *parent, void *child);
typedef struct {
in_addr_t mask;
in_addr_t addr;
ngx_uint_t deny;
ngx_uint_t flags;
} ngx_rtmp_access_rule_t;
#if (NGX_HAVE_INET6)
typedef struct {
struct in6_addr addr;
struct in6_addr mask;
ngx_uint_t deny;
ngx_uint_t flags;
} ngx_rtmp_access_rule6_t;
#endif
typedef struct {
ngx_array_t rules; /* array of ngx_rtmp_access_rule_t */
#if (NGX_HAVE_INET6)
ngx_array_t rules6; /* array of ngx_rtmp_access_rule6_t */
#endif
} ngx_rtmp_access_app_conf_t;
static ngx_command_t ngx_rtmp_access_commands[] = {
{ ngx_string("allow"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12,
ngx_rtmp_access_rule,
NGX_RTMP_APP_CONF_OFFSET,
0,
NULL },
{ ngx_string("deny"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12,
ngx_rtmp_access_rule,
NGX_RTMP_APP_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
static ngx_rtmp_module_t ngx_rtmp_access_module_ctx = {
NULL, /* preconfiguration */
ngx_rtmp_access_postconfiguration, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_rtmp_access_create_app_conf, /* create app configuration */
ngx_rtmp_access_merge_app_conf, /* merge app configuration */
};
ngx_module_t ngx_rtmp_access_module = {
NGX_MODULE_V1,
&ngx_rtmp_access_module_ctx, /* module context */
ngx_rtmp_access_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_access_create_app_conf(ngx_conf_t *cf)
{
ngx_rtmp_access_app_conf_t *aacf;
aacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_access_app_conf_t));
if (aacf == NULL) {
return NULL;
}
if (ngx_array_init(&aacf->rules, cf->pool, 1,
sizeof(ngx_rtmp_access_rule_t))
!= NGX_OK)
{
return NULL;
}
#if (NGX_HAVE_INET6)
if (ngx_array_init(&aacf->rules6, cf->pool, 1,
sizeof(ngx_rtmp_access_rule6_t))
!= NGX_OK)
{
return NULL;
}
#endif
return aacf;
}
static ngx_int_t
ngx_rtmp_access_merge_rules(ngx_array_t *prev, ngx_array_t *rules)
{
void *p;
if (prev->nelts == 0) {
return NGX_OK;
}
if (rules->nelts == 0) {
*rules = *prev;
return NGX_OK;
}
p = ngx_array_push_n(rules, prev->nelts);
if (p == NULL) {
return NGX_ERROR;
}
ngx_memcpy(p, prev->elts, prev->size * prev->nelts);
return NGX_OK;
}
static char *
ngx_rtmp_access_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_rtmp_access_app_conf_t *prev = parent;
ngx_rtmp_access_app_conf_t *conf = child;
if (ngx_rtmp_access_merge_rules(&prev->rules, &conf->rules) != NGX_OK) {
return NGX_CONF_ERROR;
}
#if (NGX_HAVE_INET6)
if (ngx_rtmp_access_merge_rules(&prev->rules6, &conf->rules6) != NGX_OK) {
return NGX_CONF_ERROR;
}
#endif
return NGX_CONF_OK;
}
static ngx_int_t
ngx_rtmp_access_found(ngx_rtmp_session_t *s, ngx_uint_t deny)
{
if (deny) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"access forbidden by rule");
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_access_inet(ngx_rtmp_session_t *s, in_addr_t addr, ngx_uint_t flag)
{
ngx_uint_t i;
ngx_rtmp_access_rule_t *rule;
ngx_rtmp_access_app_conf_t *ascf;
ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module);
rule = ascf->rules.elts;
for (i = 0; i < ascf->rules.nelts; i++) {
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, s->log, 0,
"access: %08XD %08XD %08XD",
addr, rule[i].mask, rule[i].addr);
if ((addr & rule[i].mask) == rule[i].addr && (flag & rule[i].flags)) {
return ngx_rtmp_access_found(s, rule[i].deny);
}
}
return NGX_OK;
}
#if (NGX_HAVE_INET6)
static ngx_int_t
ngx_rtmp_access_inet6(ngx_rtmp_session_t *s, u_char *p, ngx_uint_t flag)
{
ngx_uint_t n;
ngx_uint_t i;
ngx_rtmp_access_rule6_t *rule6;
ngx_rtmp_access_app_conf_t *ascf;
ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module);
rule6 = ascf->rules6.elts;
for (i = 0; i < ascf->rules6.nelts; i++) {
#if (NGX_DEBUG)
{
size_t cl, ml, al;
u_char ct[NGX_INET6_ADDRSTRLEN];
u_char mt[NGX_INET6_ADDRSTRLEN];
u_char at[NGX_INET6_ADDRSTRLEN];
cl = ngx_inet6_ntop(p, ct, NGX_INET6_ADDRSTRLEN);
ml = ngx_inet6_ntop(rule6[i].mask.s6_addr, mt, NGX_INET6_ADDRSTRLEN);
al = ngx_inet6_ntop(rule6[i].addr.s6_addr, at, NGX_INET6_ADDRSTRLEN);
ngx_log_debug6(NGX_LOG_DEBUG_HTTP, s->log, 0,
"access: %*s %*s %*s", cl, ct, ml, mt, al, at);
}
#endif
for (n = 0; n < 16; n++) {
if ((p[n] & rule6[i].mask.s6_addr[n]) != rule6[i].addr.s6_addr[n]) {
goto next;
}
}
if (flag & rule6[i].flags) {
return ngx_rtmp_access_found(s, rule6[i].deny);
}
next:
continue;
}
return NGX_OK;
}
#endif
static ngx_int_t
ngx_rtmp_access(ngx_rtmp_session_t *s, ngx_uint_t flag)
{
struct sockaddr_in *sin;
ngx_rtmp_access_app_conf_t *ascf;
#if (NGX_HAVE_INET6)
u_char *p;
in_addr_t addr;
struct sockaddr_in6 *sin6;
#endif
ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module);
if (ascf == NULL) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, s->log, 0,
"access: NULL app conf");
return NGX_ERROR;
}
/* relay etc */
if (s->sockaddr == NULL) {
return NGX_OK;
}
switch (s->sockaddr->sa_family) {
case AF_INET:
sin = (struct sockaddr_in *) s->sockaddr;
return ngx_rtmp_access_inet(s, sin->sin_addr.s_addr, flag);
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) s->sockaddr;
p = sin6->sin6_addr.s6_addr;
if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
addr = p[12] << 24;
addr += p[13] << 16;
addr += p[14] << 8;
addr += p[15];
return ngx_rtmp_access_inet(s, htonl(addr), flag);
}
return ngx_rtmp_access_inet6(s, p, flag);
#endif
}
return NGX_OK;
}
static char *
ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_rtmp_access_app_conf_t *ascf = conf;
ngx_int_t rc;
ngx_uint_t all;
ngx_str_t *value;
ngx_cidr_t cidr;
ngx_rtmp_access_rule_t *rule;
#if (NGX_HAVE_INET6)
ngx_rtmp_access_rule6_t *rule6;
#endif
size_t n;
ngx_uint_t flags;
ngx_memzero(&cidr, sizeof(ngx_cidr_t));
value = cf->args->elts;
n = 1;
flags = 0;
if (cf->args->nelts == 2) {
flags = NGX_RTMP_ACCESS_PUBLISH | NGX_RTMP_ACCESS_PLAY;
} else {
for(; n < cf->args->nelts - 1; ++n) {
if (value[n].len == sizeof("publish") - 1 &&
ngx_strcmp(value[1].data, "publish") == 0)
{
flags |= NGX_RTMP_ACCESS_PUBLISH;
continue;
}
if (value[n].len == sizeof("play") - 1 &&
ngx_strcmp(value[1].data, "play") == 0)
{
flags |= NGX_RTMP_ACCESS_PLAY;
continue;
}
ngx_log_error(NGX_LOG_ERR, cf->log, 0,
"unexpected access specified: '%V'", &value[n]);
return NGX_CONF_ERROR;
}
}
all = (value[n].len == 3 && ngx_strcmp(value[n].data, "all") == 0);
if (!all) {
rc = ngx_ptocidr(&value[n], &cidr);
if (rc == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}
if (rc == NGX_DONE) {
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
"low address bits of %V are meaningless",
&value[1]);
}
}
switch (cidr.family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
case 0: /* all */
rule6 = ngx_array_push(&ascf->rules6);
if (rule6 == NULL) {
return NGX_CONF_ERROR;
}
rule6->mask = cidr.u.in6.mask;
rule6->addr = cidr.u.in6.addr;
rule6->deny = (value[0].data[0] == 'd') ? 1 : 0;
rule6->flags = flags;
if (!all) {
break;
}
rule = ngx_array_push(&ascf->rules);
if (rule == NULL) {
return NGX_CONF_ERROR;
}
rule->mask = cidr.u.in.mask;
rule->addr = cidr.u.in.addr;
rule->deny = (value[0].data[0] == 'd') ? 1 : 0;
rule->flags = flags;
break;
/* "all" passes through */
#endif
default: /* AF_INET */
rule = ngx_array_push(&ascf->rules);
if (rule == NULL) {
return NGX_CONF_ERROR;
}
rule->mask = cidr.u.in.mask;
rule->addr = cidr.u.in.addr;
rule->deny = (value[0].data[0] == 'd') ? 1 : 0;
rule->flags = flags;
}
return NGX_CONF_OK;
}
static ngx_int_t
ngx_rtmp_access_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
{
if (s->interprocess) {
goto next;
}
if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PUBLISH) != NGX_OK) {
return NGX_ERROR;
}
next:
return next_publish(s, v);
}
static ngx_int_t
ngx_rtmp_access_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
{
if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PLAY) != NGX_OK) {
return NGX_ERROR;
}
return next_play(s, v);
}
static ngx_int_t
ngx_rtmp_access_postconfiguration(ngx_conf_t *cf)
{
/* chain handlers */
next_publish = ngx_rtmp_publish;
ngx_rtmp_publish = ngx_rtmp_access_publish;
next_play = ngx_rtmp_play;
ngx_rtmp_play = ngx_rtmp_access_play;
return NGX_OK;
}

View File

@ -0,0 +1,664 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp_amf.h"
#include "ngx_rtmp.h"
#include <string.h>
static ngx_inline void*
ngx_rtmp_amf_reverse_copy(void *dst, void* src, size_t len)
{
size_t k;
if (dst == NULL || src == NULL) {
return NULL;
}
for(k = 0; k < len; ++k) {
((u_char*)dst)[k] = ((u_char*)src)[len - 1 - k];
}
return dst;
}
#define NGX_RTMP_AMF_DEBUG_SIZE 16
#ifdef NGX_DEBUG
static void
ngx_rtmp_amf_debug(const char* op, ngx_log_t *log, u_char *p, size_t n)
{
u_char hstr[3 * NGX_RTMP_AMF_DEBUG_SIZE + 1];
u_char str[NGX_RTMP_AMF_DEBUG_SIZE + 1];
u_char *hp, *sp;
static u_char hex[] = "0123456789ABCDEF";
size_t i;
hp = hstr;
sp = str;
for(i = 0; i < n && i < NGX_RTMP_AMF_DEBUG_SIZE; ++i) {
*hp++ = ' ';
if (p) {
*hp++ = hex[(*p & 0xf0) >> 4];
*hp++ = hex[*p & 0x0f];
*sp++ = (*p >= 0x20 && *p <= 0x7e) ?
*p : (u_char)'?';
++p;
} else {
*hp++ = 'X';
*hp++ = 'X';
*sp++ = '?';
}
}
*hp = *sp = '\0';
ngx_log_debug4(NGX_LOG_DEBUG_RTMP, log, 0,
"AMF %s (%d)%s '%s'", op, n, hstr, str);
}
#endif
static ngx_int_t
ngx_rtmp_amf_get(ngx_rtmp_amf_ctx_t *ctx, void *p, size_t n)
{
size_t size;
ngx_chain_t *l;
size_t offset;
u_char *pos, *last;
#ifdef NGX_DEBUG
void *op = p;
size_t on = n;
#endif
if (!n)
return NGX_OK;
for(l = ctx->link, offset = ctx->offset; l; l = l->next, offset = 0) {
pos = l->buf->pos + offset;
last = l->buf->last;
if (last >= pos + n) {
if (p) {
p = ngx_cpymem(p, pos, n);
}
ctx->offset = offset + n;
ctx->link = l;
#ifdef NGX_DEBUG
ngx_rtmp_amf_debug("read", ctx->log, (u_char*)op, on);
#endif
return NGX_OK;
}
size = last - pos;
if (p) {
p = ngx_cpymem(p, pos, size);
}
n -= size;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ctx->log, 0,
"AMF read eof (%d)", n);
return NGX_DONE;
}
static ngx_int_t
ngx_rtmp_amf_put(ngx_rtmp_amf_ctx_t *ctx, void *p, size_t n)
{
ngx_buf_t *b;
size_t size;
ngx_chain_t *l, *ln;
#ifdef NGX_DEBUG
ngx_rtmp_amf_debug("write", ctx->log, (u_char*)p, n);
#endif
l = ctx->link;
if (ctx->link && ctx->first == NULL) {
ctx->first = ctx->link;
}
while(n) {
b = l ? l->buf : NULL;
if (b == NULL || b->last == b->end) {
ln = ctx->alloc(ctx->arg);
if (ln == NULL) {
return NGX_ERROR;
}
if (ctx->first == NULL) {
ctx->first = ln;
}
if (l) {
l->next = ln;
}
l = ln;
ctx->link = l;
b = l->buf;
}
size = b->end - b->last;
if (size >= n) {
b->last = ngx_cpymem(b->last, p, n);
return NGX_OK;
}
b->last = ngx_cpymem(b->last, p, size);
p = (u_char*)p + size;
n -= size;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_amf_read_object(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,
size_t nelts)
{
uint8_t type;
uint16_t len;
size_t n, namelen, maxlen;
ngx_int_t rc;
u_char buf[2];
maxlen = 0;
for(n = 0; n < nelts; ++n) {
namelen = elts[n].name.len;
if (namelen > maxlen)
maxlen = namelen;
}
for( ;; ) {
#if !(NGX_WIN32)
char name[maxlen];
#else
char name[1024];
if (maxlen > sizeof(name)) {
return NGX_ERROR;
}
#endif
/* read key */
switch (ngx_rtmp_amf_get(ctx, buf, 2)) {
case NGX_DONE:
/* Envivio sends unfinalized arrays */
return NGX_OK;
case NGX_OK:
break;
default:
return NGX_ERROR;
}
ngx_rtmp_amf_reverse_copy(&len, buf, 2);
if (!len)
break;
if (len <= maxlen) {
rc = ngx_rtmp_amf_get(ctx, name, len);
} else {
rc = ngx_rtmp_amf_get(ctx, name, maxlen);
if (rc != NGX_OK)
return NGX_ERROR;
rc = ngx_rtmp_amf_get(ctx, 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
&& (len != elts[n].name.len
|| ngx_strncmp(name, elts[n].name.data, len));
++n);
if (ngx_rtmp_amf_read(ctx, n < nelts ? &elts[n] : NULL, 1) != NGX_OK)
return NGX_ERROR;
}
if (ngx_rtmp_amf_get(ctx, &type, 1) != NGX_OK
|| type != NGX_RTMP_AMF_END)
{
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_amf_read_array(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,
size_t nelts)
{
uint32_t len;
size_t n;
u_char buf[4];
/* read length */
if (ngx_rtmp_amf_get(ctx, buf, 4) != NGX_OK)
return NGX_ERROR;
ngx_rtmp_amf_reverse_copy(&len, buf, 4);
for (n = 0; n < len; ++n) {
if (ngx_rtmp_amf_read(ctx, n < nelts ? &elts[n] : NULL, 1) != NGX_OK)
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_amf_read_variant(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,
size_t nelts)
{
uint8_t type;
ngx_int_t rc;
size_t n;
ngx_rtmp_amf_elt_t elt;
rc = ngx_rtmp_amf_get(ctx, &type, 1);
if (rc != NGX_OK) {
return rc;
}
ngx_memzero(&elt, sizeof(elt));
for (n = 0; n < nelts; ++n, ++elts) {
if (type == elts->type) {
elt.data = elts->data;
elt.len = elts->len;
}
}
elt.type = type | NGX_RTMP_AMF_TYPELESS;
return ngx_rtmp_amf_read(ctx, &elt, 1);
}
static ngx_int_t
ngx_rtmp_amf_is_compatible_type(uint8_t t1, uint8_t t2)
{
return t1 == t2
|| (t1 == NGX_RTMP_AMF_OBJECT && t2 == NGX_RTMP_AMF_MIXED_ARRAY)
|| (t2 == NGX_RTMP_AMF_OBJECT && t1 == NGX_RTMP_AMF_MIXED_ARRAY);
}
ngx_int_t
ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,
size_t nelts)
{
void *data;
ngx_int_t type;
uint8_t type8;
size_t n;
uint16_t len;
ngx_int_t rc;
u_char buf[8];
uint32_t max_index;
for(n = 0; n < nelts; ++n) {
if (elts && elts->type & NGX_RTMP_AMF_TYPELESS) {
type = elts->type & ~NGX_RTMP_AMF_TYPELESS;
data = elts->data;
} else {
switch (ngx_rtmp_amf_get(ctx, &type8, 1)) {
case NGX_DONE:
if (elts->type & NGX_RTMP_AMF_OPTIONAL) {
return NGX_OK;
} else {
return NGX_ERROR;
}
break;
case NGX_ERROR:
return NGX_ERROR;
}
type = type8;
data = (elts &&
ngx_rtmp_amf_is_compatible_type(
(uint8_t) (elts->type & 0xff), (uint8_t) type))
? elts->data
: NULL;
if (elts && (elts->type & NGX_RTMP_AMF_CONTEXT)) {
if (data) {
*(ngx_rtmp_amf_ctx_t *) data = *ctx;
}
data = NULL;
}
}
switch (type) {
case NGX_RTMP_AMF_NUMBER:
if (ngx_rtmp_amf_get(ctx, buf, 8) != NGX_OK) {
return NGX_ERROR;
}
ngx_rtmp_amf_reverse_copy(data, buf, 8);
break;
case NGX_RTMP_AMF_BOOLEAN:
if (ngx_rtmp_amf_get(ctx, data, 1) != NGX_OK) {
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_STRING:
if (ngx_rtmp_amf_get(ctx, buf, 2) != NGX_OK) {
return NGX_ERROR;
}
ngx_rtmp_amf_reverse_copy(&len, buf, 2);
if (data == NULL) {
rc = ngx_rtmp_amf_get(ctx, data, len);
} else if (elts->len <= len) {
rc = ngx_rtmp_amf_get(ctx, data, elts->len - 1);
if (rc != NGX_OK)
return NGX_ERROR;
((char*)data)[elts->len - 1] = 0;
rc = ngx_rtmp_amf_get(ctx, NULL, len - elts->len + 1);
} else {
rc = ngx_rtmp_amf_get(ctx, data, len);
((char*)data)[len] = 0;
}
if (rc != NGX_OK) {
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_NULL:
case NGX_RTMP_AMF_ARRAY_NULL:
break;
case NGX_RTMP_AMF_MIXED_ARRAY:
if (ngx_rtmp_amf_get(ctx, &max_index, 4) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_rtmp_amf_read_object(ctx, data,
data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0
) != NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_OBJECT:
if (ngx_rtmp_amf_read_object(ctx, data,
data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0
) != NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_ARRAY:
if (ngx_rtmp_amf_read_array(ctx, data,
data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0
) != NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_VARIANT_:
if (ngx_rtmp_amf_read_variant(ctx, data,
data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0
) != NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_INT8:
if (ngx_rtmp_amf_get(ctx, data, 1) != NGX_OK) {
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_INT16:
if (ngx_rtmp_amf_get(ctx, buf, 2) != NGX_OK) {
return NGX_ERROR;
}
ngx_rtmp_amf_reverse_copy(data, buf, 2);
break;
case NGX_RTMP_AMF_INT32:
if (ngx_rtmp_amf_get(ctx, buf, 4) != NGX_OK) {
return NGX_ERROR;
}
ngx_rtmp_amf_reverse_copy(data, buf, 4);
break;
case NGX_RTMP_AMF_END:
return NGX_OK;
default:
return NGX_ERROR;
}
if (elts) {
++elts;
}
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_amf_write_object(ngx_rtmp_amf_ctx_t *ctx,
ngx_rtmp_amf_elt_t *elts, size_t nelts)
{
uint16_t len;
size_t n;
u_char buf[2];
for(n = 0; n < nelts; ++n) {
len = (uint16_t) elts[n].name.len;
if (ngx_rtmp_amf_put(ctx,
ngx_rtmp_amf_reverse_copy(buf,
&len, 2), 2) != NGX_OK)
{
return NGX_ERROR;
}
if (ngx_rtmp_amf_put(ctx, elts[n].name.data, len) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_rtmp_amf_write(ctx, &elts[n], 1) != NGX_OK) {
return NGX_ERROR;
}
}
if (ngx_rtmp_amf_put(ctx, "\0\0", 2) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_amf_write_array(ngx_rtmp_amf_ctx_t *ctx,
ngx_rtmp_amf_elt_t *elts, size_t nelts)
{
uint32_t len;
size_t n;
u_char buf[4];
len = nelts;
if (ngx_rtmp_amf_put(ctx,
ngx_rtmp_amf_reverse_copy(buf,
&len, 4), 4) != NGX_OK)
{
return NGX_ERROR;
}
for(n = 0; n < nelts; ++n) {
if (ngx_rtmp_amf_write(ctx, &elts[n], 1) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
ngx_int_t
ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx,
ngx_rtmp_amf_elt_t *elts, size_t nelts)
{
size_t n;
ngx_int_t type;
uint8_t type8;
void *data;
uint16_t len;
uint32_t max_index;
u_char buf[8];
for(n = 0; n < nelts; ++n) {
type = elts[n].type;
data = elts[n].data;
len = (uint16_t) elts[n].len;
if (type & NGX_RTMP_AMF_TYPELESS) {
type &= ~NGX_RTMP_AMF_TYPELESS;
} else {
type8 = (uint8_t)type;
if (ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK)
return NGX_ERROR;
}
switch(type) {
case NGX_RTMP_AMF_NUMBER:
if (ngx_rtmp_amf_put(ctx,
ngx_rtmp_amf_reverse_copy(buf,
data, 8), 8) != NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_BOOLEAN:
if (ngx_rtmp_amf_put(ctx, data, 1) != NGX_OK) {
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_STRING:
if (len == 0 && data) {
len = (uint16_t) ngx_strlen((u_char*) data);
}
if (ngx_rtmp_amf_put(ctx,
ngx_rtmp_amf_reverse_copy(buf,
&len, 2), 2) != NGX_OK)
{
return NGX_ERROR;
}
if (ngx_rtmp_amf_put(ctx, data, len) != NGX_OK) {
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_NULL:
case NGX_RTMP_AMF_ARRAY_NULL:
break;
case NGX_RTMP_AMF_MIXED_ARRAY:
max_index = 0;
if (ngx_rtmp_amf_put(ctx, &max_index, 4) != NGX_OK) {
return NGX_ERROR;
}
type8 = NGX_RTMP_AMF_END;
if (ngx_rtmp_amf_write_object(ctx, data,
elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK
|| ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_OBJECT:
type8 = NGX_RTMP_AMF_END;
if (ngx_rtmp_amf_write_object(ctx, data,
elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK
|| ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_ARRAY:
if (ngx_rtmp_amf_write_array(ctx, data,
elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_INT8:
if (ngx_rtmp_amf_put(ctx, data, 1) != NGX_OK) {
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_INT16:
if (ngx_rtmp_amf_put(ctx,
ngx_rtmp_amf_reverse_copy(buf,
data, 2), 2) != NGX_OK)
{
return NGX_ERROR;
}
break;
case NGX_RTMP_AMF_INT32:
if (ngx_rtmp_amf_put(ctx,
ngx_rtmp_amf_reverse_copy(buf,
data, 4), 4) != NGX_OK)
{
return NGX_ERROR;
}
break;
default:
return NGX_ERROR;
}
}
return NGX_OK;
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#ifndef _NGX_RTMP_AMF_H_INCLUDED_
#define _NGX_RTMP_AMF_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
/* basic types */
#define NGX_RTMP_AMF_NUMBER 0x00
#define NGX_RTMP_AMF_BOOLEAN 0x01
#define NGX_RTMP_AMF_STRING 0x02
#define NGX_RTMP_AMF_OBJECT 0x03
#define NGX_RTMP_AMF_NULL 0x05
#define NGX_RTMP_AMF_ARRAY_NULL 0x06
#define NGX_RTMP_AMF_MIXED_ARRAY 0x08
#define NGX_RTMP_AMF_END 0x09
#define NGX_RTMP_AMF_ARRAY 0x0a
/* extended types */
#define NGX_RTMP_AMF_INT8 0x0100
#define NGX_RTMP_AMF_INT16 0x0101
#define NGX_RTMP_AMF_INT32 0x0102
#define NGX_RTMP_AMF_VARIANT_ 0x0103
/* r/w flags */
#define NGX_RTMP_AMF_OPTIONAL 0x1000
#define NGX_RTMP_AMF_TYPELESS 0x2000
#define NGX_RTMP_AMF_CONTEXT 0x4000
#define NGX_RTMP_AMF_VARIANT (NGX_RTMP_AMF_VARIANT_\
|NGX_RTMP_AMF_TYPELESS)
typedef struct {
ngx_int_t type;
ngx_str_t name;
void *data;
size_t len;
} ngx_rtmp_amf_elt_t;
typedef ngx_chain_t * (*ngx_rtmp_amf_alloc_pt)(void *arg);
typedef struct {
ngx_chain_t *link, *first;
size_t offset;
ngx_rtmp_amf_alloc_pt alloc;
void *arg;
ngx_log_t *log;
} ngx_rtmp_amf_ctx_t;
/* reading AMF */
ngx_int_t ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx,
ngx_rtmp_amf_elt_t *elts, size_t nelts);
/* writing AMF */
ngx_int_t ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx,
ngx_rtmp_amf_elt_t *elts, size_t nelts);
#endif /* _NGX_RTMP_AMF_H_INCLUDED_ */

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp_bandwidth.h"
void
ngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes)
{
if (ngx_cached_time->sec > bw->intl_end) {
bw->bandwidth = ngx_cached_time->sec >
bw->intl_end + NGX_RTMP_BANDWIDTH_INTERVAL
? 0
: bw->intl_bytes / NGX_RTMP_BANDWIDTH_INTERVAL;
bw->intl_bytes = 0;
bw->intl_end = ngx_cached_time->sec + NGX_RTMP_BANDWIDTH_INTERVAL;
}
bw->bytes += bytes;
bw->intl_bytes += bytes;
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#ifndef _NGX_RTMP_BANDWIDTH_H_INCLUDED_
#define _NGX_RTMP_BANDWIDTH_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
/* Bandwidth update interval in seconds */
#define NGX_RTMP_BANDWIDTH_INTERVAL 10
typedef struct {
uint64_t bytes;
uint64_t bandwidth; /* bytes/sec */
time_t intl_end;
uint64_t intl_bytes;
} ngx_rtmp_bandwidth_t;
void ngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes);
#endif /* _NGX_RTMP_BANDWIDTH_H_INCLUDED_ */

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp_bitop.h"
void
ngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos, u_char *last)
{
ngx_memzero(br, sizeof(ngx_rtmp_bit_reader_t));
br->pos = pos;
br->last = last;
}
uint64_t
ngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n)
{
uint64_t v;
ngx_uint_t d;
v = 0;
while (n) {
if (br->pos >= br->last) {
br->err = 1;
return 0;
}
d = (br->offs + n > 8 ? (ngx_uint_t) (8 - br->offs) : n);
v <<= d;
v += (*br->pos >> (8 - br->offs - d)) & ((u_char) 0xff >> (8 - d));
br->offs += d;
n -= d;
if (br->offs == 8) {
br->pos++;
br->offs = 0;
}
}
return v;
}
uint64_t
ngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br)
{
ngx_uint_t n;
for (n = 0; ngx_rtmp_bit_read(br, 1) == 0 && !br->err; n++);
return ((uint64_t) 1 << n) + ngx_rtmp_bit_read(br, n) - 1;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#ifndef _NGX_RTMP_BITOP_H_INCLUDED_
#define _NGX_RTMP_BITOP_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
typedef struct {
u_char *pos;
u_char *last;
ngx_uint_t offs;
ngx_uint_t err;
} ngx_rtmp_bit_reader_t;
void ngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos,
u_char *last);
uint64_t ngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n);
uint64_t ngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br);
#define ngx_rtmp_bit_read_err(br) ((br)->err)
#define ngx_rtmp_bit_read_eof(br) ((br)->pos == (br)->last)
#define ngx_rtmp_bit_read_8(br) \
((uint8_t) ngx_rtmp_bit_read(br, 8))
#define ngx_rtmp_bit_read_16(br) \
((uint16_t) ngx_rtmp_bit_read(br, 16))
#define ngx_rtmp_bit_read_32(br) \
((uint32_t) ngx_rtmp_bit_read(br, 32))
#define ngx_rtmp_bit_read_64(br) \
((uint64_t) ngx_rtmp_read(br, 64))
#endif /* _NGX_RTMP_BITOP_H_INCLUDED_ */

View File

@ -0,0 +1,166 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#ifndef _NGX_RTMP_CMD_H_INCLUDED_
#define _NGX_RTMP_CMD_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include "ngx_rtmp.h"
#define NGX_RTMP_MAX_NAME 256
#define NGX_RTMP_MAX_FLASHVER 256
#define NGX_RTMP_MAX_URL 256
#define NGX_RTMP_MAX_SESSION 256
#define NGX_RTMP_MAX_ARGS NGX_RTMP_MAX_NAME
/* Basic RTMP call support */
typedef struct {
double trans;
u_char app[NGX_RTMP_MAX_NAME];
u_char args[NGX_RTMP_MAX_ARGS];
u_char flashver[NGX_RTMP_MAX_FLASHVER];
u_char swf_url[NGX_RTMP_MAX_URL];
u_char tc_url[NGX_RTMP_MAX_URL];
double acodecs;
double vcodecs;
u_char page_url[NGX_RTMP_MAX_URL];
double object_encoding;
} ngx_rtmp_connect_t;
typedef struct {
double trans;
double stream;
} ngx_rtmp_create_stream_t;
typedef struct {
double stream;
} ngx_rtmp_delete_stream_t;
typedef struct {
double stream;
} ngx_rtmp_close_stream_t;
typedef struct {
u_char name[NGX_RTMP_MAX_NAME];
u_char args[NGX_RTMP_MAX_ARGS];
u_char type[16];
int silent;
} ngx_rtmp_publish_t;
typedef struct {
u_char name[NGX_RTMP_MAX_NAME];
u_char args[NGX_RTMP_MAX_ARGS];
u_char session[NGX_RTMP_MAX_SESSION];
double start;
double duration;
int reset;
int silent;
} ngx_rtmp_play_t;
typedef struct {
double offset;
} ngx_rtmp_seek_t;
typedef struct {
uint8_t pause;
double position;
} ngx_rtmp_pause_t;
typedef struct {
uint32_t msid;
} ngx_rtmp_msid_t;
typedef ngx_rtmp_msid_t ngx_rtmp_stream_begin_t;
typedef ngx_rtmp_msid_t ngx_rtmp_stream_eof_t;
typedef ngx_rtmp_msid_t ngx_rtmp_stream_dry_t;
typedef ngx_rtmp_msid_t ngx_rtmp_recorded_t;
typedef struct {
uint32_t msid;
uint32_t buflen;
} ngx_rtmp_set_buflen_t;
void ngx_rtmp_cmd_fill_args(u_char name[NGX_RTMP_MAX_NAME],
u_char args[NGX_RTMP_MAX_ARGS]);
void ngx_rtmp_cmd_middleware_init(ngx_rtmp_session_t *s);
void ngx_rtmp_cmd_stream_init(ngx_rtmp_session_t *s, u_char *name, u_char *args,
unsigned publishing);
ngx_int_t ngx_rtmp_publish_filter(ngx_rtmp_session_t *s,
ngx_rtmp_publish_t *v);
ngx_int_t ngx_rtmp_play_filter(ngx_rtmp_session_t *s,
ngx_rtmp_play_t *v);
ngx_int_t ngx_rtmp_close_stream_filter(ngx_rtmp_session_t *s,
ngx_rtmp_close_stream_t *v);
ngx_int_t ngx_rtmp_push_filter(ngx_rtmp_session_t *s);
ngx_int_t ngx_rtmp_pull_filter(ngx_rtmp_session_t *s);
typedef ngx_int_t (*ngx_rtmp_connect_pt)(ngx_rtmp_session_t *s,
ngx_rtmp_connect_t *v);
typedef ngx_int_t (*ngx_rtmp_disconnect_pt)(ngx_rtmp_session_t *s);
typedef ngx_int_t (*ngx_rtmp_create_stream_pt)(ngx_rtmp_session_t *s,
ngx_rtmp_create_stream_t *v);
typedef ngx_int_t (*ngx_rtmp_close_stream_pt)(ngx_rtmp_session_t *s,
ngx_rtmp_close_stream_t *v);
typedef ngx_int_t (*ngx_rtmp_delete_stream_pt)(ngx_rtmp_session_t *s,
ngx_rtmp_delete_stream_t *v);
typedef ngx_int_t (*ngx_rtmp_publish_pt)(ngx_rtmp_session_t *s,
ngx_rtmp_publish_t *v);
typedef ngx_int_t (*ngx_rtmp_play_pt)(ngx_rtmp_session_t *s,
ngx_rtmp_play_t *v);
typedef ngx_int_t (*ngx_rtmp_seek_pt)(ngx_rtmp_session_t *s,
ngx_rtmp_seek_t *v);
typedef ngx_int_t (*ngx_rtmp_pause_pt)(ngx_rtmp_session_t *s,
ngx_rtmp_pause_t *v);
typedef ngx_int_t (*ngx_rtmp_stream_begin_pt)(ngx_rtmp_session_t *s,
ngx_rtmp_stream_begin_t *v);
typedef ngx_int_t (*ngx_rtmp_stream_eof_pt)(ngx_rtmp_session_t *s,
ngx_rtmp_stream_eof_t *v);
typedef ngx_int_t (*ngx_rtmp_stream_dry_pt)(ngx_rtmp_session_t *s,
ngx_rtmp_stream_dry_t *v);
typedef ngx_int_t (*ngx_rtmp_recorded_pt)(ngx_rtmp_session_t *s,
ngx_rtmp_recorded_t *v);
typedef ngx_int_t (*ngx_rtmp_set_buflen_pt)(ngx_rtmp_session_t *s,
ngx_rtmp_set_buflen_t *v);
extern ngx_rtmp_connect_pt ngx_rtmp_connect;
extern ngx_rtmp_disconnect_pt ngx_rtmp_disconnect;
extern ngx_rtmp_create_stream_pt ngx_rtmp_create_stream;
extern ngx_rtmp_close_stream_pt ngx_rtmp_close_stream;
extern ngx_rtmp_delete_stream_pt ngx_rtmp_delete_stream;
extern ngx_rtmp_publish_pt ngx_rtmp_publish;
extern ngx_rtmp_play_pt ngx_rtmp_play;
extern ngx_rtmp_seek_pt ngx_rtmp_seek;
extern ngx_rtmp_pause_pt ngx_rtmp_pause;
extern ngx_rtmp_stream_begin_pt ngx_rtmp_stream_begin;
extern ngx_rtmp_stream_eof_pt ngx_rtmp_stream_eof;
extern ngx_rtmp_stream_dry_pt ngx_rtmp_stream_dry;
extern ngx_rtmp_set_buflen_pt ngx_rtmp_set_buflen;
extern ngx_rtmp_recorded_pt ngx_rtmp_recorded;
#endif /*_NGX_RTMP_CMD_H_INCLUDED_ */

View File

@ -0,0 +1,85 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#ifndef _NGX_RTMP_CODEC_H_INCLUDED_
#define _NGX_RTMP_CODEC_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
/* hevc nal type */
enum {
NAL_TRAIL_N = 0,
NAL_TRAIL_R = 1,
NAL_TSA_N = 2,
NAL_TSA_R = 3,
NAL_STSA_N = 4,
NAL_STSA_R = 5,
NAL_RADL_N = 6,
NAL_RADL_R = 7,
NAL_RASL_N = 8,
NAL_RASL_R = 9,
NAL_BLA_W_LP = 16,
NAL_BLA_W_RADL = 17,
NAL_BLA_N_LP = 18,
NAL_IDR_W_RADL = 19,
NAL_IDR_N_LP = 20,
NAL_CRA_NUT = 21,
NAL_VPS = 32,
NAL_SPS = 33,
NAL_PPS = 34,
NAL_AUD = 35,
NAL_EOS_NUT = 36,
NAL_EOB_NUT = 37,
NAL_FD_NUT = 38,
NAL_SEI_PREFIX = 39,
NAL_SEI_SUFFIX = 40,
};
u_char * ngx_rtmp_get_audio_codec_name(ngx_uint_t id);
u_char * ngx_rtmp_get_video_codec_name(ngx_uint_t id);
typedef struct {
ngx_uint_t width;
ngx_uint_t height;
ngx_uint_t duration;
double frame_rate;
ngx_uint_t video_data_rate;
ngx_uint_t video_codec_id;
ngx_uint_t audio_data_rate;
ngx_uint_t audio_codec_id;
ngx_uint_t aac_profile;
ngx_uint_t aac_chan_conf;
ngx_uint_t aac_sbr;
ngx_uint_t aac_ps;
ngx_uint_t avc_profile; /* avc or hevc */
ngx_uint_t avc_compat; /* avc or hevc */
ngx_uint_t avc_level; /* avc or hevc */
ngx_uint_t avc_nal_bytes; /* avc or hevc */
ngx_uint_t avc_ref_frames; /* avc or hevc */
ngx_uint_t sample_rate; /* 5512, 11025, 22050, 44100 */
ngx_uint_t sample_size; /* 1=8bit, 2=16bit */
ngx_uint_t audio_channels; /* 1, 2 */
u_char profile[32];
u_char level[32];
ngx_rtmp_frame_t *avc_header; /* avc or hevc */
ngx_rtmp_frame_t *aac_header;
ngx_rtmp_frame_t *meta;
ngx_uint_t meta_version;
} ngx_rtmp_codec_ctx_t;
extern ngx_module_t ngx_rtmp_codec_module;
#endif /* _NGX_RTMP_LIVE_H_INCLUDED_ */

View File

@ -0,0 +1,922 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include "ngx_rtmp.h"
#include "ngx_live.h"
#include "ngx_rtmp_live_module.h"
#include "ngx_live_record.h"
#include "ngx_rtmp_record_module.h"
#include "ngx_rtmp_dynamic.h"
static char *ngx_rtmp_control(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *ngx_rtmp_control_create_loc_conf(ngx_conf_t *cf);
static char *ngx_rtmp_control_merge_loc_conf(ngx_conf_t *cf,
void *parent, void *child);
typedef const char * (*ngx_rtmp_control_handler_t)(ngx_http_request_t *r,
ngx_rtmp_session_t *);
#define NGX_RTMP_CONTROL_ALL 0xff
#define NGX_RTMP_CONTROL_RECORD 0x01
#define NGX_RTMP_CONTROL_DROP 0x02
#define NGX_RTMP_CONTROL_REDIRECT 0x04
#define NGX_RTMP_CONTROL_PAUSE 0x08
#define NGX_RTMP_CONTROL_RESUME 0x10
enum {
NGX_RTMP_CONTROL_FILTER_CLIENT = 0,
NGX_RTMP_CONTROL_FILTER_PUBLISHER,
NGX_RTMP_CONTROL_FILTER_SUBSCRIBER
};
typedef struct {
ngx_uint_t count;
ngx_uint_t filter;
ngx_str_t method;
ngx_str_t path;
ngx_array_t sessions; /* ngx_rtmp_session_t * */
} ngx_rtmp_control_ctx_t;
typedef struct {
ngx_uint_t control;
} ngx_rtmp_control_loc_conf_t;
static ngx_conf_bitmask_t ngx_rtmp_control_masks[] = {
{ ngx_string("all"), NGX_RTMP_CONTROL_ALL },
{ ngx_string("record"), NGX_RTMP_CONTROL_RECORD },
{ ngx_string("drop"), NGX_RTMP_CONTROL_DROP },
{ ngx_string("redirect"), NGX_RTMP_CONTROL_REDIRECT },
{ ngx_string("pause"), NGX_RTMP_CONTROL_PAUSE },
{ ngx_string("resume"), NGX_RTMP_CONTROL_RESUME },
{ ngx_null_string, 0 }
};
static ngx_command_t ngx_rtmp_control_commands[] = {
{ ngx_string("rtmp_control"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
ngx_rtmp_control,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_rtmp_control_loc_conf_t, control),
ngx_rtmp_control_masks },
ngx_null_command
};
static ngx_http_module_t ngx_rtmp_control_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_rtmp_control_create_loc_conf, /* create location configuration */
ngx_rtmp_control_merge_loc_conf, /* merge location configuration */
};
ngx_module_t ngx_rtmp_control_module = {
NGX_MODULE_V1,
&ngx_rtmp_control_module_ctx, /* module context */
ngx_rtmp_control_commands, /* module directives */
NGX_HTTP_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 const char *
ngx_rtmp_control_record_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s)
{
ngx_rtmp_control_ctx_t *ctx;
ngx_uint_t rn;
ngx_str_t rec = ngx_null_string;
ngx_rtmp_core_app_conf_t *cacf;
ngx_rtmp_record_app_conf_t *racf;
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module);
racf = cacf->app_conf[ngx_rtmp_record_module.ctx_index];
if (ngx_http_arg(r, (u_char *) "rec", sizeof("rec") - 1, &rec) != NGX_OK) {
rec.len = 0;
}
rn = ngx_rtmp_record_find(racf, &rec);
if (rn == NGX_CONF_UNSET_UINT) {
return "Recorder not found";
}
if (ctx->method.len == sizeof("start") - 1 &&
ngx_strncmp(ctx->method.data, "start", ctx->method.len) == 0)
{
ngx_rtmp_record_open(s, rn, &ctx->path);
return ngx_live_record_open(s);
} else if (ctx->method.len == sizeof("stop") - 1 &&
ngx_strncmp(ctx->method.data, "stop", ctx->method.len) == 0)
{
ngx_rtmp_record_close(s, rn, &ctx->path);
return ngx_live_record_close(s);
} else {
return "Undefined method";
}
}
static const char *
ngx_rtmp_control_drop_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s)
{
ngx_rtmp_control_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
s->finalize_reason = NGX_LIVE_CONTROL_DROP;
ngx_rtmp_finalize_session(s);
++ctx->count;
return NGX_CONF_OK;
}
static const char *
ngx_rtmp_control_redirect_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s)
{
ngx_str_t name;
ngx_rtmp_play_t vplay;
ngx_rtmp_publish_t vpublish;
ngx_rtmp_live_ctx_t *lctx;
ngx_rtmp_control_ctx_t *ctx;
ngx_rtmp_close_stream_t vc;
if (ngx_http_arg(r, (u_char *) "newname", sizeof("newname") - 1, &name)
!= NGX_OK)
{
return "newname not specified";
}
if (name.len >= NGX_RTMP_MAX_NAME) {
name.len = NGX_RTMP_MAX_NAME - 1;
}
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
ctx->count++;
ngx_memzero(&vc, sizeof(ngx_rtmp_close_stream_t));
/* close_stream should be synchronous */
ngx_rtmp_close_stream_filter(s, &vc);
lctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
if (lctx && lctx->publishing) {
/* publish */
ngx_memzero(&vpublish, sizeof(ngx_rtmp_publish_t));
ngx_memcpy(vpublish.name, name.data, name.len);
ngx_rtmp_cmd_fill_args(vpublish.name, vpublish.args);
if (ngx_rtmp_publish_filter(s, &vpublish) != NGX_OK) {
return "publish failed";
}
} else {
/* play */
ngx_memzero(&vplay, sizeof(ngx_rtmp_play_t));
ngx_memcpy(vplay.name, name.data, name.len);
ngx_rtmp_cmd_fill_args(vplay.name, vplay.args);
if (ngx_rtmp_play_filter(s, &vplay) != NGX_OK) {
return "play failed";
}
}
return NGX_CONF_OK;
}
static const char *
ngx_rtmp_control_pause_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s)
{
ngx_rtmp_control_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
s->pause = 1;
++ctx->count;
return NGX_CONF_OK;
}
static const char *
ngx_rtmp_control_resume_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s)
{
ngx_rtmp_control_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
if (s->pause == 1) {
s->pause = 2;
}
++ctx->count;
return NGX_CONF_OK;
}
static const char *
ngx_rtmp_control_walk_session(ngx_http_request_t *r, ngx_rtmp_core_ctx_t *cctx)
{
ngx_str_t addr, *paddr, clientid;
ngx_rtmp_session_t *s, **ss;
ngx_rtmp_control_ctx_t *ctx;
s = cctx->session;
if (s == NULL) {
return NGX_CONF_OK;
}
if (ngx_http_arg(r, (u_char *) "addr", sizeof("addr") - 1, &addr)
== NGX_OK)
{
paddr = s->addr_text;
if (paddr->len != addr.len ||
ngx_strncmp(paddr->data, addr.data, addr.len))
{
return NGX_CONF_OK;
}
}
if (ngx_http_arg(r, (u_char *) "clientid", sizeof("clientid") - 1,
&clientid)
== NGX_OK)
{
if (s->number !=
(ngx_uint_t) ngx_atoi(clientid.data, clientid.len))
{
return NGX_CONF_OK;
}
}
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
switch (ctx->filter) {
case NGX_RTMP_CONTROL_FILTER_PUBLISHER:
if (!cctx->publishing) {
return NGX_CONF_OK;
}
break;
case NGX_RTMP_CONTROL_FILTER_SUBSCRIBER:
if (cctx->publishing) {
return NGX_CONF_OK;
}
break;
case NGX_RTMP_CONTROL_FILTER_CLIENT:
break;
}
ss = ngx_array_push(&ctx->sessions);
if (ss == NULL) {
return "allocation error";
}
*ss = s;
return NGX_CONF_OK;
}
static const char *
ngx_rtmp_control_walk_stream(ngx_http_request_t *r, ngx_live_stream_t *st)
{
const char *s;
ngx_rtmp_core_ctx_t *ctx;
for (ctx = st->play_ctx; ctx; ctx = ctx->next) {
s = ngx_rtmp_control_walk_session(r, ctx);
if (s != NGX_CONF_OK) {
return s;
}
}
for (ctx = st->publish_ctx; ctx; ctx = ctx->next) {
s = ngx_rtmp_control_walk_session(r, ctx);
if (s != NGX_CONF_OK) {
return s;
}
}
return NGX_CONF_OK;
}
static const char *
ngx_rtmp_control_walk_server(ngx_http_request_t *r, ngx_live_server_t *srv)
{
ngx_live_conf_t *lcf;
ngx_live_stream_t *st;
ngx_str_t serverid, app, name, stream;
size_t n;
const char *s;
u_char *p;
lcf = (ngx_live_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
ngx_live_module);
if (ngx_http_arg(r, (u_char *) "app", sizeof("app") - 1, &app) != NGX_OK) {
app.len = 0;
}
if (app.len == 0) {
for (n = 0; n < lcf->stream_buckets; ++n) {
for (st = srv->streams[n]; st; st = st->next) {
s = ngx_rtmp_control_walk_stream(r, st);
if (s != NGX_CONF_OK) {
return s;
}
}
}
return NGX_CONF_OK;
}
if (ngx_http_arg(r, (u_char *) "name", sizeof("name") - 1, &name)
!= NGX_OK)
{
name.len = 0;
}
serverid.data = srv->serverid;
serverid.len = ngx_strlen(srv->serverid);
/* serverid/app/name */
stream.len = serverid.len + 1 + app.len + 1 + name.len;
stream.data = ngx_pcalloc(r->pool, stream.len);
p = stream.data;
p = ngx_copy(p, serverid.data, serverid.len);
*p++ = '/';
p = ngx_copy(p, app.data, app.len);
*p++ = '/';
p = ngx_copy(p, name.data, name.len);
if (name.len == 0) {
for (n = 0; n < lcf->stream_buckets; ++n) {
for (st = srv->streams[n]; st; st = st->next) {
if (ngx_memcmp(stream.data, st->name, stream.len) == 0) {
s = ngx_rtmp_control_walk_stream(r, st);
if (s != NGX_CONF_OK) {
return s;
}
}
}
}
} else {
st = ngx_live_fetch_stream(&serverid, &stream);
if (st) {
s = ngx_rtmp_control_walk_stream(r, st);
if (s != NGX_CONF_OK) {
return s;
}
}
}
return NGX_CONF_OK;
}
static const char *
ngx_rtmp_control_walk(ngx_http_request_t *r, ngx_rtmp_control_handler_t h)
{
ngx_live_server_t *server;
ngx_str_t srv;
ngx_str_t serverid = ngx_null_string;
ngx_uint_t n;
const char *msg;
ngx_rtmp_session_t **s;
ngx_rtmp_control_ctx_t *ctx;
if (ngx_http_arg(r, (u_char *) "srv", sizeof("srv") - 1, &srv) != NGX_OK) {
return "Server not set";
}
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
serverid = srv;
if (!serverid.len) {
ngx_rmtp_get_serverid_by_domain(&serverid, &srv);
}
server = ngx_live_fetch_server(&serverid);
if (server == NULL) {
return NGX_CONF_OK;
}
msg = ngx_rtmp_control_walk_server(r, server);
if (msg != NGX_CONF_OK) {
return msg;
}
s = ctx->sessions.elts;
for (n = 0; n < ctx->sessions.nelts; n++) {
msg = h(r, s[n]);
if (msg != NGX_CONF_OK) {
return msg;
}
}
return NGX_CONF_OK;
}
static ngx_int_t
ngx_rtmp_control_record(ngx_http_request_t *r, ngx_str_t *method)
{
ngx_buf_t *b;
const char *msg;
ngx_chain_t cl;
ngx_rtmp_control_ctx_t *ctx;
size_t len;
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER;
msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_record_handler);
if (msg != NGX_CONF_OK) {
goto error;
}
/* output record path */
len = sizeof("ok\n") - 1;
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = len;
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
goto error;
}
ngx_memzero(&cl, sizeof(cl));
cl.buf = b;
b->last = ngx_cpymem(b->pos, "ok\n", len);
b->last_buf = 1;
ngx_http_send_header(r);
return ngx_http_output_filter(r, &cl);
error:
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
static ngx_int_t
ngx_rtmp_control_drop(ngx_http_request_t *r, ngx_str_t *method)
{
size_t len;
u_char *p;
ngx_buf_t *b;
ngx_chain_t cl;
const char *msg;
ngx_rtmp_control_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
if (ctx->method.len == sizeof("publisher") - 1 &&
ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0)
{
ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER;
} else if (ctx->method.len == sizeof("subscriber") - 1 &&
ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len)
== 0)
{
ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER;
} else if (method->len == sizeof("client") - 1 &&
ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0)
{
ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT;
} else {
msg = "Undefined filter";
goto error;
}
msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_drop_handler);
if (msg != NGX_CONF_OK) {
goto error;
}
/* output count */
len = NGX_INT_T_LEN;
p = ngx_palloc(r->connection->pool, len);
if (p == NULL) {
return NGX_ERROR;
}
len = (size_t) (ngx_snprintf(p, len, "%ui", ctx->count) - p);
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = len;
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
goto error;
}
b->start = b->pos = p;
b->end = b->last = p + len;
b->temporary = 1;
b->last_buf = 1;
ngx_memzero(&cl, sizeof(cl));
cl.buf = b;
ngx_http_send_header(r);
return ngx_http_output_filter(r, &cl);
error:
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
static ngx_int_t
ngx_rtmp_control_redirect(ngx_http_request_t *r, ngx_str_t *method)
{
size_t len;
u_char *p;
ngx_buf_t *b;
ngx_chain_t cl;
const char *msg;
ngx_rtmp_control_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
if (ctx->method.len == sizeof("publisher") - 1 &&
ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0)
{
ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER;
} else if (ctx->method.len == sizeof("subscriber") - 1 &&
ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len)
== 0)
{
ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER;
} else if (ctx->method.len == sizeof("client") - 1 &&
ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0)
{
ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT;
} else {
msg = "Undefined filter";
goto error;
}
msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_redirect_handler);
if (msg != NGX_CONF_OK) {
goto error;
}
/* output count */
len = NGX_INT_T_LEN;
p = ngx_pcalloc(r->connection->pool, len);
if (p == NULL) {
msg = "ngx_palloc failed";
goto error;
}
len = (size_t) (ngx_snprintf(p, len, "%ui", ctx->count) - p);
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = len;
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
msg = "calloc buf failed";
goto error;
}
b->start = b->pos = p;
b->end = b->last = p + len;
b->temporary = 1;
b->last_buf = 1;
ngx_memzero(&cl, sizeof(cl));
cl.buf = b;
ngx_http_send_header(r);
return ngx_http_output_filter(r, &cl);
error:
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"rtmp-control: redirect| %s", msg);
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
static ngx_int_t
ngx_rtmp_control_pause(ngx_http_request_t *r, ngx_str_t *method)
{
size_t len;
u_char *p;
ngx_buf_t *b;
ngx_chain_t cl;
const char *msg;
ngx_rtmp_control_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
if (ctx->method.len == sizeof("publisher") - 1 &&
ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0)
{
ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER;
} else if (ctx->method.len == sizeof("subscriber") - 1 &&
ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len)
== 0)
{
ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER;
} else if (method->len == sizeof("client") - 1 &&
ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0)
{
ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT;
} else {
msg = "Undefined filter";
goto error;
}
msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_pause_handler);
if (msg != NGX_CONF_OK) {
goto error;
}
/* output count */
len = NGX_INT_T_LEN;
p = ngx_palloc(r->connection->pool, len);
if (p == NULL) {
return NGX_ERROR;
}
len = (size_t) (ngx_snprintf(p, len, "%ui", ctx->count) - p);
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = len;
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
goto error;
}
b->start = b->pos = p;
b->end = b->last = p + len;
b->temporary = 1;
b->last_buf = 1;
ngx_memzero(&cl, sizeof(cl));
cl.buf = b;
ngx_http_send_header(r);
return ngx_http_output_filter(r, &cl);
error:
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
static ngx_int_t
ngx_rtmp_control_resume(ngx_http_request_t *r, ngx_str_t *method)
{
size_t len;
u_char *p;
ngx_buf_t *b;
ngx_chain_t cl;
const char *msg;
ngx_rtmp_control_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
if (ctx->method.len == sizeof("publisher") - 1 &&
ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0)
{
ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER;
} else if (ctx->method.len == sizeof("subscriber") - 1 &&
ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len)
== 0)
{
ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER;
} else if (method->len == sizeof("client") - 1 &&
ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0)
{
ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT;
} else {
msg = "Undefined filter";
goto error;
}
msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_resume_handler);
if (msg != NGX_CONF_OK) {
goto error;
}
/* output count */
len = NGX_INT_T_LEN;
p = ngx_palloc(r->connection->pool, len);
if (p == NULL) {
return NGX_ERROR;
}
len = (size_t) (ngx_snprintf(p, len, "%ui", ctx->count) - p);
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = len;
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
goto error;
}
b->start = b->pos = p;
b->end = b->last = p + len;
b->temporary = 1;
b->last_buf = 1;
ngx_memzero(&cl, sizeof(cl));
cl.buf = b;
ngx_http_send_header(r);
return ngx_http_output_filter(r, &cl);
error:
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
static ngx_int_t
ngx_rtmp_control_handler(ngx_http_request_t *r)
{
u_char *p;
ngx_str_t section, method;
ngx_uint_t n;
ngx_rtmp_control_ctx_t *ctx;
ngx_rtmp_control_loc_conf_t *llcf;
llcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_control_module);
if (llcf->control == 0) {
return NGX_DECLINED;
}
/* uri format: .../section/method?args */
ngx_str_null(&section);
ngx_str_null(&method);
for (n = r->uri.len; n; --n) {
p = &r->uri.data[n - 1];
if (*p != '/') {
continue;
}
if (method.data) {
section.data = p + 1;
section.len = method.data - section.data - 1;
break;
}
method.data = p + 1;
method.len = r->uri.data + r->uri.len - method.data;
}
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"rtmp_control: section='%V' method='%V'",
&section, &method);
ctx = ngx_pcalloc(r->pool, sizeof(ngx_rtmp_control_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_http_set_ctx(r, ctx, ngx_rtmp_control_module);
if (ngx_array_init(&ctx->sessions, r->pool, 1, sizeof(void *)) != NGX_OK) {
return NGX_ERROR;
}
ctx->method = method;
#define NGX_RTMP_CONTROL_SECTION(flag, secname) \
if (llcf->control & NGX_RTMP_CONTROL_##flag && \
section.len == sizeof(#secname) - 1 && \
ngx_strncmp(section.data, #secname, sizeof(#secname) - 1) == 0) \
{ \
return ngx_rtmp_control_##secname(r, &method); \
}
NGX_RTMP_CONTROL_SECTION(RECORD, record);
NGX_RTMP_CONTROL_SECTION(DROP, drop);
NGX_RTMP_CONTROL_SECTION(REDIRECT, redirect);
NGX_RTMP_CONTROL_SECTION(PAUSE, pause);
NGX_RTMP_CONTROL_SECTION(RESUME, resume);
#undef NGX_RTMP_CONTROL_SECTION
return NGX_DECLINED;
}
static void *
ngx_rtmp_control_create_loc_conf(ngx_conf_t *cf)
{
ngx_rtmp_control_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_control_loc_conf_t));
if (conf == NULL) {
return NULL;
}
conf->control = 0;
return conf;
}
static char *
ngx_rtmp_control_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_rtmp_control_loc_conf_t *prev = parent;
ngx_rtmp_control_loc_conf_t *conf = child;
ngx_conf_merge_bitmask_value(conf->control, prev->control, 0);
return NGX_CONF_OK;
}
static char *
ngx_rtmp_control(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_rtmp_control_handler;
return ngx_conf_set_bitmask_slot(cf, cmd, conf);
}

View File

@ -0,0 +1,999 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
#include "ngx_rtmp_dynamic.h"
static void *ngx_rtmp_dynamic_create_conf(ngx_conf_t *cf);
static void *ngx_rtmp_dynamic_core_create_main_conf(ngx_conf_t *cf);
static char *ngx_rtmp_dynamic_core_init_main_conf(ngx_conf_t *cf, void *conf);
static void *ngx_rtmp_dynamic_core_create_srv_conf(ngx_conf_t *cf);
static char *ngx_rtmp_dynamic_core_init_srv_conf(ngx_conf_t *cf, void *conf);
static void *ngx_rtmp_dynamic_core_create_app_conf(ngx_conf_t *cf);
static char *ngx_rtmp_dynamic_core_init_app_conf(ngx_conf_t *cf, void *conf);
static char *ngx_rtmp_dynamic_block(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_rtmp_dynamic_core_server(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_rtmp_dynamic_core_server_name(ngx_conf_t *cf,
ngx_command_t *cmd, void *conf);
static char *ngx_rtmp_dynamic_core_application(ngx_conf_t *cf,
ngx_command_t *cmd, void *conf);
typedef struct {
ngx_str_t name;
ngx_map_node_t node;
void **app_conf;
} ngx_rtmp_dynamic_core_app_conf_t;
typedef struct {
/* array of the ngx_rtmp_server_name_t, "server_name" directive */
ngx_array_t server_names;
ngx_rtmp_dynamic_core_app_conf_t *default_app;
ngx_map_t app_conf;
void **srv_conf;
} ngx_rtmp_dynamic_core_srv_conf_t;
typedef struct {
#if (NGX_PCRE)
ngx_dynamic_regex_t *regex;
#endif
ngx_rtmp_dynamic_core_srv_conf_t *server; /* virtual name server conf */
ngx_str_t name;
} ngx_rtmp_dynamic_server_name_t;
typedef struct {
/* ngx_rtmp_dynamic_srv_conf_t */
ngx_array_t servers;
ngx_uint_t server_names_hash_max_size;
ngx_uint_t server_names_hash_bucket_size;
/* the default server configuration for this address:port */
ngx_rtmp_dynamic_core_srv_conf_t *default_server;
ngx_hash_combined_t names;
#if (NGX_PCRE)
ngx_uint_t nregex;
ngx_rtmp_dynamic_server_name_t *regex;
#endif
} ngx_rtmp_dynamic_core_main_conf_t;
typedef struct {
void **main_conf;
} ngx_rtmp_dynamic_conf_t;
static ngx_core_module_t ngx_rtmp_dynamic_module_ctx = {
ngx_string("rtmp_dynamic"),
NULL,
NULL
};
static ngx_command_t ngx_rtmp_dynamic_dcommands[] = {
{ ngx_string("rtmp"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_rtmp_dynamic_block,
0,
0,
NULL },
ngx_null_command
};
static ngx_dynamic_core_module_t ngx_rtmp_dynamic_module_dctx = {
ngx_string("rtmp_dynamic"),
ngx_rtmp_dynamic_create_conf,
NULL
};
ngx_module_t ngx_rtmp_dynamic_module = {
NGX_MODULE_V1,
&ngx_rtmp_dynamic_module_ctx, /* module context */
NULL, /* 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 */
(uintptr_t) &ngx_rtmp_dynamic_module_dctx, /* module dynamic context */
(uintptr_t) ngx_rtmp_dynamic_dcommands, /* module dynamic directives */
NGX_MODULE_V1_DYNAMIC_PADDING
};
static ngx_rtmp_module_t ngx_rtmp_dynamic_core_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create application configuration */
NULL /* merge application configuration */
};
static ngx_command_t ngx_rtmp_dynamic_core_dcommands[] = {
{ ngx_string("server_names_hash_max_size"),
NGX_RTMP_MAIN_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
0,
offsetof(ngx_rtmp_dynamic_core_main_conf_t, server_names_hash_max_size),
NULL },
{ ngx_string("server_names_hash_bucket_size"),
NGX_RTMP_MAIN_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
0,
offsetof(ngx_rtmp_dynamic_core_main_conf_t,
server_names_hash_bucket_size),
NULL },
{ ngx_string("server"),
NGX_RTMP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_rtmp_dynamic_core_server,
0,
0,
NULL },
{ ngx_string("server_name"),
NGX_RTMP_SRV_CONF|NGX_CONF_1MORE,
ngx_rtmp_dynamic_core_server_name,
0,
0,
NULL },
{ ngx_string("application"),
NGX_RTMP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
ngx_rtmp_dynamic_core_application,
0,
0,
NULL },
ngx_null_command
};
static ngx_rtmp_dynamic_module_t ngx_rtmp_dynamic_core_module_dctx = {
ngx_rtmp_dynamic_core_create_main_conf, /* create main configuration */
ngx_rtmp_dynamic_core_init_main_conf, /* init main configuration */
ngx_rtmp_dynamic_core_create_srv_conf, /* create srv configuration */
ngx_rtmp_dynamic_core_init_srv_conf, /* init srv configuration */
ngx_rtmp_dynamic_core_create_app_conf, /* create app configuration */
ngx_rtmp_dynamic_core_init_app_conf /* init app configuration */
};
ngx_module_t ngx_rtmp_dynamic_core_module = {
NGX_MODULE_V1,
&ngx_rtmp_dynamic_core_module_ctx, /* module context */
NULL, /* 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 */
(uintptr_t) &ngx_rtmp_dynamic_core_module_dctx, /* module dynamic context */
(uintptr_t) ngx_rtmp_dynamic_core_dcommands, /* module dynamic directives */
NGX_MODULE_V1_DYNAMIC_PADDING
};
static void *
ngx_rtmp_dynamic_create_conf(ngx_conf_t *cf)
{
ngx_rtmp_dynamic_conf_t *rdccf;
rdccf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_dynamic_conf_t));
if (rdccf == NULL) {
return NULL;
}
return rdccf;
}
static void *
ngx_rtmp_dynamic_core_create_main_conf(ngx_conf_t *cf)
{
ngx_rtmp_dynamic_core_main_conf_t *rdcmcf;
rdcmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_dynamic_core_main_conf_t));
if (rdcmcf == NULL) {
return NULL;
}
if (ngx_array_init(&rdcmcf->servers, cf->pool, 4,
sizeof(ngx_rtmp_dynamic_core_srv_conf_t *)) != NGX_OK)
{
return NULL;
}
rdcmcf->server_names_hash_max_size = NGX_CONF_UNSET_UINT;
rdcmcf->server_names_hash_bucket_size = NGX_CONF_UNSET_UINT;
return rdcmcf;
}
static char *
ngx_rtmp_dynamic_core_init_main_conf(ngx_conf_t *cf, void *conf)
{
ngx_rtmp_dynamic_core_main_conf_t *rdcmcf;
rdcmcf = conf;
ngx_conf_init_uint_value(rdcmcf->server_names_hash_max_size, 512);
ngx_conf_init_uint_value(rdcmcf->server_names_hash_bucket_size,
ngx_cacheline_size);
return NGX_CONF_OK;
}
static void *
ngx_rtmp_dynamic_core_create_srv_conf(ngx_conf_t *cf)
{
ngx_rtmp_dynamic_core_srv_conf_t *rdcscf;
rdcscf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_dynamic_core_srv_conf_t));
if (rdcscf == NULL) {
return NULL;
}
if (ngx_array_init(&rdcscf->server_names, cf->pool, 4,
sizeof(ngx_rtmp_dynamic_server_name_t)) != NGX_OK)
{
return NULL;
}
return rdcscf;
}
static char *
ngx_rtmp_dynamic_core_init_srv_conf(ngx_conf_t *cf, void *conf)
{
return NGX_CONF_OK;
}
static void *
ngx_rtmp_dynamic_core_create_app_conf(ngx_conf_t *cf)
{
ngx_rtmp_dynamic_core_app_conf_t *rdcacf;
rdcacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_dynamic_core_app_conf_t));
if (rdcacf == NULL) {
return NULL;
}
return rdcacf;
}
static char *
ngx_rtmp_dynamic_core_init_app_conf(ngx_conf_t *cf, void *conf)
{
return NGX_CONF_OK;
}
static ngx_int_t
ngx_rtmp_dynamic_core_find_application(ngx_rtmp_session_t *s,
ngx_rtmp_dynamic_core_srv_conf_t *rdcscf,
ngx_rtmp_dynamic_core_app_conf_t **rdcacf)
{
ngx_map_node_t *node;
node = ngx_map_find(&rdcscf->app_conf, (intptr_t) &s->app);
if (node == NULL) {
*rdcacf = rdcscf->default_app;
} else {
*rdcacf = (ngx_rtmp_dynamic_core_app_conf_t *)
((char *) node - offsetof(ngx_rtmp_dynamic_core_app_conf_t, node));
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_dynamic_core_init_virtual_servers(ngx_conf_t *cf,
ngx_rtmp_dynamic_conf_t *hdcf)
{
ngx_rtmp_dynamic_core_main_conf_t *rdcmcf;
ngx_rtmp_dynamic_core_srv_conf_t **rdcscfp;
ngx_hash_init_t hash;
ngx_hash_keys_arrays_t ha;
ngx_rtmp_dynamic_server_name_t *name;
ngx_uint_t s, n;
ngx_int_t rc;
#if (NGX_PCRE)
ngx_uint_t regex, i;
regex = 0;
#endif
rdcmcf = hdcf->main_conf[ngx_rtmp_dynamic_core_module.ctx_index];
ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t));
ha.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
if (ha.temp_pool == NULL) {
return NGX_ERROR;
}
ha.pool = cf->pool;
if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) {
goto failed;
}
rdcscfp = rdcmcf->servers.elts;
rdcmcf->default_server = rdcscfp[0];
for (s = 0; s < rdcmcf->servers.nelts; ++s) {
name = rdcscfp[s]->server_names.elts;
for (n = 0; n < rdcscfp[s]->server_names.nelts; ++n) {
#if (NGX_PCRE)
if (name[n].regex) {
++regex;
continue;
}
#endif
rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server,
NGX_HASH_WILDCARD_KEY);
if (rc == NGX_ERROR) {
return NGX_ERROR;
}
if (rc == NGX_DECLINED) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"invalid server name or wildcard \"%V\"",
&name[n].name);
return NGX_ERROR;
}
if (rc == NGX_BUSY) {
ngx_log_error(NGX_LOG_WARN, cf->log, 0,
"conflicting server name \"%V\", ignored",
&name[n].name);
}
}
}
hash.key = ngx_hash_key_lc;
hash.max_size = rdcmcf->server_names_hash_max_size;
hash.bucket_size = rdcmcf->server_names_hash_bucket_size;
hash.name = "rtmp_dynamic_server_names_hash";
hash.pool = cf->pool;
if (ha.keys.nelts) {
hash.hash = &rdcmcf->names.hash;
hash.temp_pool = NULL;
if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) {
goto failed;
}
}
if (ha.dns_wc_head.nelts) {
ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts,
sizeof(ngx_hash_key_t), ngx_dynamic_cmp_dns_wildcards);
hash.hash = NULL;
hash.temp_pool = ha.temp_pool;
if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts,
ha.dns_wc_head.nelts) != NGX_OK)
{
goto failed;
}
rdcmcf->names.wc_head = (ngx_hash_wildcard_t *) hash.hash;
}
if (ha.dns_wc_tail.nelts) {
ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts,
sizeof(ngx_hash_key_t), ngx_dynamic_cmp_dns_wildcards);
hash.hash = NULL;
hash.temp_pool = ha.temp_pool;
if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts,
ha.dns_wc_tail.nelts) != NGX_OK)
{
goto failed;
}
rdcmcf->names.wc_tail = (ngx_hash_wildcard_t *) hash.hash;
}
ngx_destroy_pool(ha.temp_pool);
#if (NGX_PCRE)
if (regex == 0) {
return NGX_OK;
}
rdcmcf->nregex = regex;
rdcmcf->regex = ngx_pcalloc(cf->pool,
regex * sizeof(ngx_rtmp_dynamic_server_name_t));
if (rdcmcf->regex == NULL) {
return NGX_ERROR;
}
i = 0;
for (s = 0; s < rdcmcf->servers.nelts; ++s) {
name = rdcscfp[s]->server_names.elts;
for (n = 0; n < rdcscfp[s]->server_names.nelts; ++n) {
if (name[n].regex) {
rdcmcf->regex[i++] = name[n];
}
}
}
#endif
return NGX_OK;
failed:
ngx_destroy_pool(ha.temp_pool);
return NGX_ERROR;
}
static ngx_int_t
ngx_rtmp_dynamic_core_find_virtual_server(ngx_str_t *server,
ngx_rtmp_dynamic_core_main_conf_t *rdcmcf,
ngx_rtmp_dynamic_core_srv_conf_t **rdcscfp)
{
ngx_rtmp_dynamic_core_srv_conf_t *rdcscf;
rdcscf = ngx_hash_find_combined(&rdcmcf->names,
ngx_hash_key(server->data, server->len),
server->data, server->len);
if (rdcscf) {
*rdcscfp = rdcscf;
return NGX_OK;
}
#if (NGX_PCRE)
if (server->len && rdcmcf->nregex) {
ngx_int_t n;
ngx_uint_t i;
ngx_rtmp_dynamic_server_name_t *sn;
sn = rdcmcf->regex;
for (i = 0; i < rdcmcf->nregex; ++i) {
n = ngx_regex_exec(sn[i].regex->regex, server, NULL, 0);
if (n == NGX_REGEX_NO_MATCHED) {
continue;
}
if (n >= 0) {
*rdcscfp = sn[i].server;
return NGX_OK;
}
return NGX_ERROR;
}
}
#endif
return NGX_DECLINED;
}
static char *
ngx_rtmp_dynamic_core_application(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf)
{
ngx_rtmp_dynamic_module_t *module;
ngx_rtmp_dynamic_core_app_conf_t *rdcacf;
ngx_rtmp_dynamic_core_srv_conf_t *rdcscf;
ngx_conf_t pcf;
void *mconf, **app_conf;
ngx_uint_t i, ci;
ngx_str_t *value;
char *rv = NULL;
rdcscf = conf;
/* app_conf is temp cause hdclcf does not created now */
app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
if (app_conf == NULL) {
return NGX_CONF_ERROR;
}
for (i = 0; cf->cycle->modules[i]; ++i) {
if (cf->cycle->modules[i]->type != NGX_RTMP_MODULE) {
continue;
}
module = (ngx_rtmp_dynamic_module_t *)
cf->cycle->modules[i]->spare_hook0;
if (module == NULL) {
continue;
}
if (module->create_app_conf) {
mconf = module->create_app_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
app_conf[cf->cycle->modules[i]->ctx_index] = mconf;
}
}
rdcacf = app_conf[ngx_rtmp_dynamic_core_module.ctx_index];
rdcacf->app_conf = app_conf;
value = cf->args->elts;
rdcacf->name = value[1];
if (rdcacf->name.len == 1 && rdcacf->name.data[0] == '*') { /* default */
if (rdcscf->default_app) {
return "is duplicate";
}
rdcscf->default_app = rdcacf;
} else {
rdcacf->node.raw_key = (intptr_t) &rdcacf->name;
ngx_map_insert(&rdcscf->app_conf, &rdcacf->node, 0);
}
pcf = *cf;
cf->ctx = rdcacf->app_conf;
cf->cmd_type = NGX_RTMP_APP_CONF;
if (ngx_dynamic_conf_parse(cf, 0) != NGX_OK) {
goto failed;
}
for (i = 0; cf->cycle->modules[i]; ++i) {
if (cf->cycle->modules[i]->type != NGX_RTMP_MODULE) {
continue;
}
module = (ngx_rtmp_dynamic_module_t *)
cf->cycle->modules[i]->spare_hook0;
ci = cf->cycle->modules[i]->ctx_index;
if (module == NULL) {
continue;
}
if (module->init_app_conf) {
rv = module->init_app_conf(cf, rdcacf->app_conf[ci]);
if (rv != NGX_CONF_OK) {
goto failed;
}
}
}
*cf = pcf;
return NGX_CONF_OK;
failed:
*cf = pcf;
if (rv) {
return rv;
}
return NGX_CONF_ERROR;
}
static char *
ngx_rtmp_dynamic_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf)
{
ngx_rtmp_dynamic_core_srv_conf_t *rdcscf;
u_char ch;
ngx_str_t *value;
ngx_uint_t i;
ngx_rtmp_dynamic_server_name_t *sn;
rdcscf = conf;
value = cf->args->elts;
for (i = 1; i < cf->args->nelts; i++) {
ch = value[i].data[0];
if ((ch == '*' && (value[i].len < 3 || value[i].data[1] != '.'))
|| (ch == '.' && value[i].len < 2))
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"server name \"%V\" is invalid", &value[i]);
return NGX_CONF_ERROR;
}
if (ngx_strchr(value[i].data, '/')) {
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
"server name \"%V\" has suspicious symbols",
&value[i]);
}
sn = ngx_array_push(&rdcscf->server_names);
if (sn == NULL) {
return NGX_CONF_ERROR;
}
#if (NGX_PCRE)
sn->regex = NULL;
#endif
sn->server = rdcscf;
if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) {
sn->name = cf->cycle->hostname;
} else {
sn->name = value[i];
}
if (value[i].data[0] != '~') {
ngx_strlow(sn->name.data, sn->name.data, sn->name.len);
continue;
}
#if (NGX_PCRE)
{
u_char *p;
ngx_regex_compile_t rc;
u_char errstr[NGX_MAX_CONF_ERRSTR];
if (value[i].len == 1) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"empty regex in server name \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
value[i].len--;
value[i].data++;
ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
rc.pattern = value[i];
rc.err.len = NGX_MAX_CONF_ERRSTR;
rc.err.data = errstr;
for (p = value[i].data; p < value[i].data + value[i].len; p++) {
if (*p >= 'A' && *p <= 'Z') {
rc.options = NGX_REGEX_CASELESS;
break;
}
}
sn->regex = ngx_dynamic_regex_compile(cf, &rc);
if (sn->regex == NULL) {
return NGX_CONF_ERROR;
}
sn->name = value[i];
}
#else
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"using regex \"%V\" "
"requires PCRE library", &value[i]);
return NGX_CONF_ERROR;
#endif
}
return NGX_CONF_OK;
}
static char *
ngx_rtmp_dynamic_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_rtmp_dynamic_module_t *module;
ngx_rtmp_dynamic_core_main_conf_t *rdcmcf;
ngx_rtmp_dynamic_core_srv_conf_t *rdcscf, **rdcscfp;
ngx_conf_t pcf;
void *mconf, **srv_conf;
ngx_uint_t i, ci;
char *rv = NULL;
rdcmcf = conf;
/* srv_conf is temp cause hdcscf does not created now */
srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
if (srv_conf == NULL) {
return NGX_CONF_ERROR;
}
for (i = 0; cf->cycle->modules[i]; ++i) {
if (cf->cycle->modules[i]->type != NGX_RTMP_MODULE) {
continue;
}
module = (ngx_rtmp_dynamic_module_t *)
cf->cycle->modules[i]->spare_hook0;
if (module == NULL) {
continue;
}
if (module->create_srv_conf) {
mconf = module->create_srv_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
srv_conf[cf->cycle->modules[i]->ctx_index] = mconf;
}
}
rdcscf = srv_conf[ngx_rtmp_dynamic_core_module.ctx_index];
rdcscf->srv_conf = srv_conf;
ngx_map_init(&rdcscf->app_conf, ngx_map_hash_str, ngx_cmp_str);
/* save hdcscf into hdcmcf */
rdcscfp = ngx_array_push(&rdcmcf->servers);
if (rdcscfp == NULL) {
return NGX_CONF_ERROR;
}
*rdcscfp = rdcscf;
pcf = *cf;
cf->ctx = rdcscf->srv_conf;
cf->cmd_type = NGX_RTMP_SRV_CONF;
if (ngx_dynamic_conf_parse(cf, 0) != NGX_OK) {
goto failed;
}
for (i = 0; cf->cycle->modules[i]; ++i) {
if (cf->cycle->modules[i]->type != NGX_RTMP_MODULE) {
continue;
}
module = (ngx_rtmp_dynamic_module_t *)
cf->cycle->modules[i]->spare_hook0;
ci = cf->cycle->modules[i]->ctx_index;
if (module == NULL) {
continue;
}
if (module->init_srv_conf) {
rv = module->init_srv_conf(cf, rdcscf->srv_conf[ci]);
if (rv != NGX_CONF_OK) {
goto failed;
}
}
}
*cf = pcf;
return NGX_CONF_OK;
failed:
*cf = pcf;
if (rv) {
return rv;
}
return NGX_CONF_ERROR;
}
static char *
ngx_rtmp_dynamic_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_rtmp_dynamic_conf_t *rdccf;
ngx_rtmp_dynamic_module_t *module;
ngx_conf_t pcf;
ngx_uint_t i, ci;
char *rv = NULL;
rdccf = conf;
/* create main_conf ctx */
rdccf->main_conf = ngx_pcalloc(cf->pool, sizeof(void *)
* ngx_rtmp_max_module);
if (rdccf->main_conf == NULL) {
return NGX_CONF_ERROR;
}
/* create http dynamic conf for all http module */
for (i = 0; cf->cycle->modules[i]; ++i) {
if (cf->cycle->modules[i]->type != NGX_RTMP_MODULE) {
continue;
}
module = (ngx_rtmp_dynamic_module_t *)
cf->cycle->modules[i]->spare_hook0;
ci = cf->cycle->modules[i]->ctx_index;
if (module == NULL) {
continue;
}
if (module->create_main_conf) {
rdccf->main_conf[ci] = module->create_main_conf(cf);
if (rdccf->main_conf[ci] == NULL) {
return NGX_CONF_ERROR;
}
}
}
/* save conf for recovery */
pcf = *cf;
cf->ctx = rdccf->main_conf;
cf->module_type = NGX_RTMP_MODULE;
cf->cmd_type = NGX_RTMP_MAIN_CONF;
if (ngx_dynamic_conf_parse(cf, 0) != NGX_OK) {
goto failed;
}
for (i = 0; cf->cycle->modules[i]; ++i) {
if (cf->cycle->modules[i]->type != NGX_RTMP_MODULE) {
continue;
}
module = (ngx_rtmp_dynamic_module_t *)
cf->cycle->modules[i]->spare_hook0;
ci = cf->cycle->modules[i]->ctx_index;
if (module == NULL) {
continue;
}
if (module->init_main_conf) {
rv = module->init_main_conf(cf, rdccf->main_conf[ci]);
if (rv != NGX_CONF_OK) {
goto failed;
}
}
}
if (ngx_rtmp_dynamic_core_init_virtual_servers(cf, rdccf) != NGX_OK) {
goto failed;
}
*cf = pcf;
return NGX_CONF_OK;
failed:
*cf = pcf;
if (rv) {
return rv;
}
return NGX_CONF_ERROR;
}
/* interface from here */
void *
ngx_rtmp_get_module_main_dconf(ngx_rtmp_session_t *s, ngx_module_t *m)
{
ngx_rtmp_dynamic_conf_t *rdcf;
rdcf = ngx_get_dconf(&ngx_rtmp_dynamic_module);
if (rdcf == NULL || rdcf->main_conf == NULL) {
return NULL;
}
return rdcf->main_conf[m->ctx_index];
}
void *
ngx_rtmp_get_module_srv_dconf(ngx_rtmp_session_t *s, ngx_module_t *m)
{
ngx_rtmp_dynamic_core_main_conf_t *rdcmcf;
ngx_rtmp_dynamic_core_srv_conf_t *rdcscf;
ngx_int_t rc;
rdcmcf = ngx_rtmp_get_module_main_dconf(s, &ngx_rtmp_dynamic_core_module);
if (rdcmcf == NULL) {
return NULL;
}
rc = ngx_rtmp_dynamic_core_find_virtual_server(&s->domain, rdcmcf, &rdcscf);
switch (rc) {
case NGX_ERROR:
return NULL;
case NGX_DECLINED: /* virtual server not found */
return rdcmcf->default_server->srv_conf[m->ctx_index];
default:
return rdcscf->srv_conf[m->ctx_index];
}
}
void *
ngx_rtmp_get_module_app_dconf(ngx_rtmp_session_t *s, ngx_module_t *m)
{
ngx_rtmp_dynamic_core_srv_conf_t *rdcscf;
ngx_rtmp_dynamic_core_app_conf_t *rdcacf;
ngx_int_t rc;
rdcscf = ngx_rtmp_get_module_srv_dconf(s, &ngx_rtmp_dynamic_core_module);
if (rdcscf == NULL) {
return NULL;
}
rdcacf = NULL;
rc = ngx_rtmp_dynamic_core_find_application(s, rdcscf, &rdcacf);
if (rc == NGX_ERROR) {
return NULL;
}
if (rdcacf) {
return rdcacf->app_conf[m->ctx_index];
}
return NULL;
}
void
ngx_rmtp_get_serverid_by_domain(ngx_str_t *serverid, ngx_str_t *domain)
{
ngx_rtmp_dynamic_conf_t *rdcf;
ngx_rtmp_dynamic_core_main_conf_t *rdcmcf;
ngx_rtmp_dynamic_core_srv_conf_t *rdcscf;
ngx_rtmp_core_srv_dconf_t *rcsdcf;
rdcf = ngx_get_dconf(&ngx_rtmp_dynamic_module);
if (rdcf == NULL || rdcf->main_conf) {
goto notfound;
}
rdcmcf = rdcf->main_conf[ngx_rtmp_dynamic_core_module.ctx_index];
if (rdcmcf == NULL) {
goto notfound;
}
ngx_rtmp_dynamic_core_find_virtual_server(domain, rdcmcf, &rdcscf);
if (rdcscf && rdcscf->srv_conf) {
rcsdcf = rdcscf->srv_conf[ngx_rtmp_core_module.ctx_index];
if (rcsdcf && rcsdcf->serverid.len) {
*serverid = rcsdcf->serverid;
return;
}
}
notfound:
*serverid = *domain;
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#ifndef _NGX_RTMP_DYNAMIC_H_INCLUDED_
#define _NGX_RTMP_DYNAMIC_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_map.h"
#include "ngx_dynamic_conf.h"
#include "ngx_rtmp.h"
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 *(*init_srv_conf)(ngx_conf_t *cf, void *conf);
void *(*create_app_conf)(ngx_conf_t *cf);
char *(*init_app_conf)(ngx_conf_t *cf, void *conf);
} ngx_rtmp_dynamic_module_t;
void *ngx_rtmp_get_module_main_dconf(ngx_rtmp_session_t *s, ngx_module_t *m);
void *ngx_rtmp_get_module_srv_dconf(ngx_rtmp_session_t *s, ngx_module_t *m);
void *ngx_rtmp_get_module_app_dconf(ngx_rtmp_session_t *s, ngx_module_t *m);
void ngx_rmtp_get_serverid_by_domain(ngx_str_t *serverid, ngx_str_t *domain);
#endif

View File

@ -0,0 +1,298 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp_eval.h"
#define NGX_RTMP_EVAL_BUFLEN 16
static void
ngx_rtmp_eval_session_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret)
{
*ret = *(ngx_str_t *) ((u_char *) ctx + e->offset);
}
static void
ngx_rtmp_eval_connection_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret)
{
ngx_rtmp_session_t *s = ctx;
*ret = *(ngx_str_t *) ((u_char *) s->connection + e->offset);
}
ngx_rtmp_eval_t ngx_rtmp_eval_session[] = {
{ ngx_string("app"),
ngx_rtmp_eval_session_str,
offsetof(ngx_rtmp_session_t, app) },
{ ngx_string("flashver"),
ngx_rtmp_eval_session_str,
offsetof(ngx_rtmp_session_t, flashver) },
{ ngx_string("swfurl"),
ngx_rtmp_eval_session_str,
offsetof(ngx_rtmp_session_t, swf_url) },
{ ngx_string("tcurl"),
ngx_rtmp_eval_session_str,
offsetof(ngx_rtmp_session_t, tc_url) },
{ ngx_string("pageurl"),
ngx_rtmp_eval_session_str,
offsetof(ngx_rtmp_session_t, page_url) },
{ ngx_string("addr"),
ngx_rtmp_eval_connection_str,
offsetof(ngx_connection_t, addr_text) },
ngx_rtmp_null_eval
};
static void
ngx_rtmp_eval_append(ngx_buf_t *b, void *data, size_t len, ngx_log_t *log)
{
size_t buf_len;
if (b->last + len > b->end) {
buf_len = 2 * (b->last - b->pos) + len;
b->start = ngx_alloc(buf_len, log);
if (b->start == NULL) {
return;
}
b->last = ngx_cpymem(b->start, b->pos, b->last - b->pos);
b->pos = b->start;
b->end = b->start + buf_len;
}
b->last = ngx_cpymem(b->last, data, len);
}
static void
ngx_rtmp_eval_append_var(void *ctx, ngx_buf_t *b, ngx_rtmp_eval_t **e,
ngx_str_t *name, ngx_log_t *log)
{
ngx_uint_t k;
ngx_str_t v;
ngx_rtmp_eval_t *ee;
for (; *e; ++e) {
for (k = 0, ee = *e; ee->handler; ++k, ++ee) {
if (ee->name.len == name->len &&
ngx_memcmp(ee->name.data, name->data, name->len) == 0)
{
ee->handler(ctx, ee, &v);
ngx_rtmp_eval_append(b, v.data, v.len, log);
}
}
}
}
ngx_int_t
ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, ngx_str_t *out,
ngx_log_t *log)
{
u_char c, *p;
ngx_str_t name;
ngx_buf_t b;
ngx_uint_t n;
enum {
NORMAL,
ESCAPE,
NAME,
SNAME
} state = NORMAL;
b.pos = b.last = b.start = ngx_alloc(NGX_RTMP_EVAL_BUFLEN, log);
if (b.pos == NULL) {
return NGX_ERROR;
}
b.end = b.pos + NGX_RTMP_EVAL_BUFLEN;
name.data = NULL;
for (n = 0; n < in->len; ++n) {
p = &in->data[n];
c = *p;
switch (state) {
case SNAME:
if (c != '}') {
continue;
}
name.len = p - name.data;
ngx_rtmp_eval_append_var(ctx, &b, e, &name, log);
state = NORMAL;
continue;
case NAME:
if (c == '{' && name.data == p) {
++name.data;
state = SNAME;
continue;
}
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
continue;
}
name.len = p - name.data;
ngx_rtmp_eval_append_var(ctx, &b, e, &name, log);
switch (c) {
case '$':
name.data = p + 1;
state = NAME;
continue;
case '\\':
state = ESCAPE;
continue;
}
ngx_rtmp_eval_append(&b, &c, 1, log);
state = NORMAL;
break;
case NORMAL:
switch (c) {
case '$':
name.data = p + 1;
state = NAME;
continue;
case '\\':
state = ESCAPE;
continue;
}
ngx_rtmp_eval_append(&b, &c, 1, log);
state = NORMAL;
break;
case ESCAPE:
ngx_rtmp_eval_append(&b, &c, 1, log);
state = NORMAL;
break;
}
}
if (state == NAME) {
p = &in->data[n];
name.len = p - name.data;
ngx_rtmp_eval_append_var(ctx, &b, e, &name, log);
}
c = 0;
ngx_rtmp_eval_append(&b, &c, 1, log);
out->data = b.pos;
out->len = b.last - b.pos - 1;
return NGX_OK;
}
ngx_int_t
ngx_rtmp_eval_streams(ngx_str_t *in)
{
#if !(NGX_WIN32)
ngx_int_t mode, create, v, close_src;
ngx_fd_t dst, src;
u_char *path;
path = in->data;
while (*path >= '0' && *path <= '9') {
path++;
}
switch ((char) *path) {
case '>':
v = (path == in->data ? 1 : ngx_atoi(in->data, path - in->data));
if (v == NGX_ERROR) {
return NGX_ERROR;
}
dst = (ngx_fd_t) v;
mode = NGX_FILE_WRONLY;
create = NGX_FILE_TRUNCATE;
path++;
if (*path == (u_char) '>') {
mode = NGX_FILE_APPEND;
create = NGX_FILE_CREATE_OR_OPEN;
path++;
}
break;
case '<':
v = (path == in->data ? 0 : ngx_atoi(in->data, path - in->data));
if (v == NGX_ERROR) {
return NGX_ERROR;
}
dst = (ngx_fd_t) v;
mode = NGX_FILE_RDONLY;
create = NGX_FILE_OPEN;
path++;
break;
default:
return NGX_DONE;
}
if (*path == (u_char) '&') {
path++;
v = ngx_atoi(path, in->data + in->len - path);
if (v == NGX_ERROR) {
return NGX_ERROR;
}
src = (ngx_fd_t) v;
close_src = 0;
} else {
src = ngx_open_file(path, mode, create, NGX_FILE_DEFAULT_ACCESS);
if (src == NGX_INVALID_FILE) {
return NGX_ERROR;
}
close_src = 1;
}
if (src == dst) {
return NGX_OK;
}
dup2(src, dst);
if (close_src) {
ngx_close_file(src);
}
return NGX_OK;
#else
return NGX_DONE;
#endif
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#ifndef _NGX_RTMP_EVAL_H_INCLUDED_
#define _NGX_RTMP_EVAL_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
typedef struct ngx_rtmp_eval_s ngx_rtmp_eval_t;
typedef void (* ngx_rtmp_eval_pt)(void *ctx, ngx_rtmp_eval_t *e,
ngx_str_t *ret);
struct ngx_rtmp_eval_s {
ngx_str_t name;
ngx_rtmp_eval_pt handler;
ngx_uint_t offset;
};
#define ngx_rtmp_null_eval { ngx_null_string, NULL, 0 }
/* standard session eval variables */
extern ngx_rtmp_eval_t ngx_rtmp_eval_session[];
ngx_int_t ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e,
ngx_str_t *out, ngx_log_t *log);
ngx_int_t ngx_rtmp_eval_streams(ngx_str_t *in);
#endif /* _NGX_RTMP_EVAL_H_INCLUDED_ */

View File

@ -0,0 +1,734 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
#include "ngx_rtmp_codec_module.h"
#include "ngx_rtmp_live_module.h"
static ngx_rtmp_close_stream_pt next_close_stream;
static void *ngx_rtmp_gop_create_app_conf(ngx_conf_t *cf);
static char *ngx_rtmp_gop_merge_app_conf(ngx_conf_t *cf, void *parent,
void *child);
static ngx_int_t ngx_rtmp_gop_postconfiguration(ngx_conf_t *cf);
#define ngx_rtmp_gop_next(s, pos) ((pos + 1) % s->out_queue)
#define ngx_rtmp_gop_prev(s, pos) (pos == 0 ? s->out_queue - 1 : pos - 1)
typedef struct {
/* publisher: head of cache
* player: cache send position of publisher's out
*/
size_t gop_pos;
/* tail of cache */
size_t gop_last;
/* 0 for not send, 1 for sending, 2 for sent */
ngx_flag_t send_gop;
ngx_rtmp_frame_t *keyframe;
ngx_rtmp_frame_t *aac_header;
ngx_rtmp_frame_t *avc_header;
ngx_rtmp_frame_t *latest_aac_header;
ngx_rtmp_frame_t *latest_avc_header;
ngx_uint_t meta_version;
uint32_t first_timestamp;
/* only for publisher, must at last of ngx_rtmp_gop_ctx_t */
ngx_rtmp_frame_t *cache[];
} ngx_rtmp_gop_ctx_t;
typedef struct {
ngx_msec_t cache_time;
ngx_flag_t low_latency;
ngx_flag_t send_all;
ngx_msec_t fix_timestamp;
ngx_flag_t zero_start;
} ngx_rtmp_gop_app_conf_t;
static ngx_command_t ngx_rtmp_gop_commands[] = {
{ ngx_string("cache_time"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_gop_app_conf_t, cache_time),
NULL },
{ ngx_string("low_latency"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_gop_app_conf_t, low_latency),
NULL },
{ ngx_string("send_all"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_gop_app_conf_t, send_all),
NULL },
{ ngx_string("fix_timestamp"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_gop_app_conf_t, fix_timestamp),
NULL },
{ ngx_string("zero_start"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_gop_app_conf_t, zero_start),
NULL },
ngx_null_command
};
static ngx_rtmp_module_t ngx_rtmp_gop_module_ctx = {
NULL, /* preconfiguration */
ngx_rtmp_gop_postconfiguration, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_rtmp_gop_create_app_conf, /* create app configuration */
ngx_rtmp_gop_merge_app_conf /* merge app configuration */
};
ngx_module_t ngx_rtmp_gop_module = {
NGX_MODULE_V1,
&ngx_rtmp_gop_module_ctx, /* module context */
ngx_rtmp_gop_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_gop_create_app_conf(ngx_conf_t *cf)
{
ngx_rtmp_gop_app_conf_t *gacf;
gacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_gop_app_conf_t));
if (gacf == NULL) {
return NULL;
}
gacf->cache_time = NGX_CONF_UNSET_MSEC;
gacf->low_latency = NGX_CONF_UNSET;
gacf->send_all = NGX_CONF_UNSET;
gacf->fix_timestamp = NGX_CONF_UNSET_MSEC;
gacf->zero_start = NGX_CONF_UNSET;
return gacf;
}
static char *
ngx_rtmp_gop_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_rtmp_gop_app_conf_t *prev = parent;
ngx_rtmp_gop_app_conf_t *conf = child;
ngx_conf_merge_msec_value(conf->cache_time, prev->cache_time, 0);
ngx_conf_merge_value(conf->low_latency, prev->low_latency, 0);
ngx_conf_merge_value(conf->send_all, prev->send_all, 0);
ngx_conf_merge_msec_value(conf->fix_timestamp, prev->fix_timestamp, 10000);
ngx_conf_merge_value(conf->zero_start, prev->zero_start, 0);
return NGX_CONF_OK;
}
static ngx_int_t
ngx_rtmp_gop_link_frame(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *frame)
{
ngx_uint_t nmsg;
ngx_rtmp_live_chunk_stream_t *cs;
ngx_uint_t csidx;
ngx_rtmp_live_ctx_t *lctx;
ngx_rtmp_gop_ctx_t *ctx;
uint32_t delta;
ngx_rtmp_gop_app_conf_t *gacf;
if (frame == NULL) {
return NGX_OK;
}
gacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_gop_module);
if (gacf->fix_timestamp) {
lctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
csidx = !(frame->hdr.type == NGX_RTMP_MSG_VIDEO);
cs = &lctx->cs[csidx];
delta = frame->hdr.timestamp > cs->last_timestamp ?
frame->hdr.timestamp - cs->last_timestamp :
cs->last_timestamp - frame->hdr.timestamp;
if (delta > gacf->fix_timestamp) {
delta = 0;
}
if (!gacf->zero_start && cs->timestamp == 0) {
cs->timestamp = frame->hdr.timestamp;
} else if (frame->hdr.timestamp > cs->last_timestamp) {
cs->timestamp += delta;
} else if (cs->timestamp >= delta) {
cs->timestamp -= delta;
}
cs->last_timestamp = frame->hdr.timestamp;
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
"gop: link_frame| type %d, delta %d,"
" timestamp %uD, fixed timestamp %uD",
frame->hdr.type, delta, frame->hdr.timestamp, cs->timestamp);
frame->hdr.timestamp = cs->timestamp;
if (frame->hdr.type == NGX_RTMP_MSG_AMF_META) {
frame->hdr.timestamp = 0;
}
}
nmsg = (s->out_last - s->out_pos) % s->out_queue + 1;
if (nmsg >= s->out_queue) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"link frame nmsg(%ui) >= out_queue(%O)", nmsg, s->out_queue);
return NGX_AGAIN;
}
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_gop_module);
if (frame->hdr.type == NGX_RTMP_MSG_AUDIO && frame->av_header) {
ctx->latest_aac_header = frame;
}
if (frame->hdr.type == NGX_RTMP_MSG_VIDEO && frame->av_header) {
ctx->latest_avc_header = frame;
}
s->out[s->out_last] = frame;
s->out_last = ngx_rtmp_gop_next(s, s->out_last);
ngx_rtmp_shared_acquire_frame(frame);
return NGX_OK;
}
static void
ngx_rtmp_gop_set_avframe_tag(ngx_rtmp_frame_t *frame)
{
ngx_chain_t *cl;
if (frame->hdr.type != NGX_RTMP_MSG_AUDIO &&
frame->hdr.type != NGX_RTMP_MSG_VIDEO)
{
return;
}
cl = frame->chain;
frame->av_header = ngx_rtmp_is_codec_header(cl);
frame->keyframe = (frame->hdr.type == NGX_RTMP_MSG_VIDEO)
? (ngx_rtmp_get_video_frame_type(cl) == NGX_RTMP_VIDEO_KEY_FRAME)
: 0;
if (frame->av_header) {
frame->mandatory = 1;
}
}
static void
ngx_rtmp_gop_reset_avheader(ngx_rtmp_gop_ctx_t *ctx,
ngx_rtmp_frame_t *frame)
{
if (frame->hdr.type == NGX_RTMP_MSG_AUDIO) {
if (ctx->aac_header) {
ngx_rtmp_shared_free_frame(ctx->aac_header);
}
ctx->aac_header = frame;
} else {
if (ctx->avc_header) {
ngx_rtmp_shared_free_frame(ctx->avc_header);
}
ctx->avc_header = frame;
}
}
static void
ngx_rtmp_gop_reset_gop(ngx_rtmp_session_t *s, ngx_rtmp_gop_ctx_t *ctx,
ngx_rtmp_frame_t *frame)
{
ngx_rtmp_gop_app_conf_t *gacf;
ngx_rtmp_frame_t *f, *next_keyframe;
size_t pos;
ngx_uint_t nmsg;
/* reset av_header at the front of cache */
for (pos = ctx->gop_pos; pos != ctx->gop_last;
pos = ngx_rtmp_gop_next(s, pos))
{
if (ctx->cache[pos]->av_header) {
ngx_rtmp_gop_reset_avheader(ctx, ctx->cache[pos]);
ctx->gop_pos = ngx_rtmp_gop_next(s, ctx->gop_pos);
continue;
}
break;
}
f = ctx->cache[pos];
if (f == NULL) {
return;
}
gacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_gop_module);
/* only audio in cache */
if (ctx->keyframe == NULL) {
if (frame->hdr.timestamp - ctx->cache[ctx->gop_pos]->hdr.timestamp
> gacf->cache_time)
{
ngx_rtmp_shared_free_frame(f);
ctx->cache[ctx->gop_pos] = NULL;
ctx->gop_pos = ngx_rtmp_gop_next(s, ctx->gop_pos);
}
return;
}
/* only video of video + audio */
next_keyframe = ctx->keyframe->next;
/* only one gop in cache */
if (next_keyframe == NULL) {
return;
}
nmsg = (ctx->gop_last - ctx->gop_pos) % s->out_queue + 2;
if (nmsg >= s->out_queue) {
goto reset;
}
if (frame->hdr.type == NGX_RTMP_MSG_AUDIO) {
return;
}
if (frame->hdr.type == NGX_RTMP_MSG_VIDEO && frame->hdr.timestamp
- next_keyframe->hdr.timestamp < gacf->cache_time)
{
return;
}
reset:
for (pos = ctx->gop_pos; ctx->cache[pos] != next_keyframe;
pos = ngx_rtmp_gop_next(s, pos))
{
f = ctx->cache[pos];
if (f->av_header) {
ngx_rtmp_gop_reset_avheader(ctx, f);
} else {
ngx_rtmp_shared_free_frame(f);
}
ctx->cache[pos] = NULL;
}
ctx->keyframe = next_keyframe;
ctx->gop_pos = pos;
}
static void
ngx_rtmp_gop_print_cache(ngx_rtmp_session_t *s, ngx_rtmp_gop_ctx_t *ctx)
{
#if (NGX_DEBUG)
ngx_rtmp_frame_t *frame;
u_char content[10240], *p;
size_t pos;
ngx_memzero(content, sizeof(content));
p = content;
for (pos = ctx->gop_pos; pos != ctx->gop_last;
pos = ngx_rtmp_gop_next(s, pos))
{
frame = ctx->cache[pos];
switch (frame->hdr.type) {
case NGX_RTMP_MSG_AUDIO:
*p++ = 'A';
break;
case NGX_RTMP_MSG_VIDEO:
*p++ = 'V';
break;
default:
*p++ = 'O';
break;
}
if (frame->keyframe) {
*p++ = 'I';
}
if (frame->av_header) {
*p++ = 'H';
}
*p++ = ' ';
}
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->log, 0,
"[%z %z] [%p %p] %s", ctx->gop_pos, ctx->gop_last, ctx->aac_header,
ctx->avc_header, content);
#endif
}
ngx_int_t
ngx_rtmp_gop_cache(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *frame)
{
ngx_rtmp_gop_app_conf_t *gacf;
ngx_rtmp_gop_ctx_t *ctx;
ngx_rtmp_frame_t **keyframe;
ngx_uint_t nmsg;
gacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_gop_module);
if (gacf->cache_time == 0) {
return NGX_OK;
}
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_gop_module);
if (ctx == NULL) {
ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_gop_ctx_t)
+ s->out_queue * sizeof(ngx_rtmp_frame_t *));
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_gop_module);
}
nmsg = (ctx->gop_last - ctx->gop_pos) % s->out_queue + 1;
if (nmsg >= s->out_queue) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"cache frame nmsg(%ui) >= out_queue(%z)", nmsg, s->out_queue);
return NGX_AGAIN;
}
ngx_rtmp_gop_set_avframe_tag(frame);
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->log, 0,
"cache frame: %ud[%d %d], %ud, %ud",
frame->hdr.type, frame->keyframe, frame->av_header,
frame->hdr.timestamp, frame->hdr.mlen);
if (frame->hdr.type == NGX_RTMP_MSG_AUDIO && frame->av_header) {
ctx->latest_aac_header = frame;
}
if (frame->hdr.type == NGX_RTMP_MSG_VIDEO && frame->av_header) {
ctx->latest_avc_header = frame;
}
/* first video frame is not intra_frame or video header */
if (ctx->keyframe == NULL && frame->hdr.type == NGX_RTMP_MSG_VIDEO
&& !frame->keyframe && !frame->av_header)
{
return NGX_OK;
}
/* video intra_frame */
if (frame->keyframe && !frame->av_header) {
for (keyframe = &ctx->keyframe; *keyframe;
keyframe = &((*keyframe)->next));
*keyframe = frame;
}
ctx->cache[ctx->gop_last] = frame;
ctx->gop_last = ngx_rtmp_gop_next(s, ctx->gop_last);
ngx_rtmp_shared_acquire_frame(frame);
ngx_rtmp_gop_reset_gop(s, ctx, frame);
ngx_rtmp_gop_print_cache(s, ctx);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_gop_send_meta(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss)
{
ngx_rtmp_gop_ctx_t *ssctx;
ngx_rtmp_codec_ctx_t *cctx;
ssctx = ngx_rtmp_get_module_ctx(ss, ngx_rtmp_gop_module);
cctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
/* meta */
if (ssctx->meta_version != cctx->meta_version
&& ngx_rtmp_gop_link_frame(ss, cctx->meta) == NGX_AGAIN)
{
return NGX_AGAIN;
}
ssctx->meta_version = cctx->meta_version;
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_gop_send_gop(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss)
{
ngx_rtmp_gop_app_conf_t *gacf;
ngx_rtmp_gop_ctx_t *sctx, *ssctx;
ngx_rtmp_frame_t *frame;
size_t pos;
gacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_gop_module);
sctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_gop_module);
ssctx = ngx_rtmp_get_module_ctx(ss, ngx_rtmp_gop_module);
/* already send gop */
if (ssctx->send_gop == 3) {
return NGX_OK;
}
if (ssctx->send_gop == 0) {
if (ngx_rtmp_gop_send_meta(s, ss) == NGX_AGAIN) {
return NGX_AGAIN;
}
ngx_rtmp_send_message(ss, NULL, 0);
ssctx->send_gop = 1;
return NGX_AGAIN;
}
/* link frame in s to ss */
if (ssctx->send_gop == 1) {
ssctx->gop_pos = sctx->gop_pos;
if (sctx->cache[ssctx->gop_pos] == NULL) {
return NGX_AGAIN;
}
if (sctx->aac_header) {
if (ngx_rtmp_gop_link_frame(ss, sctx->aac_header) == NGX_AGAIN) {
return NGX_AGAIN;
}
}
if (sctx->avc_header) {
if (ngx_rtmp_gop_link_frame(ss, sctx->avc_header) == NGX_AGAIN) {
return NGX_AGAIN;
}
}
ssctx->send_gop = 2;
ssctx->first_timestamp = sctx->cache[ssctx->gop_pos]->hdr.timestamp;
} else {
if (sctx->cache[ssctx->gop_pos] == NULL) {
ssctx->gop_pos = sctx->gop_pos;
}
}
pos = ssctx->gop_pos;
frame = sctx->cache[pos];
while (frame) {
if (!gacf->send_all &&
frame->hdr.timestamp - ssctx->first_timestamp >= gacf->cache_time)
{
ssctx->send_gop = 3;
break;
}
if (ngx_rtmp_gop_link_frame(ss, frame) == NGX_AGAIN) {
break;
}
pos = ngx_rtmp_gop_next(s, pos);
frame = sctx->cache[pos];
}
if (frame == NULL) { /* send all frame in cache */
ssctx->send_gop = 3;
}
ssctx->gop_pos = pos;
ngx_rtmp_send_message(ss, NULL, 0);
return NGX_AGAIN;
}
ngx_int_t
ngx_rtmp_gop_send(ngx_rtmp_session_t *s, ngx_rtmp_session_t *ss)
{
ngx_rtmp_gop_app_conf_t *gacf;
ngx_rtmp_gop_ctx_t *sctx, *ssctx;
ngx_rtmp_frame_t *frame;
size_t pos;
gacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_gop_module);
if (gacf->cache_time == 0) {
return NGX_DECLINED;
}
sctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_gop_module);
if (sctx == NULL) { /* publisher doesn't publish av frame */
return NGX_DECLINED;
}
ssctx = ngx_rtmp_get_module_ctx(ss, ngx_rtmp_gop_module);
if (ssctx == NULL) {
ssctx = ngx_pcalloc(ss->pool, sizeof(ngx_rtmp_gop_ctx_t));
if (ssctx == NULL) {
return NGX_ERROR;
}
ngx_rtmp_set_ctx(ss, ssctx, ngx_rtmp_gop_module);
}
if (ngx_rtmp_gop_send_gop(s, ss) == NGX_AGAIN) {
return NGX_OK;
}
/* send frame by frame */
if (ngx_rtmp_gop_send_meta(s, ss) == NGX_AGAIN) {
return NGX_AGAIN;
}
pos = ngx_rtmp_gop_prev(s, sctx->gop_last);
/* new frame is video key frame */
if (sctx->cache[pos]->keyframe && !sctx->cache[pos]->av_header) {
if (gacf->low_latency && pos != ssctx->gop_pos) {
ssctx->gop_pos = pos;
ss->out_pos = ss->out_last;
ngx_log_error(NGX_LOG_INFO, ss->log, 0,
"gop, low latency, chase to new keyframe");
if (sctx->latest_aac_header
&& sctx->latest_aac_header != ssctx->latest_aac_header)
{
if (ngx_rtmp_gop_link_frame(ss, sctx->latest_aac_header)
== NGX_AGAIN)
{
return NGX_AGAIN;
}
}
if (sctx->latest_avc_header
&& sctx->latest_avc_header != ssctx->latest_avc_header)
{
if (ngx_rtmp_gop_link_frame(ss, sctx->latest_avc_header)
== NGX_AGAIN)
{
return NGX_AGAIN;
}
}
}
} else {
if (sctx->cache[ssctx->gop_pos] == NULL) {
ngx_log_error(NGX_LOG_ERR, ss->log, 0,
"gop, current gop pos is NULL, "
"skip to new postion [pos %d last %d] %d",
sctx->gop_pos, sctx->gop_last, ssctx->gop_pos);
if (sctx->aac_header
&& sctx->aac_header != ssctx->latest_aac_header)
{
if (ngx_rtmp_gop_link_frame(ss, sctx->aac_header)
== NGX_AGAIN)
{
return NGX_AGAIN;
}
}
if (sctx->avc_header
&& sctx->avc_header != ssctx->latest_avc_header)
{
if (ngx_rtmp_gop_link_frame(ss, sctx->avc_header)
== NGX_AGAIN)
{
return NGX_AGAIN;
}
}
ssctx->gop_pos = sctx->gop_pos;
}
}
frame = sctx->cache[ssctx->gop_pos];
if (ngx_rtmp_gop_link_frame(ss, frame) == NGX_AGAIN) {
return NGX_AGAIN;
}
ssctx->gop_pos = ngx_rtmp_gop_next(s, ssctx->gop_pos);
ngx_rtmp_send_message(ss, NULL, 0);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_gop_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
{
ngx_rtmp_gop_ctx_t *ctx;
ngx_rtmp_live_ctx_t *lctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_gop_module);
if (ctx == NULL) {
goto next;
}
lctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
if (!lctx->publishing) {
goto next;
}
if (ctx->avc_header) {
ngx_rtmp_shared_free_frame(ctx->avc_header);
}
if (ctx->aac_header) {
ngx_rtmp_shared_free_frame(ctx->aac_header);
}
/* free cache in publisher */
while (ctx->gop_pos != ctx->gop_last) {
ngx_rtmp_shared_free_frame(ctx->cache[ctx->gop_pos]);
ctx->gop_pos = ngx_rtmp_gop_next(s, ctx->gop_pos);
}
next:
return next_close_stream(s, v);
}
static ngx_int_t
ngx_rtmp_gop_postconfiguration(ngx_conf_t *cf)
{
next_close_stream = ngx_rtmp_close_stream;
ngx_rtmp_close_stream = ngx_rtmp_gop_close_stream;
return NGX_OK;
}

View File

@ -0,0 +1,948 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
#include "ngx_rtmp_amf.h"
#include "ngx_rbuf.h"
#include "ngx_poold.h"
#include "ngx_rtmp_monitor_module.h"
static void ngx_rtmp_recv(ngx_event_t *rev);
static void ngx_rtmp_send(ngx_event_t *rev);
static void ngx_rtmp_ping(ngx_event_t *rev);
static ngx_int_t ngx_rtmp_finalize_set_chunk_size(ngx_rtmp_session_t *s);
ngx_uint_t ngx_rtmp_naccepted;
ngx_rtmp_bandwidth_t ngx_rtmp_bw_out;
ngx_rtmp_bandwidth_t ngx_rtmp_bw_in;
#ifdef NGX_DEBUG
char*
ngx_rtmp_message_type(uint8_t type)
{
static char* types[] = {
"?",
"chunk_size",
"abort",
"ack",
"user",
"ack_size",
"bandwidth",
"edge",
"audio",
"video",
"?",
"?",
"?",
"?",
"?",
"amf3_meta",
"amf3_shared",
"amf3_cmd",
"amf_meta",
"amf_shared",
"amf_cmd",
"?",
"aggregate"
};
return type < sizeof(types) / sizeof(types[0])
? types[type]
: "?";
}
char*
ngx_rtmp_user_message_type(uint16_t evt)
{
static char* evts[] = {
"stream_begin",
"stream_eof",
"stream dry",
"set_buflen",
"recorded",
"",
"ping_request",
"ping_response",
};
return evt < sizeof(evts) / sizeof(evts[0])
? evts[evt]
: "?";
}
#endif
void
ngx_rtmp_cycle(ngx_rtmp_session_t *s)
{
ngx_connection_t *c;
c = s->connection;
c->read->handler = ngx_rtmp_recv;
c->write->handler = ngx_rtmp_send;
s->ping_evt.data = c;
s->ping_evt.log = s->log;
s->ping_evt.handler = ngx_rtmp_ping;
ngx_rtmp_reset_ping(s);
ngx_rtmp_recv(c->read);
}
static ngx_chain_t *
ngx_rtmp_alloc_in_buf(ngx_rtmp_session_t *s)
{
ngx_chain_t *cl;
ngx_buf_t *b;
size_t size;
if ((cl = ngx_alloc_chain_link(s->in_pool)) == NULL
|| (cl->buf = ngx_calloc_buf(s->in_pool)) == NULL)
{
return NULL;
}
cl->next = NULL;
b = cl->buf;
size = s->in_chunk_size + NGX_RTMP_MAX_CHUNK_HEADER;
b->start = b->last = b->pos = ngx_palloc(s->in_pool, size);
if (b->start == NULL) {
return NULL;
}
b->end = b->start + size;
return cl;
}
void
ngx_rtmp_reset_ping(ngx_rtmp_session_t *s)
{
ngx_rtmp_core_srv_conf_t *cscf;
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
if (cscf->ping == 0) {
return;
}
s->ping_active = 0;
s->ping_reset = 0;
ngx_add_timer(&s->ping_evt, cscf->ping);
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
"ping: wait %Mms", cscf->ping);
}
static void
ngx_rtmp_ping(ngx_event_t *pev)
{
ngx_connection_t *c;
ngx_rtmp_session_t *s;
ngx_rtmp_core_srv_conf_t *cscf;
c = pev->data;
s = c->data;
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
/* i/o event has happened; no need to ping */
if (s->ping_reset) {
ngx_rtmp_reset_ping(s);
return;
}
if (s->ping_active) {
ngx_log_error(NGX_LOG_INFO, s->log, 0, "ping: unresponded");
ngx_rtmp_finalize_session(s);
return;
}
if (cscf->busy) {
ngx_log_error(NGX_LOG_INFO, s->log, 0, "ping: not busy between pings");
ngx_rtmp_finalize_session(s);
return;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
"ping: schedule %Mms", cscf->ping_timeout);
if (ngx_rtmp_send_ping_request(s, (uint32_t)ngx_current_msec) != NGX_OK) {
ngx_rtmp_finalize_session(s);
return;
}
s->ping_active = 1;
ngx_add_timer(pev, cscf->ping_timeout);
}
static void
ngx_rtmp_recv(ngx_event_t *rev)
{
ngx_int_t n;
ngx_connection_t *c;
ngx_rtmp_session_t *s;
ngx_rtmp_core_srv_conf_t *cscf;
ngx_rtmp_header_t *h;
ngx_rtmp_stream_t *st, *st0;
ngx_chain_t *in, *head;
ngx_buf_t *b;
u_char *p, *pp, *old_pos;
size_t size, fsize, old_size;
uint8_t fmt, ext;
uint32_t csid, timestamp;
c = rev->data;
s = c->data;
b = NULL;
old_pos = NULL;
old_size = 0;
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
if (c->destroyed) {
return;
}
if (ngx_rtmp_core_main_conf->fast_reload && (ngx_exiting || ngx_terminate)) {
s->finalize_reason = NGX_LIVE_PROCESS_EXIT;
ngx_rtmp_finalize_session(s);
return;
}
for( ;; ) {
st = &s->in_streams[s->in_csid];
/* allocate new buffer */
if (st->in == NULL) {
st->in = ngx_rtmp_alloc_in_buf(s);
if (st->in == NULL) {
ngx_log_error(NGX_LOG_INFO, s->log, 0, "in buf alloc failed");
ngx_rtmp_finalize_session(s);
return;
}
}
h = &st->hdr;
in = st->in;
b = in->buf;
if (old_size) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
"reusing formerly read data: %d", old_size);
b->pos = b->start;
b->last = ngx_movemem(b->pos, old_pos, old_size);
if (s->in_chunk_size_changing) {
ngx_rtmp_finalize_set_chunk_size(s);
}
} else {
if (old_pos) {
b->pos = b->last = b->start;
}
n = c->recv(c, b->last, b->end - b->last);
if (n == NGX_ERROR || n == 0) {
s->finalize_reason = n == 0? NGX_LIVE_NORMAL_CLOSE:
NGX_LIVE_RTMP_RECV_ERR;
ngx_rtmp_finalize_session(s);
return;
}
if (n == NGX_AGAIN) {
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
ngx_rtmp_finalize_session(s);
}
return;
}
s->ping_reset = 1;
ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_in, n);
b->last += n;
s->in_bytes += n;
if (s->in_bytes >= 0xf0000000) {
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0,
"resetting byte counter");
s->in_bytes = 0;
s->in_last_ack = 0;
}
if (s->ack_size && s->in_bytes - s->in_last_ack >= s->ack_size) {
s->in_last_ack = s->in_bytes;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
"sending RTMP ACK(%uD)", s->in_bytes);
if (ngx_rtmp_send_ack(s, s->in_bytes)) {
ngx_rtmp_finalize_session(s);
return;
}
}
}
old_pos = NULL;
old_size = 0;
/* parse headers */
if (b->pos == b->start) {
p = b->pos;
/* chunk basic header */
fmt = (*p >> 6) & 0x03;
csid = *p++ & 0x3f;
if (csid == 0) {
if (b->last - p < 1)
continue;
csid = 64;
csid += *(uint8_t*)p++;
} else if (csid == 1) {
if (b->last - p < 2)
continue;
csid = 64;
csid += *(uint8_t*)p++;
csid += (uint32_t)256 * (*(uint8_t*)p++);
}
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0,
"RTMP bheader fmt=%d csid=%D",
(int)fmt, csid);
if (csid >= (uint32_t)cscf->max_streams) {
ngx_log_error(NGX_LOG_INFO, s->log, 0,
"RTMP in chunk stream too big: %D >= %D",
csid, cscf->max_streams);
ngx_rtmp_finalize_session(s);
return;
}
/* link orphan */
if (s->in_csid == 0) {
/* unlink from stream #0 */
st->in = st->in->next;
/* link to new stream */
s->in_csid = csid;
st = &s->in_streams[csid];
if (st->in == NULL) {
in->next = in;
} else {
in->next = st->in->next;
st->in->next = in;
}
st->in = in;
h = &st->hdr;
h->csid = csid;
}
ext = st->ext;
timestamp = st->dtime;
if (fmt <= 2 ) {
if (b->last - p < 3)
continue;
/* timestamp:
* big-endian 3b -> little-endian 4b */
pp = (u_char*)&timestamp;
pp[2] = *p++;
pp[1] = *p++;
pp[0] = *p++;
pp[3] = 0;
ext = (timestamp == 0x00ffffff);
if (fmt <= 1) {
if (b->last - p < 4)
continue;
/* size:
* big-endian 3b -> little-endian 4b
* type:
* 1b -> 1b*/
pp = (u_char*)&h->mlen;
pp[2] = *p++;
pp[1] = *p++;
pp[0] = *p++;
pp[3] = 0;
h->type = *(uint8_t*)p++;
if (fmt == 0) {
if (b->last - p < 4)
continue;
/* stream:
* little-endian 4b -> little-endian 4b */
pp = (u_char*)&h->msid;
pp[0] = *p++;
pp[1] = *p++;
pp[2] = *p++;
pp[3] = *p++;
}
}
}
/* extended header */
if (ext) {
if (b->last - p < 4)
continue;
pp = (u_char*)&timestamp;
pp[3] = *p++;
pp[2] = *p++;
pp[1] = *p++;
pp[0] = *p++;
}
if (st->len == 0) {
/* Messages with type=3 should
* never have ext timestamp field
* according to standard.
* However that's not always the case
* in real life */
st->ext = (ext && cscf->publish_time_fix);
if (fmt) {
st->dtime = timestamp;
} else {
h->timestamp = timestamp;
st->dtime = 0;
}
}
ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->log, 0,
"RTMP mheader fmt=%d %s (%d) "
"time=%uD+%uD mlen=%D len=%D msid=%D",
(int)fmt, ngx_rtmp_message_type(h->type), (int)h->type,
h->timestamp, st->dtime, h->mlen, st->len, h->msid);
/* header done */
b->pos = p;
if (h->mlen > cscf->max_message) {
ngx_log_error(NGX_LOG_INFO, s->log, 0,
"too big message: %uz", cscf->max_message);
ngx_rtmp_finalize_session(s);
return;
}
}
size = b->last - b->pos;
fsize = h->mlen - st->len;
if (size < ngx_min(fsize, s->in_chunk_size))
continue;
/* buffer is ready */
if (fsize > s->in_chunk_size) {
/* collect fragmented chunks */
st->len += s->in_chunk_size;
b->last = b->pos + s->in_chunk_size;
old_pos = b->last;
old_size = size - s->in_chunk_size;
} else {
/* handle! */
head = st->in->next;
st->in->next = NULL;
b->last = b->pos + fsize;
old_pos = b->last;
old_size = size - fsize;
st->len = 0;
h->timestamp += st->dtime;
if (ngx_rtmp_receive_message(s, h, head) != NGX_OK) {
ngx_rtmp_finalize_session(s);
return;
}
if (s->in_chunk_size_changing) {
/* copy old data to a new buffer */
if (!old_size) {
ngx_rtmp_finalize_set_chunk_size(s);
}
} else {
/* add used bufs to stream #0 */
st0 = &s->in_streams[0];
st->in->next = st0->in;
st0->in = head;
st->in = NULL;
}
}
s->in_csid = 0;
}
}
static ngx_flag_t
ngx_rtmp_relative_timestamp(ngx_rtmp_session_t *s, ngx_rtmp_header_t *lh)
{
return 0;
}
static ngx_chain_t *
ngx_rtmp_prepare_out_chain(ngx_rtmp_session_t *s)
{
ngx_rtmp_core_srv_conf_t *cscf;
ngx_rtmp_frame_t *frame;
ngx_chain_t *head, *l, **ll;
uint32_t mlen, timestamp, ext_timestamp;
uint8_t fmt, hsize, thsize;
static uint8_t hdrsize[] = { 12, 8, 4, 1 };
ngx_flag_t relative;
ngx_rtmp_header_t lh;
u_char th[7], *p, *pp;
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
frame = s->out[s->out_pos];
head = NULL;
if (frame->hdr.csid >= (uint32_t)cscf->max_streams) {
ngx_log_error(NGX_LOG_INFO, s->log, 0,
"RTMP out chunk stream too big: %D >= %D",
frame->hdr.csid, cscf->max_streams);
goto failed;
}
mlen = 0;
fmt = 0;
relative = ngx_rtmp_relative_timestamp(s, &lh);
for (l = frame->chain; l; l = l->next) {
mlen += ngx_buf_size(l->buf);
}
if (relative && lh.csid && frame->hdr.msid == lh.msid) {
++fmt;
if (frame->hdr.type == lh.type && mlen && mlen == lh.mlen) {
++fmt;
if (frame->hdr.timestamp == lh.timestamp) {
++fmt;
}
}
timestamp = frame->hdr.timestamp - lh.timestamp;
} else {
timestamp = frame->hdr.timestamp;
}
hsize = hdrsize[fmt];
ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->log, 0,
"RTMP prep %s (%d) fmt=%d csid=%uD timestamp=%uD mlen=%uD msid=%uD",
ngx_rtmp_message_type(frame->hdr.type), (int)frame->hdr.type,
(int)fmt, frame->hdr.csid, timestamp, mlen, frame->hdr.msid);
ext_timestamp = 0;
if (timestamp >= 0x00ffffff) {
ext_timestamp = timestamp;
timestamp = 0x00ffffff;
hsize += 4;
}
if (frame->hdr.csid >= 64) {
++hsize;
if (frame->hdr.csid >= 320) {
++hsize;
}
}
/* fill initial header */
head = ngx_get_chainbuf(NGX_RTMP_MAX_CHUNK_HEADER, 1);
if (head == NULL) {
goto failed;
}
p = head->buf->pos;
/* basic header */
*p = (fmt << 6);
if (frame->hdr.csid >= 2 && frame->hdr.csid <= 63) {
*p++ |= (((uint8_t)frame->hdr.csid) & 0x3f);
} else if (frame->hdr.csid >= 64 && frame->hdr.csid < 320) {
++p;
*p++ = (uint8_t)(frame->hdr.csid - 64);
} else {
*p++ |= 1;
*p++ = (uint8_t)(frame->hdr.csid - 64);
*p++ = (uint8_t)((frame->hdr.csid - 64) >> 8);
}
/* create fmt3 header for successive fragments */
thsize = p - head->buf->pos;
ngx_memcpy(th, head->buf->pos, thsize);
th[0] |= 0xc0;
/* message header */
if (fmt <= 2) {
pp = (u_char *)&timestamp;
*p++ = pp[2];
*p++ = pp[1];
*p++ = pp[0];
if (fmt <= 1) {
pp = (u_char *)&mlen;
*p++ = pp[2];
*p++ = pp[1];
*p++ = pp[0];
*p++ = frame->hdr.type;
if (fmt == 0) {
pp = (u_char *)&frame->hdr.msid;
*p++ = pp[0];
*p++ = pp[1];
*p++ = pp[2];
*p++ = pp[3];
}
}
}
/* extended timestamp */
if (ext_timestamp) {
pp = (u_char *)&ext_timestamp;
*p++ = pp[3];
*p++ = pp[2];
*p++ = pp[1];
*p++ = pp[0];
/* This CONTRADICTS the standard
* but that's the way flash client
* wants data to be encoded;
* ffmpeg complains */
if (cscf->play_time_fix) {
ngx_memcpy(&th[thsize], p - 4, 4);
thsize += 4;
}
}
head->buf->last = p;
/* append headers to successive fragments */
ll = &head->next;
l = frame->chain;
while (l && l->buf->pos == l->buf->last) {
l = l->next;
}
if (l == NULL) {
return head;
}
*ll = ngx_get_chainbuf(0, 0);
(*ll)->buf->pos = l->buf->pos;
(*ll)->buf->last = l->buf->last;
for (l = l->next; l; l = l->next) {
/* chunk header */
ll = &(*ll)->next;
*ll = ngx_get_chainbuf(NGX_RTMP_MAX_CHUNK_HEADER, 1);
(*ll)->buf->last = ngx_cpymem((*ll)->buf->last, th, thsize);
/* payload */
ll = &(*ll)->next;
*ll = ngx_get_chainbuf(0, 0);
(*ll)->buf->pos = l->buf->pos;
(*ll)->buf->last = l->buf->last;
}
ngx_rtmp_monitor_frame(s, &frame->hdr, NULL, frame->av_header, 0);
return head;
failed:
ngx_put_chainbufs(head);
ngx_rtmp_finalize_session(s);
return NULL;
}
static void
ngx_rtmp_send(ngx_event_t *wev)
{
ngx_connection_t *c;
ngx_rtmp_session_t *s;
ngx_int_t n;
ngx_chain_t *chain, *cl;
off_t sent;
c = wev->data;
s = c->data;
if (c->destroyed) {
return;
}
if (wev->timedout) {
ngx_log_error(NGX_LOG_INFO, s->log, NGX_ETIMEDOUT, "client timed out");
c->timedout = 1;
s->finalize_reason = NGX_LIVE_RTMP_SEND_TIMEOUT;
ngx_rtmp_finalize_session(s);
return;
}
if (wev->timer_set) {
ngx_del_timer(wev);
}
if (s->prepare_handler == NULL) {
s->prepare_handler = ngx_rtmp_prepare_out_chain;
}
if (ngx_rtmp_core_main_conf->fast_reload && (ngx_exiting || ngx_terminate)) {
s->finalize_reason = NGX_LIVE_PROCESS_EXIT;
ngx_rtmp_finalize_session(s);
return;
}
if (ngx_rtmp_prepare_merge_frame(s) == NGX_ERROR) {
ngx_rtmp_finalize_session(s);
return;
}
while (s->out_chain) {
sent = c->sent;
chain = c->send_chain(c, s->out_chain, 0);
n = c->sent - sent;
if (chain == NGX_CHAIN_ERROR) { /* NGX_ERROR */
c->error = 1;
s->finalize_reason = NGX_LIVE_RTMP_SEND_ERR;
ngx_rtmp_finalize_session(s);
return;
}
for (cl = s->out_chain; cl != chain;) {
s->out_chain = cl->next;
ngx_free_chain(s->pool, cl);
cl = s->out_chain;
}
if (chain) { /* NGX_AGAIN */
ngx_add_timer(c->write, s->timeout);
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
ngx_rtmp_finalize_session(s);
}
return;
}
s->out_bytes += n;
s->ping_reset = 1;
ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_out, n);
if (ngx_rtmp_prepare_merge_frame(s) == NGX_ERROR) {
ngx_rtmp_finalize_session(s);
return;
}
}
if (wev->active) {
ngx_del_event(wev, NGX_WRITE_EVENT, 0);
}
ngx_event_process_posted((ngx_cycle_t *) ngx_cycle, &s->posted_dry_events);
}
ngx_int_t
ngx_rtmp_send_message(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *out,
ngx_uint_t priority)
{
ngx_uint_t nmsg;
if (out == NULL) {
goto send;
}
nmsg = (s->out_last - s->out_pos) % s->out_queue + 1;
if (priority > 3) {
priority = 3;
}
/* drop packet?
* Note we always leave 1 slot free */
if (nmsg + priority * s->out_queue / 4 >= s->out_queue) {
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0,
"RTMP drop message bufs=%ui, priority=%ui",
nmsg, priority);
return NGX_AGAIN;
}
s->out[s->out_last++] = out;
s->out_last %= s->out_queue;
ngx_rtmp_shared_acquire_frame(out);
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0,
"RTMP send nmsg=%ui, priority=%ui #%ui",
nmsg, priority, s->out_last);
if (priority && s->out_buffer && nmsg < s->out_cork) {
return NGX_OK;
}
send:
if (!s->connection->write->active) {
ngx_post_event(s->connection->write, &ngx_posted_events);
}
return NGX_OK;
}
ngx_int_t
ngx_rtmp_receive_message(ngx_rtmp_session_t *s,
ngx_rtmp_header_t *h, ngx_chain_t *in)
{
ngx_rtmp_core_main_conf_t *cmcf;
ngx_array_t *evhs;
size_t n;
ngx_rtmp_handler_pt *evh;
cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);
#ifdef NGX_DEBUG
{
int nbufs;
ngx_chain_t *ch;
for(nbufs = 1, ch = in;
ch->next;
ch = ch->next, ++nbufs);
ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->log, 0,
"RTMP recv %s (%d) csid=%D timestamp=%D "
"mlen=%D msid=%D nbufs=%d",
ngx_rtmp_message_type(h->type), (int)h->type,
h->csid, h->timestamp, h->mlen, h->msid, nbufs);
}
#endif
if (h->type > NGX_RTMP_MSG_MAX) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
"unexpected RTMP message type: %d", (int)h->type);
return NGX_OK;
}
evhs = &cmcf->events[h->type];
evh = evhs->elts;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, "nhandlers: %d", evhs->nelts);
for(n = 0; n < evhs->nelts; ++n, ++evh) {
if (!evh) {
continue;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0, "calling handler %d", n);
switch ((*evh)(s, h, in)) {
case NGX_ERROR:
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
"handler %d failed", n);
return NGX_ERROR;
case NGX_DONE:
return NGX_OK;
}
}
return NGX_OK;
}
ngx_int_t
ngx_rtmp_set_chunk_size(ngx_rtmp_session_t *s, ngx_uint_t size)
{
ngx_rtmp_core_srv_conf_t *cscf;
ngx_chain_t *li, *fli, *lo, *flo;
ngx_buf_t *bi, *bo;
ngx_int_t n;
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
s->in_old_pool = s->in_pool;
s->in_chunk_size = size;
s->in_pool = NGX_CREATE_POOL(4096, ngx_cycle->log);
/* copy existing chunk data */
if (s->in_old_pool) {
s->in_chunk_size_changing = 1;
s->in_streams[0].in = NULL;
for(n = 1; n < cscf->max_streams; ++n) {
/* stream buffer is circular
* for all streams except for the current one
* (which caused this chunk size change);
* we can simply ignore it */
li = s->in_streams[n].in;
if (li == NULL || li->next == NULL) {
s->in_streams[n].in = NULL;
continue;
}
/* move from last to the first */
li = li->next;
fli = li;
lo = ngx_rtmp_alloc_in_buf(s);
if (lo == NULL) {
return NGX_ERROR;
}
flo = lo;
for ( ;; ) {
bi = li->buf;
bo = lo->buf;
if (bo->end - bo->last >= bi->last - bi->pos) {
bo->last = ngx_cpymem(bo->last, bi->pos,
bi->last - bi->pos);
li = li->next;
if (li == fli) {
lo->next = flo;
s->in_streams[n].in = lo;
break;
}
continue;
}
bi->pos += (ngx_cpymem(bo->last, bi->pos,
bo->end - bo->last) - bo->last);
lo->next = ngx_rtmp_alloc_in_buf(s);
lo = lo->next;
if (lo == NULL) {
return NGX_ERROR;
}
}
}
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_finalize_set_chunk_size(ngx_rtmp_session_t *s)
{
if (s->in_chunk_size_changing && s->in_old_pool) {
NGX_DESTROY_POOL(s->in_old_pool);
s->in_old_pool = NULL;
s->in_chunk_size_changing = 0;
}
return NGX_OK;
}

View File

@ -0,0 +1,641 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
#include <openssl/hmac.h>
#include <openssl/sha.h>
static void ngx_rtmp_handshake_send(ngx_event_t *wev);
static void ngx_rtmp_handshake_recv(ngx_event_t *rev);
static void ngx_rtmp_handshake_done(ngx_rtmp_session_t *s);
/* RTMP handshake :
*
* =peer1= =peer2=
* challenge ----> (.....[digest1]......) ----> 1537 bytes
* response <---- (...........[digest2]) <---- 1536 bytes
*
*
* - both packets contain random bytes except for digests
* - digest1 position is calculated on random packet bytes
* - digest2 is always at the end of the packet
*
* digest1: HMAC_SHA256(packet, peer1_partial_key)
* digest2: HMAC_SHA256(packet, HMAC_SHA256(digest1, peer2_full_key))
*/
/* Handshake keys */
static u_char
ngx_rtmp_server_key[] = {
'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ',
'S', 'e', 'r', 'v', 'e', 'r', ' ',
'0', '0', '1',
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,
0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
};
static u_char
ngx_rtmp_client_key[] = {
'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ',
'0', '0', '1',
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,
0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
};
static const u_char
ngx_rtmp_server_version[4] = {
0x0D, 0x0E, 0x0A, 0x0D
};
static const u_char
ngx_rtmp_client_version[4] = {
0x0C, 0x00, 0x0D, 0x0E
};
#define NGX_RTMP_HANDSHAKE_KEYLEN SHA256_DIGEST_LENGTH
#define NGX_RTMP_HANDSHAKE_BUFSIZE 3073
#define NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE 1537
#define NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE 1
#define NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLRESP 2
#define NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE 3
#define NGX_RTMP_HANDSHAKE_SERVER_DONE 4
#define NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE 6
#define NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE 7
#define NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE 8
#define NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE 9
#define NGX_RTMP_HANDSHAKE_CLIENT_DONE 10
static ngx_str_t ngx_rtmp_server_full_key
= { sizeof(ngx_rtmp_server_key), ngx_rtmp_server_key };
static ngx_str_t ngx_rtmp_server_partial_key
= { 36, ngx_rtmp_server_key };
static ngx_str_t ngx_rtmp_client_full_key
= { sizeof(ngx_rtmp_client_key), ngx_rtmp_client_key };
static ngx_str_t ngx_rtmp_client_partial_key
= { 30, ngx_rtmp_client_key };
static ngx_int_t
ngx_rtmp_make_digest(ngx_str_t *key, ngx_buf_t *src,
u_char *skip, u_char *dst, ngx_log_t *log)
{
static HMAC_CTX *hmac;
unsigned int len;
if (hmac == NULL) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
static HMAC_CTX shmac;
hmac = &shmac;
HMAC_CTX_init(hmac);
#else
hmac = HMAC_CTX_new();
if (hmac == NULL) {
return NGX_ERROR;
}
#endif
}
HMAC_Init_ex(hmac, key->data, key->len, EVP_sha256(), NULL);
if (skip && src->pos <= skip && skip <= src->last) {
if (skip != src->pos) {
HMAC_Update(hmac, src->pos, skip - src->pos);
}
if (src->last != skip + NGX_RTMP_HANDSHAKE_KEYLEN) {
HMAC_Update(hmac, skip + NGX_RTMP_HANDSHAKE_KEYLEN,
src->last - skip - NGX_RTMP_HANDSHAKE_KEYLEN);
}
} else {
HMAC_Update(hmac, src->pos, src->last - src->pos);
}
HMAC_Final(hmac, dst, &len);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_find_digest(ngx_buf_t *b, ngx_str_t *key, size_t base, ngx_log_t *log)
{
size_t n, offs;
u_char digest[NGX_RTMP_HANDSHAKE_KEYLEN];
u_char *p;
offs = 0;
for (n = 0; n < 4; ++n) {
offs += b->pos[base + n];
}
offs = (offs % 728) + base + 4;
p = b->pos + offs;
if (ngx_rtmp_make_digest(key, b, p, digest, log) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_memcmp(digest, p, NGX_RTMP_HANDSHAKE_KEYLEN) == 0) {
return offs;
}
return NGX_ERROR;
}
static ngx_int_t
ngx_rtmp_write_digest(ngx_buf_t *b, ngx_str_t *key, size_t base,
ngx_log_t *log)
{
size_t n, offs;
u_char *p;
offs = 0;
for (n = 8; n < 12; ++n) {
offs += b->pos[base + n];
}
offs = (offs % 728) + base + 12;
p = b->pos + offs;
if (ngx_rtmp_make_digest(key, b, p, p, log) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
static void
ngx_rtmp_fill_random_buffer(ngx_buf_t *b, u_char *end)
{
for (; b->last != end; ++b->last) {
*b->last = (u_char) rand();
}
}
static ngx_buf_t *
ngx_rtmp_alloc_handshake_buffer(ngx_rtmp_session_t *s)
{
ngx_rtmp_core_srv_conf_t *cscf;
ngx_chain_t *cl;
ngx_buf_t *b;
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0,
"handshake: allocating buffer");
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
if (cscf->free_hs) {
cl = cscf->free_hs;
b = cl->buf;
cscf->free_hs = cl->next;
ngx_free_chain(cscf->pool, cl);
} else {
b = ngx_pcalloc(cscf->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NULL;
}
b->memory = 1;
b->start = ngx_pcalloc(cscf->pool, NGX_RTMP_HANDSHAKE_BUFSIZE);
if (b->start == NULL) {
return NULL;
}
b->end = b->start + NGX_RTMP_HANDSHAKE_BUFSIZE;
}
b->pos = b->last = b->start;
return b;
}
void
ngx_rtmp_free_handshake_buffers(ngx_rtmp_session_t *s)
{
ngx_rtmp_core_srv_conf_t *cscf;
ngx_chain_t *cl;
if (s->hs_buf == NULL) {
return;
}
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
cl = ngx_alloc_chain_link(cscf->pool);
if (cl == NULL) {
return;
}
cl->buf = s->hs_buf;
cl->next = cscf->free_hs;
cscf->free_hs = cl;
s->hs_buf = NULL;
}
static ngx_int_t
ngx_rtmp_handshake_create_challenge(ngx_rtmp_session_t *s,
const u_char version[4], ngx_str_t *key)
{
ngx_buf_t *b;
u_char *end;
b = s->hs_buf;
b->last = b->pos = b->start;
end = b->start + NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE;
*b->last++ = '\x03';
b->last = ngx_rtmp_rcpymem(b->last, &s->epoch, 4);
b->last = ngx_cpymem(b->last, version, 4);
ngx_rtmp_fill_random_buffer(b, end);
++b->pos;
if (ngx_rtmp_write_digest(b, key, 0, s->log) != NGX_OK) {
return NGX_ERROR;
}
--b->pos;
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_handshake_parse_challenge(ngx_rtmp_session_t *s,
ngx_str_t *peer_key, ngx_str_t *key)
{
ngx_buf_t *b;
u_char *p;
ngx_int_t offs;
b = s->hs_buf;
if (*b->pos != '\x03') {
ngx_log_error(NGX_LOG_INFO, s->log, 0,
"handshake: unexpected RTMP version: %i",
(ngx_int_t)*b->pos);
return NGX_ERROR;
}
++b->pos;
s->peer_epoch = 0;
ngx_rtmp_rmemcpy(&s->peer_epoch, b->pos, 4);
p = b->pos + 4;
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->log, 0,
"handshake: peer version=%i.%i.%i.%i epoch=%uD",
(ngx_int_t)p[3], (ngx_int_t)p[2],
(ngx_int_t)p[1], (ngx_int_t)p[0],
(uint32_t)s->peer_epoch);
if (*(uint32_t *)p == 0) {
s->hs_old = 1;
return NGX_OK;
}
offs = ngx_rtmp_find_digest(b, peer_key, 772, s->log);
if (offs == NGX_ERROR) {
offs = ngx_rtmp_find_digest(b, peer_key, 8, s->log);
}
if (offs == NGX_ERROR) {
ngx_log_error(NGX_LOG_INFO, s->log, 0, "handshake: digest not found");
s->hs_old = 1;
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
"handshake: digest found at pos=%i", offs);
b->pos += offs;
b->last = b->pos + NGX_RTMP_HANDSHAKE_KEYLEN;
s->hs_digest = ngx_palloc(s->pool, NGX_RTMP_HANDSHAKE_KEYLEN);
if (ngx_rtmp_make_digest(key, b, NULL, s->hs_digest, s->log)
!= NGX_OK)
{
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_handshake_create_response(ngx_rtmp_session_t *s)
{
ngx_buf_t *b;
u_char *p, *pos;
ngx_str_t key;
b = s->hs_buf;
pos = b->pos;
b->pos = b->start + NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE;
ngx_rtmp_fill_random_buffer(b, b->end);
if (s->hs_digest) {
p = b->last - NGX_RTMP_HANDSHAKE_KEYLEN;
key.data = s->hs_digest;
key.len = NGX_RTMP_HANDSHAKE_KEYLEN;
if (ngx_rtmp_make_digest(&key, b, p, p, s->log) != NGX_OK) {
return NGX_ERROR;
}
}
b->pos = pos;
return NGX_OK;
}
static void
ngx_rtmp_handshake_done(ngx_rtmp_session_t *s)
{
ngx_rtmp_free_handshake_buffers(s);
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0,
"handshake: done");
if (ngx_rtmp_fire_event(s, NGX_RTMP_HANDSHAKE_DONE,
NULL, NULL) != NGX_OK)
{
ngx_rtmp_finalize_session(s);
return;
}
s->stage = NGX_LIVE_HANDSHAKE_DONE;
s->handshake_done_time = ngx_current_msec;
ngx_rtmp_cycle(s);
}
static void
ngx_rtmp_handshake_recv(ngx_event_t *rev)
{
ssize_t n;
ngx_connection_t *c;
ngx_rtmp_session_t *s;
ngx_buf_t *b;
c = rev->data;
s = c->data;
if (c->destroyed) {
return;
}
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, s->log, NGX_ETIMEDOUT,
"handshake: recv: client timed out");
c->timedout = 1;
ngx_rtmp_finalize_session(s);
return;
}
if (rev->timer_set) {
ngx_del_timer(rev);
}
b = s->hs_buf;
while (b->last != b->end) {
n = c->recv(c, b->last, b->end - b->last);
if (n == NGX_ERROR || n == 0) {
ngx_rtmp_finalize_session(s);
return;
}
if (n == NGX_AGAIN) {
ngx_add_timer(rev, s->timeout);
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"handshake: recv: handle read event error");
ngx_rtmp_finalize_session(s);
}
return;
}
b->last += n;
}
if (rev->active) {
ngx_del_event(rev, NGX_READ_EVENT, 0);
}
++s->hs_stage;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
"handshake: stage %ui", s->hs_stage);
switch (s->hs_stage) {
case NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLRESP:
if (ngx_rtmp_handshake_parse_challenge(s,
&ngx_rtmp_client_partial_key,
&ngx_rtmp_server_full_key) != NGX_OK)
{
ngx_log_error(NGX_LOG_INFO, s->log, 0,
"handshake: error parsing challenge");
ngx_rtmp_finalize_session(s);
return;
}
// create version and challege
if (ngx_rtmp_handshake_create_challenge(s, ngx_rtmp_server_version,
&ngx_rtmp_server_partial_key) != NGX_OK)
{
ngx_log_error(NGX_LOG_INFO, s->log, 0,
"handshake: error creating challenge");
ngx_rtmp_finalize_session(s);
return;
}
// create response
if (s->hs_old) {
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0,
"handshake: old-style response");
s->hs_buf->last = s->hs_buf->end;
} else if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {
ngx_log_error(NGX_LOG_INFO, s->log, 0,
"handshake: response error");
ngx_rtmp_finalize_session(s);
return;
}
ngx_rtmp_handshake_send(c->write);
break;
case NGX_RTMP_HANDSHAKE_SERVER_DONE:
ngx_rtmp_handshake_done(s);
break;
case NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE:
if (ngx_rtmp_handshake_parse_challenge(s,
&ngx_rtmp_server_partial_key,
&ngx_rtmp_client_full_key) != NGX_OK)
{
ngx_log_error(NGX_LOG_INFO, s->log, 0,
"handshake: error parsing challenge");
ngx_rtmp_finalize_session(s);
return;
}
s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start
+ NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE;
ngx_rtmp_handshake_recv(c->read);
break;
case NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE:
if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {
ngx_log_error(NGX_LOG_INFO, s->log, 0,
"handshake: response error");
ngx_rtmp_finalize_session(s);
return;
}
ngx_rtmp_handshake_send(c->write);
break;
}
}
static void
ngx_rtmp_handshake_send(ngx_event_t *wev)
{
ngx_int_t n;
ngx_connection_t *c;
ngx_rtmp_session_t *s;
ngx_buf_t *b;
c = wev->data;
s = c->data;
if (c->destroyed) {
return;
}
if (wev->timedout) {
ngx_log_error(NGX_LOG_INFO, s->log, NGX_ETIMEDOUT,
"handshake: send: client timed out");
c->timedout = 1;
ngx_rtmp_finalize_session(s);
return;
}
if (wev->timer_set) {
ngx_del_timer(wev);
}
b = s->hs_buf;
while(b->pos != b->last) {
n = c->send(c, b->pos, b->last - b->pos);
if (n == NGX_ERROR) {
ngx_rtmp_finalize_session(s);
return;
}
if (n == NGX_AGAIN || n == 0) {
ngx_add_timer(c->write, s->timeout);
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"handshake: recv: handle write event error");
ngx_rtmp_finalize_session(s);
}
return;
}
b->pos += n;
}
if (wev->active) {
ngx_del_event(wev, NGX_WRITE_EVENT, 0);
}
++s->hs_stage;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
"handshake: stage %ui", s->hs_stage);
switch (s->hs_stage) {
case NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE:
s->hs_buf->last = s->hs_buf->start
+ NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE;
ngx_rtmp_handshake_recv(c->read);
break;
case NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE:
s->hs_buf->pos = s->hs_buf->last = s->hs_buf->end
- NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE;
ngx_rtmp_handshake_recv(c->read);
break;
case NGX_RTMP_HANDSHAKE_CLIENT_DONE:
ngx_rtmp_handshake_done(s);
break;
}
}
void
ngx_rtmp_handshake(ngx_rtmp_session_t *s)
{
ngx_connection_t *c;
c = s->connection;
c->read->handler = ngx_rtmp_handshake_recv;
c->write->handler = ngx_rtmp_handshake_send;
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0,
"handshake: start server handshake");
s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);
s->hs_buf->pos = s->hs_buf->last = s->hs_buf->end
- NGX_RTMP_HANDSHAKE_CHALLENGE_SIZE;
s->hs_stage = NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE;
ngx_rtmp_handshake_recv(c->read);
}
void
ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async)
{
ngx_connection_t *c;
c = s->connection;
c->read->handler = ngx_rtmp_handshake_recv;
c->write->handler = ngx_rtmp_handshake_send;
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0,
"handshake: start client handshake");
s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);
s->hs_stage = NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE;
if (ngx_rtmp_handshake_create_challenge(s,
ngx_rtmp_client_version,
&ngx_rtmp_client_partial_key) != NGX_OK)
{
ngx_rtmp_finalize_session(s);
return;
}
if (async) {
ngx_add_timer(c->write, s->timeout);
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
ngx_rtmp_finalize_session(s);
}
return;
}
ngx_rtmp_handshake_send(c->write);
}

View File

@ -0,0 +1,767 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
#include "ngx_rtmp_cmd_module.h"
#include "ngx_live_relay.h"
#include "ngx_rtmp_proxy_protocol.h"
#include "ngx_http_client.h"
#include "ngx_rbuf.h"
#include "ngx_poold.h"
static void ngx_rtmp_close_connection(ngx_connection_t *c);
static u_char * ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len);
typedef struct {
ngx_str_t *client;
ngx_rtmp_session_t *session;
void *data; // combined log
ngx_log_handler_pt handler; // combined log handler
} ngx_rtmp_error_log_ctx_t;
char *ngx_live_stage[] = {
"init",
"handshake_done",
"connect",
"create_stream",
"publish",
"play",
"audio_video",
"close_stream",
};
char *ngx_live_err[] = {
"internal_err",
"normal_close",
"rtmp_send_err",
"rtmp_send_timeout",
"flv_send_err",
"flv_send_timeout",
"rtmp_recv_err",
"flv_recv_err",
"relay_transit",
"relay_timeout",
"control_drop",
"drop_idle",
"notify_notify_err",
"notify_relay_err",
"notify_para_err",
"relay_close",
"process_exit",
};
void
ngx_rtmp_init_connection(ngx_connection_t *c)
{
ngx_uint_t i;
ngx_rtmp_port_t *port;
struct sockaddr *sa;
struct sockaddr_in *sin;
ngx_rtmp_in_addr_t *addr;
ngx_rtmp_session_t *s;
ngx_rtmp_addr_conf_t *addr_conf;
ngx_int_t unix_socket;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
ngx_rtmp_in6_addr_t *addr6;
#endif
++ngx_rtmp_naccepted;
/* find the server configuration for the address:port */
/* AF_INET only */
port = c->listening->servers;
unix_socket = 0;
if (port->naddrs > 1) {
/*
* There are several addresses on this port and one of them
* is the "*:port" wildcard so getsockname() is needed to determine
* the server address.
*
* AcceptEx() already gave this address.
*/
if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
ngx_rtmp_close_connection(c);
return;
}
sa = c->local_sockaddr;
switch (sa->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) sa;
addr6 = port->addrs;
/* the last address is "*" */
for (i = 0; i < port->naddrs - 1; i++) {
if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
break;
}
}
addr_conf = &addr6[i].conf;
break;
#endif
case AF_UNIX:
unix_socket = 1;
sin = (struct sockaddr_in *) sa;
addr = port->addrs;
/* the last address is "*" */
for (i = 0; i < port->naddrs - 1; i++) {
if (addr[i].addr == sin->sin_addr.s_addr) {
break;
}
}
addr_conf = &addr[i].conf;
break;
default: /* AF_INET */
sin = (struct sockaddr_in *) sa;
addr = port->addrs;
/* the last address is "*" */
for (i = 0; i < port->naddrs - 1; i++) {
if (addr[i].addr == sin->sin_addr.s_addr) {
break;
}
}
addr_conf = &addr[i].conf;
break;
}
} else {
switch (c->local_sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
addr6 = port->addrs;
addr_conf = &addr6[0].conf;
break;
#endif
case AF_UNIX:
unix_socket = 1;
addr = port->addrs;
addr_conf = &addr[0].conf;
break;
default: /* AF_INET */
addr = port->addrs;
addr_conf = &addr[0].conf;
break;
}
}
ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%ui client connected '%V'",
c->number, &c->addr_text);
s = ngx_rtmp_create_session(addr_conf);
if (s == NULL) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "create rtmp session failed");
return;
}
s->log->connection = c->number;
s->number = c->number;
s->remote_addr_text.data = ngx_pcalloc(s->pool, c->addr_text.len);
s->remote_addr_text.len = c->addr_text.len;
ngx_memcpy(s->remote_addr_text.data, c->addr_text.data, c->addr_text.len);
s->sockaddr = ngx_pcalloc(s->pool, sizeof(struct sockaddr));
ngx_memcpy(s->sockaddr, c->sockaddr, sizeof(struct sockaddr));
ngx_rtmp_init_session(s, c);
/* only auto-pushed connections are
* done through unix socket */
s->interprocess = unix_socket;
if (addr_conf->proxy_protocol) {
ngx_rtmp_proxy_protocol(s);
} else {
ngx_rtmp_handshake(s);
}
}
static u_char *
ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len)
{
u_char *p;
ngx_rtmp_session_t *s;
ngx_rtmp_error_log_ctx_t *ctx;
p = buf;
ctx = log->data;
if (ctx->client) {
p = ngx_snprintf(buf, len, ", client: %V", ctx->client);
len -= p - buf;
buf = p;
}
s = ctx->session;
if (s == NULL) {
return p;
}
p = ngx_snprintf(buf, len, ", server: %V, session: %p", s->addr_text, s);
len -= p - buf;
buf = p;
if (s->stream.len) {
p = ngx_snprintf(buf, len, ", stream: %V", &s->stream);
len -= p - buf;
buf = p;
}
if (ctx->data) { // combined log
log->data = ctx->data;
p = ctx->handler(log, p, len);
log->data = ctx;
}
return p;
}
static void
ngx_rtmp_close_connection(ngx_connection_t *c)
{
ngx_pool_t *pool;
if (c == NULL) {
return;
}
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "close connection");
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, -1);
#endif
pool = c->pool;
ngx_close_connection(c);
ngx_destroy_pool(pool);
}
static void
ngx_rtmp_close_session(ngx_rtmp_session_t *s)
{
if (s->ping_evt.timer_set) {
ngx_del_timer(&s->ping_evt);
}
if (s->in_old_pool) {
NGX_DESTROY_POOL(s->in_old_pool);
s->in_old_pool = NULL;
}
if (s->in_pool) {
NGX_DESTROY_POOL(s->in_pool);
s->in_pool = NULL;
}
if (s->live_type == NGX_HTTP_FLV_LIVE) {
ngx_put_chainbufs(s->in_streams[0].in);
}
ngx_rtmp_free_handshake_buffers(s);
ngx_rtmp_free_merge_frame(s);
if (s->live_type == NGX_MPEGTS_LIVE) {
while (s->out_pos != s->out_last) {
ngx_rtmp_shared_free_mpegts_frame(s->mpegts_out[s->out_pos++]);
s->out_pos %= s->out_queue;
}
} else {
while (s->out_pos != s->out_last) {
ngx_rtmp_shared_free_frame(s->out[s->out_pos++]);
s->out_pos %= s->out_queue;
}
}
if (s->pool) {
NGX_DESTROY_POOL(s->pool);
}
}
static void
ngx_rtmp_close_session_handler(ngx_event_t *e)
{
ngx_rtmp_session_t *s;
ngx_connection_t *c;
s = e->data;
c = s->connection;
if (c) {
c->destroyed = 1;
}
ngx_log_error(NGX_LOG_INFO, s->log, 0, "async close session");
ngx_rtmp_fire_event(s, NGX_RTMP_DISCONNECT, NULL, NULL);
// close connection before session, connection log use session log
// may cause memory error
ngx_rtmp_close_connection(c);
ngx_rtmp_close_session(s);
}
static void
ngx_rtmp_async_finalize_http_client(ngx_event_t *ev)
{
ngx_rtmp_session_t *s;
ngx_http_request_t *hcr;
s = ev->data;
hcr = s->request;
ngx_log_error(NGX_LOG_INFO, s->log, 0, "asyn finalize http client");
if (hcr) {
ngx_http_client_finalize_request(hcr, 1);
}
}
static void
ngx_rtmp_async_finalize_http_request(ngx_event_t *ev)
{
ngx_rtmp_session_t *s;
ngx_http_request_t *r;
s = ev->data;
r = s->request;
ngx_log_error(NGX_LOG_INFO, s->log, 0, "asyn finalize http request");
if (r->header_sent) {
ngx_http_finalize_request(r, NGX_HTTP_CLIENT_CLOSED_REQUEST);
ngx_http_run_posted_requests(r->connection);
} else {
r->error_page = 1;
if (s->status) {
ngx_http_finalize_request(r, s->status);
} else {
ngx_http_finalize_request(r, NGX_HTTP_SERVICE_UNAVAILABLE);
}
}
}
void
ngx_rtmp_finalize_session(ngx_rtmp_session_t *s)
{
ngx_event_t *e;
ngx_connection_t *c;
ngx_log_error(NGX_LOG_INFO, s->log, 0, "finalize session");
if (s->live_type == NGX_HLS_LIVE) {
ngx_rtmp_finalize_fake_session(s);
return;
}
c = s->connection;
if (c && c->destroyed) {
return;
}
if (s->live_type != NGX_RTMP_LIVE) {
e = &s->close;
e->data = s;
if (s->relay) {
e->handler = ngx_rtmp_async_finalize_http_client;
} else {
e->handler = ngx_rtmp_async_finalize_http_request;
}
e->log = s->log;
ngx_post_event(e, &ngx_posted_events);
return;
}
e = &s->close;
e->data = s;
e->handler = ngx_rtmp_close_session_handler;
e->log = s->log;
ngx_post_event(e, &ngx_posted_events);
}
// session combined with http request or http client request
void
ngx_rtmp_finalize_fake_session(ngx_rtmp_session_t *s)
{
ngx_log_error(NGX_LOG_INFO, s->log, 0, "finalize fake session");
ngx_rtmp_fire_event(s, NGX_RTMP_DISCONNECT, NULL, NULL);
ngx_rtmp_close_session(s);
}
void
ngx_rtmp_set_combined_log(ngx_rtmp_session_t *s, void *d, ngx_log_handler_pt h)
{
ngx_rtmp_error_log_ctx_t *ctx;
ctx = s->log->data;
ctx->data = d;
ctx->handler = h;
}
// Only rtmp need to init session
void
ngx_rtmp_init_session(ngx_rtmp_session_t *s, ngx_connection_t *c)
{
ngx_rtmp_error_log_ctx_t *ctx;
ngx_rtmp_core_main_conf_t *cmcf;
cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);
c->data = s;
s->connection = c;
c->log = s->log;
c->write->log = s->log;
c->read->log = s->log;
ctx = s->log->data;
ctx->client = &c->addr_text;
s->log->connection = c->number;
c->log_error = NGX_ERROR_INFO;
ngx_rtmp_set_chunk_size(s, NGX_RTMP_DEFAULT_CHUNK_SIZE);
if (s->variables == NULL) {
s->variables = ngx_pcalloc(s->pool, cmcf->variables.nelts
* sizeof(ngx_http_variable_value_t));
}
if (s->variables == NULL) {
ngx_rtmp_finalize_session(s);
return;
}
return;
}
ngx_rtmp_session_t *
ngx_rtmp_create_relay_session(ngx_rtmp_session_t *s, void *tag)
{
ngx_rtmp_session_t *rs;
ngx_live_relay_ctx_t *rctx;
ngx_rtmp_core_main_conf_t *cmcf;
rs = ngx_rtmp_create_session(s->addr_conf);
if (rs == NULL) {
return NULL;
}
rs->relay = 1;
rs->main_conf = s->main_conf;
rs->srv_conf = s->srv_conf;
rs->app_conf = s->app_conf;
// clone para from origin session
#define NGX_RTMP_RELAY_SESSION_COPY_PARA(para) \
if (ngx_copy_str(rs->pool, &rs->para, &s->para) != NGX_OK) { \
goto destroy; \
}
NGX_RTMP_RELAY_SESSION_COPY_PARA(stream);
NGX_RTMP_RELAY_SESSION_COPY_PARA(name);
NGX_RTMP_RELAY_SESSION_COPY_PARA(pargs);
NGX_RTMP_RELAY_SESSION_COPY_PARA(app);
NGX_RTMP_RELAY_SESSION_COPY_PARA(args);
NGX_RTMP_RELAY_SESSION_COPY_PARA(flashver);
NGX_RTMP_RELAY_SESSION_COPY_PARA(swf_url);
NGX_RTMP_RELAY_SESSION_COPY_PARA(tc_url);
NGX_RTMP_RELAY_SESSION_COPY_PARA(page_url);
NGX_RTMP_RELAY_SESSION_COPY_PARA(serverid);
#undef NGX_RTMP_RELAY_SESSION_COPY_PARA
rs->acodecs = s->acodecs;
rs->vcodecs = s->vcodecs;
ngx_rtmp_cmd_middleware_init(rs);
// create relay ctx
rctx = ngx_pcalloc(rs->pool, sizeof(ngx_live_relay_ctx_t));
if (rctx == NULL) {
goto destroy;
}
rctx->domain = rs->domain;
rctx->app = rs->app;
rctx->args = rs->args;
rctx->name = rs->name;
rctx->pargs = rs->pargs;
rctx->referer = rs->page_url;
rctx->user_agent = rs->flashver;
rctx->swf_url = rs->swf_url;
rctx->acodecs = rs->acodecs;
rctx->vcodecs = rs->vcodecs;
rctx->tag = tag;
ngx_rtmp_set_ctx(rs, rctx, ngx_live_relay_module);
cmcf = ngx_rtmp_get_module_main_conf(rs, ngx_rtmp_core_module);
if (rs->variables == NULL) {
rs->variables = ngx_pcalloc(rs->pool, cmcf->variables.nelts
* sizeof(ngx_http_variable_value_t));
}
return rs;
destroy:
ngx_rtmp_close_session(rs);
return NULL;
}
ngx_rtmp_session_t *
ngx_rtmp_create_static_session(ngx_live_relay_t *relay,
ngx_rtmp_addr_conf_t *addr_conf, void *tag)
{
ngx_rtmp_session_t *rs;
ngx_live_relay_ctx_t *rctx;
ngx_uint_t n;
ngx_rtmp_core_srv_conf_t *cscf;
ngx_rtmp_core_app_conf_t **cacfp;
rs = ngx_rtmp_create_session(addr_conf);
if (rs == NULL) {
return NULL;
}
rs->static_pull = 1;
rs->relay = 1;
// clone para from origin session
#define NGX_RTMP_RELAY_SESSION_COPY_PARA(to, from) \
if (ngx_copy_str(rs->pool, &rs->to, &relay->from) != NGX_OK) { \
goto destroy; \
}
NGX_RTMP_RELAY_SESSION_COPY_PARA(app, app);
NGX_RTMP_RELAY_SESSION_COPY_PARA(name, name);
NGX_RTMP_RELAY_SESSION_COPY_PARA(pargs, pargs);
NGX_RTMP_RELAY_SESSION_COPY_PARA(page_url, referer);
NGX_RTMP_RELAY_SESSION_COPY_PARA(flashver, user_agent);
NGX_RTMP_RELAY_SESSION_COPY_PARA(stream, stream);
#undef NGX_RTMP_RELAY_SESSION_COPY_PARA
rs->tc_url.len = sizeof("rtmp://") - 1 + relay->domain.len
+ sizeof("/") - 1 + relay->app.len;
rs->tc_url.data = ngx_pcalloc(rs->pool, rs->tc_url.len);
if (rs->tc_url.data == NULL) {
goto destroy;
}
ngx_snprintf(rs->tc_url.data, rs->tc_url.len, "rtmp://%V/%V",
&relay->domain, &relay->app);
ngx_rtmp_cmd_middleware_init(rs);
if (ngx_rtmp_set_virtual_server(rs, &rs->domain)) {
goto destroy;
}
cscf = ngx_rtmp_get_module_srv_conf(rs, ngx_rtmp_core_module);
rs->live_server = ngx_live_create_server(&rs->serverid);
cacfp = cscf->applications.elts;
for (n = 0; n < cscf->applications.nelts; ++n, ++cacfp) {
if ((*cacfp)->name.len == rs->app.len &&
ngx_strncmp((*cacfp)->name.data, rs->app.data, rs->app.len) == 0)
{
/* found app! */
rs->app_conf = (*cacfp)->app_conf;
break;
}
}
if (rs->app_conf == NULL) {
if (cscf->default_app == NULL || cscf->default_app->app_conf == NULL) {
ngx_log_error(NGX_LOG_ERR, rs->log, 0,
"static session: application not found: '%V'", &rs->app);
goto destroy;
}
rs->app_conf = cscf->default_app->app_conf;
}
// create relay ctx
rctx = ngx_pcalloc(rs->pool, sizeof(ngx_live_relay_ctx_t));
if (rctx == NULL) {
goto destroy;
}
rctx->domain = rs->domain;
rctx->app = rs->app;
rctx->args = rs->args;
rctx->name = rs->name;
rctx->pargs = rs->pargs;
rctx->referer = rs->page_url;
rctx->user_agent = rs->flashver;
rctx->swf_url = rs->swf_url;
rctx->acodecs = rs->acodecs;
rctx->vcodecs = rs->vcodecs;
rctx->tag = tag;
ngx_rtmp_set_ctx(rs, rctx, ngx_live_relay_module);
return rs;
destroy:
ngx_rtmp_close_session(rs);
return NULL;
}
// if return NULL, memory must be error
ngx_rtmp_session_t *
ngx_rtmp_create_session(ngx_rtmp_addr_conf_t *addr_conf)
{
ngx_rtmp_session_t *s;
ngx_rtmp_core_srv_conf_t *cscf;
ngx_pool_t *pool;
ngx_rtmp_error_log_ctx_t *ctx;
pool = NGX_CREATE_POOL(4096, ngx_cycle->log);
if (pool == NULL) {
return NULL;
}
s = ngx_pcalloc(pool, sizeof(ngx_rtmp_session_t) +
sizeof(ngx_rtmp_frame_t *) * ((ngx_rtmp_core_srv_conf_t *)
addr_conf->default_server->ctx-> srv_conf[ngx_rtmp_core_module
.ctx_index])->out_queue);
if (s == NULL) {
goto destroy;
}
s->pool = pool;
s->addr_conf = addr_conf;
s->main_conf = addr_conf->default_server->ctx->main_conf;
s->srv_conf = addr_conf->default_server->ctx->srv_conf;
s->addr_text = ngx_pcalloc(s->pool, sizeof(ngx_str_t));
s->addr_text->data = ngx_pcalloc(s->pool, addr_conf->addr_text.len);
s->addr_text->len = addr_conf->addr_text.len;
ngx_memcpy(s->addr_text->data,
addr_conf->addr_text.data, addr_conf->addr_text.len);
s->ctx = ngx_pcalloc(pool, sizeof(void *) * ngx_rtmp_max_module);
if (s->ctx == NULL) {
goto destroy;
}
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
s->out_queue = cscf->out_queue;
s->out_cork = cscf->out_cork;
s->in_streams = ngx_pcalloc(pool, sizeof(ngx_rtmp_stream_t)
* cscf->max_streams);
if (s->in_streams == NULL) {
goto destroy;
}
#if (nginx_version >= 1007005)
ngx_queue_init(&s->posted_dry_events);
#endif
s->epoch = ngx_current_msec;
s->timeout = cscf->timeout;
s->buflen = cscf->buflen;
// init log
ctx = ngx_pcalloc(pool, sizeof(ngx_rtmp_error_log_ctx_t));
if (ctx == NULL) {
goto destroy;
}
ctx->session = s;
s->log = ngx_pcalloc(pool, sizeof(ngx_log_t));
if (s->pool == NULL) {
goto destroy;
}
*s->log = ngx_cycle->new_log;
s->log->handler = ngx_rtmp_log_error;
s->log->data = ctx;
s->stage = NGX_LIVE_INIT;
s->init_time = ngx_current_msec;
s->mpegts_out = ngx_pcalloc(s->pool, sizeof(ngx_mpegts_frame_t *) *
((ngx_rtmp_core_srv_conf_t *) addr_conf->default_server->ctx->
srv_conf[ngx_rtmp_core_module.ctx_index])->out_queue);
return s;
destroy:
if (pool) {
NGX_DESTROY_POOL(pool);
}
return NULL;
}

View File

@ -0,0 +1,205 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
typedef struct {
ngx_int_t max_conn;
ngx_shm_zone_t *shm_zone;
} ngx_rtmp_limit_main_conf_t;
static ngx_str_t shm_name = ngx_string("rtmp_limit");
static ngx_int_t ngx_rtmp_limit_postconfiguration(ngx_conf_t *cf);
static void *ngx_rtmp_limit_create_main_conf(ngx_conf_t *cf);
static ngx_command_t ngx_rtmp_limit_commands[] = {
{ ngx_string("max_connections"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_RTMP_MAIN_CONF_OFFSET,
offsetof(ngx_rtmp_limit_main_conf_t, max_conn),
NULL },
ngx_null_command
};
static ngx_rtmp_module_t ngx_rtmp_limit_module_ctx = {
NULL, /* preconfiguration */
ngx_rtmp_limit_postconfiguration, /* postconfiguration */
ngx_rtmp_limit_create_main_conf, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create app configuration */
NULL /* merge app configuration */
};
ngx_module_t ngx_rtmp_limit_module = {
NGX_MODULE_V1,
&ngx_rtmp_limit_module_ctx, /* module context */
ngx_rtmp_limit_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_limit_create_main_conf(ngx_conf_t *cf)
{
ngx_rtmp_limit_main_conf_t *lmcf;
lmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_limit_main_conf_t));
if (lmcf == NULL) {
return NULL;
}
lmcf->max_conn = NGX_CONF_UNSET;
return lmcf;
}
static ngx_int_t
ngx_rtmp_limit_connect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
ngx_rtmp_limit_main_conf_t *lmcf;
ngx_slab_pool_t *shpool;
ngx_shm_zone_t *shm_zone;
uint32_t *nconn, n;
ngx_int_t rc;
lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module);
if (lmcf->max_conn == NGX_CONF_UNSET) {
return NGX_OK;
}
shm_zone = lmcf->shm_zone;
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
nconn = shm_zone->data;
ngx_shmtx_lock(&shpool->mutex);
n = ++*nconn;
ngx_shmtx_unlock(&shpool->mutex);
rc = n > (ngx_uint_t) lmcf->max_conn ? NGX_ERROR : NGX_OK;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
"limit: inc conection counter: %uD", n);
if (rc != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"limit: too many connections: %uD > %i",
n, lmcf->max_conn);
}
return rc;
}
static ngx_int_t
ngx_rtmp_limit_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
ngx_rtmp_limit_main_conf_t *lmcf;
ngx_slab_pool_t *shpool;
ngx_shm_zone_t *shm_zone;
uint32_t *nconn, n;
lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module);
if (lmcf->max_conn == NGX_CONF_UNSET) {
return NGX_OK;
}
shm_zone = lmcf->shm_zone;
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
nconn = shm_zone->data;
ngx_shmtx_lock(&shpool->mutex);
n = --*nconn;
ngx_shmtx_unlock(&shpool->mutex);
(void) n;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
"limit: dec conection counter: %uD", n);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_limit_shm_init(ngx_shm_zone_t *shm_zone, void *data)
{
ngx_slab_pool_t *shpool;
uint32_t *nconn;
if (data) {
shm_zone->data = data;
return NGX_OK;
}
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
nconn = ngx_slab_alloc(shpool, 4);
if (nconn == NULL) {
return NGX_ERROR;
}
*nconn = 0;
shm_zone->data = nconn;
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_limit_postconfiguration(ngx_conf_t *cf)
{
ngx_rtmp_core_main_conf_t *cmcf;
ngx_rtmp_limit_main_conf_t *lmcf;
ngx_rtmp_handler_pt *h;
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
h = ngx_array_push(&cmcf->events[NGX_RTMP_CONNECT]);
*h = ngx_rtmp_limit_connect;
h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]);
*h = ngx_rtmp_limit_disconnect;
lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_limit_module);
if (lmcf->max_conn == NGX_CONF_UNSET) {
return NGX_OK;
}
lmcf->shm_zone = ngx_shared_memory_add(cf, &shm_name, ngx_pagesize * 2,
&ngx_rtmp_limit_module);
if (lmcf->shm_zone == NULL) {
return NGX_ERROR;
}
lmcf->shm_zone->init = ngx_rtmp_limit_shm_init;
return NGX_OK;
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#ifndef _NGX_RTMP_LIVE_H_INCLUDED_
#define _NGX_RTMP_LIVE_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp.h"
#include "ngx_rtmp_cmd_module.h"
#include "ngx_rtmp_bandwidth.h"
#include "ngx_rtmp_streams.h"
#include "ngx_map.h"
typedef struct {
unsigned active:1;
uint32_t timestamp;
uint32_t last_timestamp;
uint32_t csid;
uint32_t dropped;
} ngx_rtmp_live_chunk_stream_t;
struct ngx_rtmp_live_ctx_s {
ngx_map_node_t node;
ngx_int_t pubpri;
ngx_rtmp_session_t *session;
ngx_live_stream_t *stream;
ngx_rtmp_live_ctx_t *next;
ngx_uint_t ndropped;
ngx_rtmp_live_chunk_stream_t cs[2];
ngx_uint_t meta_version;
ngx_event_t idle_evt;
unsigned active:1;
unsigned publishing:1;
unsigned silent:1;
unsigned paused:1;
};
typedef struct {
ngx_flag_t live;
ngx_flag_t meta;
ngx_msec_t sync;
ngx_msec_t idle_timeout;
ngx_flag_t atc;
ngx_flag_t interleave;
ngx_flag_t wait_key;
ngx_flag_t wait_video;
ngx_flag_t publish_notify;
ngx_flag_t play_restart;
ngx_flag_t idle_streams;
ngx_flag_t fix_timestamp;
ngx_msec_t buflen;
} ngx_rtmp_live_app_conf_t;
extern ngx_module_t ngx_rtmp_live_module;
#endif /* _NGX_RTMP_LIVE_H_INCLUDED_ */

View File

@ -0,0 +1,458 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#include "ngx_rtmp_monitor_module.h"
#include "ngx_rtmp_cmd_module.h"
#include "ngx_rtmp_codec_module.h"
static ngx_rtmp_close_stream_pt next_close_stream;
static void *ngx_rtmp_monitor_create_app_conf(ngx_conf_t *cf);
static char *ngx_rtmp_monitor_merge_app_conf(ngx_conf_t *cf,
void *parent, void *child);
static ngx_int_t ngx_rtmp_monitor_postconfiguration(ngx_conf_t *cf);
static char *ngx_rtmp_monitor_dump(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_rtmp_monitor_buffered_log(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
#define NGX_RTMP_MONITOR_BUFFER_SIZE 61
typedef struct {
ngx_str_t dump_path;
ngx_flag_t monitor;
ngx_log_t *buffered_log;
} ngx_rtmp_monitor_app_conf_t;
typedef struct {
ngx_event_t consume;
ngx_msec_t buffered;
ngx_uint_t nbuffered;
double frame_rate;
double nframes;
double buffers[NGX_RTMP_MONITOR_BUFFER_SIZE];
ngx_uint_t fps[NGX_RTMP_MONITOR_BUFFER_SIZE];
ngx_uint_t curr;
ngx_flag_t dump;
unsigned publishing:1;
} ngx_rtmp_monitor_ctx_t;
static ngx_command_t ngx_rtmp_monitor_commands[] = {
{ ngx_string("dump"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_rtmp_monitor_dump,
NGX_RTMP_APP_CONF_OFFSET,
0,
NULL },
{ ngx_string("buffered_log"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE,
ngx_rtmp_monitor_buffered_log,
NGX_RTMP_APP_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
static ngx_rtmp_module_t ngx_rtmp_monitor_module_ctx = {
NULL, /* preconfiguration */
ngx_rtmp_monitor_postconfiguration, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_rtmp_monitor_create_app_conf, /* create app configuration */
ngx_rtmp_monitor_merge_app_conf /* merge app configuration */
};
ngx_module_t ngx_rtmp_monitor_module = {
NGX_MODULE_V1,
&ngx_rtmp_monitor_module_ctx, /* module context */
ngx_rtmp_monitor_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_monitor_dump_frame(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
}
static void
ngx_rtmp_monitor_consume(ngx_event_t *ev)
{
ngx_rtmp_session_t *s, *ps;
ngx_rtmp_monitor_ctx_t *ctx;
ngx_rtmp_codec_ctx_t *cctx;
ngx_rtmp_monitor_app_conf_t *macf;
u_char peer[NGX_SOCKADDR_STRLEN];
u_char local[NGX_SOCKADDR_STRLEN];
struct sockaddr paddr, laddr;
socklen_t plen, llen;
s = ev->data;
macf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_monitor_module);
if (macf->monitor == 0) {
return;
}
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_monitor_module);
if (ctx == NULL) {
return;
}
if (ctx->publishing) {
ps = s;
} else {
if (s->live_stream->publish_ctx) {
ps = s->live_stream->publish_ctx->session;
} else {
goto next;
}
}
cctx = ngx_rtmp_get_module_ctx(ps, ngx_rtmp_codec_module);
if (cctx == NULL) {
goto next;
}
if (cctx->frame_rate != 0) {
ctx->frame_rate = cctx->frame_rate;
}
next:
if (ctx->frame_rate == 0) {
ngx_log_error(NGX_LOG_ERR, s->log, 0,
"monitor, frame rate error, stream: %V, frame rate: %.2f",
&s->stream, ctx->frame_rate);
return;
}
ctx->nframes -= ctx->frame_rate;
if (ctx->nframes <= 0) {
ctx->nframes = 0;
if (ctx->buffered == 0) {
++ctx->nbuffered;
}
++ctx->buffered;
ngx_memzero(local, sizeof(local));
ngx_memzero(peer, sizeof(peer));
plen = sizeof(paddr);
llen = sizeof(laddr);
if (s->connection) {
getpeername(s->connection->fd, &paddr, &plen);
getsockname(s->connection->fd, &laddr, &llen);
ngx_sock_ntop(&paddr, plen, peer, NGX_SOCKADDR_STRLEN, 1);
ngx_sock_ntop(&laddr, llen, local, NGX_SOCKADDR_STRLEN, 1);
ngx_log_error(NGX_LOG_ERR, macf->buffered_log, 0,
"%p %s, peer: %s, local: %s, "
"stream: %V, buffered: %ui, time: %uis",
s, ctx->publishing ? "publisher" : "player", peer, local,
&s->stream, ctx->nbuffered, ctx->buffered);
}
} else {
ctx->buffered = 0;
}
ctx->buffers[ctx->curr] = ctx->nframes;
ctx->curr = (ctx->curr + 1) % NGX_RTMP_MONITOR_BUFFER_SIZE;
ctx->fps[ctx->curr] = 0;
ngx_add_timer(&ctx->consume, 1000);
}
static ngx_int_t
ngx_rtmp_monitor_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
ngx_rtmp_monitor_frame(s, h, in, ngx_rtmp_is_codec_header(in), 1);
// if (h->type == NGX_RTMP_MSG_VIDEO) {
// ngx_log_error(NGX_LOG_INFO, s->log, 0,
// "!!!!!!video(%d): %02xD %02xD %02xD %02xD %02xD",
// h->mlen, in->buf->pos[0], in->buf->pos[1], in->buf->pos[2],
// in->buf->pos[3], in->buf->pos[4], in->buf->pos[5]);
// }
//
// if (h->type == NGX_RTMP_MSG_AUDIO) {
// ngx_log_error(NGX_LOG_INFO, s->log, 0,
// "!!!!!!audio(%d): %02xD %02xD %02xD %02xD %02xD",
// h->mlen, in->buf->pos[0], in->buf->pos[1], in->buf->pos[2],
// in->buf->pos[3], in->buf->pos[4], in->buf->pos[5]);
// }
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_monitor_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
if (s->first_metadata == 0) {
s->stage = NGX_LIVE_AV;
s->first_metadata = ngx_current_msec;
s->first_data = s->first_data == 0? ngx_current_msec: s->first_data;
}
ngx_log_error(NGX_LOG_INFO, s->log, 0, "receive metadata");
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_monitor_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
{
ngx_rtmp_monitor_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_monitor_module);
if (ctx == NULL) {
goto next;
}
if (ctx->consume.timer_set) {
ngx_del_timer(&ctx->consume);
}
if (ctx->consume.posted) {
ngx_delete_posted_event(&ctx->consume);
}
next:
return next_close_stream(s, v);
}
void
ngx_rtmp_monitor_frame(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in, ngx_flag_t is_header, ngx_flag_t publishing)
{
ngx_rtmp_monitor_app_conf_t *macf;
ngx_rtmp_monitor_ctx_t *ctx;
if (s->first_metadata == 0 && (h->type == NGX_RTMP_MSG_AMF_META
|| h->type == NGX_RTMP_MSG_AMF3_META))
{
s->stage = NGX_LIVE_AV;
s->first_metadata = ngx_current_msec;
s->first_data = s->first_data == 0? ngx_current_msec: s->first_data;
}
if (s->first_audio == 0 && h->type == NGX_RTMP_MSG_AUDIO) {
s->stage = NGX_LIVE_AV;
s->first_audio = ngx_current_msec;
s->first_data = s->first_data == 0? ngx_current_msec: s->first_data;
}
if (s->first_video == 0 && h->type == NGX_RTMP_MSG_VIDEO) {
s->stage = NGX_LIVE_AV;
s->first_video = ngx_current_msec;
s->first_data = s->first_data == 0? ngx_current_msec: s->first_data;
}
if (h->type == NGX_RTMP_MSG_AUDIO && is_header) {
if (s->publishing) {
ngx_log_error(NGX_LOG_INFO, s->log, 0, "receive audio header");
} else {
ngx_log_error(NGX_LOG_INFO, s->log, 0, "send audio header");
}
}
if (h->type == NGX_RTMP_MSG_VIDEO && is_header) {
if (s->publishing) {
ngx_log_error(NGX_LOG_INFO, s->log, 0, "receive video header");
} else {
ngx_log_error(NGX_LOG_INFO, s->log, 0, "send video header");
}
}
if (h->type != NGX_RTMP_MSG_VIDEO) {
return;
}
macf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_monitor_module);
if (macf->monitor == 0) {
return;
}
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_monitor_module);
if (ctx == NULL) {
ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_monitor_ctx_t));
if (ctx == NULL) {
return;
}
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_monitor_module);
ctx->dump = macf->dump_path.len > 0;
ctx->publishing = publishing;
ctx->consume.data = s;
ctx->consume.log = s->log;
ctx->consume.handler = ngx_rtmp_monitor_consume;
ngx_add_timer(&ctx->consume, 1000);
}
if (publishing && ctx->dump) {
ngx_rtmp_monitor_dump_frame(s, h, in);
}
if (is_header) {
return;
}
++ctx->nframes;
++ctx->fps[ctx->curr];
}
static void *
ngx_rtmp_monitor_create_app_conf(ngx_conf_t *cf)
{
ngx_rtmp_monitor_app_conf_t *macf;
macf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_monitor_app_conf_t));
if (macf == NULL) {
return NULL;
}
macf->monitor = NGX_CONF_UNSET;
return macf;
}
static char *
ngx_rtmp_monitor_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_rtmp_monitor_app_conf_t *prev = parent;
ngx_rtmp_monitor_app_conf_t *conf = child;
if (conf->dump_path.data == NULL) {
conf->dump_path = prev->dump_path;
}
ngx_conf_merge_value(conf->monitor, prev->monitor, 0);
if (conf->buffered_log == NULL) {
if (prev->buffered_log) {
conf->buffered_log = prev->buffered_log;
} else {
conf->buffered_log = &cf->cycle->new_log;
}
}
return NGX_CONF_OK;
}
static char *
ngx_rtmp_monitor_dump(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_rtmp_monitor_app_conf_t *macf;
ngx_str_t *value;
macf = conf;
if (macf->dump_path.len) {
return "is duplicate";
}
value = cf->args->elts;
macf->dump_path = value[1];
if (macf->dump_path.len > 0 &&
macf->dump_path.data[macf->dump_path.len - 1] == '/')
{
--macf->dump_path.len;
}
if (ngx_conf_full_name(cf->cycle, &macf->dump_path, 0) != NGX_OK) {
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
static char *
ngx_rtmp_monitor_buffered_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_rtmp_monitor_app_conf_t *macf;
macf = conf;
macf->monitor = 1;
return ngx_log_set_log(cf, &macf->buffered_log);
}
static ngx_int_t
ngx_rtmp_monitor_postconfiguration(ngx_conf_t *cf)
{
ngx_rtmp_core_main_conf_t *cmcf;
ngx_rtmp_handler_pt *h;
ngx_rtmp_amf_handler_t *ch;
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);
*h = ngx_rtmp_monitor_av;
h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);
*h = ngx_rtmp_monitor_av;
/* register metadata handler */
ch = ngx_array_push(&cmcf->amf);
if (ch == NULL) {
return NGX_ERROR;
}
ngx_str_set(&ch->name, "@setDataFrame");
ch->handler = ngx_rtmp_monitor_meta_data;
ch = ngx_array_push(&cmcf->amf);
if (ch == NULL) {
return NGX_ERROR;
}
ngx_str_set(&ch->name, "onMetaData");
ch->handler = ngx_rtmp_monitor_meta_data;
next_close_stream = ngx_rtmp_close_stream;
ngx_rtmp_close_stream = ngx_rtmp_monitor_close_stream;
return NGX_OK;
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*/
#ifndef _NGX_RTMP_MONITOR_H_INCLUDED_
#define _NGX_RTMP_MONITOR_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include "ngx_rtmp.h"
void ngx_rtmp_monitor_frame(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in, ngx_flag_t is_header, ngx_flag_t publishing);
#endif

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
*
*/
#ifndef _NGX_RTMP_OCLP_MODULE_H_INCLUDED_
#define _NGX_RTMP_OCLP_MODULE_H_INCLUDED_
#include "ngx_rtmp.h"
#include "ngx_netcall.h"
void ngx_rtmp_notify_stream_start(ngx_rtmp_session_t *s);
void ngx_rtmp_notify_stream_done(ngx_rtmp_session_t *s);
#endif

View File

@ -0,0 +1,197 @@
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <nginx.h>
#include "ngx_rtmp_proxy_protocol.h"
static void ngx_rtmp_proxy_protocol_recv(ngx_event_t *rev);
void
ngx_rtmp_proxy_protocol(ngx_rtmp_session_t *s)
{
ngx_event_t *rev;
ngx_connection_t *c;
c = s->connection;
rev = c->read;
rev->handler = ngx_rtmp_proxy_protocol_recv;
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0,
"proxy_protocol: start");
if (rev->ready) {
/* the deferred accept(), rtsig, aio, iocp */
if (ngx_use_accept_mutex) {
ngx_post_event(rev, &ngx_posted_events);
return;
}
rev->handler(rev);
return;
}
ngx_add_timer(rev, s->timeout);
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_rtmp_finalize_session(s);
return;
}
}
static void
ngx_rtmp_proxy_protocol_recv(ngx_event_t *rev)
{
u_char buf[107], *p, *pp, *text;
size_t len;
ssize_t n;
ngx_err_t err;
ngx_int_t i;
ngx_addr_t addr;
ngx_connection_t *c;
ngx_rtmp_session_t *s;
c = rev->data;
s = c->data;
if (c->destroyed) {
return;
}
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
"proxy_protocol: recv: client timed out");
c->timedout = 1;
ngx_rtmp_finalize_session(s);
return;
}
if (rev->timer_set) {
ngx_del_timer(rev);
}
n = recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK);
err = ngx_socket_errno;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0, "recv(): %d", n);
if (n == -1) {
if (err == NGX_EAGAIN) {
ngx_add_timer(rev, s->timeout);
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
ngx_rtmp_finalize_session(s);
}
return;
}
ngx_rtmp_finalize_session(s);
return;
}
p = buf;
if (n <= 8 && ngx_strncmp(p, "PROXY ", 6) != 0) {
goto bad_header;
}
n -= 6;
p += 6;
ngx_memzero(&addr, sizeof(ngx_addr_t));
if (n >= 7 && ngx_strncmp(p, "UNKNOWN", 7) == 0) {
n -= 7;
p += 7;
goto skip;
}
if (n < 5 || ngx_strncmp(p, "TCP", 3) != 0
|| (p[3] != '4' && p[3] != '6') || p[4] != ' ')
{
goto bad_header;
}
n -= 5;
p += 5;
pp = ngx_strlchr(p, p + n, ' ');
if (pp == NULL) {
goto bad_header;
}
if (ngx_parse_addr(s->pool, &addr, p, pp - p) != NGX_OK) {
goto bad_header;
}
n -= pp - p;
p = pp;
skip:
for (i = 0; i + 1 < n; i++) {
if (p[i] == CR && p[i + 1] == LF) {
break;
}
}
if (i + 1 >= n) {
goto bad_header;
}
n = p - buf + i + 2;
if (c->recv(c, buf, n) != n) {
goto failed;
}
if (addr.socklen) {
text = ngx_palloc(s->pool, NGX_SOCKADDR_STRLEN);
if (text == NULL) {
goto failed;
}
len = ngx_sock_ntop(addr.sockaddr,
#if (nginx_version >= 1005003)
addr.socklen,
#endif
text, NGX_SOCKADDR_STRLEN, 0);
if (len == 0) {
goto failed;
}
c->sockaddr = addr.sockaddr;
c->socklen = addr.socklen;
c->addr_text.data = text;
c->addr_text.len = len;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,
"proxy_protocol: remote_addr:'%V'", &c->addr_text);
}
ngx_rtmp_handshake(s);
return;
bad_header:
ngx_log_error(NGX_LOG_INFO, c->log, 0, "proxy_protocol: bad header");
failed:
ngx_rtmp_finalize_session(s);
}

Some files were not shown because too many files have changed in this diff Show More