luci-app-nginx-pingos: add package
This commit is contained in:
parent
9b74564a2a
commit
76a09141eb
98
package/lienol/luci-app-nginx-pingos/Makefile
Normal file
98
package/lienol/luci-app-nginx-pingos/Makefile
Normal 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)))
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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>
|
||||
@ -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.
|
||||
|
||||
@ -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
@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
@ -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 \
|
||||
"
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
@ -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"
|
||||
@ -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
|
||||
@ -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"
|
||||
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -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);
|
||||
}
|
||||
@ -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];
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -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 \
|
||||
"
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -0,0 +1,9 @@
|
||||
Project author:
|
||||
|
||||
Roman Arutyunyan
|
||||
Moscow, Russia, Pingo
|
||||
|
||||
Contacts:
|
||||
arut@qip.ru
|
||||
arutyunyan.roman@gmail.com
|
||||
cczjp89@gmail.com
|
||||
@ -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.
|
||||
@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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_ */
|
||||
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
@ -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_ */
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
@ -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;
|
||||
}
|
||||
@ -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
@ -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
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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_ */
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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_ */
|
||||
@ -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;
|
||||
}
|
||||
@ -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_ */
|
||||
File diff suppressed because it is too large
Load Diff
@ -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_ */
|
||||
File diff suppressed because it is too large
Load Diff
@ -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_ */
|
||||
@ -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(§ion);
|
||||
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'",
|
||||
§ion, &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);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
}
|
||||
@ -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_ */
|
||||
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
@ -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*)×tamp;
|
||||
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*)×tamp;
|
||||
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 *)×tamp;
|
||||
*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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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_ */
|
||||
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
@ -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
Loading…
Reference in New Issue
Block a user