Upload Modules
This commit is contained in:
46
ngx_http_flv_module/.github/CODE_OF_CONDUCT.md
vendored
Normal file
46
ngx_http_flv_module/.github/CODE_OF_CONDUCT.md
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at winshining@163.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
41
ngx_http_flv_module/.github/CONTRIBUTING.md
vendored
Normal file
41
ngx_http_flv_module/.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
## Guidelines to contribute
|
||||
|
||||
#### **When you find a bug**
|
||||
|
||||
* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/winshining/nginx-http-flv-module/issues).
|
||||
|
||||
* If there is no issue addressing the problem, [open a new one](https://github.com/winshining/nginx-http-flv-module/issues/new). Be sure to include a **title prefixed by '[bug]' and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
|
||||
|
||||
#### **Write a patch that fixes a bug**
|
||||
|
||||
* Open a new GitHub pull request with the patch.
|
||||
|
||||
* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
|
||||
|
||||
* Before submitting, be sure the commit description is prefixed by:
|
||||
* **[add]** if new features were added.
|
||||
* **[dev]** if codes were changed.
|
||||
* **[fix]** if bugs were fixed.
|
||||
* **[misc]** if some changes were done and bugs were fixed.
|
||||
|
||||
* Ensure that your codes conform to code conventions:
|
||||
* All files are prefixed by 'ngx\_'.
|
||||
* Include #ifndef \_FILE\_NAME\_H\_INCLUDED\_, #define \_FILE\_NAME\_H\_INCLUDED\_ and #endif in header files.
|
||||
* Comments use /* ... */ are preferable.
|
||||
* It would be better that built-in types appear before customized types.
|
||||
* There should be no less than 2 spaces between types and variables.
|
||||
* Variables are aligned by character, not '\*'.
|
||||
* No more than 80 characters in a single code or comment line.
|
||||
* Two blank lines between two functions, styles of macro and type definitions are same as functions.
|
||||
|
||||
#### **Add a new feature or change an existing one**
|
||||
|
||||
* Open an issue on GitHub prefixed by '[feature]' until you have collected positive feedback about the change.
|
||||
|
||||
#### **Questions about the source code**
|
||||
|
||||
* Open an issue on GitHub prefixed by '[misc]', describe as clear as possible.
|
||||
|
||||
Thanks!
|
||||
|
||||
Winshining
|
||||
1
ngx_http_flv_module/.github/FUNDING.yml
vendored
Normal file
1
ngx_http_flv_module/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
custom: ['https://www.paypal.me/ShingWong']
|
||||
14
ngx_http_flv_module/.github/ISSUE_TEMPLATE.md
vendored
Normal file
14
ngx_http_flv_module/.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
When you meet a bug, please open the issue including a title prefixed by '[bug]' and describe it as follows:
|
||||
(当你碰到一个 bug,请在提出问题时以 '[bug]' 为前缀写明标题,并且像下面的内容一样描述它):
|
||||
|
||||
### Expected behavior (期望行为)
|
||||
|
||||
### Actual behavior (实际行为)
|
||||
|
||||
### OS and Nginx version (操作系统和 Nginx 版本号)
|
||||
|
||||
### Configuration file (配置文件)
|
||||
|
||||
### Steps to reproduce the behavior (复现问题步骤)
|
||||
|
||||
### Error log if any (错误日志)
|
||||
42
ngx_http_flv_module/.github/workflows/nginx-http-flv-module.yml
vendored
Normal file
42
ngx_http_flv_module/.github/workflows/nginx-http-flv-module.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: nginx-http-flv-module CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
NGINX_VERSION: nginx-1.28.0
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: download nginx
|
||||
working-directory: ../
|
||||
run: wget https://nginx.org/download/${{env.NGINX_VERSION}}.tar.gz
|
||||
- name: uncompress nginx
|
||||
working-directory: ../
|
||||
run: tar zxvf ${{env.NGINX_VERSION}}.tar.gz
|
||||
- name: configure (build into nginx)
|
||||
working-directory: ../${{env.NGINX_VERSION}}
|
||||
run: ./configure --add-module=../nginx-http-flv-module
|
||||
- name: make
|
||||
working-directory: ../${{env.NGINX_VERSION}}
|
||||
run: make
|
||||
- name: clean
|
||||
working-directory: ../${{env.NGINX_VERSION}}
|
||||
run: make clean
|
||||
- name: configure (build as a dynamic module)
|
||||
working-directory: ../${{env.NGINX_VERSION}}
|
||||
run: ./configure --add-dynamic-module=../nginx-http-flv-module
|
||||
- name: make
|
||||
working-directory: ../${{env.NGINX_VERSION}}
|
||||
run: make
|
||||
- name: remove
|
||||
working-directory: ../
|
||||
run: rm -rf "${{env.NGINX_VERSION}}*"
|
||||
63
ngx_http_flv_module/AUTHORS
Normal file
63
ngx_http_flv_module/AUTHORS
Normal file
@@ -0,0 +1,63 @@
|
||||
Project author:
|
||||
|
||||
Roman Arutyunyan
|
||||
Moscow, Russia
|
||||
Contacts:
|
||||
arut@qip.ru
|
||||
arutyunyan.roman@gmail.com
|
||||
|
||||
Winshining
|
||||
Beijing, China
|
||||
Contacts:
|
||||
winshining@163.com
|
||||
|
||||
Gnolizuh
|
||||
Beijing, China
|
||||
Contacts:
|
||||
huzilong_007@163.com
|
||||
huzilong@kingsoft.com
|
||||
|
||||
han4235
|
||||
Suzhou, China
|
||||
Contacts:
|
||||
https://github.com/han4235
|
||||
|
||||
plainheart
|
||||
Zhengzhou, China
|
||||
Contacts:
|
||||
https://github.com/plainheart
|
||||
|
||||
HeyJupiter:
|
||||
Seattle, US
|
||||
Contacts:
|
||||
https://github.com/HeyJupiter
|
||||
|
||||
Vladimir Vainer
|
||||
-
|
||||
Contacts:
|
||||
https://github.com/ferreus
|
||||
|
||||
ever4Keny
|
||||
China
|
||||
Contacts:
|
||||
https://github.com/ever4Keny
|
||||
|
||||
spacewander
|
||||
Guangzhou, China
|
||||
Contacts:
|
||||
spacewanderlzx@gmail.com
|
||||
|
||||
ham3r
|
||||
-
|
||||
Contacts:
|
||||
https://github.com/ham3r
|
||||
|
||||
deamos
|
||||
Scranton, PA
|
||||
Contacts:
|
||||
https://github.com/deamos
|
||||
|
||||
vacing
|
||||
Shenzhen, China
|
||||
Contacts:
|
||||
https://github.com/vacing
|
||||
32
ngx_http_flv_module/LICENSE
Normal file
32
ngx_http_flv_module/LICENSE
Normal file
@@ -0,0 +1,32 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2012-2017, Roman Arutyunyan
|
||||
Copyright (c) 2017-2025, Winshining
|
||||
Copyright (c) 2018, han4235, Vladimir Vainer
|
||||
Copyright (c) 2018-2019, plainheart, HeyJupiter
|
||||
Copyright (c) 2019, ever4Keny
|
||||
Copyright (c) 2020, spacewander, ham3r
|
||||
Copyright (c) 2022, deamons
|
||||
Copyright (c) 2024, vacing
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* 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 HOLDER 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.
|
||||
417
ngx_http_flv_module/README.CN.md
Normal file
417
ngx_http_flv_module/README.CN.md
Normal file
@@ -0,0 +1,417 @@
|
||||
# nginx-http-flv-module
|
||||
|
||||

|
||||
|
||||
一款基于 [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module) 的流媒体服务器。
|
||||
|
||||
[English README](https://github.com/winshining/nginx-http-flv-module/blob/master/README.md)。
|
||||
|
||||
如果您喜欢这个模块,可以通过赞赏来支持我的工作,非常感谢!
|
||||
|
||||

|
||||
|
||||
### 感谢
|
||||
|
||||
* Igor Sysoev,[NGINX](http://nginx.org) 的作者。
|
||||
|
||||
* Roman Arutyunyan,[nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module) 的作者。
|
||||
|
||||
* 贡献者,详情见 [AUTHORS](https://github.com/winshining/nginx-http-flv-module/blob/master/AUTHORS)。
|
||||
|
||||
## 功能
|
||||
|
||||
* [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module) 提供的所有功能。
|
||||
|
||||
* nginx-http-flv-module 的其他功能与 [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module) 的对比:
|
||||
|
||||
| 功能 | nginx-http-flv-module | nginx-rtmp-module | 备注 |
|
||||
| :----------------------: | :-------------------: | :---------------: | :--------------------------------------: |
|
||||
| HTTP-FLV (播放) | √ | x | 支持 HTTPS-FLV 和 chunked 回复 |
|
||||
| GOP 缓存 | √ | x | |
|
||||
| 虚拟主机 | √ | x | |
|
||||
| 省略 `listen` 配置项 | √ | 见备注 | 配置中必须有一个 `listen` |
|
||||
| RTMP/HTTP-FLV 纯音频支持 | √ | 见备注 |`wait_video` 或 `wait_key` 开启后无法工作 |
|
||||
| HLS 单轨支持 | √ | x | |
|
||||
| `reuseport` 支持 | √ | x | |
|
||||
| 定时打印访问记录 | √ | x | |
|
||||
| JSON 风格的数据信息 | √ | x | |
|
||||
| 录制的数据信息 | √ | x | |
|
||||
| 大小端无关 | √ | 见备注 | `big-endian` 分支部分支持 |
|
||||
|
||||
## 兼容性
|
||||
|
||||
[NGINX](http://nginx.org) 的版本**应该**大于或者等于 1.2.6,与其他版本的兼容性未知。
|
||||
|
||||
## 支持的系统
|
||||
|
||||
* Linux(推荐)/ FreeBSD / MacOS / Windows(受限)。
|
||||
|
||||
## 支持的播放器
|
||||
|
||||
* [VLC](http://www.videolan.org) (RTMP & HTTP-FLV) / [OBS](https://obsproject.com) (RTMP & HTTP-FLV) / [JW Player](https://www.jwplayer.com) (RTMP) / [flv.js](https://github.com/Bilibili/flv.js) (HTTP-FLV).
|
||||
|
||||
### 注意
|
||||
|
||||
* Adobe 将在 2020 年 12 月 31 日之后停止对 [flash 播放器](https://www.adobe.com/products/flashplayer.html) 的官方支持,详情见 [Adobe Flash Player EOL General Information Page](https://www.adobe.com/products/flashplayer/end-of-life.html)。主流浏览器随后将移除 flash 播放器,使用 flash 播放器的插件将不再可用。
|
||||
|
||||
* [flv.js](https://github.com/Bilibili/flv.js) 只能运行在支持 [Media Source Extensions](https://www.w3.org/TR/media-source) 的浏览器上。
|
||||
|
||||
## 依赖
|
||||
|
||||
* 在类 Unix 系统上,需要 GNU make,用于调用编译器来编译软件。
|
||||
|
||||
* 在类 Unix 系统上,需要 GCC。或者在 Windows 上,需要 MSVC,用于编译软件。
|
||||
|
||||
* 在类 Unix 系统上,需要 GDB,用于调试软件(可选)。
|
||||
|
||||
* [FFmpeg](http://ffmpeg.org) 或者 [OBS](https://obsproject.com),用于发布媒体流。
|
||||
|
||||
* [VLC](http://www.videolan.org)(推荐)或者 [flv.js](https://github.com/Bilibili/flv.js)(推荐),用于播放媒体流。
|
||||
|
||||
* 如果 NGINX 要支持正则表达式,需要 [PCRE库](http://www.pcre.org)。
|
||||
|
||||
* 如果 NGINX 要支持加密访问,需要 [OpenSSL库](https://www.openssl.org)。
|
||||
|
||||
* 如果 NGINX 要支持压缩,需要 [zlib库](http://www.zlib.net)。
|
||||
|
||||
## 创建
|
||||
|
||||
### 注意
|
||||
|
||||
nginx-http-flv-module 包含了 [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module) 所有的功能,所以**不要**将 nginx-http-flv-module 和 [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module) 一起编译。
|
||||
|
||||
### 在 Windows 上
|
||||
|
||||
编译步骤请参考 [Building nginx on the Win32 platform with Visual C](http://nginx.org/en/docs/howto_build_on_win32.html),不要忘了在 `Run configure script` 步骤中添加 `--add-module=/path/to/nginx-http-flv-module`。
|
||||
|
||||
#### 注意
|
||||
|
||||
如果使用没有完整支持 x64 的编译器来编译此模块,例如 VS2010,请务必使用默认设置(目标机器类型 x86)。
|
||||
|
||||
### 在类 Unix 系统上
|
||||
|
||||
下载 [NGINX](http://nginx.org) 和 nginx-http-flv-module。
|
||||
|
||||
将它们解压到某一路径。
|
||||
|
||||
打开 NGINX 的源代码路径并执行:
|
||||
|
||||
#### 将模块编译进 [NGINX](http://nginx.org)
|
||||
|
||||
./configure --add-module=/path/to/nginx-http-flv-module
|
||||
make
|
||||
make install
|
||||
|
||||
或者
|
||||
|
||||
#### 将模块编译为动态模块
|
||||
|
||||
./configure --add-dynamic-module=/path/to/nginx-http-flv-module
|
||||
make
|
||||
make install
|
||||
|
||||
#### 注意
|
||||
|
||||
如果将模块编译为动态模块,那么 [NGINX](http://nginx.org) 的版本号**必须**大于或者等于 1.9.11。
|
||||
|
||||
## 使用方法
|
||||
|
||||
关于 [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module) 用法的详情,请参考 [README.md](https://github.com/arut/nginx-rtmp-module/blob/master/README.md)。
|
||||
|
||||
### 发布
|
||||
|
||||
为了简单起见,不用转码:
|
||||
|
||||
ffmpeg -re -i MEDIA_FILE_NAME -c copy -f flv rtmp://example.com[:port]/appname/streamname
|
||||
|
||||
#### 注意
|
||||
|
||||
一些旧版本的 [FFmpeg](http://ffmpeg.org) 不支持选项 `-c copy`,可以使用选项 `-vcodec copy -acodec copy` 替代。
|
||||
|
||||
`appname` 用于匹配 rtmp 配置块中的 application 块(更多详情见下文)。
|
||||
|
||||
`streamname` 可以随意指定,但是**不能**省略。
|
||||
|
||||
**RTMP 默认端口**为 **1935**,如果要使用其他端口,必须指定 `:port`。
|
||||
|
||||
### 播放
|
||||
|
||||
#### HTTP-FLV 方式
|
||||
|
||||
http://example.com[:port]/dir?[port=xxx&]app=appname&stream=streamname
|
||||
|
||||
#### 注意
|
||||
|
||||
* 如果使用 [ffplay](http://www.ffmpeg.org/ffplay.html) 命令行方式播放流,那么**必须**为上述的 url 加上引号,否则 url 中的参数会被丢弃(有些不太智能的 shell 会把 "&" 解释为"后台运行")。
|
||||
|
||||
* 如果使用 [flv.js](https://github.com/Bilibili/flv.js) 播放流,那么请保证发布的流被正确编码,因为 [flv.js](https://github.com/Bilibili/flv.js) **只支持 H.264 编码的视频和 AAC/MP3 编码的音频**。
|
||||
|
||||
参数 `dir` 用于匹配 http 配置块中的 location 块(更多详情见下文)。
|
||||
|
||||
**HTTP 默认端口**为 **80**, 如果使用了其他端口,必须指定 `:port`。
|
||||
|
||||
**RTMP 默认端口**为 **1935**,如果使用了其他端口,必须指定 `port=xxx`。
|
||||
|
||||
参数 `app` 的值(appname)用来匹配 application 块,但是如果请求的 `app` 出现在多个 server 块中,并且这些 server 块有相同的地址和端口配置,那么还需要用匹配主机名的 `server_name` 配置项来区分请求的是哪个 application 块,否则,将匹配第一个 application 块。
|
||||
|
||||
参数 `stream` 的值(streamname)用来匹配发布的流的名称。
|
||||
|
||||
#### 例子
|
||||
|
||||
假设在 `http` 配置块中的 `listen` 配置项是:
|
||||
|
||||
http {
|
||||
...
|
||||
server {
|
||||
listen 8080; #不是默认的 80 端口
|
||||
...
|
||||
|
||||
location /live {
|
||||
flv_live on;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
在 `rtmp` 配置块中的 `listen` 配置项是:
|
||||
|
||||
rtmp {
|
||||
...
|
||||
server {
|
||||
listen 1985; #不是默认的 1935 端口
|
||||
...
|
||||
|
||||
application myapp {
|
||||
live on;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
并且发布的流的名称是 `mystream`,那么基于 HTTP 的播放 url 是:
|
||||
|
||||
http://example.com:8080/live?port=1985&app=myapp&stream=mystream
|
||||
|
||||
#### 注意
|
||||
|
||||
由于一些播放器不支持 HTTP 块传输, 这种情况下最好在指定了 `flv_live on;` 的 location 中指定 `chunked_transfer_encoding off`,否则播放会失败。
|
||||
|
||||
#### RTMP 方式
|
||||
|
||||
rtmp://example.com[:port]/appname/streamname
|
||||
|
||||
#### HLS 方式
|
||||
|
||||
http://example.com[:port]/dir/streamname.m3u8
|
||||
|
||||
#### DASH 方式
|
||||
|
||||
http://example.com[:port]/dir/streamname.mpd
|
||||
|
||||
## 示例图片
|
||||
|
||||
### RTMP ([JW Player](https://www.jwplayer.com)) & HTTP-FLV ([VLC](http://www.videolan.org))
|
||||
|
||||

|
||||
|
||||
### HTTP-FLV ([flv.js](https://github.com/Bilibili/flv.js))
|
||||
|
||||

|
||||
|
||||
## nginx.conf 实例
|
||||
|
||||
### 注意
|
||||
|
||||
配置项 `rtmp_auto_push`,`rtmp_auto_push_reconnect` 和 `rtmp_socket_dir` 在 Windows 上不起作用,除了 Windows 10 17063 以及后续版本之外,因为多进程模式的 `relay` 需要 Unix domain socket 的支持,详情请参考 [Unix domain socket on Windows 10](https://blogs.msdn.microsoft.com/commandline/2017/12/19/af_unix-comes-to-windows)。
|
||||
|
||||
最好将配置项 `worker_processes` 设置为 1,因为在多进程模式下,`ngx_rtmp_stat_module` 可能不会从指定的 worker 进程获取统计数据,因为 HTTP 请求是被随机分配给 worker 进程的。`ngx_rtmp_control_module` 也有同样的问题。这个问题可以通过这个补丁 [per-worker-listener](https://github.com/arut/nginx-patches/blob/master/per-worker-listener) 优化。
|
||||
|
||||
另外,`vhost` 功能在单进程模式下没有问题,但是在多进程模式下还不能完全正确运行,等待修复。例如,下面的配置在多进程模式下是没有问题的:
|
||||
|
||||
rtmp {
|
||||
...
|
||||
server {
|
||||
listen 1935;
|
||||
server_name domain_name;
|
||||
|
||||
application myapp {
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
而使用下面的配置,当 publisher 在第二个 `server` 上发布媒体流,播放请求以该配置(不管端口是不是 1935)访问非 publisher 的 worker 进程时是有问题的:
|
||||
|
||||
rtmp {
|
||||
...
|
||||
server {
|
||||
listen 1935;
|
||||
server_name 1st_domain_name;
|
||||
|
||||
application myapp {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 1945;
|
||||
server_name 2nd_domain_name;
|
||||
|
||||
application myapp {
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
如果 [NGINX](http://nginx.org) 是以多进程模式运行并且平台支持 socket 选项 `SO_REUSEPORT`,那么在配置项 `listen` 后添加选项 `reuseport` 可以解决惊群问题。
|
||||
|
||||
rtmp {
|
||||
...
|
||||
|
||||
server {
|
||||
listen 1935 reuseport;
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
### 配置实例
|
||||
|
||||
worker_processes 1; #运行在 Windows 上时,设置为 1,因为 Windows 不支持 Unix domain socket
|
||||
#worker_processes auto; #1.3.8 和 1.2.5 以及之后的版本
|
||||
|
||||
#worker_cpu_affinity 0001 0010 0100 1000; #只能用于 FreeBSD 和 Linux
|
||||
#worker_cpu_affinity auto; #1.9.10 以及之后的版本
|
||||
|
||||
error_log logs/error.log error;
|
||||
|
||||
#如果此模块被编译为动态模块并且要使用与 RTMP 相关的功
|
||||
#能时,必须指定下面的配置项并且它必须位于 events 配置
|
||||
#项之前,否则 NGINX 启动时不会加载此模块或者加载失败
|
||||
|
||||
#load_module modules/ngx_http_flv_live_module.so;
|
||||
|
||||
events {
|
||||
worker_connections 4096;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
root /var/www;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root html;
|
||||
}
|
||||
|
||||
location /live {
|
||||
flv_live on; #打开 HTTP 播放 FLV 直播流功能
|
||||
chunked_transfer_encoding on; #支持 'Transfer-Encoding: chunked' 方式回复
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*'; #添加额外的 HTTP 头
|
||||
add_header 'Access-Control-Allow-Credentials' 'true'; #添加额外的 HTTP 头
|
||||
}
|
||||
|
||||
location /hls {
|
||||
types {
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
video/mp2t ts;
|
||||
}
|
||||
|
||||
root /tmp;
|
||||
add_header 'Cache-Control' 'no-cache';
|
||||
}
|
||||
|
||||
location /dash {
|
||||
root /tmp;
|
||||
add_header 'Cache-Control' 'no-cache';
|
||||
}
|
||||
|
||||
location /stat {
|
||||
#推流播放和录制统计数据的配置
|
||||
|
||||
rtmp_stat all;
|
||||
rtmp_stat_stylesheet stat.xsl;
|
||||
}
|
||||
|
||||
location /stat.xsl {
|
||||
root /var/www/rtmp; #指定 stat.xsl 的位置
|
||||
}
|
||||
|
||||
#如果需要 JSON 风格的 stat, 不用指定 stat.xsl
|
||||
#但是需要指定一个新的配置项 rtmp_stat_format
|
||||
|
||||
#location /stat {
|
||||
# rtmp_stat all;
|
||||
# rtmp_stat_format json;
|
||||
#}
|
||||
|
||||
location /control {
|
||||
rtmp_control all; #rtmp 控制模块的配置
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rtmp_auto_push on;
|
||||
rtmp_auto_push_reconnect 1s;
|
||||
rtmp_socket_dir /tmp;
|
||||
|
||||
rtmp {
|
||||
out_queue 4096;
|
||||
out_cork 8;
|
||||
max_streams 128;
|
||||
timeout 15s;
|
||||
drop_idle_publisher 15s;
|
||||
|
||||
log_interval 5s; #log 模块在 access.log 中记录日志的间隔时间,对调试非常有用
|
||||
log_size 1m; #log 模块用来记录日志的缓冲区大小
|
||||
|
||||
server {
|
||||
listen 1935;
|
||||
server_name www.test.*; #用于虚拟主机名后缀通配
|
||||
|
||||
application myapp {
|
||||
live on;
|
||||
gop_cache on; #打开 GOP 缓存,减少首屏等待时间
|
||||
}
|
||||
|
||||
application hls {
|
||||
live on;
|
||||
hls on;
|
||||
hls_path /tmp/hls;
|
||||
}
|
||||
|
||||
application dash {
|
||||
live on;
|
||||
dash on;
|
||||
dash_path /tmp/dash;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 1935;
|
||||
server_name *.test.com; #用于虚拟主机名前缀通配
|
||||
|
||||
application myapp {
|
||||
live on;
|
||||
gop_cache on; #打开 GOP 缓存,减少首屏等待时间
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 1935;
|
||||
server_name www.test.com; #用于虚拟主机名完全匹配
|
||||
|
||||
application myapp {
|
||||
live on;
|
||||
gop_cache on; #打开 GOP 缓存,减少首屏等待时间
|
||||
}
|
||||
}
|
||||
}
|
||||
418
ngx_http_flv_module/README.md
Normal file
418
ngx_http_flv_module/README.md
Normal file
@@ -0,0 +1,418 @@
|
||||
# nginx-http-flv-module
|
||||
|
||||

|
||||
|
||||
A media streaming server based on [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module).
|
||||
|
||||
[中文说明](https://github.com/winshining/nginx-http-flv-module/blob/master/README.CN.md).
|
||||
|
||||
Donate if you like this module. Many thanks to you!
|
||||
|
||||
<a href="https://www.paypal.me/ShingWong" target="_blank"><img src="https://www.paypalobjects.com/digitalassets/c/website/marketing/apac/C2/logos-buttons/optimize/44_Grey_PayPal_Pill_Button.png" alt="PayPal" /></a>
|
||||
|
||||
### Credits
|
||||
|
||||
* Igor Sysoev, the creator of [NGINX](http://nginx.org).
|
||||
|
||||
* Roman Arutyunyan, who created [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module).
|
||||
|
||||
* Contributors, refer to [AUTHORS](https://github.com/winshining/nginx-http-flv-module/blob/master/AUTHORS) for details.
|
||||
|
||||
## Features
|
||||
|
||||
* All features [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module) provides.
|
||||
|
||||
* Other features provided by nginx-http-flv-module vs [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module):
|
||||
|
||||
| Features | nginx-http-flv-module | nginx-rtmp-module | Remarks |
|
||||
| :--------------------------------: | :-------------------: | :---------------: | :--------------------------------------------: |
|
||||
| HTTP-FLV (for play) | √ | x | HTTPS-FLV and chunked response supported |
|
||||
| GOP cache | √ | x | |
|
||||
| Virtual Host | √ | x | |
|
||||
| Omit `listen` directive | √ | See remarks | There MUST be at least one `listen` directive |
|
||||
|Audio-only support for RTMP/HTTP-FLV| √ | See remarks | Won't work if `wait_video` or `wait_key` is on |
|
||||
| Single-track support for HLS | √ | x | |
|
||||
| `reuseport` support | √ | x | |
|
||||
| Timer for access log | √ | x | |
|
||||
| JSON style statistics | √ | x | |
|
||||
| Statistics for recordings | √ | x | |
|
||||
| Independent of endianness | √ | See remarks | Partially supported in branch `big-endian` |
|
||||
|
||||
## Compatibility
|
||||
|
||||
The [NGINX](http://nginx.org) version **SHOULD** be equal to or greater than 1.2.6, the compatibility with other versions is unknown.
|
||||
|
||||
## Systems supported
|
||||
|
||||
* Linux (recommended) / FreeBSD / MacOS / Windows (limited).
|
||||
|
||||
## Players supported
|
||||
|
||||
* [VLC](http://www.videolan.org) (RTMP & HTTP-FLV) / [OBS](https://obsproject.com) (RTMP & HTTP-FLV) / [JW Player](https://www.jwplayer.com) (RTMP) / [flv.js](https://github.com/Bilibili/flv.js) (HTTP-FLV).
|
||||
|
||||
### Note
|
||||
|
||||
* [Flash player](https://www.adobe.com/products/flashplayer.html) will be no longer supported officially by Adobe after December 31, 2020, refer to [Adobe Flash Player EOL General Information Page](https://www.adobe.com/products/flashplayer/end-of-life.html) for details. Plugins that use flash player won't work after the major browsers subsequently remove flash player.
|
||||
|
||||
* [flv.js](https://github.com/Bilibili/flv.js) can only run with browsers that support [Media Source Extensions](https://www.w3.org/TR/media-source).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* GNU make for activating compiler on Unix-like systems to compile software.
|
||||
|
||||
* GCC for compilation on Unix-like systems or MSVC for compilation on Windows.
|
||||
|
||||
* GDB for debug on Unix-like systems.
|
||||
|
||||
* [FFmpeg](http://ffmpeg.org) or [OBS](https://obsproject.com) for publishing media streams.
|
||||
|
||||
* [VLC](http://www.videolan.org) (recommended) or [flv.js](https://github.com/Bilibili/flv.js) (recommended) for playing media streams.
|
||||
|
||||
* [PCRE](http://www.pcre.org) for NGINX if regular expressions needed.
|
||||
|
||||
* [OpenSSL](https://www.openssl.org) for NGINX if encrypted access needed.
|
||||
|
||||
* [zlib](http://www.zlib.net) for NGINX if compression needed.
|
||||
|
||||
## Build
|
||||
|
||||
### Note
|
||||
|
||||
nginx-http-flv-module has all features that [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module) provides, so **DON'T** compile nginx-http-flv-module along with [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module).
|
||||
|
||||
### On Windows
|
||||
|
||||
For details about build steps, please refer to [Building nginx on the Win32 platform with Visual C](http://nginx.org/en/docs/howto_build_on_win32.html), and don't forget to add `--add-module=/path/to/nginx-http-flv-module` in `Run configure script` step.
|
||||
|
||||
#### Note
|
||||
|
||||
If some compilers which do not support x64 perfectly, VS2010 for example, are used to compile the module, please make sure that the default settings are used (target machine type x86).
|
||||
|
||||
### On Unix-like systems
|
||||
|
||||
Download [NGINX](http://nginx.org) and nginx-http-flv-module.
|
||||
|
||||
Uncompress them.
|
||||
|
||||
cd to NGINX source directory & run this:
|
||||
|
||||
#### Compile the module into [NGINX](http://nginx.org)
|
||||
|
||||
./configure --add-module=/path/to/nginx-http-flv-module
|
||||
make
|
||||
make install
|
||||
|
||||
or
|
||||
|
||||
#### Compile the module as a dynamic module
|
||||
|
||||
./configure --add-dynamic-module=/path/to/nginx-http-flv-module
|
||||
make
|
||||
make install
|
||||
|
||||
#### Note
|
||||
|
||||
If the module is compiled as a dynamic module, the [NGINX](http://nginx.org) version **MUST** be equal to or greater than 1.9.11.
|
||||
|
||||
## Usage
|
||||
|
||||
For details of usages of [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module), please refer to [README.md](https://github.com/arut/nginx-rtmp-module/blob/master/README.md).
|
||||
|
||||
### Publish
|
||||
|
||||
For simplicity, transcoding is not used (so **-c copy** is used):
|
||||
|
||||
ffmpeg -re -i MEDIA_FILE_NAME -c copy -f flv rtmp://example.com[:port]/appname/streamname
|
||||
|
||||
#### Note
|
||||
|
||||
Some legacy versions of [FFmpeg](http://ffmpeg.org) don't support the option `-c copy`, the options `-vcodec copy -acodec copy` can be used instead.
|
||||
|
||||
The `appname` is used to match an application block in rtmp block (see below for details).
|
||||
|
||||
The `streamname` can be specified at will but can **NOT** be omitted.
|
||||
|
||||
The **default port for RTMP** is **1935**, if some other ports were used, `:port` must be specified.
|
||||
|
||||
### Play
|
||||
|
||||
#### via HTTP-FLV
|
||||
|
||||
http://example.com[:port]/dir?[port=xxx&]app=appname&stream=streamname
|
||||
|
||||
#### Note
|
||||
|
||||
* If [ffplay](http://www.ffmpeg.org/ffplay.html) is used in command line to play the stream, the url above **MUST** be enclosed by quotation marks, or arguments in url will be discarded (some shells not so smart will interpret "&" as "run in background").
|
||||
|
||||
* If [flv.js](https://github.com/Bilibili/flv.js) is used to play the stream, make sure that the published stream is encoded properly, for [flv.js](https://github.com/Bilibili/flv.js) supports **ONLY H.264 encoded video and AAC/MP3 encoded audio**.
|
||||
|
||||
The `dir` is used to match location blocks in http block (see below for details).
|
||||
|
||||
The **default port for HTTP** is **80**, if some other ports were used, `:port` must be specified.
|
||||
|
||||
The **default port for RTMP** is **1935**, if some other ports were used, `port=xxx` must be specified.
|
||||
|
||||
The value of `app` (appname) is used to match an application block, but if the requested `app` appears in several server blocks and those blocks have the same address and port configuration, host name matches `server_name` directive will be additionally used to identify the requested application block, otherwise the first one is matched.
|
||||
|
||||
The value of `stream` (streamname) is used to match the name of published stream.
|
||||
|
||||
#### Example
|
||||
|
||||
Assume that `listen` directive specified in `http` block is:
|
||||
|
||||
http {
|
||||
...
|
||||
server {
|
||||
listen 8080; #not default port 80
|
||||
...
|
||||
|
||||
location /live {
|
||||
flv_live on;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
And `listen` directive specified in `rtmp` block is:
|
||||
|
||||
rtmp {
|
||||
...
|
||||
server {
|
||||
listen 1985; #not default port 1935
|
||||
...
|
||||
|
||||
application myapp {
|
||||
live on;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
And the name of published stream is `mystream`, then the url of playback based on HTTP is:
|
||||
|
||||
http://example.com:8080/live?port=1985&app=myapp&stream=mystream
|
||||
|
||||
#### Note
|
||||
|
||||
Since some players don't support HTTP chunked transmission, it's better to specify `chunked_transfer_encoding off;` in location where `flv_live on;` is specified in this case, or play will fail.
|
||||
|
||||
#### via RTMP
|
||||
|
||||
rtmp://example.com[:port]/appname/streamname
|
||||
|
||||
#### via HLS
|
||||
|
||||
http://example.com[:port]/dir/streamname.m3u8
|
||||
|
||||
#### via DASH
|
||||
|
||||
http://example.com[:port]/dir/streamname.mpd
|
||||
|
||||
## Sample Pictures
|
||||
|
||||
### RTMP ([JW Player](https://www.jwplayer.com)) & HTTP-FLV ([VLC](http://www.videolan.org))
|
||||
|
||||

|
||||
|
||||
### HTTP-FLV ([flv.js](https://github.com/Bilibili/flv.js))
|
||||
|
||||

|
||||
|
||||
## Example nginx.conf
|
||||
|
||||
### Note
|
||||
|
||||
The directives `rtmp_auto_push`, `rtmp_auto_push_reconnect` and `rtmp_socket_dir` will not function on Windows except on Windows 10 17063 and later versions, because `relay` in multiple processes mode needs help of Unix domain socket, please refer to [Unix domain socket on Windows 10](https://blogs.msdn.microsoft.com/commandline/2017/12/19/af_unix-comes-to-windows) for details.
|
||||
|
||||
It's better to specify the directive `worker_processes` as 1, because `ngx_rtmp_stat_module` may not get statistics from a specified worker process in multi-processes mode, for HTTP requests are randomly distributed to worker processes. `ngx_rtmp_control_module` has the same problem. The problem can be optimized by this patch [per-worker-listener](https://github.com/arut/nginx-patches/blob/master/per-worker-listener).
|
||||
|
||||
In addtion, `vhost` feature is OK in single process mode but not perfect in multi-processes mode yet, waiting to be fixed. For example, the following configuration is OK in multi-processes mode:
|
||||
|
||||
rtmp {
|
||||
...
|
||||
server {
|
||||
listen 1935;
|
||||
server_name domain_name;
|
||||
|
||||
application myapp {
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
While the following configuration doesn't work properly for play requests distinated to the second `server` (whether port is 1935 or not) of non-publisher worker processes:
|
||||
|
||||
rtmp {
|
||||
...
|
||||
server {
|
||||
listen 1935;
|
||||
server_name 1st_domain_name;
|
||||
|
||||
application myapp {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 1945;
|
||||
server_name 2nd_domain_name;
|
||||
|
||||
application myapp {
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
If [NGINX](http://nginx.org) is running in muti-processes mode and socket option `SO_REUSEPORT` is supported by platform, adding option `reuseport` for the directive `listen` will resolve the thundering herd problem.
|
||||
|
||||
rtmp {
|
||||
...
|
||||
|
||||
server {
|
||||
listen 1935 reuseport;
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
### Example configuration
|
||||
|
||||
worker_processes 1; #should be 1 for Windows, for it doesn't support Unix domain socket
|
||||
#worker_processes auto; #from versions 1.3.8 and 1.2.5
|
||||
|
||||
#worker_cpu_affinity 0001 0010 0100 1000; #only available on FreeBSD and Linux
|
||||
#worker_cpu_affinity auto; #from version 1.9.10
|
||||
|
||||
error_log logs/error.log error;
|
||||
|
||||
#if the module is compiled as a dynamic module and features relevant
|
||||
#to RTMP are needed, the command below MUST be specified and MUST be
|
||||
#located before events directive, otherwise the module won't be loaded
|
||||
#or will be loaded unsuccessfully when NGINX is started
|
||||
|
||||
#load_module modules/ngx_http_flv_live_module.so;
|
||||
|
||||
events {
|
||||
worker_connections 4096;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
root /var/www;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root html;
|
||||
}
|
||||
|
||||
location /live {
|
||||
flv_live on; #open flv live streaming (subscribe)
|
||||
chunked_transfer_encoding on; #open 'Transfer-Encoding: chunked' response
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*'; #add additional HTTP header
|
||||
add_header 'Access-Control-Allow-Credentials' 'true'; #add additional HTTP header
|
||||
}
|
||||
|
||||
location /hls {
|
||||
types {
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
video/mp2t ts;
|
||||
}
|
||||
|
||||
root /tmp;
|
||||
add_header 'Cache-Control' 'no-cache';
|
||||
}
|
||||
|
||||
location /dash {
|
||||
root /tmp;
|
||||
add_header 'Cache-Control' 'no-cache';
|
||||
}
|
||||
|
||||
location /stat {
|
||||
#configuration of streaming & recording statistics
|
||||
|
||||
rtmp_stat all;
|
||||
rtmp_stat_stylesheet stat.xsl;
|
||||
}
|
||||
|
||||
location /stat.xsl {
|
||||
root /var/www/rtmp; #specify in where stat.xsl located
|
||||
}
|
||||
|
||||
#if JSON style stat needed, no need to specify
|
||||
#stat.xsl but a new directive rtmp_stat_format
|
||||
|
||||
#location /stat {
|
||||
# rtmp_stat all;
|
||||
# rtmp_stat_format json;
|
||||
#}
|
||||
|
||||
location /control {
|
||||
rtmp_control all; #configuration of control module of rtmp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rtmp_auto_push on;
|
||||
rtmp_auto_push_reconnect 1s;
|
||||
rtmp_socket_dir /tmp;
|
||||
|
||||
rtmp {
|
||||
out_queue 4096;
|
||||
out_cork 8;
|
||||
max_streams 128;
|
||||
timeout 15s;
|
||||
drop_idle_publisher 15s;
|
||||
|
||||
log_interval 5s; #interval used by log module to log in access.log, it is very useful for debug
|
||||
log_size 1m; #buffer size used by log module to log in access.log
|
||||
|
||||
server {
|
||||
listen 1935;
|
||||
server_name www.test.*; #for suffix wildcard matching of virtual host name
|
||||
|
||||
application myapp {
|
||||
live on;
|
||||
gop_cache on; #open GOP cache for reducing the wating time for the first picture of video
|
||||
}
|
||||
|
||||
application hls {
|
||||
live on;
|
||||
hls on;
|
||||
hls_path /tmp/hls;
|
||||
}
|
||||
|
||||
application dash {
|
||||
live on;
|
||||
dash on;
|
||||
dash_path /tmp/dash;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 1935;
|
||||
server_name *.test.com; #for prefix wildcard matching of virtual host name
|
||||
|
||||
application myapp {
|
||||
live on;
|
||||
gop_cache on; #open GOP cache for reducing the wating time for the first picture of video
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 1935;
|
||||
server_name www.test.com; #for completely matching of virtual host name
|
||||
|
||||
application myapp {
|
||||
live on;
|
||||
gop_cache on; #open GOP cache for reducing the wating time for the first picture of video
|
||||
}
|
||||
}
|
||||
}
|
||||
161
ngx_http_flv_module/config
Normal file
161
ngx_http_flv_module/config
Normal file
@@ -0,0 +1,161 @@
|
||||
ngx_addon_name="ngx_http_flv_live_module"
|
||||
|
||||
RTMP_CORE_MODULES=" \
|
||||
ngx_rtmp_module \
|
||||
ngx_rtmp_core_module \
|
||||
ngx_rtmp_cmd_module \
|
||||
ngx_rtmp_gop_cache_module \
|
||||
ngx_rtmp_codec_module \
|
||||
ngx_rtmp_access_module \
|
||||
ngx_rtmp_record_module \
|
||||
ngx_rtmp_live_module \
|
||||
ngx_rtmp_flv_live_index_module \
|
||||
ngx_rtmp_play_module \
|
||||
ngx_rtmp_flv_module \
|
||||
ngx_rtmp_mp4_module \
|
||||
ngx_rtmp_netcall_module \
|
||||
ngx_rtmp_relay_module \
|
||||
ngx_rtmp_exec_module \
|
||||
ngx_rtmp_auto_push_module \
|
||||
ngx_rtmp_auto_push_index_module \
|
||||
ngx_rtmp_log_module \
|
||||
ngx_rtmp_limit_module \
|
||||
ngx_rtmp_hls_module \
|
||||
ngx_rtmp_dash_module \
|
||||
ngx_rtmp_notify_module \
|
||||
"
|
||||
|
||||
|
||||
RTMP_HTTP_MODULES=" \
|
||||
ngx_rtmp_stat_module \
|
||||
ngx_rtmp_control_module \
|
||||
ngx_http_flv_live_module \
|
||||
"
|
||||
|
||||
|
||||
RTMP_DEPS=" \
|
||||
$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_gop_cache_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_netcall_module.h \
|
||||
$ngx_addon_dir/ngx_rtmp_play_module.h \
|
||||
$ngx_addon_dir/ngx_rtmp_record_module.h \
|
||||
$ngx_addon_dir/ngx_rtmp_relay_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_variables.h \
|
||||
$ngx_addon_dir/hls/ngx_rtmp_hls_module.h \
|
||||
$ngx_addon_dir/hls/ngx_rtmp_mpegts.h \
|
||||
$ngx_addon_dir/hls/ngx_rtmp_mpegts_crc.h \
|
||||
$ngx_addon_dir/dash/ngx_rtmp_mp4.h \
|
||||
"
|
||||
|
||||
|
||||
RTMP_CORE_SRCS=" \
|
||||
$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_shared.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_gop_cache_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_codec_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_access_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_record_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_live_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_flv_live_index_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_play_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_flv_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_mp4_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_netcall_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_relay_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_bandwidth.c \
|
||||
$ngx_addon_dir/ngx_rtmp_exec_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_auto_push_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/ngx_rtmp_variables.c \
|
||||
$ngx_addon_dir/ngx_rtmp_parse.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/hls/ngx_rtmp_mpegts_crc.c \
|
||||
$ngx_addon_dir/dash/ngx_rtmp_mp4.c \
|
||||
"
|
||||
|
||||
|
||||
RTMP_HTTP_DEPS=" \
|
||||
$ngx_addon_dir/ngx_http_flv_live_module.h \
|
||||
"
|
||||
|
||||
|
||||
RTMP_HTTP_SRCS=" \
|
||||
$ngx_addon_dir/ngx_rtmp_stat_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_control_module.c \
|
||||
$ngx_addon_dir/ngx_http_flv_live_module.c \
|
||||
"
|
||||
|
||||
if [ -f auto/module ] ; then
|
||||
ngx_module_incs=$ngx_addon_dir
|
||||
ngx_module_deps="$RTMP_DEPS $RTMP_HTTP_DEPS"
|
||||
|
||||
if [ $ngx_module_link = DYNAMIC ] ; then
|
||||
ngx_module_name="$ngx_addon_name $RTMP_CORE_MODULES $RTMP_HTTP_MODULES"
|
||||
ngx_module_srcs="$RTMP_CORE_SRCS $RTMP_HTTP_SRCS"
|
||||
|
||||
. auto/module
|
||||
|
||||
dynamic_modules=`eval echo '$'"${ngx_module}_MODULES" | sed -e "s/ \{0,\}$ngx_addon_name//"`
|
||||
eval ${ngx_module}_MODULES=\"$dynamic_modules\"
|
||||
unset dynamic_modules
|
||||
|
||||
order_modules=`eval echo '$'"${ngx_module}_ORDER"`
|
||||
if [ -n "$order_modules" ]
|
||||
then
|
||||
eval ${ngx_module}_ORDER=\"`echo "$order_modules" | sed -e "s/ \{0,\}$ngx_addon_name//"`\"
|
||||
unset order_modules
|
||||
fi
|
||||
else
|
||||
ngx_module_type=CORE
|
||||
ngx_module_name=$RTMP_CORE_MODULES
|
||||
ngx_module_deps=$RTMP_DEPS
|
||||
ngx_module_srcs=$RTMP_CORE_SRCS
|
||||
|
||||
. auto/module
|
||||
|
||||
|
||||
ngx_module_type=HTTP
|
||||
ngx_module_name=$RTMP_HTTP_MODULES
|
||||
ngx_module_deps=$RTMP_HTTP_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 $RTMP_HTTP_DEPS"
|
||||
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $RTMP_CORE_SRCS $RTMP_HTTP_SRCS"
|
||||
|
||||
CFLAGS="$CFLAGS -I$ngx_addon_dir"
|
||||
fi
|
||||
|
||||
USE_OPENSSL=YES
|
||||
|
||||
1539
ngx_http_flv_module/dash/ngx_rtmp_dash_module.c
Normal file
1539
ngx_http_flv_module/dash/ngx_rtmp_dash_module.c
Normal file
File diff suppressed because it is too large
Load Diff
1167
ngx_http_flv_module/dash/ngx_rtmp_mp4.c
Normal file
1167
ngx_http_flv_module/dash/ngx_rtmp_mp4.c
Normal file
File diff suppressed because it is too large
Load Diff
52
ngx_http_flv_module/dash/ngx_rtmp_mp4.h
Normal file
52
ngx_http_flv_module/dash/ngx_rtmp_mp4.h
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_MP4_H_INCLUDED_
|
||||
#define _NGX_RTMP_MP4_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_rtmp.h>
|
||||
|
||||
|
||||
#define NGX_RTMP_MP4_SAMPLE_SIZE 0x01
|
||||
#define NGX_RTMP_MP4_SAMPLE_DURATION 0x02
|
||||
#define NGX_RTMP_MP4_SAMPLE_DELAY 0x04
|
||||
#define NGX_RTMP_MP4_SAMPLE_KEY 0x08
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t size;
|
||||
uint32_t duration;
|
||||
uint32_t delay;
|
||||
uint32_t timestamp;
|
||||
unsigned key:1;
|
||||
} ngx_rtmp_mp4_sample_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
NGX_RTMP_MP4_FILETYPE_INIT,
|
||||
NGX_RTMP_MP4_FILETYPE_SEG
|
||||
} ngx_rtmp_mp4_file_type_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
NGX_RTMP_MP4_VIDEO_TRACK,
|
||||
NGX_RTMP_MP4_AUDIO_TRACK
|
||||
} ngx_rtmp_mp4_track_type_t;
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_mp4_write_ftyp(ngx_buf_t *b);
|
||||
ngx_int_t ngx_rtmp_mp4_write_styp(ngx_buf_t *b);
|
||||
ngx_int_t ngx_rtmp_mp4_write_moov(ngx_rtmp_session_t *s, ngx_buf_t *b,
|
||||
ngx_rtmp_mp4_track_type_t ttype);
|
||||
ngx_int_t ngx_rtmp_mp4_write_moof(ngx_buf_t *b, uint32_t earliest_pres_time,
|
||||
uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples,
|
||||
ngx_uint_t sample_mask, uint32_t index);
|
||||
ngx_int_t ngx_rtmp_mp4_write_sidx(ngx_buf_t *b,
|
||||
ngx_uint_t reference_size, uint32_t earliest_pres_time,
|
||||
uint32_t latest_pres_time);
|
||||
ngx_uint_t ngx_rtmp_mp4_write_mdat(ngx_buf_t *b, ngx_uint_t size);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_MP4_H_INCLUDED_ */
|
||||
2
ngx_http_flv_module/doc/README.md
Normal file
2
ngx_http_flv_module/doc/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
Documentation is available here:
|
||||
https://github.com/arut/nginx-rtmp-module/wiki
|
||||
2591
ngx_http_flv_module/hls/ngx_rtmp_hls_module.c
Normal file
2591
ngx_http_flv_module/hls/ngx_rtmp_hls_module.c
Normal file
File diff suppressed because it is too large
Load Diff
15
ngx_http_flv_module/hls/ngx_rtmp_hls_module.h
Normal file
15
ngx_http_flv_module/hls/ngx_rtmp_hls_module.h
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_HLS_MODULE_H_
|
||||
#define _NGX_RTMP_HLS_MODULE_H_
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_hls_copy(ngx_rtmp_session_t *s, void *dst, u_char **src,
|
||||
size_t n, ngx_chain_t **in);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_HLS_MODULE_H_ */
|
||||
474
ngx_http_flv_module/hls/ngx_rtmp_mpegts.c
Normal file
474
ngx_http_flv_module/hls/ngx_rtmp_mpegts.c
Normal file
@@ -0,0 +1,474 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_mpegts.h"
|
||||
#include "ngx_rtmp_mpegts_crc.h"
|
||||
|
||||
|
||||
static u_char ngx_rtmp_mpegts_header[] = {
|
||||
|
||||
/* 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,
|
||||
|
||||
/* TS */
|
||||
0x47, 0x50, 0x01, 0x10, 0x00,
|
||||
/* PSI */
|
||||
0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00,
|
||||
/* PMT */
|
||||
0xe1, 0x00,
|
||||
0xf0, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, /* video, filled dynamically */
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, /* audio, filled dynamically */
|
||||
/* CRC */
|
||||
0xff, 0xff, 0xff, 0xff, /* calculated dynamically */
|
||||
/* stuffing 157 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
|
||||
};
|
||||
|
||||
|
||||
static u_char ngx_rtmp_mpegts_h264_header[] = {
|
||||
0x1b, 0xe1, 0x00, 0xf0, 0x00
|
||||
};
|
||||
|
||||
|
||||
static u_char ngx_rtmp_mpegts_aac_header[] = {
|
||||
0x0f, 0xe1, 0x01, 0xf0, 0x00
|
||||
};
|
||||
|
||||
|
||||
static u_char ngx_rtmp_mpegts_mp3_header[] = {
|
||||
0x03, 0xe1, 0x01, 0xf0, 0x00
|
||||
};
|
||||
|
||||
|
||||
#define NGX_RTMP_MPEGTS_PMT_CRC_START_OFFSET 193
|
||||
#define NGX_RTMP_MPEGTS_PMT_SECTION_LENGTH_OFFSET 195
|
||||
#define NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET 205
|
||||
#define NGX_RTMP_MPEGTS_PMT_CRC_MIN_LENGTH 12
|
||||
#define NGX_RTMP_MPEGTS_STREAM_BYTES 5
|
||||
|
||||
|
||||
/* 700 ms PCR delay */
|
||||
#define NGX_RTMP_HLS_DELAY 63000
|
||||
|
||||
|
||||
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);
|
||||
|
||||
rc = ngx_write_fd(file->fd, 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;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file,
|
||||
ngx_rtmp_codec_ctx_t *codec_ctx, ngx_uint_t counter)
|
||||
{
|
||||
ngx_int_t stream_bytes;
|
||||
ngx_rtmp_mpegts_crc_t crc;
|
||||
u_char buf[sizeof(ngx_rtmp_mpegts_header)];
|
||||
|
||||
if (codec_ctx->video_codec_id == 0 && codec_ctx->audio_codec_id == 0) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
stream_bytes = 0;
|
||||
ngx_memcpy(buf, ngx_rtmp_mpegts_header, sizeof(ngx_rtmp_mpegts_header));
|
||||
|
||||
/* 4 bits, periodical */
|
||||
counter %= 0x10;
|
||||
/* fill headers */
|
||||
buf[3] = (buf[3] & 0xf0) + (u_char) counter;
|
||||
buf[191] = (buf[191] & 0xf0) + (u_char) counter;
|
||||
|
||||
if (codec_ctx->video_codec_id) {
|
||||
/* video info */
|
||||
ngx_memcpy(buf + NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET + stream_bytes,
|
||||
ngx_rtmp_mpegts_h264_header, NGX_RTMP_MPEGTS_STREAM_BYTES);
|
||||
|
||||
stream_bytes += NGX_RTMP_MPEGTS_STREAM_BYTES;
|
||||
}
|
||||
|
||||
if (codec_ctx->audio_codec_id) {
|
||||
/* audio info */
|
||||
if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) {
|
||||
ngx_memcpy(buf + NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET + stream_bytes,
|
||||
ngx_rtmp_mpegts_aac_header, NGX_RTMP_MPEGTS_STREAM_BYTES);
|
||||
} else {
|
||||
ngx_memcpy(buf + NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET + stream_bytes,
|
||||
ngx_rtmp_mpegts_mp3_header, NGX_RTMP_MPEGTS_STREAM_BYTES);
|
||||
}
|
||||
|
||||
stream_bytes += NGX_RTMP_MPEGTS_STREAM_BYTES;
|
||||
}
|
||||
|
||||
/* calculate section length */
|
||||
buf[NGX_RTMP_MPEGTS_PMT_SECTION_LENGTH_OFFSET] = 13 + stream_bytes;
|
||||
|
||||
/* calculate CRC */
|
||||
crc = ngx_rtmp_mpegts_crc_init();
|
||||
crc = ngx_rtmp_mpegts_crc_update(crc,
|
||||
buf + NGX_RTMP_MPEGTS_PMT_CRC_START_OFFSET,
|
||||
NGX_RTMP_MPEGTS_PMT_CRC_MIN_LENGTH + stream_bytes);
|
||||
crc = ngx_rtmp_mpegts_crc_finalize(crc);
|
||||
|
||||
buf[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET + stream_bytes] = (crc >> 24) & 0xff;
|
||||
buf[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET + stream_bytes + 1] = (crc >> 16) & 0xff;
|
||||
buf[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET + stream_bytes + 2] = (crc >> 8) & 0xff;
|
||||
buf[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET + stream_bytes + 3] = crc & 0xff;
|
||||
|
||||
return ngx_rtmp_mpegts_write_file(file, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
|
||||
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_rtmp_codec_ctx_t *codec_ctx, ngx_uint_t counter, 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, codec_ctx, counter) != 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;
|
||||
}
|
||||
48
ngx_http_flv_module/hls/ngx_rtmp_mpegts.h
Normal file
48
ngx_http_flv_module/hls/ngx_rtmp_mpegts.h
Normal file
@@ -0,0 +1,48 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_MPEGTS_H_INCLUDED_
|
||||
#define _NGX_RTMP_MPEGTS_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <openssl/aes.h>
|
||||
#include "ngx_rtmp_codec_module.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_fd_t fd;
|
||||
ngx_log_t *log;
|
||||
unsigned encrypt:1;
|
||||
unsigned size:4;
|
||||
u_char buf[16];
|
||||
u_char iv[16];
|
||||
AES_KEY key;
|
||||
} ngx_rtmp_mpegts_file_t;
|
||||
|
||||
|
||||
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_rtmp_codec_ctx_t *codec_ctx, ngx_uint_t counter, 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_frame(ngx_rtmp_mpegts_file_t *file,
|
||||
ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_MPEGTS_H_INCLUDED_ */
|
||||
81
ngx_http_flv_module/hls/ngx_rtmp_mpegts_crc.c
Normal file
81
ngx_http_flv_module/hls/ngx_rtmp_mpegts_crc.c
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* \file crc.c
|
||||
* Functions and types for CRC checks.
|
||||
*
|
||||
* Generated on Thu May 5 15:32:31 2016,
|
||||
* by pycrc v0.9, https://pycrc.org
|
||||
* using the configuration:
|
||||
* Width = 32
|
||||
* Poly = 0x04c11db7
|
||||
* Xor_In = 0xffffffff
|
||||
* ReflectIn = False
|
||||
* Xor_Out = 0x00000000
|
||||
* ReflectOut = False
|
||||
* Algorithm = table-driven
|
||||
*****************************************************************************/
|
||||
#include <ngx_config.h>
|
||||
#include "ngx_rtmp_mpegts_crc.h" /* include the header file generated with pycrc */
|
||||
|
||||
|
||||
/**
|
||||
* Static table used for the table_driven implementation.
|
||||
*****************************************************************************/
|
||||
static const ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_table[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
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update the crc value with new data.
|
||||
*
|
||||
* \param crc The current crc value.
|
||||
* \param data Pointer to a buffer of \a data_len bytes.
|
||||
* \param data_len Number of bytes in the \a data buffer.
|
||||
* \return The updated crc value.
|
||||
*****************************************************************************/
|
||||
ngx_rtmp_mpegts_crc_t
|
||||
ngx_rtmp_mpegts_crc_update(ngx_rtmp_mpegts_crc_t crc, const void *data, size_t data_len)
|
||||
{
|
||||
uint32_t tbl_idx;
|
||||
const u_char *d = (const unsigned char *) data;
|
||||
|
||||
while (data_len--) {
|
||||
tbl_idx = ((crc >> 24) ^ *d) & 0xff;
|
||||
crc = (ngx_rtmp_mpegts_crc_table[tbl_idx] ^ (crc << 8)) & 0xffffffff;
|
||||
|
||||
d++;
|
||||
}
|
||||
|
||||
return crc & 0xffffffff;
|
||||
}
|
||||
85
ngx_http_flv_module/hls/ngx_rtmp_mpegts_crc.h
Normal file
85
ngx_http_flv_module/hls/ngx_rtmp_mpegts_crc.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* \file crc.h
|
||||
* Functions and types for CRC checks.
|
||||
*
|
||||
* Generated on Thu May 5 15:32:22 2016,
|
||||
* by pycrc v0.9, https://pycrc.org
|
||||
* using the configuration:
|
||||
* Width = 32
|
||||
* Poly = 0x04c11db7
|
||||
* Xor_In = 0xffffffff
|
||||
* ReflectIn = False
|
||||
* Xor_Out = 0x00000000
|
||||
* ReflectOut = False
|
||||
* Algorithm = table-driven
|
||||
*****************************************************************************/
|
||||
#ifndef _NGX_RTMP_MPEGTS_CRC_H_INCLUDED_
|
||||
#define _NGX_RTMP_MPEGTS_CRC_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_core.h>
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* The definition of the used algorithm.
|
||||
*
|
||||
* This is not used anywhere in the generated code, but it may be used by the
|
||||
* application code to call algoritm-specific code, is desired.
|
||||
*****************************************************************************/
|
||||
#define CRC_ALGO_TABLE_DRIVEN 1
|
||||
|
||||
|
||||
/**
|
||||
* The type of the CRC values.
|
||||
*
|
||||
* This type must be big enough to contain at least 32 bits.
|
||||
*****************************************************************************/
|
||||
typedef uint_fast32_t ngx_rtmp_mpegts_crc_t;
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the initial crc value.
|
||||
*
|
||||
* \return The initial crc value.
|
||||
*****************************************************************************/
|
||||
static ngx_inline ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_init(void)
|
||||
{
|
||||
return 0xffffffff;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the crc value with new data.
|
||||
*
|
||||
* \param crc The current crc value.
|
||||
* \param data Pointer to a buffer of \a data_len bytes.
|
||||
* \param data_len Number of bytes in the \a data buffer.
|
||||
* \return The updated crc value.
|
||||
*****************************************************************************/
|
||||
ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_update(ngx_rtmp_mpegts_crc_t crc,
|
||||
const void *data, size_t data_len);
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the final crc value.
|
||||
*
|
||||
* \param crc The current crc value.
|
||||
* \return The final crc value.
|
||||
*****************************************************************************/
|
||||
static ngx_inline ngx_rtmp_mpegts_crc_t
|
||||
ngx_rtmp_mpegts_crc_finalize(ngx_rtmp_mpegts_crc_t crc)
|
||||
{
|
||||
return crc ^ 0x00000000;
|
||||
}
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* closing brace for extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* _NGX_RTMP_MPEGTS_CRC_H_INCLUDED_ */
|
||||
2552
ngx_http_flv_module/ngx_http_flv_live_module.c
Normal file
2552
ngx_http_flv_module/ngx_http_flv_live_module.c
Normal file
File diff suppressed because it is too large
Load Diff
78
ngx_http_flv_module/ngx_http_flv_live_module.h
Normal file
78
ngx_http_flv_module/ngx_http_flv_live_module.h
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
#ifndef _NGX_HTTP_FLV_LIVE_H_INCLUDED_
|
||||
#define _NGX_HTTP_FLV_LIVE_H_INCLUDED_
|
||||
|
||||
|
||||
#include "ngx_rtmp_cmd_module.h"
|
||||
#include "ngx_rtmp_live_module.h"
|
||||
#include "ngx_rtmp_codec_module.h"
|
||||
|
||||
|
||||
#define NGX_HASH_MAX_SIZE 0x80
|
||||
#define NGX_HASH_MAX_BUKET_SIZE 0x40
|
||||
#define NGX_BUFF_MAX_SIZE 0x80
|
||||
#define NGX_FLV_TAG_HEADER_SIZE 11
|
||||
|
||||
|
||||
extern ngx_module_t ngx_rtmp_module;
|
||||
|
||||
|
||||
extern ngx_rtmp_play_pt http_flv_live_next_play;
|
||||
extern ngx_rtmp_close_stream_pt http_flv_live_next_close_stream;
|
||||
|
||||
|
||||
#define ngx_rtmp_cycle_get_module_main_conf(cycle, module) \
|
||||
(cycle->conf_ctx[ngx_rtmp_module.index] ? \
|
||||
((ngx_rtmp_conf_ctx_t *) cycle->conf_ctx[ngx_rtmp_module.index]) \
|
||||
->main_conf[module.ctx_index]: \
|
||||
NULL)
|
||||
|
||||
|
||||
typedef struct ngx_http_flv_live_ctx_s {
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_flag_t header_sent;
|
||||
|
||||
ngx_str_t app;
|
||||
ngx_str_t port;
|
||||
ngx_str_t stream;
|
||||
} ngx_http_flv_live_ctx_t;
|
||||
|
||||
|
||||
typedef struct ngx_http_flv_live_conf_s {
|
||||
ngx_flag_t flv_live;
|
||||
} ngx_http_flv_live_conf_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_chain_t *meta;
|
||||
ngx_chain_t *apkt;
|
||||
ngx_chain_t *acopkt;
|
||||
ngx_chain_t *rpkt;
|
||||
|
||||
ngx_int_t (*send_message_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t *out, ngx_uint_t priority);
|
||||
ngx_chain_t *(*meta_message_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t *in);
|
||||
ngx_chain_t *(*append_message_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_rtmp_header_t *lh,
|
||||
ngx_chain_t *in);
|
||||
void (*free_message_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t *in);
|
||||
} ngx_rtmp_live_proc_handler_t;
|
||||
|
||||
|
||||
ngx_int_t ngx_http_flv_live_play(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_play_t *v);
|
||||
ngx_int_t ngx_http_flv_live_close_stream(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_close_stream_t *v);
|
||||
|
||||
ngx_int_t ngx_http_flv_live_send_header(ngx_rtmp_session_t *s);
|
||||
void ngx_http_flv_live_set_status(ngx_rtmp_session_t *s, unsigned active);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
1509
ngx_http_flv_module/ngx_rtmp.c
Normal file
1509
ngx_http_flv_module/ngx_rtmp.c
Normal file
File diff suppressed because it is too large
Load Diff
850
ngx_http_flv_module/ngx_rtmp.h
Normal file
850
ngx_http_flv_module/ngx_rtmp.h
Normal file
@@ -0,0 +1,850 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_H_INCLUDED_
|
||||
#define _NGX_RTMP_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
#include <ngx_event_connect.h>
|
||||
#include <nginx.h>
|
||||
|
||||
#include "ngx_rtmp_amf.h"
|
||||
#include "ngx_rtmp_bandwidth.h"
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_core_srv_conf_s ngx_rtmp_core_srv_conf_t;
|
||||
typedef struct ngx_rtmp_session_s ngx_rtmp_session_t;
|
||||
typedef struct ngx_rtmp_virtual_names_s ngx_rtmp_virtual_names_t;
|
||||
|
||||
|
||||
#include "ngx_rtmp_variables.h"
|
||||
|
||||
|
||||
#if (NGX_WIN32)
|
||||
typedef __int8 int8_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
void **main_conf;
|
||||
void **srv_conf;
|
||||
void **app_conf;
|
||||
} ngx_rtmp_conf_ctx_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t addr_text;
|
||||
|
||||
/* the default server configuration for this address:port */
|
||||
ngx_rtmp_core_srv_conf_t *default_server;
|
||||
|
||||
ngx_rtmp_virtual_names_t *virtual_names;
|
||||
|
||||
unsigned proxy_protocol:1;
|
||||
} ngx_rtmp_addr_conf_t;
|
||||
|
||||
typedef struct {
|
||||
in_addr_t addr;
|
||||
ngx_rtmp_addr_conf_t conf;
|
||||
} ngx_rtmp_in_addr_t;
|
||||
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
|
||||
typedef struct {
|
||||
struct in6_addr addr6;
|
||||
ngx_rtmp_addr_conf_t conf;
|
||||
} ngx_rtmp_in6_addr_t;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* ngx_rtmp_in_addr_t or ngx_rtmp_in_addr6_t */
|
||||
void *addrs;
|
||||
ngx_uint_t naddrs;
|
||||
} ngx_rtmp_port_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
int family;
|
||||
in_port_t port;
|
||||
ngx_array_t addrs; /* array of ngx_rtmp_conf_addr_t */
|
||||
} ngx_rtmp_conf_port_t;
|
||||
|
||||
|
||||
#if (nginx_version <= 1010003)
|
||||
typedef union {
|
||||
struct sockaddr sockaddr;
|
||||
struct sockaddr_in sockaddr_in;
|
||||
#if (NGX_HAVE_INET6)
|
||||
struct sockaddr_in6 sockaddr_in6;
|
||||
#endif
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
struct sockaddr_un sockaddr_un;
|
||||
#endif
|
||||
} ngx_sockaddr_t;
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_sockaddr_t sockaddr;
|
||||
socklen_t socklen;
|
||||
|
||||
unsigned set:1;
|
||||
unsigned default_server:1;
|
||||
unsigned bind:1;
|
||||
unsigned wildcard:1;
|
||||
#if (NGX_HAVE_INET6)
|
||||
unsigned ipv6only:1;
|
||||
#endif
|
||||
unsigned deferred_accept:1;
|
||||
unsigned reuseport:1;
|
||||
unsigned so_keepalive:2;
|
||||
unsigned proxy_protocol:1;
|
||||
|
||||
int backlog;
|
||||
int rcvbuf;
|
||||
int sndbuf;
|
||||
#if (NGX_HAVE_SETFIB)
|
||||
int setfib;
|
||||
#endif
|
||||
#if (NGX_HAVE_TCP_FASTOPEN)
|
||||
int fastopen;
|
||||
#endif
|
||||
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
|
||||
int tcp_keepidle;
|
||||
int tcp_keepintvl;
|
||||
int tcp_keepcnt;
|
||||
#endif
|
||||
|
||||
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
|
||||
char *accept_filter;
|
||||
#endif
|
||||
|
||||
u_char addr[NGX_SOCKADDR_STRLEN + 1];
|
||||
} ngx_rtmp_listen_opt_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
#if (NGX_PCRE)
|
||||
ngx_rtmp_regex_t *regex;
|
||||
#endif
|
||||
ngx_rtmp_core_srv_conf_t *server; /* virtual name server conf */
|
||||
ngx_str_t name;
|
||||
} ngx_rtmp_server_name_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_rtmp_listen_opt_t opt;
|
||||
|
||||
ngx_hash_t hash;
|
||||
ngx_hash_wildcard_t *wc_head;
|
||||
ngx_hash_wildcard_t *wc_tail;
|
||||
|
||||
#if (NGX_PCRE)
|
||||
ngx_uint_t nregex;
|
||||
ngx_rtmp_server_name_t *regex;
|
||||
#endif
|
||||
|
||||
/* the default server configuration for this address:port */
|
||||
ngx_rtmp_core_srv_conf_t *default_server;
|
||||
ngx_array_t servers; /* array of ngx_rtmp_core_srv_conf_t */
|
||||
} ngx_rtmp_conf_addr_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_rtmp_addr_conf_t *addr_conf;
|
||||
ngx_rtmp_conf_ctx_t *conf_ctx;
|
||||
|
||||
ngx_buf_t **busy;
|
||||
ngx_int_t nbusy;
|
||||
|
||||
ngx_buf_t **free;
|
||||
ngx_int_t nfree;
|
||||
} ngx_rtmp_connection_t;
|
||||
|
||||
|
||||
#define NGX_RTMP_VERSION 3
|
||||
|
||||
#define NGX_LOG_DEBUG_RTMP NGX_LOG_DEBUG_CORE
|
||||
|
||||
#define NGX_RTMP_DEFAULT_CHUNK_SIZE 128
|
||||
|
||||
|
||||
/* RTMP message types */
|
||||
#define NGX_RTMP_MSG_CHUNK_SIZE 1
|
||||
#define NGX_RTMP_MSG_ABORT 2
|
||||
#define NGX_RTMP_MSG_ACK 3
|
||||
#define NGX_RTMP_MSG_USER 4
|
||||
#define NGX_RTMP_MSG_ACK_SIZE 5
|
||||
#define NGX_RTMP_MSG_BANDWIDTH 6
|
||||
#define NGX_RTMP_MSG_EDGE 7
|
||||
#define NGX_RTMP_MSG_AUDIO 8
|
||||
#define NGX_RTMP_MSG_VIDEO 9
|
||||
#define NGX_RTMP_MSG_AMF3_META 15
|
||||
#define NGX_RTMP_MSG_AMF3_SHARED 16
|
||||
#define NGX_RTMP_MSG_AMF3_CMD 17
|
||||
#define NGX_RTMP_MSG_AMF_META 18
|
||||
#define NGX_RTMP_MSG_AMF_SHARED 19
|
||||
#define NGX_RTMP_MSG_AMF_CMD 20
|
||||
#define NGX_RTMP_MSG_AGGREGATE 22
|
||||
#define NGX_RTMP_MSG_MAX 22
|
||||
|
||||
#define NGX_RTMP_MAX_CHUNK_SIZE 10485760
|
||||
|
||||
#define NGX_RTMP_CONNECT NGX_RTMP_MSG_MAX + 1
|
||||
#define NGX_RTMP_DISCONNECT NGX_RTMP_MSG_MAX + 2
|
||||
#define NGX_RTMP_HANDSHAKE_DONE NGX_RTMP_MSG_MAX + 3
|
||||
#define NGX_HTTP_FLV_LIVE_REQUEST NGX_RTMP_MSG_MAX + 4
|
||||
#define NGX_RTMP_MAX_EVENT NGX_RTMP_MSG_MAX + 5
|
||||
|
||||
|
||||
/* RMTP control message types */
|
||||
#define NGX_RTMP_USER_STREAM_BEGIN 0
|
||||
#define NGX_RTMP_USER_STREAM_EOF 1
|
||||
#define NGX_RTMP_USER_STREAM_DRY 2
|
||||
#define NGX_RTMP_USER_SET_BUFLEN 3
|
||||
#define NGX_RTMP_USER_RECORDED 4
|
||||
#define NGX_RTMP_USER_PING_REQUEST 6
|
||||
#define NGX_RTMP_USER_PING_RESPONSE 7
|
||||
#define NGX_RTMP_USER_UNKNOWN 8
|
||||
#define NGX_RTMP_USER_BUFFER_END 31
|
||||
|
||||
|
||||
/* Chunk header:
|
||||
* max 3 basic header
|
||||
* + max 11 message header
|
||||
* + max 4 extended header (timestamp) */
|
||||
#define NGX_RTMP_MAX_CHUNK_HEADER 18
|
||||
|
||||
|
||||
enum {
|
||||
NGX_RTMP_PROTOCOL_RTMP = 0,
|
||||
NGX_RTMP_PROTOCOL_HTTP
|
||||
};
|
||||
|
||||
|
||||
#define NGX_RTMP_INTERNAL_SERVER_ERROR 500
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t csid; /* chunk stream id */
|
||||
uint32_t timestamp; /* timestamp (delta) */
|
||||
uint32_t mlen; /* message length */
|
||||
uint8_t type; /* message type id */
|
||||
uint32_t msid; /* message stream id */
|
||||
} ngx_rtmp_header_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_rtmp_header_t hdr;
|
||||
uint32_t dtime;
|
||||
uint32_t len; /* current fragment length */
|
||||
uint8_t ext;
|
||||
ngx_chain_t *in;
|
||||
} ngx_rtmp_stream_t;
|
||||
|
||||
|
||||
/* disable zero-sized array warning by msvc */
|
||||
|
||||
#if (NGX_WIN32)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4200)
|
||||
#endif
|
||||
|
||||
|
||||
struct ngx_rtmp_session_s {
|
||||
uint32_t signature; /* "RTMP" */ /* <-- FIXME wtf */
|
||||
|
||||
ngx_int_t port;
|
||||
ngx_buf_t *request_line;
|
||||
ngx_str_t uri;
|
||||
ngx_str_t unparsed_uri;
|
||||
|
||||
time_t start_sec;
|
||||
ngx_msec_t start_msec;
|
||||
|
||||
ngx_event_t close;
|
||||
|
||||
void **ctx;
|
||||
void **main_conf;
|
||||
void **srv_conf;
|
||||
void **app_conf;
|
||||
|
||||
void *data;
|
||||
ngx_event_t push_evt;
|
||||
|
||||
ngx_str_t *addr_text;
|
||||
ngx_flag_t connected;
|
||||
|
||||
#if (nginx_version >= 1007005)
|
||||
ngx_queue_t posted_dry_events;
|
||||
#else
|
||||
ngx_event_t *posted_dry_events;
|
||||
#endif
|
||||
|
||||
ngx_rtmp_variable_value_t *variables;
|
||||
|
||||
/* client buffer time in msec */
|
||||
uint32_t buflen;
|
||||
uint32_t ack_size;
|
||||
|
||||
/* connection parameters */
|
||||
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;
|
||||
|
||||
/* handshake data */
|
||||
ngx_buf_t *hs_buf;
|
||||
u_char *hs_digest;
|
||||
unsigned hs_old:1;
|
||||
ngx_uint_t hs_stage;
|
||||
|
||||
/* connection timestamps */
|
||||
ngx_msec_t epoch;
|
||||
ngx_msec_t peer_epoch;
|
||||
ngx_msec_t base_time;
|
||||
uint32_t current_time;
|
||||
|
||||
/* ping */
|
||||
ngx_event_t ping_evt;
|
||||
unsigned ping_active:1;
|
||||
unsigned ping_reset:1;
|
||||
|
||||
/* auto-pushed? */
|
||||
unsigned auto_pushed:1;
|
||||
unsigned relay:1;
|
||||
unsigned static_relay:1;
|
||||
|
||||
/* URI with "/." and on Win32 with "//" */
|
||||
unsigned complex_uri:1;
|
||||
/* URI with "%" */
|
||||
unsigned quoted_uri:1;
|
||||
/* URI with "+" */
|
||||
unsigned plus_in_uri:1;
|
||||
/* URI with " " */
|
||||
unsigned space_in_uri:1;
|
||||
|
||||
unsigned offset_timestamp_set:1;
|
||||
|
||||
uint32_t offset_timestamp;
|
||||
|
||||
u_char *uri_start;
|
||||
u_char *uri_end;
|
||||
u_char *args_start;
|
||||
u_char *schema_start;
|
||||
u_char *schema_end;
|
||||
u_char *host_start;
|
||||
u_char *host_end;
|
||||
u_char *port_start;
|
||||
u_char *port_end;
|
||||
|
||||
unsigned keepalive:1;
|
||||
|
||||
unsigned valid_unparsed_uri:1;
|
||||
|
||||
#if (NGX_PCRE)
|
||||
ngx_uint_t ncaptures;
|
||||
int *captures;
|
||||
u_char *captures_data;
|
||||
#endif
|
||||
|
||||
ngx_rtmp_connection_t *rtmp_connection;
|
||||
|
||||
ngx_rtmp_session_t *publisher;
|
||||
|
||||
ngx_pool_t *in_streams_pool;
|
||||
ngx_pool_t *in_streams_temp_pool;
|
||||
|
||||
ngx_pool_t *out_pool;
|
||||
ngx_pool_t *out_temp_pool;
|
||||
|
||||
unsigned server_changed:1;
|
||||
unsigned notify_connect:1;
|
||||
unsigned notify_play:1;
|
||||
|
||||
/* input stream 0 (reserved by RTMP spec)
|
||||
* is used as free chain link */
|
||||
|
||||
ngx_rtmp_stream_t *in_streams;
|
||||
uint32_t in_csid;
|
||||
ngx_uint_t in_chunk_size;
|
||||
ngx_pool_t *in_pool;
|
||||
uint32_t in_bytes;
|
||||
uint32_t in_last_ack;
|
||||
|
||||
ngx_pool_t *in_old_pool;
|
||||
ngx_int_t in_chunk_size_changing;
|
||||
|
||||
ngx_connection_t *connection;
|
||||
|
||||
/* circular buffer of RTMP message pointers */
|
||||
ngx_msec_t timeout;
|
||||
uint32_t out_bytes;
|
||||
size_t out_pos, out_last;
|
||||
ngx_chain_t *out_chain;
|
||||
u_char *out_bpos;
|
||||
unsigned out_buffer:1;
|
||||
size_t out_queue;
|
||||
size_t out_cork;
|
||||
ngx_chain_t **out;
|
||||
};
|
||||
|
||||
|
||||
#if (NGX_WIN32)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
|
||||
/* handler result code:
|
||||
* NGX_ERROR - error
|
||||
* NGX_OK - success, may continue
|
||||
* NGX_DONE - success, input parsed, reply sent; need no
|
||||
* more calls on this event */
|
||||
typedef ngx_int_t (*ngx_rtmp_handler_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t name;
|
||||
ngx_rtmp_handler_pt handler;
|
||||
} ngx_rtmp_amf_handler_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_array_t servers; /* ngx_rtmp_core_srv_conf_t */
|
||||
|
||||
ngx_array_t events[NGX_RTMP_MAX_EVENT];
|
||||
|
||||
ngx_hash_t amf_hash;
|
||||
ngx_array_t amf_arrays;
|
||||
ngx_array_t amf;
|
||||
|
||||
ngx_hash_t variables_hash;
|
||||
|
||||
ngx_array_t variables; /* ngx_http_variable_t */
|
||||
ngx_array_t prefix_variables; /* ngx_http_variable_t */
|
||||
ngx_uint_t ncaptures;
|
||||
|
||||
ngx_uint_t server_names_hash_max_size;
|
||||
ngx_uint_t server_names_hash_bucket_size;
|
||||
|
||||
ngx_uint_t variables_hash_max_size;
|
||||
ngx_uint_t variables_hash_bucket_size;
|
||||
|
||||
ngx_hash_keys_arrays_t *variables_keys;
|
||||
ngx_array_t *ports; /* ngx_rtmp_conf_port_t */
|
||||
} ngx_rtmp_core_main_conf_t;
|
||||
|
||||
|
||||
/* global main conf for stats */
|
||||
extern ngx_rtmp_core_main_conf_t *ngx_rtmp_core_main_conf;
|
||||
|
||||
|
||||
struct ngx_rtmp_core_srv_conf_s {
|
||||
/* array of the ngx_rtmp_server_name_t, "server_name" directive */
|
||||
ngx_array_t server_names;
|
||||
|
||||
ngx_array_t applications; /* ngx_rtmp_core_app_conf_t */
|
||||
|
||||
ngx_uint_t index; /* index in server array */
|
||||
|
||||
ngx_msec_t timeout;
|
||||
ngx_msec_t ping;
|
||||
ngx_msec_t ping_timeout;
|
||||
ngx_flag_t so_keepalive;
|
||||
ngx_int_t max_streams;
|
||||
|
||||
ngx_uint_t ack_window;
|
||||
|
||||
ngx_int_t chunk_size;
|
||||
ngx_pool_t *pool;
|
||||
ngx_chain_t *free;
|
||||
ngx_chain_t *free_hs;
|
||||
size_t max_message;
|
||||
ngx_flag_t play_time_fix;
|
||||
ngx_flag_t publish_time_fix;
|
||||
ngx_flag_t busy;
|
||||
size_t out_queue;
|
||||
size_t out_cork;
|
||||
ngx_msec_t buflen;
|
||||
|
||||
ngx_rtmp_conf_ctx_t *ctx;
|
||||
|
||||
ngx_str_t server_name;
|
||||
|
||||
size_t connection_pool_size;
|
||||
|
||||
ngx_flag_t merge_slashes;
|
||||
|
||||
unsigned listen:1;
|
||||
#if (NGX_PCRE)
|
||||
unsigned captures:1;
|
||||
#endif
|
||||
|
||||
in_port_t port;
|
||||
};
|
||||
|
||||
|
||||
struct ngx_rtmp_virtual_names_s {
|
||||
ngx_hash_combined_t names;
|
||||
|
||||
ngx_uint_t nregex;
|
||||
ngx_rtmp_server_name_t *regex;
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_array_t applications; /* ngx_rtmp_core_app_conf_t */
|
||||
ngx_str_t name;
|
||||
void **app_conf;
|
||||
|
||||
#if (NGX_PCRE)
|
||||
ngx_rtmp_regex_t *regex;
|
||||
#endif
|
||||
|
||||
size_t send_lowat;
|
||||
|
||||
ngx_msec_t send_timeout;
|
||||
ngx_msec_t resolver_timeout;
|
||||
|
||||
ngx_resolver_t *resolver;
|
||||
|
||||
ngx_flag_t tcp_nopush;
|
||||
ngx_flag_t tcp_nodelay;
|
||||
} ngx_rtmp_core_app_conf_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t *client;
|
||||
ngx_rtmp_session_t *session;
|
||||
} ngx_rtmp_error_log_ctx_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
|
||||
ngx_int_t (*postconfiguration)(ngx_conf_t *cf);
|
||||
|
||||
void *(*create_main_conf)(ngx_conf_t *cf);
|
||||
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
|
||||
|
||||
void *(*create_srv_conf)(ngx_conf_t *cf);
|
||||
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,
|
||||
void *conf);
|
||||
|
||||
void *(*create_app_conf)(ngx_conf_t *cf);
|
||||
char *(*merge_app_conf)(ngx_conf_t *cf, void *prev,
|
||||
void *conf);
|
||||
} ngx_rtmp_module_t;
|
||||
|
||||
#define NGX_RTMP_MODULE 0x504D5452 /* "RTMP" */
|
||||
|
||||
#define NGX_RTMP_MAIN_CONF 0x02000000
|
||||
#define NGX_RTMP_SRV_CONF 0x04000000
|
||||
#define NGX_RTMP_APP_CONF 0x08000000
|
||||
#define NGX_RTMP_REC_CONF 0x10000000
|
||||
#define NGX_RTMP_UPS_CONF 0x20000000
|
||||
|
||||
#define NGX_RTMP_MAIN_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, main_conf)
|
||||
#define NGX_RTMP_SRV_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, srv_conf)
|
||||
#define NGX_RTMP_APP_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, app_conf)
|
||||
|
||||
|
||||
#define ngx_rtmp_get_module_ctx(s, module) (s)->ctx[module.ctx_index]
|
||||
#define ngx_rtmp_set_ctx(s, c, module) s->ctx[module.ctx_index] = c;
|
||||
#define ngx_rtmp_delete_ctx(s, module) s->ctx[module.ctx_index] = NULL;
|
||||
|
||||
|
||||
#define ngx_rtmp_get_module_main_conf(s, module) \
|
||||
(s)->main_conf[module.ctx_index]
|
||||
#define ngx_rtmp_get_module_srv_conf(s, module) (s)->srv_conf[module.ctx_index]
|
||||
#define ngx_rtmp_get_module_app_conf(s, module) ((s)->app_conf ? \
|
||||
(s)->app_conf[module.ctx_index] : NULL)
|
||||
|
||||
#define ngx_rtmp_conf_get_module_main_conf(cf, module) \
|
||||
((ngx_rtmp_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
|
||||
#define ngx_rtmp_conf_get_module_srv_conf(cf, module) \
|
||||
((ngx_rtmp_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
|
||||
#define ngx_rtmp_conf_get_module_app_conf(cf, module) \
|
||||
((ngx_rtmp_conf_ctx_t *) cf->ctx)->app_conf[module.ctx_index]
|
||||
|
||||
|
||||
#ifdef NGX_DEBUG
|
||||
char *ngx_rtmp_message_type(uint8_t type);
|
||||
char *ngx_rtmp_user_message_type(uint16_t evt);
|
||||
#endif
|
||||
|
||||
void ngx_rtmp_init_connection(ngx_connection_t *c);
|
||||
ngx_rtmp_session_t *ngx_rtmp_init_session(ngx_connection_t *c,
|
||||
ngx_rtmp_addr_conf_t *addr_conf);
|
||||
void ngx_rtmp_finalize_session(ngx_rtmp_session_t *s);
|
||||
void ngx_rtmp_handshake(ngx_rtmp_session_t *s);
|
||||
void ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async);
|
||||
void ngx_rtmp_free_handshake_buffers(ngx_rtmp_session_t *s);
|
||||
void ngx_rtmp_cycle(ngx_rtmp_session_t *s);
|
||||
void ngx_rtmp_reset_ping(ngx_rtmp_session_t *s);
|
||||
|
||||
ngx_chain_t *ngx_rtmp_alloc_in_buf(ngx_rtmp_session_t *s);
|
||||
ngx_int_t ngx_rtmp_finalize_set_chunk_size(ngx_rtmp_session_t *s);
|
||||
|
||||
ngx_int_t ngx_rtmp_fire_event(ngx_rtmp_session_t *s, ngx_uint_t evt,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_set_chunk_size(ngx_rtmp_session_t *s, ngx_uint_t size);
|
||||
|
||||
|
||||
/* Bit reverse: we need big-endians in many places */
|
||||
void *ngx_rtmp_rmemcpy(void *dst, const void *src, size_t n);
|
||||
u_char *ngx_rtmp_h4_to_n3(u_char *dst, uint32_t h);
|
||||
uint32_t ngx_rtmp_n3_to_h4(u_char *n);
|
||||
|
||||
|
||||
static ngx_inline uint16_t
|
||||
ngx_rtmp_r16(uint16_t n)
|
||||
{
|
||||
#if (NGX_HAVE_LITTLE_ENDIAN)
|
||||
return (n << 8) | (n >> 8);
|
||||
#else
|
||||
return n;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static ngx_inline uint32_t
|
||||
ngx_rtmp_r32(uint32_t n)
|
||||
{
|
||||
#if (NGX_HAVE_LITTLE_ENDIAN)
|
||||
return (n << 24) | ((n << 8) & 0xff0000) | ((n >> 8) & 0xff00) | (n >> 24);
|
||||
#else
|
||||
return n;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static ngx_inline uint64_t
|
||||
ngx_rtmp_r64(uint64_t n)
|
||||
{
|
||||
#if (NGX_HAVE_LITTLE_ENDIAN)
|
||||
return (uint64_t) ngx_rtmp_r32((uint32_t) n) << 32 |
|
||||
ngx_rtmp_r32((uint32_t) (n >> 32));
|
||||
#else
|
||||
return n;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/* Receiving messages */
|
||||
ngx_int_t ngx_rtmp_receive_message(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
ngx_int_t ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
ngx_int_t ngx_rtmp_user_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
ngx_int_t ngx_rtmp_aggregate_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
ngx_int_t ngx_rtmp_amf_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
ngx_int_t ngx_rtmp_amf_shared_object_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
|
||||
|
||||
/* Shared output buffers */
|
||||
|
||||
/* Store refcount in negative bytes of shared buffer */
|
||||
|
||||
#define NGX_RTMP_REFCOUNT_TYPE uint32_t
|
||||
#define NGX_RTMP_REFCOUNT_BYTES sizeof(NGX_RTMP_REFCOUNT_TYPE)
|
||||
|
||||
#define ngx_rtmp_ref(b) \
|
||||
*((NGX_RTMP_REFCOUNT_TYPE*)(b) - 1)
|
||||
|
||||
#define ngx_rtmp_ref_set(b, v) \
|
||||
ngx_rtmp_ref(b) = v
|
||||
|
||||
#define ngx_rtmp_ref_get(b) \
|
||||
++ngx_rtmp_ref(b)
|
||||
|
||||
#define ngx_rtmp_ref_put(b) \
|
||||
--ngx_rtmp_ref(b)
|
||||
|
||||
ngx_chain_t *ngx_rtmp_alloc_shared_buf(ngx_rtmp_core_srv_conf_t *cscf);
|
||||
void ngx_rtmp_free_shared_chain(ngx_rtmp_core_srv_conf_t *cscf,
|
||||
ngx_chain_t *in);
|
||||
ngx_chain_t *ngx_rtmp_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf,
|
||||
ngx_chain_t *head, ngx_chain_t *in);
|
||||
|
||||
#define ngx_rtmp_acquire_shared_chain(in) \
|
||||
ngx_rtmp_ref_get(in); \
|
||||
|
||||
|
||||
/* Sending messages */
|
||||
void ngx_rtmp_prepare_message(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_rtmp_header_t *lh, ngx_chain_t *out);
|
||||
ngx_int_t ngx_rtmp_send_message(ngx_rtmp_session_t *s, ngx_chain_t *out,
|
||||
ngx_uint_t priority);
|
||||
|
||||
/* Note on priorities:
|
||||
* the bigger value the lower the priority.
|
||||
* priority=0 is the highest */
|
||||
|
||||
|
||||
#define NGX_RTMP_LIMIT_SOFT 0
|
||||
#define NGX_RTMP_LIMIT_HARD 1
|
||||
#define NGX_RTMP_LIMIT_DYNAMIC 2
|
||||
|
||||
/* Protocol control messages */
|
||||
ngx_chain_t *ngx_rtmp_create_chunk_size(ngx_rtmp_session_t *s,
|
||||
uint32_t chunk_size);
|
||||
ngx_chain_t *ngx_rtmp_create_abort(ngx_rtmp_session_t *s,
|
||||
uint32_t csid);
|
||||
ngx_chain_t *ngx_rtmp_create_ack(ngx_rtmp_session_t *s,
|
||||
uint32_t seq);
|
||||
ngx_chain_t *ngx_rtmp_create_ack_size(ngx_rtmp_session_t *s,
|
||||
uint32_t ack_size);
|
||||
ngx_chain_t *ngx_rtmp_create_bandwidth(ngx_rtmp_session_t *s,
|
||||
uint32_t ack_size, uint8_t limit_type);
|
||||
|
||||
ngx_int_t ngx_rtmp_send_chunk_size(ngx_rtmp_session_t *s,
|
||||
uint32_t chunk_size);
|
||||
ngx_int_t ngx_rtmp_send_abort(ngx_rtmp_session_t *s,
|
||||
uint32_t csid);
|
||||
ngx_int_t ngx_rtmp_send_ack(ngx_rtmp_session_t *s,
|
||||
uint32_t seq);
|
||||
ngx_int_t ngx_rtmp_send_ack_size(ngx_rtmp_session_t *s,
|
||||
uint32_t ack_size);
|
||||
ngx_int_t ngx_rtmp_send_bandwidth(ngx_rtmp_session_t *s,
|
||||
uint32_t ack_size, uint8_t limit_type);
|
||||
|
||||
/* User control messages */
|
||||
ngx_chain_t *ngx_rtmp_create_stream_begin(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_chain_t *ngx_rtmp_create_stream_eof(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_chain_t *ngx_rtmp_create_stream_dry(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_chain_t *ngx_rtmp_create_set_buflen(ngx_rtmp_session_t *s,
|
||||
uint32_t msid, uint32_t buflen_msec);
|
||||
ngx_chain_t *ngx_rtmp_create_recorded(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_chain_t *ngx_rtmp_create_ping_request(ngx_rtmp_session_t *s,
|
||||
uint32_t timestamp);
|
||||
ngx_chain_t *ngx_rtmp_create_ping_response(ngx_rtmp_session_t *s,
|
||||
uint32_t timestamp);
|
||||
|
||||
ngx_int_t ngx_rtmp_send_stream_begin(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_int_t ngx_rtmp_send_stream_eof(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_int_t ngx_rtmp_send_stream_dry(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_int_t ngx_rtmp_send_set_buflen(ngx_rtmp_session_t *s,
|
||||
uint32_t msid, uint32_t buflen_msec);
|
||||
ngx_int_t ngx_rtmp_send_recorded(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_int_t ngx_rtmp_send_ping_request(ngx_rtmp_session_t *s,
|
||||
uint32_t timestamp);
|
||||
ngx_int_t ngx_rtmp_send_ping_response(ngx_rtmp_session_t *s,
|
||||
uint32_t timestamp);
|
||||
|
||||
/* AMF sender/receiver */
|
||||
ngx_int_t ngx_rtmp_append_amf(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t **first, ngx_chain_t **last,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts);
|
||||
ngx_int_t ngx_rtmp_receive_amf(ngx_rtmp_session_t *s, ngx_chain_t *in,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts);
|
||||
|
||||
ngx_chain_t *ngx_rtmp_create_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts);
|
||||
ngx_int_t ngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts);
|
||||
|
||||
/* AMF status sender */
|
||||
ngx_chain_t *ngx_rtmp_create_status(ngx_rtmp_session_t *s, char *code,
|
||||
char *level, char *desc);
|
||||
ngx_chain_t *ngx_rtmp_create_play_status(ngx_rtmp_session_t *s, char *code,
|
||||
char *level, ngx_uint_t duration, ngx_uint_t bytes);
|
||||
ngx_chain_t *ngx_rtmp_create_sample_access(ngx_rtmp_session_t *s);
|
||||
|
||||
ngx_int_t ngx_rtmp_send_status(ngx_rtmp_session_t *s, char *code,
|
||||
char *level, char *desc);
|
||||
ngx_int_t ngx_rtmp_send_play_status(ngx_rtmp_session_t *s, char *code,
|
||||
char *level, ngx_uint_t duration, ngx_uint_t bytes);
|
||||
ngx_int_t ngx_rtmp_send_sample_access(ngx_rtmp_session_t *s);
|
||||
|
||||
|
||||
/* Frame types */
|
||||
#define NGX_RTMP_VIDEO_KEY_FRAME 1
|
||||
#define NGX_RTMP_VIDEO_INTER_FRAME 2
|
||||
#define NGX_RTMP_VIDEO_DISPOSABLE_FRAME 3
|
||||
|
||||
|
||||
static ngx_inline ngx_int_t
|
||||
ngx_rtmp_get_video_frame_type(ngx_chain_t *in)
|
||||
{
|
||||
return (in->buf->pos[0] & 0xf0) >> 4;
|
||||
}
|
||||
|
||||
|
||||
static ngx_inline ngx_int_t
|
||||
ngx_rtmp_is_codec_header(ngx_chain_t *in)
|
||||
{
|
||||
return in->buf->pos + 1 < in->buf->last && in->buf->pos[1] == 0;
|
||||
}
|
||||
|
||||
|
||||
extern ngx_rtmp_bandwidth_t ngx_rtmp_bw_out;
|
||||
extern ngx_rtmp_bandwidth_t ngx_rtmp_bw_in;
|
||||
|
||||
|
||||
extern ngx_uint_t ngx_rtmp_naccepted;
|
||||
#if (nginx_version >= 1007011)
|
||||
extern ngx_queue_t ngx_rtmp_init_queue;
|
||||
#elif (nginx_version >= 1007005)
|
||||
extern ngx_thread_volatile ngx_queue_t ngx_rtmp_init_queue;
|
||||
#else
|
||||
extern ngx_thread_volatile ngx_event_t *ngx_rtmp_init_queue;
|
||||
#endif
|
||||
|
||||
extern ngx_uint_t ngx_rtmp_max_module;
|
||||
extern ngx_module_t ngx_rtmp_core_module;
|
||||
|
||||
|
||||
u_char *ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len);
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_parse_request_line(ngx_rtmp_session_t *s, ngx_buf_t *b);
|
||||
ngx_int_t ngx_rtmp_process_request_uri(ngx_rtmp_session_t *s);
|
||||
ngx_int_t ngx_rtmp_parse_complex_uri(ngx_rtmp_session_t *s,
|
||||
ngx_uint_t merge_slashes);
|
||||
|
||||
ngx_int_t ngx_rtmp_process_virtual_host(ngx_rtmp_session_t *s);
|
||||
ngx_int_t ngx_rtmp_validate_host(ngx_str_t *host, ngx_pool_t *pool,
|
||||
ngx_uint_t alloc);
|
||||
ngx_int_t ngx_rtmp_set_virtual_server(ngx_rtmp_session_t *s, ngx_str_t *host);
|
||||
ngx_int_t ngx_rtmp_process_request_line(ngx_rtmp_session_t *s,
|
||||
const u_char *name, const u_char *args, const u_char *cmd);
|
||||
#if (nginx_version <= 1011001)
|
||||
in_port_t ngx_inet_get_port(struct sockaddr *sa);
|
||||
void ngx_inet_set_port(struct sockaddr *sa, in_port_t port);
|
||||
#endif
|
||||
|
||||
ngx_int_t ngx_rtmp_send_fcpublish(ngx_rtmp_session_t *s, u_char *desc);
|
||||
ngx_int_t ngx_rtmp_send_fcunpublish(ngx_rtmp_session_t *s, u_char *desc);
|
||||
|
||||
#endif /* _NGX_RTMP_H_INCLUDED_ */
|
||||
472
ngx_http_flv_module/ngx_rtmp_access_module.c
Normal file
472
ngx_http_flv_module/ngx_rtmp_access_module.c
Normal file
@@ -0,0 +1,472 @@
|
||||
|
||||
/*
|
||||
* 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->connection->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->connection->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->connection->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->connection->log, 0,
|
||||
"access: NULL app conf");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* relay etc */
|
||||
if (s->connection->sockaddr == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
switch (s->connection->sockaddr->sa_family) {
|
||||
|
||||
case AF_INET:
|
||||
sin = (struct sockaddr_in *) s->connection->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->connection->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;
|
||||
}
|
||||
|
||||
/* "all" passes through */
|
||||
#endif
|
||||
/* fall through */
|
||||
|
||||
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->auto_pushed) {
|
||||
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;
|
||||
}
|
||||
653
ngx_http_flv_module/ngx_rtmp_amf.c
Normal file
653
ngx_http_flv_module/ngx_rtmp_amf.c
Normal file
@@ -0,0 +1,653 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#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)
|
||||
{
|
||||
if (dst == NULL || src == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_LITTLE_ENDIAN)
|
||||
ngx_rtmp_rmemcpy(dst, src, len);
|
||||
#else
|
||||
ngx_memcpy(dst, src, len);
|
||||
#endif
|
||||
|
||||
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 && elts->type & NGX_RTMP_AMF_OPTIONAL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* fall through */
|
||||
|
||||
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 && 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;
|
||||
}
|
||||
|
||||
/* fall through */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* fall through */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
71
ngx_http_flv_module/ngx_rtmp_amf.h
Normal file
71
ngx_http_flv_module/ngx_rtmp_amf.h
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_AMF_H_INCLUDED_
|
||||
#define _NGX_RTMP_AMF_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
|
||||
|
||||
/* basic types */
|
||||
#define NGX_RTMP_AMF_NUMBER 0x00
|
||||
#define NGX_RTMP_AMF_BOOLEAN 0x01
|
||||
#define NGX_RTMP_AMF_STRING 0x02
|
||||
#define NGX_RTMP_AMF_OBJECT 0x03
|
||||
#define NGX_RTMP_AMF_NULL 0x05
|
||||
#define NGX_RTMP_AMF_ARRAY_NULL 0x06
|
||||
#define NGX_RTMP_AMF_MIXED_ARRAY 0x08
|
||||
#define NGX_RTMP_AMF_END 0x09
|
||||
#define NGX_RTMP_AMF_ARRAY 0x0a
|
||||
|
||||
/* extended types */
|
||||
#define NGX_RTMP_AMF_INT8 0x0100
|
||||
#define NGX_RTMP_AMF_INT16 0x0101
|
||||
#define NGX_RTMP_AMF_INT32 0x0102
|
||||
#define NGX_RTMP_AMF_VARIANT_ 0x0103
|
||||
|
||||
/* r/w flags */
|
||||
#define NGX_RTMP_AMF_OPTIONAL 0x1000
|
||||
#define NGX_RTMP_AMF_TYPELESS 0x2000
|
||||
#define NGX_RTMP_AMF_CONTEXT 0x4000
|
||||
|
||||
#define NGX_RTMP_AMF_VARIANT (NGX_RTMP_AMF_VARIANT_\
|
||||
|NGX_RTMP_AMF_TYPELESS)
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_int_t type;
|
||||
ngx_str_t name;
|
||||
void *data;
|
||||
size_t len;
|
||||
} ngx_rtmp_amf_elt_t;
|
||||
|
||||
|
||||
typedef ngx_chain_t * (*ngx_rtmp_amf_alloc_pt)(void *arg);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_chain_t *link, *first;
|
||||
size_t offset;
|
||||
ngx_rtmp_amf_alloc_pt alloc;
|
||||
void *arg;
|
||||
ngx_log_t *log;
|
||||
} ngx_rtmp_amf_ctx_t;
|
||||
|
||||
|
||||
/* reading AMF */
|
||||
ngx_int_t ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts);
|
||||
|
||||
/* writing AMF */
|
||||
ngx_int_t ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_AMF_H_INCLUDED_ */
|
||||
|
||||
706
ngx_http_flv_module/ngx_rtmp_auto_push_module.c
Normal file
706
ngx_http_flv_module/ngx_rtmp_auto_push_module.c
Normal file
@@ -0,0 +1,706 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_cmd_module.h"
|
||||
#include "ngx_rtmp_relay_module.h"
|
||||
|
||||
|
||||
static ngx_rtmp_publish_pt next_publish;
|
||||
static ngx_rtmp_delete_stream_pt next_delete_stream;
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_auto_push_init_process(ngx_cycle_t *cycle);
|
||||
static void ngx_rtmp_auto_push_exit_process(ngx_cycle_t *cycle);
|
||||
static void * ngx_rtmp_auto_push_create_conf(ngx_cycle_t *cf);
|
||||
static char * ngx_rtmp_auto_push_init_conf(ngx_cycle_t *cycle, void *conf);
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
static ngx_int_t ngx_rtmp_auto_push_publish(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_publish_t *v);
|
||||
static ngx_int_t ngx_rtmp_auto_push_delete_stream(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_delete_stream_t *v);
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_auto_push_ctx_s ngx_rtmp_auto_push_ctx_t;
|
||||
|
||||
struct ngx_rtmp_auto_push_ctx_s {
|
||||
ngx_int_t *slots; /* NGX_MAX_PROCESSES */
|
||||
u_char name[NGX_RTMP_MAX_NAME];
|
||||
u_char args[NGX_RTMP_MAX_ARGS];
|
||||
ngx_event_t push_evt;
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_flag_t auto_push;
|
||||
ngx_str_t socket_dir;
|
||||
ngx_msec_t push_reconnect;
|
||||
} ngx_rtmp_auto_push_conf_t;
|
||||
|
||||
|
||||
static ngx_command_t ngx_rtmp_auto_push_commands[] = {
|
||||
|
||||
{ ngx_string("rtmp_auto_push"),
|
||||
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_flag_slot,
|
||||
0,
|
||||
offsetof(ngx_rtmp_auto_push_conf_t, auto_push),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("rtmp_auto_push_reconnect"),
|
||||
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_msec_slot,
|
||||
0,
|
||||
offsetof(ngx_rtmp_auto_push_conf_t, push_reconnect),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("rtmp_socket_dir"),
|
||||
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_str_slot,
|
||||
0,
|
||||
offsetof(ngx_rtmp_auto_push_conf_t, socket_dir),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_core_module_t ngx_rtmp_auto_push_module_ctx = {
|
||||
ngx_string("rtmp_auto_push"),
|
||||
ngx_rtmp_auto_push_create_conf, /* create conf */
|
||||
ngx_rtmp_auto_push_init_conf /* init conf */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_auto_push_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_auto_push_module_ctx, /* module context */
|
||||
ngx_rtmp_auto_push_commands, /* module directives */
|
||||
NGX_CORE_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
ngx_rtmp_auto_push_init_process, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
ngx_rtmp_auto_push_exit_process, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_auto_push_index_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
NULL, /* postconfiguration */
|
||||
NULL, /* 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_auto_push_index_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_auto_push_index_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 */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
#define NGX_RTMP_AUTO_PUSH_SOCKNAME "nginx-http-flv"
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_auto_push_init_process(ngx_cycle_t *cycle)
|
||||
{
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
ngx_rtmp_auto_push_conf_t *apcf;
|
||||
ngx_listening_t *ls, *lss;
|
||||
struct sockaddr_un *saun;
|
||||
#if (nginx_version >= 1009011)
|
||||
ngx_event_t *rev;
|
||||
ngx_connection_t *c;
|
||||
ngx_module_t **modules;
|
||||
ngx_int_t i, auto_push_index, event_core_index;
|
||||
#endif
|
||||
int reuseaddr;
|
||||
ngx_socket_t s;
|
||||
size_t n;
|
||||
ngx_file_info_t fi;
|
||||
ngx_pid_t pid;
|
||||
|
||||
if (ngx_process != NGX_PROCESS_WORKER) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(cycle->conf_ctx,
|
||||
ngx_rtmp_auto_push_module);
|
||||
if (apcf->auto_push == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
next_publish = ngx_rtmp_publish;
|
||||
ngx_rtmp_publish = ngx_rtmp_auto_push_publish;
|
||||
|
||||
next_delete_stream = ngx_rtmp_delete_stream;
|
||||
ngx_rtmp_delete_stream = ngx_rtmp_auto_push_delete_stream;
|
||||
|
||||
reuseaddr = 1;
|
||||
s = (ngx_socket_t) -1;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, cycle->log, 0,
|
||||
"auto_push: creating sockets");
|
||||
|
||||
/*TODO: clone all RTMP listenings? */
|
||||
ls = cycle->listening.elts;
|
||||
lss = NULL;
|
||||
for (n = 0; n < cycle->listening.nelts; ++n, ++ls) {
|
||||
if (ls->handler == ngx_rtmp_init_connection) {
|
||||
lss = ls;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lss == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ls = ngx_array_push(&cycle->listening);
|
||||
if (ls == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
*ls = *lss;
|
||||
|
||||
/* Disable unix socket client address extraction
|
||||
* from accept call
|
||||
* Nginx generates bad addr_text with this enabled */
|
||||
ls->addr_ntop = 0;
|
||||
|
||||
ls->socklen = sizeof(struct sockaddr_un);
|
||||
saun = ngx_pcalloc(cycle->pool, ls->socklen);
|
||||
ls->sockaddr = (struct sockaddr *) saun;
|
||||
if (ls->sockaddr == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
saun->sun_family = AF_UNIX;
|
||||
pid = ngx_getpid();
|
||||
*ngx_snprintf((u_char *) saun->sun_path, sizeof(saun->sun_path),
|
||||
"%V/" NGX_RTMP_AUTO_PUSH_SOCKNAME ".%P",
|
||||
&apcf->socket_dir, pid)
|
||||
= 0;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, cycle->log, 0,
|
||||
"auto_push: create socket '%s'",
|
||||
saun->sun_path);
|
||||
|
||||
if (ngx_file_info(saun->sun_path, &fi) != ENOENT) {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, cycle->log, 0,
|
||||
"auto_push: delete existing socket '%s'",
|
||||
saun->sun_path);
|
||||
ngx_delete_file(saun->sun_path);
|
||||
}
|
||||
|
||||
ls->addr_text.data = (u_char *) saun->sun_path;
|
||||
ls->addr_text.len = ngx_strlen(saun->sun_path);
|
||||
|
||||
s = ngx_socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (s == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
|
||||
ngx_socket_n " %s failed", saun->sun_path);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
|
||||
(const void *) &reuseaddr, sizeof(int))
|
||||
== -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
|
||||
"setsockopt(SO_REUSEADDR) %s failed", saun->sun_path);
|
||||
goto sock_error;
|
||||
}
|
||||
|
||||
if (!(ngx_event_flags & NGX_USE_AIO_EVENT)) {
|
||||
if (ngx_nonblocking(s) == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
|
||||
ngx_nonblocking_n " %s failed", saun->sun_path);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (bind(s, (struct sockaddr *) saun, sizeof(*saun)) == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
|
||||
ngx_nonblocking_n " %s bind failed", saun->sun_path);
|
||||
goto sock_error;
|
||||
}
|
||||
|
||||
if (listen(s, NGX_LISTEN_BACKLOG) == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
|
||||
"listen() to %s, backlog %d failed",
|
||||
saun->sun_path, NGX_LISTEN_BACKLOG);
|
||||
goto sock_error;
|
||||
}
|
||||
|
||||
ls->fd = s;
|
||||
ls->listen = 1;
|
||||
|
||||
/* Socket option `SO_REUSEPORT` has been supported since nginx-1.9.1,
|
||||
* if option `reuseport` is added for the directive `listen`, listening
|
||||
* structure of unix domain socket in the non-first process will not be
|
||||
* initialized, in fact `reuseport` is useless for a unix domain socket
|
||||
* on which there is only a process listening */
|
||||
|
||||
#if (NGX_HAVE_REUSEPORT)
|
||||
ls->reuseport = 0;
|
||||
#endif
|
||||
|
||||
/* for dynamic module */
|
||||
#if (nginx_version >= 1009011)
|
||||
auto_push_index = -1;
|
||||
event_core_index = -1;
|
||||
|
||||
modules = cycle->modules;
|
||||
|
||||
for (i = 0; modules[i]; ++i) {
|
||||
if (ngx_strcmp(modules[i]->name, "ngx_event_core_module") == 0) {
|
||||
event_core_index = i;
|
||||
}
|
||||
|
||||
if (ngx_strcmp(modules[i]->name, "ngx_rtmp_auto_push_module") == 0) {
|
||||
auto_push_index = i;
|
||||
}
|
||||
|
||||
if (auto_push_index != -1 && event_core_index != -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto_push_index > event_core_index) {
|
||||
c = ngx_get_connection(ls->fd, cycle->log);
|
||||
if (c == NULL) {
|
||||
goto sock_error;
|
||||
}
|
||||
|
||||
rev = c->read;
|
||||
|
||||
#if (nginx_version >= 1009013)
|
||||
c->type = ls->type;
|
||||
#endif
|
||||
c->log = &ls->log;
|
||||
|
||||
c->listening = ls;
|
||||
ls->connection = c;
|
||||
|
||||
rev->log = c->log;
|
||||
rev->accept = 1;
|
||||
|
||||
#if (NGX_HAVE_DEFERRED_ACCEPT)
|
||||
rev->deferred_accept = ls->deferred_accept;
|
||||
#endif
|
||||
|
||||
#if (nginx_version >= 1009013)
|
||||
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
|
||||
: ngx_event_recvmsg;
|
||||
#else
|
||||
rev->handler = ngx_event_accept;
|
||||
#endif
|
||||
|
||||
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
sock_error:
|
||||
if (s != (ngx_socket_t) -1 && ngx_close_socket(s) == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
|
||||
ngx_close_socket_n " %s failed", saun->sun_path);
|
||||
}
|
||||
ngx_delete_file(saun->sun_path);
|
||||
|
||||
return NGX_ERROR;
|
||||
|
||||
#else /* NGX_HAVE_UNIX_DOMAIN */
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
#endif /* NGX_HAVE_UNIX_DOMAIN */
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_auto_push_exit_process(ngx_cycle_t *cycle)
|
||||
{
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
ngx_rtmp_auto_push_conf_t *apcf;
|
||||
u_char path[NGX_MAX_PATH];
|
||||
|
||||
ngx_listening_t *ls;
|
||||
ngx_connection_t *c;
|
||||
size_t n;
|
||||
ngx_pid_t pid;
|
||||
|
||||
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(cycle->conf_ctx,
|
||||
ngx_rtmp_auto_push_module);
|
||||
if (apcf->auto_push == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ls = cycle->listening.elts;
|
||||
|
||||
for (n = 0; n < cycle->listening.nelts; ++n, ++ls) {
|
||||
if ((ls->handler == ngx_rtmp_init_connection) &&
|
||||
(ls->sockaddr && ls->sockaddr->sa_family == AF_UNIX))
|
||||
{
|
||||
c = ls->connection;
|
||||
|
||||
if (c) {
|
||||
if (c->read->active) {
|
||||
if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
|
||||
|
||||
/*
|
||||
* delete the old accept events that were bound to
|
||||
* the old cycle read events array
|
||||
*/
|
||||
|
||||
ngx_del_event(c->read,
|
||||
NGX_READ_EVENT, NGX_CLOSE_EVENT);
|
||||
|
||||
ngx_free_connection(c);
|
||||
|
||||
c->fd = (ngx_socket_t) -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ngx_close_socket(ls->fd) == -1) {
|
||||
ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_socket_errno,
|
||||
ngx_close_socket_n "%V failed",
|
||||
&ls->addr_text);
|
||||
}
|
||||
|
||||
ls->fd = (ngx_socket_t) -1;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pid = ngx_getpid();
|
||||
*ngx_snprintf(path, sizeof(path),
|
||||
"%V/" NGX_RTMP_AUTO_PUSH_SOCKNAME ".%P",
|
||||
&apcf->socket_dir, pid)
|
||||
= 0;
|
||||
|
||||
ngx_delete_file(path);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_rtmp_auto_push_create_conf(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_rtmp_auto_push_conf_t *apcf;
|
||||
|
||||
apcf = ngx_pcalloc(cycle->pool, sizeof(ngx_rtmp_auto_push_conf_t));
|
||||
if (apcf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
apcf->auto_push = NGX_CONF_UNSET;
|
||||
apcf->push_reconnect = NGX_CONF_UNSET_MSEC;
|
||||
|
||||
return apcf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_auto_push_init_conf(ngx_cycle_t *cycle, void *conf)
|
||||
{
|
||||
ngx_rtmp_auto_push_conf_t *apcf = conf;
|
||||
|
||||
ngx_conf_init_value(apcf->auto_push, 0);
|
||||
ngx_conf_init_msec_value(apcf->push_reconnect, 100);
|
||||
|
||||
if (apcf->socket_dir.len == 0) {
|
||||
ngx_str_set(&apcf->socket_dir, "/tmp");
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
static void
|
||||
ngx_rtmp_auto_push_reconnect(ngx_event_t *ev)
|
||||
{
|
||||
ngx_rtmp_session_t *s = ev->data;
|
||||
|
||||
ngx_rtmp_auto_push_conf_t *apcf;
|
||||
ngx_rtmp_auto_push_ctx_t *ctx;
|
||||
ngx_int_t *slot;
|
||||
ngx_int_t n;
|
||||
ngx_rtmp_relay_target_t at;
|
||||
u_char path[sizeof("unix:") + NGX_MAX_PATH];
|
||||
u_char flash_ver[sizeof("APSH ,") +
|
||||
NGX_INT_T_LEN * 2];
|
||||
u_char play_path[NGX_RTMP_MAX_NAME];
|
||||
ngx_str_t name;
|
||||
u_char *p;
|
||||
ngx_str_t *u;
|
||||
ngx_pid_t pid;
|
||||
ngx_int_t npushed;
|
||||
ngx_core_conf_t *ccf;
|
||||
ngx_file_info_t fi;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"auto_push: reconnect");
|
||||
|
||||
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
|
||||
ngx_rtmp_auto_push_module);
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);
|
||||
if (ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
name.data = ctx->name;
|
||||
name.len = ngx_strlen(name.data);
|
||||
|
||||
ngx_memzero(&at, sizeof(at));
|
||||
ngx_str_set(&at.page_url, "nginx-auto-push");
|
||||
at.tag = &ngx_rtmp_auto_push_module;
|
||||
|
||||
if (s->app.len) {
|
||||
at.app.data = s->app.data;
|
||||
at.app.len = s->app.len;
|
||||
}
|
||||
|
||||
if (ctx->args[0]) {
|
||||
at.play_path.data = play_path;
|
||||
at.play_path.len = ngx_snprintf(play_path, sizeof(play_path),
|
||||
"%s?%s", ctx->name, ctx->args) -
|
||||
play_path;
|
||||
}
|
||||
|
||||
slot = ctx->slots;
|
||||
npushed = 0;
|
||||
|
||||
for (n = 0; n < NGX_MAX_PROCESSES; ++n, ++slot) {
|
||||
if (n == ngx_process_slot) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pid = ngx_processes[n].pid;
|
||||
if (pid == 0 || pid == NGX_INVALID_PID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*slot) {
|
||||
npushed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
at.data = &ngx_processes[n];
|
||||
|
||||
ngx_memzero(&at.url, sizeof(at.url));
|
||||
u = &at.url.url;
|
||||
p = ngx_snprintf(path, sizeof(path) - 1,
|
||||
"unix:%V/" NGX_RTMP_AUTO_PUSH_SOCKNAME ".%P",
|
||||
&apcf->socket_dir, pid);
|
||||
*p = 0;
|
||||
|
||||
if (ngx_file_info(path + sizeof("unix:") - 1, &fi) != NGX_OK) {
|
||||
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"auto_push: " ngx_file_info_n " failed: "
|
||||
"slot=%i pid=%P socket='%s'" "url='%V' name='%s'",
|
||||
n, pid, path, u, ctx->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
u->data = path;
|
||||
u->len = p - path;
|
||||
if (ngx_parse_url(s->connection->pool, &at.url) != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"auto_push: auto-push parse_url failed "
|
||||
"url='%V' name='%s'",
|
||||
u, ctx->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
p = ngx_snprintf(flash_ver, sizeof(flash_ver) - 1, "APSH %i,%i",
|
||||
(ngx_int_t) ngx_process_slot, (ngx_int_t) ngx_pid);
|
||||
at.flash_ver.data = flash_ver;
|
||||
at.flash_ver.len = p - flash_ver;
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"auto_push: connect slot=%i pid=%P socket='%s' name='%s'",
|
||||
n, pid, path, ctx->name);
|
||||
|
||||
if (ngx_rtmp_relay_push(s, &name, &at) == NGX_OK) {
|
||||
*slot = 1;
|
||||
npushed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"auto_push: connect failed: slot=%i pid=%P socket='%s'"
|
||||
"url='%V' name='%s'",
|
||||
n, pid, path, u, ctx->name);
|
||||
}
|
||||
|
||||
ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
|
||||
ngx_core_module);
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"auto_push: pushed=%i total=%i failed=%i",
|
||||
npushed, ccf->worker_processes,
|
||||
ccf->worker_processes - 1 - npushed);
|
||||
|
||||
if (ccf->worker_processes == npushed + 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* several workers failed */
|
||||
|
||||
slot = ctx->slots;
|
||||
|
||||
for (n = 0; n < NGX_MAX_PROCESSES; ++n, ++slot) {
|
||||
pid = ngx_processes[n].pid;
|
||||
|
||||
if (n == ngx_process_slot || *slot == 1 ||
|
||||
pid == 0 || pid == NGX_INVALID_PID)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"auto_push: connect failed: slot=%i pid=%P name='%s'",
|
||||
n, pid, ctx->name);
|
||||
}
|
||||
|
||||
if (!ctx->push_evt.timer_set) {
|
||||
ngx_add_timer(&ctx->push_evt, apcf->push_reconnect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_auto_push_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
|
||||
{
|
||||
ngx_rtmp_auto_push_conf_t *apcf;
|
||||
ngx_rtmp_auto_push_ctx_t *ctx;
|
||||
|
||||
if (s->auto_pushed || (s->relay && !s->static_relay)) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
|
||||
ngx_rtmp_auto_push_module);
|
||||
if (apcf->auto_push == 0) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);
|
||||
if (ctx == NULL) {
|
||||
ctx = ngx_palloc(s->connection->pool,
|
||||
sizeof(ngx_rtmp_auto_push_ctx_t));
|
||||
if (ctx == NULL) {
|
||||
goto next;
|
||||
}
|
||||
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_auto_push_index_module);
|
||||
|
||||
}
|
||||
ngx_memzero(ctx, sizeof(*ctx));
|
||||
|
||||
ctx->push_evt.data = s;
|
||||
ctx->push_evt.log = s->connection->log;
|
||||
ctx->push_evt.handler = ngx_rtmp_auto_push_reconnect;
|
||||
|
||||
ctx->slots = ngx_pcalloc(s->connection->pool,
|
||||
sizeof(ngx_int_t) * NGX_MAX_PROCESSES);
|
||||
if (ctx->slots == NULL) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
ngx_memcpy(ctx->name, v->name, sizeof(ctx->name));
|
||||
ngx_memcpy(ctx->args, v->args, sizeof(ctx->args));
|
||||
|
||||
ngx_rtmp_auto_push_reconnect(&ctx->push_evt);
|
||||
|
||||
next:
|
||||
return next_publish(s, v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_auto_push_delete_stream(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_delete_stream_t *v)
|
||||
{
|
||||
ngx_rtmp_auto_push_conf_t *apcf;
|
||||
ngx_rtmp_auto_push_ctx_t *ctx, *pctx;
|
||||
ngx_rtmp_relay_ctx_t *rctx;
|
||||
ngx_int_t slot;
|
||||
|
||||
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
|
||||
ngx_rtmp_auto_push_module);
|
||||
if (apcf->auto_push == 0) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);
|
||||
if (ctx) {
|
||||
if (ctx->push_evt.timer_set) {
|
||||
ngx_del_timer(&ctx->push_evt);
|
||||
}
|
||||
goto next;
|
||||
}
|
||||
|
||||
/* skip non-relays & publishers */
|
||||
rctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
|
||||
if (rctx == NULL ||
|
||||
rctx->tag != &ngx_rtmp_auto_push_module ||
|
||||
rctx->publish == NULL)
|
||||
{
|
||||
goto next;
|
||||
}
|
||||
|
||||
slot = (ngx_process_t *) rctx->data - &ngx_processes[0];
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"auto_push: disconnect slot=%i app='%V' name='%V'",
|
||||
slot, &rctx->app, &rctx->name);
|
||||
|
||||
pctx = ngx_rtmp_get_module_ctx(rctx->publish->session,
|
||||
ngx_rtmp_auto_push_index_module);
|
||||
if (pctx == NULL) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
pctx->slots[slot] = 0;
|
||||
|
||||
/* push reconnect */
|
||||
if (!pctx->push_evt.timer_set) {
|
||||
ngx_add_timer(&pctx->push_evt, apcf->push_reconnect);
|
||||
}
|
||||
|
||||
next:
|
||||
return next_delete_stream(s, v);
|
||||
}
|
||||
#endif /* NGX_HAVE_UNIX_DOMAIN */
|
||||
26
ngx_http_flv_module/ngx_rtmp_bandwidth.c
Normal file
26
ngx_http_flv_module/ngx_rtmp_bandwidth.c
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_bandwidth.h"
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes)
|
||||
{
|
||||
if (ngx_cached_time->sec > bw->intl_end) {
|
||||
bw->bandwidth = ngx_cached_time->sec >
|
||||
bw->intl_end + NGX_RTMP_BANDWIDTH_INTERVAL
|
||||
? 0
|
||||
: bw->intl_bytes / NGX_RTMP_BANDWIDTH_INTERVAL;
|
||||
bw->intl_bytes = 0;
|
||||
bw->intl_end = ngx_cached_time->sec + NGX_RTMP_BANDWIDTH_INTERVAL;
|
||||
}
|
||||
|
||||
bw->bytes += bytes;
|
||||
bw->intl_bytes += bytes;
|
||||
}
|
||||
31
ngx_http_flv_module/ngx_rtmp_bandwidth.h
Normal file
31
ngx_http_flv_module/ngx_rtmp_bandwidth.h
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_BANDWIDTH_H_INCLUDED_
|
||||
#define _NGX_RTMP_BANDWIDTH_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
|
||||
|
||||
/* Bandwidth update interval in seconds */
|
||||
#define NGX_RTMP_BANDWIDTH_INTERVAL 10
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint64_t bytes;
|
||||
uint64_t bandwidth; /* bytes/sec */
|
||||
|
||||
time_t intl_end;
|
||||
uint64_t intl_bytes;
|
||||
} ngx_rtmp_bandwidth_t;
|
||||
|
||||
|
||||
void ngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_BANDWIDTH_H_INCLUDED_ */
|
||||
63
ngx_http_flv_module/ngx_rtmp_bitop.c
Normal file
63
ngx_http_flv_module/ngx_rtmp_bitop.c
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_bitop.h"
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos, u_char *last)
|
||||
{
|
||||
ngx_memzero(br, sizeof(ngx_rtmp_bit_reader_t));
|
||||
|
||||
br->pos = pos;
|
||||
br->last = last;
|
||||
}
|
||||
|
||||
|
||||
uint64_t
|
||||
ngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n)
|
||||
{
|
||||
uint64_t v;
|
||||
ngx_uint_t d;
|
||||
|
||||
v = 0;
|
||||
|
||||
while (n) {
|
||||
|
||||
if (br->pos >= br->last) {
|
||||
br->err = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
d = (br->offs + n > 8 ? (ngx_uint_t) (8 - br->offs) : n);
|
||||
|
||||
v <<= d;
|
||||
v += (*br->pos >> (8 - br->offs - d)) & ((u_char) 0xff >> (8 - d));
|
||||
|
||||
br->offs += d;
|
||||
n -= d;
|
||||
|
||||
if (br->offs == 8) {
|
||||
br->pos++;
|
||||
br->offs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
uint64_t
|
||||
ngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br)
|
||||
{
|
||||
ngx_uint_t n;
|
||||
|
||||
for (n = 0; ngx_rtmp_bit_read(br, 1) == 0 && !br->err; n++);
|
||||
|
||||
return ((uint64_t) 1 << n) + ngx_rtmp_bit_read(br, n) - 1;
|
||||
}
|
||||
46
ngx_http_flv_module/ngx_rtmp_bitop.h
Normal file
46
ngx_http_flv_module/ngx_rtmp_bitop.h
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_BITOP_H_INCLUDED_
|
||||
#define _NGX_RTMP_BITOP_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
u_char *pos;
|
||||
u_char *last;
|
||||
ngx_uint_t offs;
|
||||
ngx_uint_t err;
|
||||
} ngx_rtmp_bit_reader_t;
|
||||
|
||||
|
||||
void ngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos,
|
||||
u_char *last);
|
||||
uint64_t ngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n);
|
||||
uint64_t ngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br);
|
||||
|
||||
|
||||
#define ngx_rtmp_bit_read_err(br) ((br)->err)
|
||||
|
||||
#define ngx_rtmp_bit_read_eof(br) ((br)->pos == (br)->last)
|
||||
|
||||
#define ngx_rtmp_bit_read_8(br) \
|
||||
((uint8_t) ngx_rtmp_bit_read(br, 8))
|
||||
|
||||
#define ngx_rtmp_bit_read_16(br) \
|
||||
((uint16_t) ngx_rtmp_bit_read(br, 16))
|
||||
|
||||
#define ngx_rtmp_bit_read_32(br) \
|
||||
((uint32_t) ngx_rtmp_bit_read(br, 32))
|
||||
|
||||
#define ngx_rtmp_bit_read_64(br) \
|
||||
((uint64_t) ngx_rtmp_bit_read(br, 64))
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_BITOP_H_INCLUDED_ */
|
||||
973
ngx_http_flv_module/ngx_rtmp_cmd_module.c
Normal file
973
ngx_http_flv_module/ngx_rtmp_cmd_module.c
Normal file
@@ -0,0 +1,973 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_cmd_module.h"
|
||||
#include "ngx_rtmp_streams.h"
|
||||
|
||||
|
||||
#define NGX_RTMP_FMS_VERSION "FMS/3,0,1,123"
|
||||
#define NGX_RTMP_CAPABILITIES 31
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_cmd_connect(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_connect_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_disconnect(ngx_rtmp_session_t *s);
|
||||
static ngx_int_t ngx_rtmp_cmd_create_stream(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_create_stream_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_close_stream(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_close_stream_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_delete_stream(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_delete_stream_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_publish(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_publish_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_play(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_play_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_seek(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_seek_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_pause(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_pause_t *v);
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_cmd_stream_begin(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_stream_begin_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_stream_eof(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_stream_eof_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_stream_dry(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_stream_dry_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_recorded(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_recorded_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_set_buflen_t *v);
|
||||
|
||||
|
||||
ngx_rtmp_connect_pt ngx_rtmp_connect;
|
||||
ngx_rtmp_disconnect_pt ngx_rtmp_disconnect;
|
||||
ngx_rtmp_create_stream_pt ngx_rtmp_create_stream;
|
||||
ngx_rtmp_close_stream_pt ngx_rtmp_close_stream;
|
||||
ngx_rtmp_delete_stream_pt ngx_rtmp_delete_stream;
|
||||
ngx_rtmp_publish_pt ngx_rtmp_publish;
|
||||
ngx_rtmp_play_pt ngx_rtmp_play;
|
||||
ngx_rtmp_seek_pt ngx_rtmp_seek;
|
||||
ngx_rtmp_pause_pt ngx_rtmp_pause;
|
||||
|
||||
|
||||
ngx_rtmp_stream_begin_pt ngx_rtmp_stream_begin;
|
||||
ngx_rtmp_stream_eof_pt ngx_rtmp_stream_eof;
|
||||
ngx_rtmp_stream_dry_pt ngx_rtmp_stream_dry;
|
||||
ngx_rtmp_recorded_pt ngx_rtmp_recorded;
|
||||
ngx_rtmp_set_buflen_pt ngx_rtmp_set_buflen;
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf);
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_cmd_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_rtmp_cmd_postconfiguration, /* postconfiguration */
|
||||
NULL, /* 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_cmd_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_cmd_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 */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_cmd_fill_args(u_char name[NGX_RTMP_MAX_NAME],
|
||||
u_char args[NGX_RTMP_MAX_ARGS])
|
||||
{
|
||||
u_char *p;
|
||||
|
||||
p = (u_char *)ngx_strchr(name, '?');
|
||||
if (p == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
*p++ = 0;
|
||||
ngx_cpystrn(args, p, NGX_RTMP_MAX_ARGS);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_connect_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
static ngx_rtmp_connect_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_cmd[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("app"),
|
||||
v.app, sizeof(v.app) },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("flashVer"),
|
||||
v.flashver, sizeof(v.flashver) },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("swfUrl"),
|
||||
v.swf_url, sizeof(v.swf_url) },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("tcUrl"),
|
||||
v.tc_url, sizeof(v.tc_url) },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("audioCodecs"),
|
||||
&v.acodecs, sizeof(v.acodecs) },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("videoCodecs"),
|
||||
&v.vcodecs, sizeof(v.vcodecs) },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("pageUrl"),
|
||||
v.page_url, sizeof(v.page_url) },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("serverName"),
|
||||
v.server_name, sizeof(v.server_name) },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("objectEncoding"),
|
||||
&v.object_encoding, 0},
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
in_cmd, sizeof(in_cmd) },
|
||||
};
|
||||
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (v.tc_url[0]) {
|
||||
/* compatibility for case: rtmps -> converter -> rtmp */
|
||||
if (ngx_strncasecmp(v.tc_url, (u_char *) "rtmps://", 8) == 0) {
|
||||
ngx_log_error(NGX_LOG_WARN, s->connection->log, 0,
|
||||
"connect: rtmps tcUrl received: %s", v.tc_url);
|
||||
|
||||
ngx_memmove(v.tc_url + 4, v.tc_url + 5, ngx_strlen(v.tc_url) - 5);
|
||||
}
|
||||
}
|
||||
|
||||
#define NGX_RTMP_SET_STRPAR(name) \
|
||||
s->name.len = ngx_strlen(v.name); \
|
||||
s->name.data = ngx_palloc(s->connection->pool, s->name.len); \
|
||||
if (s->name.data == NULL) { \
|
||||
return NGX_ERROR; \
|
||||
} \
|
||||
ngx_memcpy(s->name.data, v.name, s->name.len)
|
||||
|
||||
NGX_RTMP_SET_STRPAR(app);
|
||||
NGX_RTMP_SET_STRPAR(args);
|
||||
NGX_RTMP_SET_STRPAR(flashver);
|
||||
NGX_RTMP_SET_STRPAR(swf_url);
|
||||
NGX_RTMP_SET_STRPAR(tc_url);
|
||||
NGX_RTMP_SET_STRPAR(page_url);
|
||||
|
||||
#undef NGX_RTMP_SET_STRPAR
|
||||
|
||||
if (s->auto_pushed) {
|
||||
s->host_start = v.server_name;
|
||||
s->host_end = v.server_name + ngx_strlen(v.server_name);
|
||||
}
|
||||
|
||||
if (ngx_rtmp_process_virtual_host(s) != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"connect: failed to process virtual host");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_rtmp_cmd_fill_args(v.app, v.args);
|
||||
|
||||
len = ngx_strlen(v.app);
|
||||
if (len > 10 && !ngx_memcmp(v.app + len - 10, "/_definst_", 10)) {
|
||||
v.app[len - 10] = 0;
|
||||
} else if (len && v.app[len - 1] == '/') {
|
||||
v.app[len - 1] = 0;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"connect: app='%s' args='%s' flashver='%s' swf_url='%s' "
|
||||
"tc_url='%s' page_url='%s' acodecs=%uD vcodecs=%uD "
|
||||
"object_encoding=%ui",
|
||||
v.app, v.args, v.flashver, v.swf_url, v.tc_url, v.page_url,
|
||||
(uint32_t)v.acodecs, (uint32_t)v.vcodecs,
|
||||
(ngx_int_t)v.object_encoding);
|
||||
|
||||
return ngx_rtmp_connect(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_connect(ngx_rtmp_session_t *s, ngx_rtmp_connect_t *v)
|
||||
{
|
||||
int tcp_nodelay;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
ngx_rtmp_core_app_conf_t **cacfp, *cacf;
|
||||
ngx_uint_t n;
|
||||
ngx_rtmp_header_t h;
|
||||
ngx_connection_t *c;
|
||||
u_char *p;
|
||||
|
||||
static double trans;
|
||||
static double capabilities = NGX_RTMP_CAPABILITIES;
|
||||
static double object_encoding = 0;
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_obj[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("fmsVer"),
|
||||
NGX_RTMP_FMS_VERSION, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("capabilities"),
|
||||
&capabilities, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_inf[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("level"),
|
||||
"status", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("code"),
|
||||
"NetConnection.Connect.Success", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("description"),
|
||||
"Connection succeeded.", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("objectEncoding"),
|
||||
&object_encoding, 0 }
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"_result", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
out_obj, sizeof(out_obj) },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
out_inf, sizeof(out_inf) },
|
||||
};
|
||||
|
||||
if (s->connected) {
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"connect: duplicate connection");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
trans = v->trans;
|
||||
|
||||
/* fill session parameters */
|
||||
s->connected = 1;
|
||||
|
||||
ngx_memzero(&h, sizeof(h));
|
||||
h.csid = NGX_RTMP_CSID_AMF_INI;
|
||||
h.type = NGX_RTMP_MSG_AMF_CMD;
|
||||
|
||||
|
||||
#define NGX_RTMP_SET_STRPAR(name) \
|
||||
do { \
|
||||
if (s->name.len != ngx_strlen(v->name) \
|
||||
|| ngx_strncasecmp(s->name.data, v->name, s->name.len)) \
|
||||
{ \
|
||||
s->name.len = ngx_strlen(v->name); \
|
||||
s->name.data = ngx_palloc(s->connection->pool, s->name.len); \
|
||||
if (s->name.data == NULL) { \
|
||||
return NGX_ERROR; \
|
||||
} \
|
||||
ngx_memcpy(s->name.data, v->name, s->name.len); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
NGX_RTMP_SET_STRPAR(app);
|
||||
NGX_RTMP_SET_STRPAR(args);
|
||||
NGX_RTMP_SET_STRPAR(flashver);
|
||||
NGX_RTMP_SET_STRPAR(swf_url);
|
||||
NGX_RTMP_SET_STRPAR(tc_url);
|
||||
NGX_RTMP_SET_STRPAR(page_url);
|
||||
|
||||
#undef NGX_RTMP_SET_STRPAR
|
||||
|
||||
p = ngx_strlchr(s->app.data, s->app.data + s->app.len, '?');
|
||||
if (p) {
|
||||
s->app.len = (p - s->app.data);
|
||||
}
|
||||
|
||||
s->acodecs = (uint32_t) v->acodecs;
|
||||
s->vcodecs = (uint32_t) v->vcodecs;
|
||||
|
||||
/* find application & set app_conf */
|
||||
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) {
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"connect: application not found: '%V'", &s->app);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
object_encoding = v->object_encoding;
|
||||
|
||||
if (s->data == NULL) {
|
||||
cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module);
|
||||
c = s->connection;
|
||||
|
||||
if (!cacf->tcp_nopush) {
|
||||
c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;
|
||||
}
|
||||
|
||||
if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) {
|
||||
if (ngx_tcp_push(c->fd) == -1) {
|
||||
ngx_connection_error(c, ngx_socket_errno,
|
||||
ngx_tcp_push_n " failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;
|
||||
tcp_nodelay = ngx_tcp_nodelay_and_tcp_nopush ? 1 : 0;
|
||||
} else {
|
||||
tcp_nodelay = 1;
|
||||
}
|
||||
|
||||
if (tcp_nodelay && cacf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return ngx_rtmp_send_ack_size(s, cscf->ack_window) != NGX_OK ||
|
||||
ngx_rtmp_send_bandwidth(s, cscf->ack_window,
|
||||
NGX_RTMP_LIMIT_DYNAMIC) != NGX_OK ||
|
||||
ngx_rtmp_send_chunk_size(s, cscf->chunk_size) != 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_rtmp_cmd_create_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_create_stream_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.trans, sizeof(v.trans) },
|
||||
};
|
||||
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "createStream");
|
||||
|
||||
return ngx_rtmp_create_stream(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_create_stream(ngx_rtmp_session_t *s, ngx_rtmp_create_stream_t *v)
|
||||
{
|
||||
/* support one message stream per connection */
|
||||
static double stream;
|
||||
static double trans;
|
||||
ngx_rtmp_header_t h;
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"_result", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&stream, sizeof(stream) },
|
||||
};
|
||||
|
||||
trans = v->trans;
|
||||
stream = NGX_RTMP_MSID;
|
||||
|
||||
ngx_memzero(&h, sizeof(h));
|
||||
|
||||
h.csid = NGX_RTMP_CSID_AMF_INI;
|
||||
h.type = NGX_RTMP_MSG_AMF_CMD;
|
||||
|
||||
return ngx_rtmp_send_amf(s, &h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0])) == NGX_OK ?
|
||||
NGX_DONE : NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_close_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_close_stream_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.stream, 0 },
|
||||
};
|
||||
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "closeStream");
|
||||
|
||||
return ngx_rtmp_close_stream(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_delete_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_delete_stream_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.stream, 0 },
|
||||
};
|
||||
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return ngx_rtmp_delete_stream(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_delete_stream(ngx_rtmp_session_t *s, ngx_rtmp_delete_stream_t *v)
|
||||
{
|
||||
ngx_rtmp_close_stream_t cv;
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "deleteStream");
|
||||
|
||||
cv.stream = 0;
|
||||
|
||||
return ngx_rtmp_close_stream(s, &cv);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_publish_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_publish_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
/* transaction is always 0 */
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
&v.name, sizeof(v.name) },
|
||||
|
||||
{ NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
&v.type, sizeof(v.type) },
|
||||
};
|
||||
|
||||
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_rtmp_cmd_fill_args(v.name, v.args);
|
||||
|
||||
if (ngx_strlen(v.name) == 0) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"publish: no stream name specified");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_process_request_line(s, v.name, v.args,
|
||||
(const u_char *) "publish") != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"publish: name='%s' args='%s' type=%s silent=%d",
|
||||
v.name, v.args, v.type, v.silent);
|
||||
|
||||
return ngx_rtmp_publish(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_play_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_play_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
/* transaction is always 0 */
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
&v.name, sizeof(v.name) },
|
||||
|
||||
{ NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.start, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.duration, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_BOOLEAN,
|
||||
ngx_null_string,
|
||||
&v.reset, 0 }
|
||||
};
|
||||
|
||||
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_rtmp_cmd_fill_args(v.name, v.args);
|
||||
|
||||
if (ngx_strlen(v.name) == 0) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"play: no stream name specified");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_process_request_line(s, v.name, v.args,
|
||||
(const u_char *) "play") != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"play: name='%s' args='%s' start=%i duration=%i "
|
||||
"reset=%i silent=%i",
|
||||
v.name, v.args, (ngx_int_t) v.start,
|
||||
(ngx_int_t) v.duration, (ngx_int_t) v.reset,
|
||||
(ngx_int_t) v.silent);
|
||||
|
||||
return ngx_rtmp_play(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
||||
{
|
||||
ngx_add_timer(s->connection->write, s->timeout);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_play2_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_play_t v;
|
||||
static ngx_rtmp_close_stream_t vc;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_obj[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("start"),
|
||||
&v.start, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("streamName"),
|
||||
&v.name, sizeof(v.name) },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
/* transaction is always 0 */
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
&in_obj, sizeof(in_obj) }
|
||||
};
|
||||
|
||||
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_rtmp_cmd_fill_args(v.name, v.args);
|
||||
|
||||
if (ngx_strlen(v.name) == 0) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"play2: no stream name specified");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"play2: name='%s' args='%s' start=%i",
|
||||
v.name, v.args, (ngx_int_t) v.start);
|
||||
|
||||
/* continue from current timestamp */
|
||||
|
||||
if (v.start < 0) {
|
||||
v.start = s->current_time;
|
||||
}
|
||||
|
||||
ngx_memzero(&vc, sizeof(vc));
|
||||
|
||||
/* close_stream should be synchronous */
|
||||
ngx_rtmp_close_stream(s, &vc);
|
||||
|
||||
return ngx_rtmp_play(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_pause_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_pause_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_BOOLEAN,
|
||||
ngx_null_string,
|
||||
&v.pause, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.position, 0 },
|
||||
};
|
||||
|
||||
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_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"pause: pause=%i position=%i",
|
||||
(ngx_int_t) v.pause, (ngx_int_t) v.position);
|
||||
|
||||
return ngx_rtmp_pause(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_pause(ngx_rtmp_session_t *s, ngx_rtmp_pause_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_disconnect_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "disconnect");
|
||||
|
||||
return ngx_rtmp_disconnect(s);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_disconnect(ngx_rtmp_session_t *s)
|
||||
{
|
||||
return ngx_rtmp_delete_stream(s, NULL);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_seek_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_seek_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
/* transaction is always 0 */
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.offset, sizeof(v.offset) },
|
||||
};
|
||||
|
||||
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_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"seek: offset=%i", (ngx_int_t) v.offset);
|
||||
|
||||
return ngx_rtmp_seek(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_seek(ngx_rtmp_session_t *s, ngx_rtmp_seek_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_stream_dry(ngx_rtmp_session_t *s, ngx_rtmp_stream_dry_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_recorded(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_recorded_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s, ngx_rtmp_set_buflen_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_rtmp_amf_handler_t ngx_rtmp_cmd_map[] = {
|
||||
{ ngx_string("connect"), ngx_rtmp_cmd_connect_init },
|
||||
{ ngx_string("createStream"), ngx_rtmp_cmd_create_stream_init },
|
||||
{ ngx_string("closeStream"), ngx_rtmp_cmd_close_stream_init },
|
||||
{ ngx_string("deleteStream"), ngx_rtmp_cmd_delete_stream_init },
|
||||
{ ngx_string("publish"), ngx_rtmp_cmd_publish_init },
|
||||
{ ngx_string("play"), ngx_rtmp_cmd_play_init },
|
||||
{ ngx_string("play2"), ngx_rtmp_cmd_play2_init },
|
||||
{ ngx_string("seek"), ngx_rtmp_cmd_seek_init },
|
||||
{ ngx_string("pause"), ngx_rtmp_cmd_pause_init },
|
||||
{ ngx_string("pauseraw"), ngx_rtmp_cmd_pause_init },
|
||||
};
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
ngx_rtmp_handler_pt *h;
|
||||
ngx_rtmp_amf_handler_t *ch, *bh;
|
||||
size_t n, ncalls;
|
||||
|
||||
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
|
||||
|
||||
/* redirect disconnects to deleteStream
|
||||
* to free client modules from registering
|
||||
* disconnect callback */
|
||||
|
||||
h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]);
|
||||
if (h == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
*h = ngx_rtmp_cmd_disconnect_init;
|
||||
|
||||
/* register AMF callbacks */
|
||||
|
||||
ncalls = sizeof(ngx_rtmp_cmd_map) / sizeof(ngx_rtmp_cmd_map[0]);
|
||||
|
||||
ch = ngx_array_push_n(&cmcf->amf, ncalls);
|
||||
if (ch == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
bh = ngx_rtmp_cmd_map;
|
||||
|
||||
for(n = 0; n < ncalls; ++n, ++ch, ++bh) {
|
||||
*ch = *bh;
|
||||
}
|
||||
|
||||
ngx_rtmp_connect = ngx_rtmp_cmd_connect;
|
||||
ngx_rtmp_disconnect = ngx_rtmp_cmd_disconnect;
|
||||
ngx_rtmp_create_stream = ngx_rtmp_cmd_create_stream;
|
||||
ngx_rtmp_close_stream = ngx_rtmp_cmd_close_stream;
|
||||
ngx_rtmp_delete_stream = ngx_rtmp_cmd_delete_stream;
|
||||
ngx_rtmp_publish = ngx_rtmp_cmd_publish;
|
||||
ngx_rtmp_play = ngx_rtmp_cmd_play;
|
||||
ngx_rtmp_seek = ngx_rtmp_cmd_seek;
|
||||
ngx_rtmp_pause = ngx_rtmp_cmd_pause;
|
||||
|
||||
ngx_rtmp_stream_begin = ngx_rtmp_cmd_stream_begin;
|
||||
ngx_rtmp_stream_eof = ngx_rtmp_cmd_stream_eof;
|
||||
ngx_rtmp_stream_dry = ngx_rtmp_cmd_stream_dry;
|
||||
ngx_rtmp_recorded = ngx_rtmp_cmd_recorded;
|
||||
ngx_rtmp_set_buflen = ngx_rtmp_cmd_set_buflen;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
152
ngx_http_flv_module/ngx_rtmp_cmd_module.h
Normal file
152
ngx_http_flv_module/ngx_rtmp_cmd_module.h
Normal file
@@ -0,0 +1,152 @@
|
||||
|
||||
/*
|
||||
* 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_URL 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[64];
|
||||
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];
|
||||
u_char server_name[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];
|
||||
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]);
|
||||
|
||||
|
||||
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_ */
|
||||
1031
ngx_http_flv_module/ngx_rtmp_codec_module.c
Normal file
1031
ngx_http_flv_module/ngx_rtmp_codec_module.c
Normal file
File diff suppressed because it is too large
Load Diff
119
ngx_http_flv_module/ngx_rtmp_codec_module.h
Normal file
119
ngx_http_flv_module/ngx_rtmp_codec_module.h
Normal file
@@ -0,0 +1,119 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_CODEC_H_INCLUDED_
|
||||
#define _NGX_RTMP_CODEC_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
/* AVC NAL unit types */
|
||||
enum {
|
||||
NGX_RTMP_NALU_SLICE = 1,
|
||||
NGX_RTMP_NALU_DPA = 2,
|
||||
NGX_RTMP_NALU_DPB = 3,
|
||||
NGX_RTMP_NALU_DPC = 4,
|
||||
NGX_RTMP_NALU_IDR = 5,
|
||||
NGX_RTMP_NALU_SEI = 6,
|
||||
NGX_RTMP_NALU_SPS = 7,
|
||||
NGX_RTMP_NALU_PPS = 8,
|
||||
NGX_RTMP_NALU_AUD = 9,
|
||||
NGX_RTMP_NALU_EOSEQ = 10,
|
||||
NGX_RTMP_NALU_EOSTREAM = 11,
|
||||
NGX_RTMP_NALU_FILL = 12,
|
||||
NGX_RTMP_NALU_SPS_EXT = 13,
|
||||
NGX_RTMP_NALU_AUXILIARY_SLICE = 19
|
||||
};
|
||||
|
||||
|
||||
/* AVC frame types */
|
||||
enum {
|
||||
NGX_RTMP_FRAME_IDR = 1,
|
||||
NGX_RTMP_FRAME_INTER = 2,
|
||||
NGX_RTMP_FRAME_DISPOSABLE = 3,
|
||||
NGX_RTMP_FRAME_GENERATED = 4,
|
||||
NGX_RTMP_FRAME_VIDEOINFOCMD = 5
|
||||
};
|
||||
|
||||
|
||||
/* Audio codecs */
|
||||
enum {
|
||||
/* Uncompressed codec id is actually 0,
|
||||
* but we use another value for consistency */
|
||||
NGX_RTMP_AUDIO_UNCOMPRESSED = 16,
|
||||
NGX_RTMP_AUDIO_ADPCM = 1,
|
||||
NGX_RTMP_AUDIO_MP3 = 2,
|
||||
NGX_RTMP_AUDIO_LINEAR_LE = 3,
|
||||
NGX_RTMP_AUDIO_NELLY16 = 4,
|
||||
NGX_RTMP_AUDIO_NELLY8 = 5,
|
||||
NGX_RTMP_AUDIO_NELLY = 6,
|
||||
NGX_RTMP_AUDIO_G711A = 7,
|
||||
NGX_RTMP_AUDIO_G711U = 8,
|
||||
NGX_RTMP_AUDIO_AAC = 10,
|
||||
NGX_RTMP_AUDIO_SPEEX = 11,
|
||||
NGX_RTMP_AUDIO_MP3_8 = 14,
|
||||
NGX_RTMP_AUDIO_DEVSPEC = 15
|
||||
};
|
||||
|
||||
|
||||
/* Video codecs */
|
||||
enum {
|
||||
NGX_RTMP_VIDEO_JPEG = 1,
|
||||
NGX_RTMP_VIDEO_SORENSON_H263 = 2,
|
||||
NGX_RTMP_VIDEO_SCREEN = 3,
|
||||
NGX_RTMP_VIDEO_ON2_VP6 = 4,
|
||||
NGX_RTMP_VIDEO_ON2_VP6_ALPHA = 5,
|
||||
NGX_RTMP_VIDEO_SCREEN2 = 6,
|
||||
NGX_RTMP_VIDEO_H264 = 7
|
||||
};
|
||||
|
||||
|
||||
u_char * ngx_rtmp_get_audio_codec_name(ngx_uint_t id);
|
||||
u_char * ngx_rtmp_get_video_codec_name(ngx_uint_t id);
|
||||
|
||||
|
||||
#define NGX_RTMP_SPS_MAX_LENGTH 256
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t width;
|
||||
ngx_uint_t height;
|
||||
double duration;
|
||||
double frame_rate;
|
||||
double video_data_rate;
|
||||
ngx_uint_t video_codec_id;
|
||||
double 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;
|
||||
ngx_uint_t avc_compat;
|
||||
ngx_uint_t avc_level;
|
||||
ngx_uint_t avc_nal_bytes;
|
||||
ngx_uint_t avc_ref_frames;
|
||||
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_chain_t *avc_header;
|
||||
ngx_chain_t *aac_header;
|
||||
|
||||
ngx_chain_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_ */
|
||||
732
ngx_http_flv_module/ngx_rtmp_control_module.c
Normal file
732
ngx_http_flv_module/ngx_rtmp_control_module.c
Normal file
@@ -0,0 +1,732 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_http.h>
|
||||
#include "ngx_rtmp.h"
|
||||
#include "ngx_rtmp_live_module.h"
|
||||
#include "ngx_rtmp_record_module.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
|
||||
|
||||
|
||||
enum {
|
||||
NGX_RTMP_CONTROL_FILTER_CLIENT = 0,
|
||||
NGX_RTMP_CONTROL_FILTER_PUBLISHER,
|
||||
NGX_RTMP_CONTROL_FILTER_SUBSCRIBER
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t count;
|
||||
ngx_str_t path;
|
||||
ngx_uint_t filter;
|
||||
ngx_str_t method;
|
||||
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_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_int_t rc;
|
||||
ngx_str_t rec;
|
||||
ngx_uint_t rn;
|
||||
ngx_rtmp_control_ctx_t *ctx;
|
||||
ngx_rtmp_core_app_conf_t *cacf;
|
||||
ngx_rtmp_record_app_conf_t *racf;
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
|
||||
|
||||
if (ctx->method.len == sizeof("start") - 1 &&
|
||||
ngx_strncmp(ctx->method.data, "start", ctx->method.len) == 0)
|
||||
{
|
||||
rc = ngx_rtmp_record_open(s, rn, &ctx->path);
|
||||
|
||||
} else if (ctx->method.len == sizeof("stop") - 1 &&
|
||||
ngx_strncmp(ctx->method.data, "stop", ctx->method.len) == 0)
|
||||
{
|
||||
rc = ngx_rtmp_record_close(s, rn, &ctx->path);
|
||||
|
||||
} else {
|
||||
return "Undefined method";
|
||||
}
|
||||
|
||||
if (rc == NGX_ERROR) {
|
||||
return "Recorder error";
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
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(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(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(s, &vplay) != NGX_OK) {
|
||||
return "play failed";
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
ngx_rtmp_control_walk_session(ngx_http_request_t *r,
|
||||
ngx_rtmp_live_ctx_t *lctx)
|
||||
{
|
||||
ngx_str_t addr, *paddr, clientid;
|
||||
ngx_rtmp_session_t *s, **ss;
|
||||
ngx_rtmp_control_ctx_t *ctx;
|
||||
|
||||
s = lctx->session;
|
||||
|
||||
if (s == NULL || s->connection == NULL) {
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
if (ngx_http_arg(r, (u_char *) "addr", sizeof("addr") - 1, &addr)
|
||||
== NGX_OK)
|
||||
{
|
||||
paddr = &s->connection->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->connection->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 (!lctx->publishing) {
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_CONTROL_FILTER_SUBSCRIBER:
|
||||
if (lctx->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_rtmp_live_stream_t *ls)
|
||||
{
|
||||
const char *s;
|
||||
ngx_rtmp_live_ctx_t *lctx;
|
||||
|
||||
for (lctx = ls->ctx; lctx; lctx = lctx->next) {
|
||||
s = ngx_rtmp_control_walk_session(r, lctx);
|
||||
if (s != NGX_CONF_OK) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
ngx_rtmp_control_walk_app(ngx_http_request_t *r,
|
||||
ngx_rtmp_core_app_conf_t *cacf)
|
||||
{
|
||||
size_t len;
|
||||
ngx_str_t name;
|
||||
const char *s;
|
||||
ngx_uint_t n;
|
||||
ngx_rtmp_live_stream_t *ls;
|
||||
ngx_rtmp_live_app_conf_t *lacf;
|
||||
|
||||
lacf = cacf->app_conf[ngx_rtmp_live_module.ctx_index];
|
||||
|
||||
if (ngx_http_arg(r, (u_char *) "name", sizeof("name") - 1, &name) != NGX_OK)
|
||||
{
|
||||
for (n = 0; n < (ngx_uint_t) lacf->nbuckets; ++n) {
|
||||
for (ls = lacf->streams[n]; ls; ls = ls->next) {
|
||||
s = ngx_rtmp_control_walk_stream(r, ls);
|
||||
if (s != NGX_CONF_OK) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
for (ls = lacf->streams[ngx_hash_key(name.data, name.len) % lacf->nbuckets];
|
||||
ls; ls = ls->next)
|
||||
{
|
||||
len = ngx_strlen(ls->name);
|
||||
if (name.len != len || ngx_strncmp(name.data, ls->name, name.len)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
s = ngx_rtmp_control_walk_stream(r, ls);
|
||||
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_rtmp_core_srv_conf_t *cscf)
|
||||
{
|
||||
ngx_str_t app;
|
||||
ngx_uint_t n;
|
||||
const char *s;
|
||||
ngx_rtmp_core_app_conf_t **pcacf;
|
||||
|
||||
if (ngx_http_arg(r, (u_char *) "app", sizeof("app") - 1, &app) != NGX_OK) {
|
||||
app.len = 0;
|
||||
}
|
||||
|
||||
pcacf = cscf->applications.elts;
|
||||
|
||||
for (n = 0; n < cscf->applications.nelts; ++n, ++pcacf) {
|
||||
if (app.len && ((*pcacf)->name.len != app.len ||
|
||||
ngx_strncmp((*pcacf)->name.data, app.data, app.len)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
s = ngx_rtmp_control_walk_app(r, *pcacf);
|
||||
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_rtmp_core_main_conf_t *cmcf = ngx_rtmp_core_main_conf;
|
||||
|
||||
ngx_str_t srv;
|
||||
ngx_uint_t sn, n;
|
||||
const char *msg;
|
||||
ngx_rtmp_session_t **s;
|
||||
ngx_rtmp_control_ctx_t *ctx;
|
||||
ngx_rtmp_core_srv_conf_t **pcscf;
|
||||
|
||||
sn = 0;
|
||||
if (ngx_http_arg(r, (u_char *) "srv", sizeof("srv") - 1, &srv) == NGX_OK) {
|
||||
sn = ngx_atoi(srv.data, srv.len);
|
||||
}
|
||||
|
||||
if (sn >= cmcf->servers.nelts) {
|
||||
return "Server index out of range";
|
||||
}
|
||||
|
||||
pcscf = cmcf->servers.elts;
|
||||
pcscf += sn;
|
||||
|
||||
msg = ngx_rtmp_control_walk_server(r, *pcscf);
|
||||
if (msg != NGX_CONF_OK) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (ctx->path.len == 0) {
|
||||
return NGX_HTTP_NO_CONTENT;
|
||||
}
|
||||
|
||||
/* output record path */
|
||||
|
||||
r->headers_out.status = NGX_HTTP_OK;
|
||||
r->headers_out.content_length_n = ctx->path.len;
|
||||
|
||||
b = ngx_create_temp_buf(r->pool, ctx->path.len);
|
||||
if (b == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
ngx_memzero(&cl, sizeof(cl));
|
||||
cl.buf = b;
|
||||
|
||||
b->last = ngx_cpymem(b->pos, ctx->path.data, ctx->path.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_palloc(r->connection->pool, len);
|
||||
if (p == NULL) {
|
||||
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) {
|
||||
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_debug2(NGX_LOG_DEBUG_RTMP, 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);
|
||||
|
||||
#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);
|
||||
}
|
||||
1535
ngx_http_flv_module/ngx_rtmp_core_module.c
Normal file
1535
ngx_http_flv_module/ngx_rtmp_core_module.c
Normal file
File diff suppressed because it is too large
Load Diff
293
ngx_http_flv_module/ngx_rtmp_eval.c
Normal file
293
ngx_http_flv_module/ngx_rtmp_eval.c
Normal file
@@ -0,0 +1,293 @@
|
||||
|
||||
/*
|
||||
* 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;
|
||||
u_char *old;
|
||||
|
||||
if (b->last + len > b->end) {
|
||||
buf_len = (2 * (b->last - b->pos) + len +
|
||||
NGX_RTMP_EVAL_BUFLEN - 1) & ~(NGX_RTMP_EVAL_BUFLEN - 1);
|
||||
|
||||
old = b->start;
|
||||
|
||||
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;
|
||||
|
||||
ngx_free(old);
|
||||
}
|
||||
|
||||
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_str_t v;
|
||||
ngx_rtmp_eval_t *ee;
|
||||
|
||||
for (; *e; ++e) {
|
||||
for (ee = *e; ee->handler; ++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);
|
||||
|
||||
/* fall through */
|
||||
|
||||
case NORMAL:
|
||||
switch (c) {
|
||||
case '$':
|
||||
name.data = p + 1;
|
||||
state = NAME;
|
||||
continue;
|
||||
case '\\':
|
||||
state = ESCAPE;
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* fall through */
|
||||
|
||||
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
|
||||
}
|
||||
44
ngx_http_flv_module/ngx_rtmp_eval.h
Normal file
44
ngx_http_flv_module/ngx_rtmp_eval.h
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_EVAL_H_INCLUDED_
|
||||
#define _NGX_RTMP_EVAL_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_eval_s ngx_rtmp_eval_t;
|
||||
|
||||
|
||||
typedef void (* ngx_rtmp_eval_pt)(void *ctx, ngx_rtmp_eval_t *e,
|
||||
ngx_str_t *ret);
|
||||
|
||||
|
||||
struct ngx_rtmp_eval_s {
|
||||
ngx_str_t name;
|
||||
ngx_rtmp_eval_pt handler;
|
||||
ngx_uint_t offset;
|
||||
};
|
||||
|
||||
|
||||
#define ngx_rtmp_null_eval { ngx_null_string, NULL, 0 }
|
||||
|
||||
|
||||
/* standard session eval variables */
|
||||
extern ngx_rtmp_eval_t ngx_rtmp_eval_session[];
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e,
|
||||
ngx_str_t *out, ngx_log_t *log);
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_eval_streams(ngx_str_t *in);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_EVAL_H_INCLUDED_ */
|
||||
1604
ngx_http_flv_module/ngx_rtmp_exec_module.c
Normal file
1604
ngx_http_flv_module/ngx_rtmp_exec_module.c
Normal file
File diff suppressed because it is too large
Load Diff
65
ngx_http_flv_module/ngx_rtmp_flv_live_index_module.c
Normal file
65
ngx_http_flv_module/ngx_rtmp_flv_live_index_module.c
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_http_flv_live_module.h"
|
||||
|
||||
|
||||
static ngx_rtmp_play_pt next_play;
|
||||
static ngx_rtmp_close_stream_pt next_close_stream;
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_flv_live_index_postconfiguration(ngx_conf_t *cf);
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_flv_live_module_ctx = {
|
||||
NULL,
|
||||
ngx_rtmp_flv_live_index_postconfiguration, /* postconfiguration */
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
static ngx_command_t ngx_rtmp_flv_live_index_commands[] = {
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_flv_live_index_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_flv_live_module_ctx,
|
||||
ngx_rtmp_flv_live_index_commands,
|
||||
NGX_RTMP_MODULE,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_live_index_postconfiguration(ngx_conf_t *cf)
|
||||
{
|
||||
next_play = ngx_rtmp_play;
|
||||
ngx_rtmp_play = ngx_http_flv_live_play;
|
||||
|
||||
next_close_stream = ngx_rtmp_close_stream;
|
||||
ngx_rtmp_close_stream = ngx_http_flv_live_close_stream;
|
||||
|
||||
http_flv_live_next_play = next_play;
|
||||
http_flv_live_next_close_stream = next_close_stream;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
669
ngx_http_flv_module/ngx_rtmp_flv_module.c
Normal file
669
ngx_http_flv_module/ngx_rtmp_flv_module.c
Normal file
@@ -0,0 +1,669 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_play_module.h"
|
||||
#include "ngx_rtmp_codec_module.h"
|
||||
#include "ngx_rtmp_streams.h"
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf);
|
||||
static void ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f);
|
||||
static ngx_int_t ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f, ngx_int_t timestamp);
|
||||
static ngx_int_t ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f,
|
||||
ngx_int_t aindex, ngx_int_t vindex);
|
||||
static ngx_int_t ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f);
|
||||
static ngx_int_t ngx_rtmp_flv_seek(ngx_rtmp_session_t *s, ngx_file_t *f,
|
||||
ngx_uint_t offset);
|
||||
static ngx_int_t ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f);
|
||||
static ngx_int_t ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f,
|
||||
ngx_uint_t *ts);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t nelts;
|
||||
ngx_uint_t offset;
|
||||
} ngx_rtmp_flv_index_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_int_t offset;
|
||||
ngx_int_t start_timestamp;
|
||||
ngx_event_t write_evt;
|
||||
uint32_t last_audio;
|
||||
uint32_t last_video;
|
||||
ngx_uint_t msg_mask;
|
||||
uint32_t epoch;
|
||||
|
||||
unsigned meta_read:1;
|
||||
ngx_rtmp_flv_index_t filepositions;
|
||||
ngx_rtmp_flv_index_t times;
|
||||
} ngx_rtmp_flv_ctx_t;
|
||||
|
||||
|
||||
#define NGX_RTMP_FLV_BUFFER (1024*1024)
|
||||
#define NGX_RTMP_FLV_BUFLEN_ADDON 1000
|
||||
#define NGX_RTMP_FLV_TAG_HEADER 11
|
||||
#define NGX_RTMP_FLV_DATA_OFFSET 13
|
||||
|
||||
|
||||
static u_char ngx_rtmp_flv_buffer[
|
||||
NGX_RTMP_FLV_BUFFER];
|
||||
static u_char ngx_rtmp_flv_header[
|
||||
NGX_RTMP_FLV_TAG_HEADER];
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_flv_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_rtmp_flv_postconfiguration, /* postconfiguration */
|
||||
NULL, /* 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_flv_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_flv_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 */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_fill_index(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_flv_index_t *idx)
|
||||
{
|
||||
uint32_t nelts;
|
||||
ngx_buf_t *b;
|
||||
|
||||
/* we have AMF array pointed by context;
|
||||
* need to extract its size (4 bytes) &
|
||||
* save offset of actual array data */
|
||||
|
||||
b = ctx->link->buf;
|
||||
|
||||
if (b->last - b->pos < (ngx_int_t) ctx->offset + 4) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
nelts = htonl(*(uint32_t *) (b->pos + ctx->offset));
|
||||
|
||||
idx->nelts = nelts;
|
||||
idx->offset = ctx->offset + 4;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_init_index(ngx_rtmp_session_t *s, ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
|
||||
static ngx_rtmp_amf_ctx_t filepositions_ctx;
|
||||
static ngx_rtmp_amf_ctx_t times_ctx;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_keyframes[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,
|
||||
ngx_string("filepositions"),
|
||||
&filepositions_ctx, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,
|
||||
ngx_string("times"),
|
||||
×_ctx, 0 }
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_inf[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_string("keyframes"),
|
||||
in_keyframes, sizeof(in_keyframes) }
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
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_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL || in == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: init index");
|
||||
|
||||
ngx_memzero(&filepositions_ctx, sizeof(filepositions_ctx));
|
||||
ngx_memzero(×_ctx, sizeof(times_ctx));
|
||||
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: init index error");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (filepositions_ctx.link && ngx_rtmp_flv_fill_index(&filepositions_ctx,
|
||||
&ctx->filepositions)
|
||||
!= NGX_OK)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: failed to init filepositions");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: filepositions nelts=%ui offset=%ui",
|
||||
ctx->filepositions.nelts, ctx->filepositions.offset);
|
||||
|
||||
if (times_ctx.link && ngx_rtmp_flv_fill_index(×_ctx,
|
||||
&ctx->times)
|
||||
!= NGX_OK)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: failed to init times");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: times nelts=%ui offset=%ui",
|
||||
ctx->times.nelts, ctx->times.offset);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static double
|
||||
ngx_rtmp_flv_index_value(void *src)
|
||||
{
|
||||
return *(double *) src;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s, ngx_file_t *f,
|
||||
ngx_int_t timestamp)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
ssize_t n, size;
|
||||
ngx_uint_t offset, index, ret, nelts;
|
||||
double v;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
goto rewind;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: lookup index start timestamp=%i",
|
||||
timestamp);
|
||||
|
||||
if (ctx->meta_read == 0) {
|
||||
ngx_rtmp_flv_read_meta(s, f);
|
||||
ctx->meta_read = 1;
|
||||
}
|
||||
|
||||
if (timestamp <= 0 || ctx->filepositions.nelts == 0
|
||||
|| ctx->times.nelts == 0)
|
||||
{
|
||||
goto rewind;
|
||||
}
|
||||
|
||||
/* read index table from file given offset */
|
||||
offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER +
|
||||
ctx->times.offset;
|
||||
|
||||
/* index should fit in the buffer */
|
||||
nelts = ngx_min(ctx->times.nelts, sizeof(ngx_rtmp_flv_buffer) / 9);
|
||||
size = nelts * 9;
|
||||
|
||||
n = ngx_read_file(f, ngx_rtmp_flv_buffer, size, offset);
|
||||
|
||||
if (n != size) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: could not read times index");
|
||||
goto rewind;
|
||||
}
|
||||
|
||||
/*TODO: implement binary search */
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: lookup times nelts=%ui", nelts);
|
||||
|
||||
for (index = 0; index < nelts - 1; ++index) {
|
||||
v = ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer +
|
||||
index * 9 + 1) * 1000;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: lookup times index=%ui value=%ui",
|
||||
index, (ngx_uint_t) v);
|
||||
|
||||
if (timestamp < v) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index >= ctx->filepositions.nelts) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: index out of bounds: %ui>=%ui",
|
||||
index, ctx->filepositions.nelts);
|
||||
goto rewind;
|
||||
}
|
||||
|
||||
/* take value from filepositions */
|
||||
offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER +
|
||||
ctx->filepositions.offset + index * 9;
|
||||
|
||||
n = ngx_read_file(f, ngx_rtmp_flv_buffer, 8, offset + 1);
|
||||
|
||||
if (n != 8) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: could not read filepositions index");
|
||||
goto rewind;
|
||||
}
|
||||
|
||||
ret = (ngx_uint_t) ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: lookup index timestamp=%i offset=%ui",
|
||||
timestamp, ret);
|
||||
|
||||
return ret;
|
||||
|
||||
rewind:
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: lookup index timestamp=%i offset=begin",
|
||||
timestamp);
|
||||
|
||||
return NGX_RTMP_FLV_DATA_OFFSET;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
ssize_t n;
|
||||
ngx_rtmp_header_t h;
|
||||
ngx_chain_t *out, in;
|
||||
ngx_buf_t in_buf;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
uint32_t size;
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: read meta");
|
||||
|
||||
/* read tag header */
|
||||
n = ngx_read_file(f, ngx_rtmp_flv_header, sizeof(ngx_rtmp_flv_header),
|
||||
NGX_RTMP_FLV_DATA_OFFSET);
|
||||
|
||||
if (n != sizeof(ngx_rtmp_flv_header)) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: could not read metadata tag header");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_flv_header[0] != NGX_RTMP_MSG_AMF_META) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: first tag is not metadata, giving up");
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_memzero(&h, sizeof(h));
|
||||
|
||||
h.type = NGX_RTMP_MSG_AMF_META;
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
h.csid = NGX_RTMP_CSID_AMF;
|
||||
|
||||
size = ngx_rtmp_n3_to_h4(ngx_rtmp_flv_header + 1);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: metadata size=%D", size);
|
||||
|
||||
if (size > sizeof(ngx_rtmp_flv_buffer)) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: too big metadata");
|
||||
return;
|
||||
}
|
||||
|
||||
/* read metadata */
|
||||
n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,
|
||||
sizeof(ngx_rtmp_flv_header) +
|
||||
NGX_RTMP_FLV_DATA_OFFSET);
|
||||
|
||||
if (n != (ssize_t) size) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: could not read metadata");
|
||||
return;
|
||||
}
|
||||
|
||||
/* prepare input chain */
|
||||
ngx_memzero(&in, sizeof(in));
|
||||
ngx_memzero(&in_buf, sizeof(in_buf));
|
||||
|
||||
in.buf = &in_buf;
|
||||
in_buf.pos = ngx_rtmp_flv_buffer;
|
||||
in_buf.last = ngx_rtmp_flv_buffer + size;
|
||||
|
||||
ngx_rtmp_flv_init_index(s, &in);
|
||||
|
||||
/* output chain */
|
||||
out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
|
||||
|
||||
ngx_rtmp_prepare_message(s, &h, NULL, out);
|
||||
ngx_rtmp_send_message(s, out, 0);
|
||||
ngx_rtmp_free_shared_chain(cscf, out);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t *ts)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
uint32_t last_timestamp;
|
||||
ngx_rtmp_header_t h, lh;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
ngx_chain_t *out, in;
|
||||
ngx_buf_t in_buf;
|
||||
ngx_int_t rc;
|
||||
ssize_t n;
|
||||
uint32_t buflen, end_timestamp, size;
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ctx->offset == -1) {
|
||||
ctx->offset = ngx_rtmp_flv_timestamp_to_offset(s, f,
|
||||
ctx->start_timestamp);
|
||||
ctx->start_timestamp = -1; /* set later from actual timestamp */
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: read tag at offset=%i", ctx->offset);
|
||||
|
||||
/* read tag header */
|
||||
n = ngx_read_file(f, ngx_rtmp_flv_header,
|
||||
sizeof(ngx_rtmp_flv_header), ctx->offset);
|
||||
|
||||
if (n != sizeof(ngx_rtmp_flv_header)) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: could not read flv tag header");
|
||||
return NGX_DONE;
|
||||
}
|
||||
|
||||
/* parse header fields */
|
||||
ngx_memzero(&h, sizeof(h));
|
||||
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
h.type = ngx_rtmp_flv_header[0];
|
||||
|
||||
size = ngx_rtmp_n3_to_h4(ngx_rtmp_flv_header + 1);
|
||||
h.timestamp = ngx_rtmp_n3_to_h4(ngx_rtmp_flv_header + 4);
|
||||
h.timestamp |= ((uint32_t) ngx_rtmp_flv_header[7] << 24);
|
||||
|
||||
ctx->offset += (sizeof(ngx_rtmp_flv_header) + size + 4);
|
||||
|
||||
last_timestamp = 0;
|
||||
|
||||
switch (h.type) {
|
||||
|
||||
case NGX_RTMP_MSG_AUDIO:
|
||||
h.csid = NGX_RTMP_CSID_AUDIO;
|
||||
last_timestamp = ctx->last_audio;
|
||||
ctx->last_audio = h.timestamp;
|
||||
break;
|
||||
|
||||
case NGX_RTMP_MSG_VIDEO:
|
||||
h.csid = NGX_RTMP_CSID_VIDEO;
|
||||
last_timestamp = ctx->last_video;
|
||||
ctx->last_video = h.timestamp;
|
||||
break;
|
||||
|
||||
default:
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: read tag type=%i size=%uD timestamp=%uD "
|
||||
"last_timestamp=%uD",
|
||||
(ngx_int_t) h.type,size, h.timestamp, last_timestamp);
|
||||
|
||||
lh = h;
|
||||
lh.timestamp = last_timestamp;
|
||||
|
||||
if (size > sizeof(ngx_rtmp_flv_buffer)) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: too big message: %D>%uz", size,
|
||||
sizeof(ngx_rtmp_flv_buffer));
|
||||
goto next;
|
||||
}
|
||||
|
||||
/* read tag body */
|
||||
n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,
|
||||
ctx->offset - size - 4);
|
||||
|
||||
if (n != (ssize_t) size) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: could not read flv tag");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* prepare input chain */
|
||||
ngx_memzero(&in, sizeof(in));
|
||||
ngx_memzero(&in_buf, sizeof(in_buf));
|
||||
|
||||
in.buf = &in_buf;
|
||||
in_buf.pos = ngx_rtmp_flv_buffer;
|
||||
in_buf.last = ngx_rtmp_flv_buffer + size;
|
||||
|
||||
/* output chain */
|
||||
out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
|
||||
|
||||
ngx_rtmp_prepare_message(s, &h,
|
||||
ctx->msg_mask & ((ngx_uint_t) 1 << h.type) ?
|
||||
&lh : NULL, out);
|
||||
rc = ngx_rtmp_send_message(s, out, 0);
|
||||
ngx_rtmp_free_shared_chain(cscf, out);
|
||||
|
||||
if (rc == NGX_AGAIN) {
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ctx->msg_mask |= ((ngx_uint_t) 1 << h.type);
|
||||
|
||||
next:
|
||||
if (ctx->start_timestamp == -1) {
|
||||
ctx->start_timestamp = h.timestamp;
|
||||
ctx->epoch = ngx_current_msec;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: start_timestamp=%i", ctx->start_timestamp);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
buflen = s->buflen + NGX_RTMP_FLV_BUFLEN_ADDON;
|
||||
|
||||
end_timestamp = (ngx_current_msec - ctx->epoch) +
|
||||
ctx->start_timestamp + buflen;
|
||||
|
||||
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i",
|
||||
h.timestamp > end_timestamp ? "schedule" : "advance",
|
||||
h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0,
|
||||
h.timestamp, end_timestamp, (ngx_int_t) buflen);
|
||||
|
||||
s->current_time = h.timestamp;
|
||||
|
||||
/* too much data sent; schedule timeout */
|
||||
if (h.timestamp > end_timestamp) {
|
||||
return h.timestamp - end_timestamp;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_int_t aindex,
|
||||
ngx_int_t vindex)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_flv_ctx_t));
|
||||
|
||||
if (ctx == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_flv_module);
|
||||
}
|
||||
|
||||
ngx_memzero(ctx, sizeof(*ctx));
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: start");
|
||||
|
||||
ctx->offset = -1;
|
||||
ctx->msg_mask = 0;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_seek(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t timestamp)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: seek timestamp=%ui", timestamp);
|
||||
|
||||
ctx->start_timestamp = timestamp;
|
||||
ctx->epoch = ngx_current_msec;
|
||||
ctx->offset = -1;
|
||||
ctx->msg_mask = 0;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: stop");
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_play_main_conf_t *pmcf;
|
||||
ngx_rtmp_play_fmt_t **pfmt, *fmt;
|
||||
|
||||
pmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module);
|
||||
|
||||
pfmt = ngx_array_push(&pmcf->fmts);
|
||||
|
||||
if (pfmt == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
fmt = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_fmt_t));
|
||||
|
||||
if (fmt == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
*pfmt = fmt;
|
||||
|
||||
ngx_str_set(&fmt->name, "flv-format");
|
||||
|
||||
ngx_str_null(&fmt->pfx); /* default fmt */
|
||||
ngx_str_set(&fmt->sfx, ".flv");
|
||||
|
||||
fmt->init = ngx_rtmp_flv_init;
|
||||
fmt->start = ngx_rtmp_flv_start;
|
||||
fmt->seek = ngx_rtmp_flv_seek;
|
||||
fmt->stop = ngx_rtmp_flv_stop;
|
||||
fmt->send = ngx_rtmp_flv_send;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
1016
ngx_http_flv_module/ngx_rtmp_gop_cache_module.c
Normal file
1016
ngx_http_flv_module/ngx_rtmp_gop_cache_module.c
Normal file
File diff suppressed because it is too large
Load Diff
65
ngx_http_flv_module/ngx_rtmp_gop_cache_module.h
Normal file
65
ngx_http_flv_module/ngx_rtmp_gop_cache_module.h
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Gnolizuh
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
#ifndef _NGX_RTMP_GOP_CACHE_H_INCLUDE_
|
||||
#define _NGX_RTMP_GOP_CACHE_H_INCLUDE_
|
||||
|
||||
|
||||
#define NGX_GOP_CACHE_POOL_CREATE_SIZE 4096
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_gop_frame_s ngx_rtmp_gop_frame_t;
|
||||
typedef struct ngx_rtmp_gop_cache_s ngx_rtmp_gop_cache_t;
|
||||
|
||||
|
||||
struct ngx_rtmp_gop_frame_s {
|
||||
ngx_rtmp_header_t h;
|
||||
ngx_uint_t prio;
|
||||
ngx_chain_t *frame;
|
||||
ngx_rtmp_gop_frame_t *next;
|
||||
};
|
||||
|
||||
|
||||
struct ngx_rtmp_gop_cache_s {
|
||||
ngx_rtmp_gop_frame_t *frame_head;
|
||||
ngx_rtmp_gop_frame_t *frame_tail;
|
||||
ngx_rtmp_gop_cache_t *next;
|
||||
|
||||
ngx_chain_t *video_seq_header;
|
||||
ngx_chain_t *audio_seq_header;
|
||||
ngx_chain_t *meta;
|
||||
|
||||
ngx_uint_t meta_version;
|
||||
|
||||
ngx_int_t video_frame_in_this;
|
||||
ngx_int_t audio_frame_in_this;
|
||||
};
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_gop_cache_app_conf_s {
|
||||
ngx_flag_t gop_cache;
|
||||
size_t gop_cache_count;
|
||||
size_t gop_max_frame_count;
|
||||
size_t gop_max_video_count;
|
||||
size_t gop_max_audio_count;
|
||||
} ngx_rtmp_gop_cache_app_conf_t;
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_gop_cache_ctx_s {
|
||||
ngx_pool_t *pool;
|
||||
ngx_rtmp_gop_cache_t *cache_head;
|
||||
ngx_rtmp_gop_cache_t *cache_tail;
|
||||
ngx_rtmp_gop_cache_t *free_cache;
|
||||
ngx_rtmp_gop_frame_t *free_frame;
|
||||
|
||||
size_t gop_cache_count;
|
||||
size_t video_frame_in_all;
|
||||
size_t audio_frame_in_all;
|
||||
} ngx_rtmp_gop_cache_ctx_t;
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
940
ngx_http_flv_module/ngx_rtmp_handler.c
Normal file
940
ngx_http_flv_module/ngx_rtmp_handler.c
Normal file
@@ -0,0 +1,940 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_live_module.h"
|
||||
#include "ngx_rtmp_amf.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);
|
||||
|
||||
|
||||
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 = c->log;
|
||||
s->ping_evt.handler = ngx_rtmp_ping;
|
||||
ngx_rtmp_reset_ping(s);
|
||||
|
||||
ngx_rtmp_recv(c->read);
|
||||
}
|
||||
|
||||
|
||||
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->connection->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, c->log, 0,
|
||||
"ping: unresponded");
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cscf->busy) {
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, 0,
|
||||
"ping: not busy between pings");
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->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, *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;
|
||||
}
|
||||
|
||||
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, c->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, c->log, 0,
|
||||
"reusing formerly read data: %d", old_size);
|
||||
|
||||
b->pos = b->start;
|
||||
|
||||
size = ngx_min((size_t) (b->end - b->start), old_size);
|
||||
b->last = ngx_movemem(b->pos, old_pos, 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) {
|
||||
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, c->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, c->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 += *p++;
|
||||
|
||||
} else if (csid == 1) {
|
||||
if (b->last - p < 2)
|
||||
continue;
|
||||
csid = 64;
|
||||
csid += *p++;
|
||||
csid += ((uint32_t) *p++ << 8);
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, c->log, 0,
|
||||
"RTMP bheader fmt=%d csid=%D",
|
||||
(int)fmt, csid);
|
||||
|
||||
if (csid >= (uint32_t)cscf->max_streams) {
|
||||
ngx_log_error(NGX_LOG_INFO, c->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 */
|
||||
|
||||
timestamp = 0;
|
||||
timestamp |= ((uint32_t) *p++ << 16);
|
||||
timestamp |= ((uint32_t) *p++ << 8);
|
||||
timestamp |= *p++;
|
||||
|
||||
ext = (timestamp == 0x00ffffff);
|
||||
|
||||
if (fmt <= 1) {
|
||||
if (b->last - p < 4)
|
||||
continue;
|
||||
|
||||
/* size: big-endian 3B -> little-endian 4B */
|
||||
|
||||
h->mlen = 0;
|
||||
h->mlen |= ((uint32_t) *p++ << 16);
|
||||
h->mlen |= ((uint32_t) *p++ << 8);
|
||||
h->mlen |= *p++;
|
||||
|
||||
h->type = *p++;
|
||||
|
||||
if (fmt == 0) {
|
||||
if (b->last - p < 4)
|
||||
continue;
|
||||
|
||||
/* stream: little-endian 4B */
|
||||
|
||||
h->msid = *p++;
|
||||
h->msid |= ((uint32_t) *p++ << 8);
|
||||
h->msid |= ((uint32_t) *p++ << 16);
|
||||
h->msid |= ((uint32_t) *p++ << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* extended header */
|
||||
if (ext) {
|
||||
if (b->last - p < 4)
|
||||
continue;
|
||||
|
||||
/* timestamp: big-endian 4B */
|
||||
|
||||
timestamp = 0;
|
||||
timestamp |= ((uint32_t) *p++ << 24);
|
||||
timestamp |= ((uint32_t) *p++ << 16);
|
||||
timestamp |= ((uint32_t) *p++ << 8);
|
||||
timestamp |= *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, c->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, c->log, 0,
|
||||
"too big message: %uz, %uz",
|
||||
h->mlen, 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;
|
||||
}
|
||||
|
||||
/* server configuration may change due to virtual server match */
|
||||
if (s->server_changed) {
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
st = &s->in_streams[s->in_csid];
|
||||
|
||||
s->server_changed = 0;
|
||||
}
|
||||
|
||||
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 void
|
||||
ngx_rtmp_send(ngx_event_t *wev)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_int_t n;
|
||||
ngx_rtmp_live_ctx_t *lctx;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
|
||||
c = wev->data;
|
||||
s = c->data;
|
||||
|
||||
if (c->destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wev->timedout) {
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
|
||||
"client timed out");
|
||||
c->timedout = 1;
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wev->timer_set) {
|
||||
ngx_del_timer(wev);
|
||||
}
|
||||
|
||||
if (s->out_chain == NULL && s->out_pos != s->out_last) {
|
||||
s->out_chain = s->out[s->out_pos];
|
||||
s->out_bpos = s->out_chain->buf->pos;
|
||||
}
|
||||
|
||||
while (s->out_chain) {
|
||||
n = c->send(c, s->out_bpos, s->out_chain->buf->last - s->out_bpos);
|
||||
|
||||
if (n == NGX_AGAIN || n == 0) {
|
||||
ngx_add_timer(c->write, s->timeout);
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
ngx_rtmp_finalize_session(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (n < 0) {
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
|
||||
s->out_bytes += n;
|
||||
s->ping_reset = 1;
|
||||
ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_out, n);
|
||||
s->out_bpos += n;
|
||||
if (s->out_bpos == s->out_chain->buf->last) {
|
||||
s->out_chain = s->out_chain->next;
|
||||
if (s->out_chain == NULL) {
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
ngx_rtmp_free_shared_chain(cscf, s->out[s->out_pos]);
|
||||
++s->out_pos;
|
||||
s->out_pos %= s->out_queue;
|
||||
if (s->out_pos == s->out_last) {
|
||||
break;
|
||||
}
|
||||
s->out_chain = s->out[s->out_pos];
|
||||
}
|
||||
s->out_bpos = s->out_chain->buf->pos;
|
||||
}
|
||||
}
|
||||
|
||||
lctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
|
||||
if (lctx && !lctx->publishing && !wev->timer_set) {
|
||||
ngx_add_timer(wev, s->timeout);
|
||||
}
|
||||
|
||||
if (wev->active) {
|
||||
ngx_del_event(wev, NGX_WRITE_EVENT, 0);
|
||||
}
|
||||
|
||||
ngx_event_process_posted((ngx_cycle_t *) ngx_cycle, &s->posted_dry_events);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_prepare_message(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_rtmp_header_t *lh, ngx_chain_t *out)
|
||||
{
|
||||
ngx_chain_t *l;
|
||||
u_char *p;
|
||||
ngx_int_t hsize, thsize, nbufs;
|
||||
uint32_t mlen, timestamp, ext_timestamp;
|
||||
static uint8_t hdrsize[] = { 12, 8, 4, 1 };
|
||||
u_char th[7];
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
uint8_t fmt;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = s->connection;
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
if (h->csid >= (uint32_t)cscf->max_streams) {
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, 0,
|
||||
"RTMP out chunk stream too big: %D >= %D",
|
||||
h->csid, cscf->max_streams);
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
|
||||
/* detect packet size */
|
||||
mlen = 0;
|
||||
nbufs = 0;
|
||||
for(l = out; l; l = l->next) {
|
||||
mlen += (l->buf->last - l->buf->pos);
|
||||
++nbufs;
|
||||
}
|
||||
|
||||
fmt = 0;
|
||||
if (lh && lh->csid && h->msid == lh->msid) {
|
||||
++fmt;
|
||||
if (h->type == lh->type && mlen && mlen == lh->mlen) {
|
||||
++fmt;
|
||||
if (h->timestamp == lh->timestamp) {
|
||||
++fmt;
|
||||
}
|
||||
}
|
||||
|
||||
if (h->type == NGX_RTMP_MSG_VIDEO || h->type == NGX_RTMP_MSG_AUDIO) {
|
||||
timestamp = h->timestamp - s->offset_timestamp - lh->timestamp;
|
||||
|
||||
if (lh->timestamp) {
|
||||
timestamp += s->offset_timestamp;
|
||||
}
|
||||
} else {
|
||||
timestamp = h->timestamp - lh->timestamp;
|
||||
}
|
||||
} else {
|
||||
if (h->type == NGX_RTMP_MSG_VIDEO || h->type == NGX_RTMP_MSG_AUDIO) {
|
||||
if (!s->offset_timestamp_set) {
|
||||
s->offset_timestamp_set = 1;
|
||||
s->offset_timestamp = h->timestamp;
|
||||
} else if (h->timestamp == 0) {
|
||||
s->offset_timestamp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
timestamp = h->timestamp - s->offset_timestamp;
|
||||
}
|
||||
|
||||
/*if (lh) {
|
||||
*lh = *h;
|
||||
lh->mlen = mlen;
|
||||
}*/
|
||||
|
||||
hsize = hdrsize[fmt];
|
||||
|
||||
(void) nbufs;
|
||||
ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"RTMP prep %s (%d) fmt=%d csid=%uD timestamp=%uD "
|
||||
"mlen=%uD msid=%uD nbufs=%d",
|
||||
ngx_rtmp_message_type(h->type), (int)h->type, (int)fmt,
|
||||
h->csid, timestamp, mlen, h->msid, nbufs);
|
||||
|
||||
ext_timestamp = 0;
|
||||
if (timestamp >= 0x00ffffff) {
|
||||
ext_timestamp = timestamp;
|
||||
timestamp = 0x00ffffff;
|
||||
hsize += 4;
|
||||
}
|
||||
|
||||
if (h->csid >= 64) {
|
||||
++hsize;
|
||||
if (h->csid >= 320) {
|
||||
++hsize;
|
||||
}
|
||||
}
|
||||
|
||||
/* fill initial header */
|
||||
out->buf->pos -= hsize;
|
||||
p = out->buf->pos;
|
||||
|
||||
/* basic header */
|
||||
*p = (fmt << 6);
|
||||
if (h->csid >= 2 && h->csid <= 63) {
|
||||
*p++ |= (((uint8_t)h->csid) & 0x3f);
|
||||
} else if (h->csid >= 64 && h->csid < 320) {
|
||||
++p;
|
||||
*p++ = (uint8_t)(h->csid - 64);
|
||||
} else {
|
||||
*p++ |= 1;
|
||||
*p++ = (uint8_t)(h->csid - 64);
|
||||
*p++ = (uint8_t)((h->csid - 64) >> 8);
|
||||
}
|
||||
|
||||
/* create fmt3 header for successive fragments */
|
||||
thsize = p - out->buf->pos;
|
||||
ngx_memcpy(th, out->buf->pos, thsize);
|
||||
th[0] |= 0xc0;
|
||||
|
||||
/* message header */
|
||||
if (fmt <= 2) {
|
||||
*p++ = (u_char) (timestamp >> 16);
|
||||
*p++ = (u_char) (timestamp >> 8);
|
||||
*p++ = (u_char) timestamp;
|
||||
|
||||
if (fmt <= 1) {
|
||||
*p++ = (u_char) (mlen >> 16);
|
||||
*p++ = (u_char) (mlen >> 8);
|
||||
*p++ = (u_char) mlen;
|
||||
|
||||
*p++ = h->type;
|
||||
|
||||
if (fmt == 0) {
|
||||
*p++ = (u_char) h->msid;
|
||||
*p++ = (u_char) (h->msid >> 8);
|
||||
*p++ = (u_char) (h->msid >> 16);
|
||||
*p++ = (u_char) (h->msid >> 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* extended header */
|
||||
if (ext_timestamp) {
|
||||
*p++ = (u_char) (ext_timestamp >> 24);
|
||||
*p++ = (u_char) (ext_timestamp >> 16);
|
||||
*p++ = (u_char) (ext_timestamp >> 8);
|
||||
*p++ = (u_char) ext_timestamp;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/* append headers to successive fragments */
|
||||
for(out = out->next; out; out = out->next) {
|
||||
out->buf->pos -= thsize;
|
||||
ngx_memcpy(out->buf->pos, th, thsize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_message(ngx_rtmp_session_t *s, ngx_chain_t *out,
|
||||
ngx_uint_t priority)
|
||||
{
|
||||
ngx_uint_t nmsg;
|
||||
|
||||
nmsg = (s->out_last + s->out_queue - 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->connection->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_acquire_shared_chain(out);
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->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;
|
||||
}
|
||||
|
||||
if (!s->connection->write->active) {
|
||||
ngx_rtmp_send(s->connection->write);
|
||||
/*return ngx_add_event(s->connection->write, NGX_WRITE_EVENT, NGX_CLEAR_EVENT);*/
|
||||
}
|
||||
|
||||
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->connection->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->connection->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->connection->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->connection->log, 0,
|
||||
"calling handler %d", n);
|
||||
|
||||
switch ((*evh)(s, h, in)) {
|
||||
case NGX_ERROR:
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->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;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"setting chunk_size=%ui", size);
|
||||
|
||||
if (size > NGX_RTMP_MAX_CHUNK_SIZE) {
|
||||
ngx_log_error(NGX_LOG_ALERT, s->connection->log, 0,
|
||||
"too big RTMP chunk size:%ui", size);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
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, s->connection->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;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
647
ngx_http_flv_module/ngx_rtmp_handshake.c
Normal file
647
ngx_http_flv_module/ngx_rtmp_handshake.c
Normal file
@@ -0,0 +1,647 @@
|
||||
|
||||
/*
|
||||
* 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 1537
|
||||
|
||||
|
||||
#define NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE 1
|
||||
#define NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE 2
|
||||
#define NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE 3
|
||||
#define NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE 4
|
||||
#define NGX_RTMP_HANDSHAKE_SERVER_DONE 5
|
||||
|
||||
|
||||
#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)
|
||||
{
|
||||
for (; b->last != b->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->connection->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;
|
||||
|
||||
b = s->hs_buf;
|
||||
b->last = b->pos = b->start;
|
||||
*b->last++ = '\x03';
|
||||
*(uint32_t *) b->last = htonl(s->epoch);
|
||||
b->last += 4;
|
||||
b->last = ngx_cpymem(b->last, version, 4);
|
||||
ngx_rtmp_fill_random_buffer(b);
|
||||
++b->pos;
|
||||
if (ngx_rtmp_write_digest(b, key, 0, s->connection->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->connection->log, 0,
|
||||
"handshake: unexpected RTMP version: %i",
|
||||
(ngx_int_t)*b->pos);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
++b->pos;
|
||||
s->peer_epoch = ntohl(*(uint32_t *) b->pos);
|
||||
|
||||
p = b->pos + 4;
|
||||
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->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->connection->log);
|
||||
if (offs == NGX_ERROR) {
|
||||
offs = ngx_rtmp_find_digest(b, peer_key, 8, s->connection->log);
|
||||
}
|
||||
if (offs == NGX_ERROR) {
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"handshake: digest not found");
|
||||
s->hs_old = 1;
|
||||
return NGX_OK;
|
||||
}
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->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->connection->pool, NGX_RTMP_HANDSHAKE_KEYLEN);
|
||||
if (s->hs_digest == NULL) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"handshake: failed to allocate for digest");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_make_digest(key, b, NULL, s->hs_digest, s->connection->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;
|
||||
ngx_str_t key;
|
||||
|
||||
b = s->hs_buf;
|
||||
b->pos = b->last = b->start + 1;
|
||||
ngx_rtmp_fill_random_buffer(b);
|
||||
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->connection->log) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
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->connection->log, 0,
|
||||
"handshake: done");
|
||||
|
||||
if (ngx_rtmp_fire_event(s, NGX_RTMP_HANDSHAKE_DONE,
|
||||
NULL, NULL) != NGX_OK)
|
||||
{
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
|
||||
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, c->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_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->connection->log, 0,
|
||||
"handshake: stage %ui", s->hs_stage);
|
||||
|
||||
switch (s->hs_stage) {
|
||||
case NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE:
|
||||
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, c->log, 0,
|
||||
"handshake: error parsing challenge");
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
if (s->hs_old) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"handshake: old-style challenge");
|
||||
s->hs_buf->pos = s->hs_buf->start;
|
||||
s->hs_buf->last = s->hs_buf->end;
|
||||
} else if (ngx_rtmp_handshake_create_challenge(s,
|
||||
ngx_rtmp_server_version,
|
||||
&ngx_rtmp_server_partial_key) != NGX_OK)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, 0,
|
||||
"handshake: error creating challenge");
|
||||
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, c->log, 0,
|
||||
"handshake: error parsing challenge");
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1;
|
||||
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, c->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, c->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_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->connection->log, 0,
|
||||
"handshake: stage %ui", s->hs_stage);
|
||||
|
||||
switch (s->hs_stage) {
|
||||
case NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE:
|
||||
if (s->hs_old) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"handshake: old-style response");
|
||||
s->hs_buf->pos = s->hs_buf->start + 1;
|
||||
s->hs_buf->last = s->hs_buf->end;
|
||||
} else if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, 0,
|
||||
"handshake: response error");
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
ngx_rtmp_handshake_send(wev);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE:
|
||||
s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1;
|
||||
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->start;
|
||||
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;
|
||||
ngx_time_t *tp;
|
||||
|
||||
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->connection->log, 0,
|
||||
"handshake: start server handshake");
|
||||
|
||||
s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);
|
||||
s->hs_stage = NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE;
|
||||
|
||||
tp = ngx_timeofday();
|
||||
s->start_sec = tp->sec;
|
||||
s->start_msec = tp->msec;
|
||||
|
||||
ngx_rtmp_handshake_recv(c->read);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
ngx_time_t *tp;
|
||||
|
||||
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->connection->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;
|
||||
}
|
||||
|
||||
tp = ngx_timeofday();
|
||||
s->start_sec = tp->sec;
|
||||
s->start_msec = tp->msec;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
458
ngx_http_flv_module/ngx_rtmp_init.c
Normal file
458
ngx_http_flv_module/ngx_rtmp_init.c
Normal file
@@ -0,0 +1,458 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
#include "ngx_rtmp_proxy_protocol.h"
|
||||
|
||||
|
||||
static void ngx_rtmp_close_connection(ngx_connection_t *c);
|
||||
static void ngx_rtmp_process_unix_socket(ngx_rtmp_connection_t *rconn);
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_init_connection(ngx_connection_t *c)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
ngx_rtmp_port_t *port;
|
||||
struct sockaddr_in *sin;
|
||||
ngx_rtmp_in_addr_t *addr;
|
||||
ngx_rtmp_connection_t *rconn;
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_int_t unix_socket;
|
||||
#if (NGX_HAVE_INET6)
|
||||
struct sockaddr_in6 *sin6;
|
||||
ngx_rtmp_in6_addr_t *addr6;
|
||||
#endif
|
||||
|
||||
rconn = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_connection_t));
|
||||
if (rconn == NULL) {
|
||||
ngx_rtmp_close_connection(c);
|
||||
return;
|
||||
}
|
||||
|
||||
++ngx_rtmp_naccepted;
|
||||
|
||||
c->data = rconn;
|
||||
|
||||
/* find the server configuration for the address:port */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
switch (c->local_sockaddr->sa_family) {
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
case AF_INET6:
|
||||
sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
rconn->addr_conf = &addr6[i].conf;
|
||||
|
||||
break;
|
||||
#endif
|
||||
|
||||
case AF_UNIX:
|
||||
unix_socket = 1;
|
||||
|
||||
ngx_rtmp_process_unix_socket(rconn);
|
||||
|
||||
break;
|
||||
|
||||
default: /* AF_INET */
|
||||
sin = (struct sockaddr_in *) c->local_sockaddr;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
rconn->addr_conf = &addr[i].conf;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
switch (c->local_sockaddr->sa_family) {
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
case AF_INET6:
|
||||
addr6 = port->addrs;
|
||||
rconn->addr_conf = &addr6[0].conf;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case AF_UNIX:
|
||||
unix_socket = 1;
|
||||
|
||||
ngx_rtmp_process_unix_socket(rconn);
|
||||
|
||||
break;
|
||||
|
||||
default: /* AF_INET */
|
||||
addr = port->addrs;
|
||||
rconn->addr_conf = &addr[0].conf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* the default server configuration for the address:port */
|
||||
rconn->conf_ctx = rconn->addr_conf->default_server->ctx;
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%ui client connected '%V'",
|
||||
c->number, &c->addr_text);
|
||||
|
||||
s = ngx_rtmp_init_session(c, rconn->addr_conf);
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* only auto-pushed connections are
|
||||
* done through unix socket */
|
||||
|
||||
s->auto_pushed = unix_socket;
|
||||
|
||||
if (rconn->addr_conf->proxy_protocol) {
|
||||
ngx_rtmp_proxy_protocol(s);
|
||||
|
||||
} else {
|
||||
ngx_rtmp_handshake(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_rtmp_session_t *
|
||||
ngx_rtmp_init_session(ngx_connection_t *c, ngx_rtmp_addr_conf_t *addr_conf)
|
||||
{
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
ngx_rtmp_error_log_ctx_t *ctx;
|
||||
|
||||
s = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_session_t));
|
||||
if (s == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
s->rtmp_connection = c->data;
|
||||
|
||||
s->main_conf = addr_conf->default_server->ctx->main_conf;
|
||||
s->srv_conf = addr_conf->default_server->ctx->srv_conf;
|
||||
|
||||
s->addr_text = &addr_conf->addr_text;
|
||||
|
||||
c->data = s;
|
||||
s->connection = c;
|
||||
|
||||
ctx = ngx_palloc(c->pool, sizeof(ngx_rtmp_error_log_ctx_t));
|
||||
if (ctx == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
ctx->client = &c->addr_text;
|
||||
ctx->session = s;
|
||||
|
||||
c->log->connection = c->number;
|
||||
c->log->handler = ngx_rtmp_log_error;
|
||||
c->log->data = ctx;
|
||||
c->log->action = NULL;
|
||||
|
||||
c->log_error = NGX_ERROR_INFO;
|
||||
|
||||
s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_rtmp_max_module);
|
||||
if (s->ctx == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
s->out_pool = ngx_create_pool(4096, c->log);
|
||||
if (s->out_pool == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
s->out = ngx_pcalloc(s->out_pool, sizeof(ngx_chain_t *)
|
||||
* ((ngx_rtmp_core_srv_conf_t *)
|
||||
addr_conf->default_server->ctx->srv_conf
|
||||
[ngx_rtmp_core_module.ctx_index])->out_queue);
|
||||
if (s->out == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
s->in_streams_pool = ngx_create_pool(4096, c->log);
|
||||
if (s->in_streams_pool == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
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(s->in_streams_pool, sizeof(ngx_rtmp_stream_t)
|
||||
* cscf->max_streams);
|
||||
if (s->in_streams == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
#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;
|
||||
ngx_rtmp_set_chunk_size(s, NGX_RTMP_DEFAULT_CHUNK_SIZE);
|
||||
|
||||
|
||||
if (ngx_rtmp_fire_event(s, NGX_RTMP_CONNECT, NULL, NULL) != NGX_OK) {
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return s;
|
||||
|
||||
failed:
|
||||
if (s && s->out_pool) {
|
||||
ngx_destroy_pool(s->out_pool);
|
||||
s->out_pool = NULL;
|
||||
}
|
||||
|
||||
if (s && s->in_streams_pool) {
|
||||
ngx_destroy_pool(s->in_streams_pool);
|
||||
s->in_streams_pool = NULL;
|
||||
}
|
||||
|
||||
ngx_rtmp_close_connection(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
if (log->action) {
|
||||
p = ngx_snprintf(buf, len, " while %s", log->action);
|
||||
len -= p - buf;
|
||||
buf = p;
|
||||
}
|
||||
|
||||
ctx = log->data;
|
||||
|
||||
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", s->addr_text);
|
||||
len -= p - buf;
|
||||
buf = p;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_close_connection(ngx_connection_t *c)
|
||||
{
|
||||
ngx_pool_t *pool;
|
||||
|
||||
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_handler(ngx_event_t *e)
|
||||
{
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_connection_t *c;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
|
||||
s = e->data;
|
||||
c = s->connection;
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "close session");
|
||||
|
||||
ngx_rtmp_fire_event(s, NGX_RTMP_DISCONNECT, NULL, NULL);
|
||||
|
||||
if (s->ping_evt.timer_set) {
|
||||
ngx_del_timer(&s->ping_evt);
|
||||
}
|
||||
|
||||
if (s->in_old_pool) {
|
||||
ngx_destroy_pool(s->in_old_pool);
|
||||
}
|
||||
|
||||
if (s->in_pool) {
|
||||
ngx_destroy_pool(s->in_pool);
|
||||
}
|
||||
|
||||
ngx_rtmp_free_handshake_buffers(s);
|
||||
|
||||
while (s->out_pos != s->out_last) {
|
||||
ngx_rtmp_free_shared_chain(cscf, s->out[s->out_pos]);
|
||||
|
||||
s->out_pos++;
|
||||
s->out_pos %= s->out_queue;
|
||||
}
|
||||
|
||||
if (s->in_streams_pool) {
|
||||
ngx_destroy_pool(s->in_streams_pool);
|
||||
}
|
||||
|
||||
if (s->out_pool) {
|
||||
ngx_destroy_pool(s->out_pool);
|
||||
}
|
||||
|
||||
ngx_rtmp_close_connection(c);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_finalize_session(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = s->connection;
|
||||
if (c->destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "finalize session");
|
||||
|
||||
c->destroyed = 1;
|
||||
e = &s->close;
|
||||
e->data = s;
|
||||
e->handler = ngx_rtmp_close_session_handler;
|
||||
e->log = c->log;
|
||||
|
||||
ngx_post_event(e, &ngx_posted_events);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_process_unix_socket(ngx_rtmp_connection_t *rconn)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
ngx_rtmp_port_t *port;
|
||||
struct sockaddr_in *sin;
|
||||
ngx_rtmp_in_addr_t *addr;
|
||||
#if (NGX_HAVE_INET6)
|
||||
struct sockaddr_in6 *sin6;
|
||||
ngx_rtmp_in6_addr_t *addr6;
|
||||
#endif
|
||||
ngx_listening_t *ls;
|
||||
|
||||
ls = ngx_cycle->listening.elts;
|
||||
for (i = 0; i < ngx_cycle->listening.nelts; ++i, ++ls) {
|
||||
if (ls->handler == ngx_rtmp_init_connection) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
port = ls->servers;
|
||||
|
||||
if (port->naddrs > 1) {
|
||||
switch (ls->sockaddr->sa_family) {
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
case AF_INET6:
|
||||
sin6 = (struct sockaddr_in6 *) ls->sockaddr;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
rconn->addr_conf = &addr6[i].conf;
|
||||
|
||||
break;
|
||||
#endif
|
||||
|
||||
default: /* AF_INET */
|
||||
sin = (struct sockaddr_in *) ls->sockaddr;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
rconn->addr_conf = &addr[i].conf;
|
||||
}
|
||||
} else {
|
||||
switch (ls->sockaddr->sa_family) {
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
case AF_INET6:
|
||||
addr6 = port->addrs;
|
||||
rconn->addr_conf = &addr6[0].conf;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default: /* AF_INET */
|
||||
addr = port->addrs;
|
||||
rconn->addr_conf = &addr[0].conf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
205
ngx_http_flv_module/ngx_rtmp_limit_module.c
Normal file
205
ngx_http_flv_module/ngx_rtmp_limit_module.c
Normal file
@@ -0,0 +1,205 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_int_t max_conn;
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
} ngx_rtmp_limit_main_conf_t;
|
||||
|
||||
|
||||
static ngx_str_t shm_name = ngx_string("rtmp_limit");
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_limit_postconfiguration(ngx_conf_t *cf);
|
||||
static void *ngx_rtmp_limit_create_main_conf(ngx_conf_t *cf);
|
||||
|
||||
|
||||
static ngx_command_t ngx_rtmp_limit_commands[] = {
|
||||
|
||||
{ ngx_string("max_connections"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
NGX_RTMP_MAIN_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_limit_main_conf_t, max_conn),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_limit_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_rtmp_limit_postconfiguration, /* postconfiguration */
|
||||
ngx_rtmp_limit_create_main_conf, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
NULL, /* create app configuration */
|
||||
NULL /* merge app configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_limit_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_limit_module_ctx, /* module context */
|
||||
ngx_rtmp_limit_commands, /* module directives */
|
||||
NGX_RTMP_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static void *
|
||||
ngx_rtmp_limit_create_main_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_limit_main_conf_t *lmcf;
|
||||
|
||||
lmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_limit_main_conf_t));
|
||||
if (lmcf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lmcf->max_conn = NGX_CONF_UNSET;
|
||||
|
||||
return lmcf;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_limit_connect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_limit_main_conf_t *lmcf;
|
||||
ngx_slab_pool_t *shpool;
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
uint32_t *nconn, n;
|
||||
ngx_int_t rc;
|
||||
|
||||
lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module);
|
||||
if (lmcf->max_conn == NGX_CONF_UNSET) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
shm_zone = lmcf->shm_zone;
|
||||
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
||||
nconn = shm_zone->data;
|
||||
|
||||
ngx_shmtx_lock(&shpool->mutex);
|
||||
n = ++*nconn;
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
rc = n > (ngx_uint_t) lmcf->max_conn ? NGX_ERROR : NGX_OK;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"limit: inc connection counter: %uD", n);
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->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->connection->log, 0,
|
||||
"limit: dec connection 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;
|
||||
}
|
||||
1682
ngx_http_flv_module/ngx_rtmp_live_module.c
Normal file
1682
ngx_http_flv_module/ngx_rtmp_live_module.c
Normal file
File diff suppressed because it is too large
Load Diff
91
ngx_http_flv_module/ngx_rtmp_live_module.h
Normal file
91
ngx_http_flv_module/ngx_rtmp_live_module.h
Normal file
@@ -0,0 +1,91 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#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"
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_live_ctx_s ngx_rtmp_live_ctx_t;
|
||||
typedef struct ngx_rtmp_live_stream_s ngx_rtmp_live_stream_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned active:1;
|
||||
uint32_t timestamp;
|
||||
uint32_t csid;
|
||||
uint32_t dropped;
|
||||
} ngx_rtmp_live_chunk_stream_t;
|
||||
|
||||
|
||||
struct ngx_rtmp_live_ctx_s {
|
||||
ngx_rtmp_session_t *session;
|
||||
ngx_rtmp_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;
|
||||
ngx_uint_t protocol;
|
||||
};
|
||||
|
||||
|
||||
struct ngx_rtmp_live_stream_s {
|
||||
u_char name[NGX_RTMP_MAX_NAME];
|
||||
ngx_rtmp_live_stream_t *next;
|
||||
ngx_rtmp_live_ctx_t *ctx;
|
||||
ngx_rtmp_live_ctx_t *pub_ctx;
|
||||
ngx_rtmp_bandwidth_t bw_in;
|
||||
ngx_rtmp_bandwidth_t bw_in_audio;
|
||||
ngx_rtmp_bandwidth_t bw_in_video;
|
||||
ngx_rtmp_bandwidth_t bw_in_data;
|
||||
ngx_rtmp_bandwidth_t bw_out;
|
||||
ngx_msec_t epoch;
|
||||
unsigned active:1;
|
||||
unsigned publishing:1;
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_int_t nbuckets;
|
||||
ngx_rtmp_live_stream_t **streams;
|
||||
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_msec_t buflen;
|
||||
ngx_pool_t *pool;
|
||||
ngx_rtmp_live_stream_t *free_streams;
|
||||
} ngx_rtmp_live_app_conf_t;
|
||||
|
||||
|
||||
extern ngx_module_t ngx_rtmp_live_module;
|
||||
|
||||
|
||||
ngx_rtmp_live_stream_t **ngx_rtmp_live_get_stream(ngx_rtmp_session_t *s,
|
||||
u_char *name, int create);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_LIVE_H_INCLUDED_ */
|
||||
1228
ngx_http_flv_module/ngx_rtmp_log_module.c
Normal file
1228
ngx_http_flv_module/ngx_rtmp_log_module.c
Normal file
File diff suppressed because it is too large
Load Diff
2600
ngx_http_flv_module/ngx_rtmp_mp4_module.c
Normal file
2600
ngx_http_flv_module/ngx_rtmp_mp4_module.c
Normal file
File diff suppressed because it is too large
Load Diff
733
ngx_http_flv_module/ngx_rtmp_netcall_module.c
Normal file
733
ngx_http_flv_module/ngx_rtmp_netcall_module.c
Normal file
@@ -0,0 +1,733 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_netcall_module.h"
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_netcall_postconfiguration(ngx_conf_t *cf);
|
||||
static void * ngx_rtmp_netcall_create_srv_conf(ngx_conf_t *cf);
|
||||
static char * ngx_rtmp_netcall_merge_srv_conf(ngx_conf_t *cf,
|
||||
void *parent, void *child);
|
||||
|
||||
static void ngx_rtmp_netcall_close(ngx_connection_t *cc);
|
||||
static void ngx_rtmp_netcall_detach(ngx_connection_t *cc);
|
||||
|
||||
static void ngx_rtmp_netcall_recv(ngx_event_t *rev);
|
||||
static void ngx_rtmp_netcall_send(ngx_event_t *wev);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_msec_t timeout;
|
||||
size_t bufsize;
|
||||
ngx_log_t *log;
|
||||
} ngx_rtmp_netcall_srv_conf_t;
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_netcall_session_s {
|
||||
ngx_rtmp_session_t *session;
|
||||
ngx_peer_connection_t *pc;
|
||||
ngx_url_t *url;
|
||||
struct ngx_rtmp_netcall_session_s *next;
|
||||
void *arg;
|
||||
ngx_rtmp_netcall_handle_pt handle;
|
||||
ngx_rtmp_netcall_filter_pt filter;
|
||||
ngx_rtmp_netcall_sink_pt sink;
|
||||
ngx_chain_t *in;
|
||||
ngx_chain_t *inlast;
|
||||
ngx_chain_t *out;
|
||||
ngx_msec_t timeout;
|
||||
unsigned detached:1;
|
||||
size_t bufsize;
|
||||
} ngx_rtmp_netcall_session_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t nb_cs;
|
||||
ngx_rtmp_netcall_session_t *cs;
|
||||
} ngx_rtmp_netcall_ctx_t;
|
||||
|
||||
|
||||
static ngx_command_t ngx_rtmp_netcall_commands[] = {
|
||||
|
||||
{ ngx_string("netcall_timeout"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_msec_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_netcall_srv_conf_t, timeout),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("netcall_buffer"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_size_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_netcall_srv_conf_t, bufsize),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_netcall_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_rtmp_netcall_postconfiguration, /* postconfiguration */
|
||||
NULL, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
ngx_rtmp_netcall_create_srv_conf, /* create server configuration */
|
||||
ngx_rtmp_netcall_merge_srv_conf, /* merge server configuration */
|
||||
NULL, /* create app configuration */
|
||||
NULL /* merge app configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_netcall_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_netcall_module_ctx, /* module context */
|
||||
ngx_rtmp_netcall_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_netcall_create_srv_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_netcall_srv_conf_t *nscf;
|
||||
|
||||
nscf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_netcall_srv_conf_t));
|
||||
if (nscf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
nscf->timeout = NGX_CONF_UNSET_MSEC;
|
||||
nscf->bufsize = NGX_CONF_UNSET_SIZE;
|
||||
|
||||
nscf->log = &cf->cycle->new_log;
|
||||
|
||||
return nscf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_netcall_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
|
||||
{
|
||||
ngx_rtmp_netcall_srv_conf_t *prev = parent;
|
||||
ngx_rtmp_netcall_srv_conf_t *conf = child;
|
||||
|
||||
ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 10000);
|
||||
ngx_conf_merge_size_value(conf->bufsize, prev->bufsize, 1024);
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_netcall_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_netcall_ctx_t *ctx;
|
||||
ngx_rtmp_netcall_session_t *cs;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module);
|
||||
|
||||
if (ctx) {
|
||||
for (cs = ctx->cs; cs; cs = cs->next) {
|
||||
ngx_rtmp_netcall_detach(cs->pc->connection);
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_netcall_get_peer(ngx_peer_connection_t *pc, void *data)
|
||||
{
|
||||
ngx_rtmp_netcall_session_t *cs = data;
|
||||
|
||||
pc->sockaddr =(struct sockaddr *)&cs->url->sockaddr;
|
||||
pc->socklen = cs->url->socklen;
|
||||
pc->name = &cs->url->host;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_netcall_free_peer(ngx_peer_connection_t *pc, void *data,
|
||||
ngx_uint_t state)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_netcall_create(ngx_rtmp_session_t *s, ngx_rtmp_netcall_init_t *ci)
|
||||
{
|
||||
ngx_rtmp_netcall_ctx_t *ctx;
|
||||
ngx_peer_connection_t *pc;
|
||||
ngx_rtmp_netcall_session_t *cs;
|
||||
ngx_rtmp_netcall_srv_conf_t *nscf;
|
||||
ngx_connection_t *c, *cc;
|
||||
ngx_pool_t *pool;
|
||||
ngx_int_t rc;
|
||||
|
||||
pool = NULL;
|
||||
c = s->connection;
|
||||
|
||||
nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_netcall_module);
|
||||
if (nscf == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* get module context */
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module);
|
||||
if (ctx == NULL) {
|
||||
ctx = ngx_pcalloc(c->pool,
|
||||
sizeof(ngx_rtmp_netcall_ctx_t));
|
||||
if (ctx == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_netcall_module);
|
||||
} else {
|
||||
/* I don't know why? But it works! */
|
||||
if (ctx->nb_cs == 0) {
|
||||
ctx->cs = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create netcall pool, connection, session.
|
||||
* Note we use shared (app-wide) log because
|
||||
* s->connection->log might be unavailable
|
||||
* in detached netcall when it's being closed */
|
||||
pool = ngx_create_pool(4096, nscf->log);
|
||||
if (pool == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
pc = ngx_pcalloc(pool, sizeof(ngx_peer_connection_t));
|
||||
if (pc == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
cs = ngx_pcalloc(pool, sizeof(ngx_rtmp_netcall_session_t));
|
||||
if (cs == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* copy arg to connection pool */
|
||||
if (ci->argsize) {
|
||||
cs->arg = ngx_pcalloc(pool, ci->argsize);
|
||||
if (cs->arg == NULL) {
|
||||
goto error;
|
||||
}
|
||||
ngx_memcpy(cs->arg, ci->arg, ci->argsize);
|
||||
}
|
||||
|
||||
cs->timeout = nscf->timeout;
|
||||
cs->bufsize = nscf->bufsize;
|
||||
cs->url = ci->url;
|
||||
cs->session = s;
|
||||
cs->filter = ci->filter;
|
||||
cs->sink = ci->sink;
|
||||
cs->handle = ci->handle;
|
||||
if (cs->handle == NULL) {
|
||||
cs->detached = 1;
|
||||
}
|
||||
|
||||
pc->log = nscf->log;
|
||||
pc->get = ngx_rtmp_netcall_get_peer;
|
||||
pc->free = ngx_rtmp_netcall_free_peer;
|
||||
pc->data = cs;
|
||||
|
||||
/* connect */
|
||||
rc = ngx_event_connect_peer(pc);
|
||||
if (rc != NGX_OK && rc != NGX_AGAIN ) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"netcall: connection failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
cc = pc->connection;
|
||||
cc->data = cs;
|
||||
cc->pool = pool;
|
||||
cs->pc = pc;
|
||||
|
||||
cs->out = ci->create(s, ci->arg, pool);
|
||||
if (cs->out == NULL) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"netcall: creation failed");
|
||||
ngx_close_connection(pc->connection);
|
||||
goto error;
|
||||
}
|
||||
|
||||
cc->write->handler = ngx_rtmp_netcall_send;
|
||||
cc->read->handler = ngx_rtmp_netcall_recv;
|
||||
|
||||
if (!cs->detached) {
|
||||
cs->next = ctx->cs;
|
||||
ctx->cs = cs;
|
||||
ctx->nb_cs++;
|
||||
}
|
||||
|
||||
ngx_rtmp_netcall_send(cc->write);
|
||||
|
||||
return c->destroyed ? NGX_ERROR : NGX_OK;
|
||||
|
||||
error:
|
||||
if (pool) {
|
||||
ngx_destroy_pool(pool);
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_netcall_close(ngx_connection_t *cc)
|
||||
{
|
||||
ngx_rtmp_netcall_session_t *cs, **css;
|
||||
ngx_pool_t *pool;
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_rtmp_netcall_ctx_t *ctx;
|
||||
ngx_buf_t *b;
|
||||
|
||||
cs = cc->data;
|
||||
|
||||
if (cc->destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
cc->destroyed = 1;
|
||||
|
||||
if (!cs->detached) {
|
||||
s = cs->session;
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module);
|
||||
|
||||
if (cs->in && cs->sink) {
|
||||
cs->sink(cs->session, cs->in);
|
||||
|
||||
b = cs->in->buf;
|
||||
b->pos = b->last = b->start;
|
||||
|
||||
}
|
||||
|
||||
for(css = &ctx->cs; *css; css = &((*css)->next)) {
|
||||
if (*css == cs) {
|
||||
*css = cs->next;
|
||||
ctx->nb_cs--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cs->handle && cs->handle(s, cs->arg, cs->in) != NGX_OK) {
|
||||
ngx_rtmp_finalize_session(s);
|
||||
}
|
||||
}
|
||||
|
||||
pool = cc->pool;
|
||||
ngx_close_connection(cc);
|
||||
ngx_destroy_pool(pool);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_netcall_detach(ngx_connection_t *cc)
|
||||
{
|
||||
ngx_rtmp_netcall_session_t *cs;
|
||||
|
||||
cs = cc->data;
|
||||
cs->detached = 1;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_netcall_recv(ngx_event_t *rev)
|
||||
{
|
||||
ngx_rtmp_netcall_session_t *cs;
|
||||
ngx_connection_t *cc;
|
||||
ngx_chain_t *cl;
|
||||
ngx_int_t n;
|
||||
ngx_buf_t *b;
|
||||
|
||||
cc = rev->data;
|
||||
cs = cc->data;
|
||||
|
||||
if (cc->destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rev->timedout) {
|
||||
cc->timedout = 1;
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rev->timer_set) {
|
||||
ngx_del_timer(rev);
|
||||
}
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
if (cs->inlast == NULL ||
|
||||
cs->inlast->buf->last == cs->inlast->buf->end)
|
||||
{
|
||||
if (cs->in && cs->sink) {
|
||||
if (!cs->detached) {
|
||||
if (cs->sink(cs->session, cs->in) != NGX_OK) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
b = cs->in->buf;
|
||||
b->pos = b->last = b->start;
|
||||
|
||||
} else {
|
||||
cl = ngx_alloc_chain_link(cc->pool);
|
||||
if (cl == NULL) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
cl->next = NULL;
|
||||
|
||||
cl->buf = ngx_create_temp_buf(cc->pool, cs->bufsize);
|
||||
if (cl->buf == NULL) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cs->in == NULL) {
|
||||
cs->in = cl;
|
||||
} else {
|
||||
cs->inlast->next = cl;
|
||||
}
|
||||
|
||||
cs->inlast = cl;
|
||||
}
|
||||
}
|
||||
|
||||
b = cs->inlast->buf;
|
||||
|
||||
n = cc->recv(cc, b->last, b->end - b->last);
|
||||
|
||||
if (n == NGX_ERROR || n == 0) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == NGX_AGAIN) {
|
||||
if (cs->filter && cs->in
|
||||
&& cs->filter(cs->in) != NGX_AGAIN)
|
||||
{
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_add_timer(rev, cs->timeout);
|
||||
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
b->last += n;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_netcall_send(ngx_event_t *wev)
|
||||
{
|
||||
ngx_rtmp_netcall_session_t *cs;
|
||||
ngx_connection_t *cc;
|
||||
ngx_chain_t *cl;
|
||||
|
||||
cc = wev->data;
|
||||
cs = cc->data;
|
||||
|
||||
if (cc->destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wev->timedout) {
|
||||
ngx_log_error(NGX_LOG_INFO, cc->log, NGX_ETIMEDOUT,
|
||||
"netcall: client send timed out");
|
||||
cc->timedout = 1;
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wev->timer_set) {
|
||||
ngx_del_timer(wev);
|
||||
}
|
||||
|
||||
cl = cc->send_chain(cc, cs->out, 0);
|
||||
|
||||
if (cl == NGX_CHAIN_ERROR) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
cs->out = cl;
|
||||
|
||||
/* more data to send? */
|
||||
if (cl) {
|
||||
ngx_add_timer(wev, cs->timeout);
|
||||
if (ngx_handle_write_event(wev, 0) != NGX_OK) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* we've sent everything we had.
|
||||
* now receive reply */
|
||||
ngx_del_event(wev, NGX_WRITE_EVENT, 0);
|
||||
|
||||
ngx_rtmp_netcall_recv(cc->read);
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_netcall_http_format_request(ngx_int_t method, ngx_str_t *host,
|
||||
ngx_str_t *uri, ngx_chain_t *args,
|
||||
ngx_chain_t *body, ngx_pool_t *pool,
|
||||
ngx_str_t *content_type)
|
||||
{
|
||||
ngx_chain_t *al, *bl, *ret;
|
||||
ngx_buf_t *b;
|
||||
size_t content_length;
|
||||
static const char *methods[2] = { "GET", "POST" };
|
||||
static const char rq_tmpl[] = " HTTP/1.0\r\n"
|
||||
"Host: %V\r\n"
|
||||
"Content-Type: %V\r\n"
|
||||
"Connection: Close\r\n"
|
||||
"Content-Length: %uz\r\n"
|
||||
"\r\n";
|
||||
|
||||
content_length = 0;
|
||||
for (al = body; al; al = al->next) {
|
||||
b = al->buf;
|
||||
content_length += (b->last - b->pos);
|
||||
}
|
||||
|
||||
/* create first buffer */
|
||||
|
||||
al = ngx_alloc_chain_link(pool);
|
||||
if (al == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b = ngx_create_temp_buf(pool, sizeof("POST") + /* longest method + 1 */
|
||||
uri->len);
|
||||
if (b == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b->last = ngx_snprintf(b->last, b->end - b->last, "%s %V",
|
||||
methods[method], uri);
|
||||
|
||||
al->buf = b;
|
||||
|
||||
ret = al;
|
||||
|
||||
if (args) {
|
||||
*b->last++ = '?';
|
||||
al->next = args;
|
||||
for (al = args; al->next; al = al->next);
|
||||
}
|
||||
|
||||
/* create second buffer */
|
||||
|
||||
bl = ngx_alloc_chain_link(pool);
|
||||
if (bl == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b = ngx_create_temp_buf(pool, sizeof(rq_tmpl) + host->len +
|
||||
content_type->len + NGX_SIZE_T_LEN);
|
||||
if (b == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bl->buf = b;
|
||||
|
||||
b->last = ngx_snprintf(b->last, b->end - b->last, rq_tmpl,
|
||||
host, content_type, content_length);
|
||||
|
||||
al->next = bl;
|
||||
bl->next = body;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, ngx_pool_t *pool)
|
||||
{
|
||||
ngx_chain_t *cl;
|
||||
ngx_buf_t *b;
|
||||
ngx_str_t *addr_text;
|
||||
|
||||
addr_text = &s->connection->addr_text;
|
||||
|
||||
cl = ngx_alloc_chain_link(pool);
|
||||
if (cl == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b = ngx_create_temp_buf(pool,
|
||||
sizeof("app=") - 1 + s->app.len * 3 +
|
||||
sizeof("&flashver=") - 1 + s->flashver.len * 3 +
|
||||
sizeof("&swfurl=") - 1 + s->swf_url.len * 3 +
|
||||
sizeof("&tcurl=") - 1 + s->tc_url.len * 3 +
|
||||
sizeof("&pageurl=") - 1 + s->page_url.len * 3 +
|
||||
sizeof("&addr=") - 1 + addr_text->len * 3 +
|
||||
sizeof("&clientid=") - 1 + NGX_INT_T_LEN
|
||||
);
|
||||
|
||||
if (b == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cl->buf = b;
|
||||
cl->next = NULL;
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "app=", sizeof("app=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->app.data, s->app.len,
|
||||
NGX_ESCAPE_ARGS);
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&flashver=",
|
||||
sizeof("&flashver=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->flashver.data,
|
||||
s->flashver.len, NGX_ESCAPE_ARGS);
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&swfurl=",
|
||||
sizeof("&swfurl=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->swf_url.data,
|
||||
s->swf_url.len, NGX_ESCAPE_ARGS);
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&tcurl=",
|
||||
sizeof("&tcurl=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->tc_url.data,
|
||||
s->tc_url.len, NGX_ESCAPE_ARGS);
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&pageurl=",
|
||||
sizeof("&pageurl=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->page_url.data,
|
||||
s->page_url.len, NGX_ESCAPE_ARGS);
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&addr=", sizeof("&addr=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, addr_text->data,
|
||||
addr_text->len, NGX_ESCAPE_ARGS);
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&clientid=",
|
||||
sizeof("&clientid=") - 1);
|
||||
b->last = ngx_sprintf(b->last, "%ui", (ngx_uint_t) s->connection->number);
|
||||
|
||||
return cl;
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_netcall_http_skip_header(ngx_chain_t *in)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
|
||||
/* find \n[\r]\n */
|
||||
enum {
|
||||
normal,
|
||||
lf,
|
||||
lfcr
|
||||
} state = normal;
|
||||
|
||||
if (in == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b = in->buf;
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
while (b->pos == b->last) {
|
||||
in = in->next;
|
||||
if (in == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
b = in->buf;
|
||||
}
|
||||
|
||||
switch (*b->pos++) {
|
||||
case '\r':
|
||||
state = (state == lf) ? lfcr : normal;
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
if (state != normal) {
|
||||
return in;
|
||||
}
|
||||
state = lf;
|
||||
break;
|
||||
|
||||
default:
|
||||
state = normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_netcall_memcache_set(ngx_rtmp_session_t *s, ngx_pool_t *pool,
|
||||
ngx_str_t *key, ngx_str_t *value, ngx_uint_t flags, ngx_uint_t sec)
|
||||
{
|
||||
ngx_chain_t *cl;
|
||||
ngx_buf_t *b;
|
||||
|
||||
cl = ngx_alloc_chain_link(pool);
|
||||
if (cl == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b = ngx_create_temp_buf(pool, sizeof("set ") - 1 + key->len +
|
||||
(1 + NGX_INT_T_LEN) * 3 +
|
||||
(sizeof("\r\n") - 1) * 2 + value->len);
|
||||
|
||||
if (b == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cl->next = NULL;
|
||||
cl->buf = b;
|
||||
|
||||
b->last = ngx_sprintf(b->pos, "set %V %ui %ui %ui\r\n%V\r\n",
|
||||
key, flags, sec, (ngx_uint_t) value->len, value);
|
||||
|
||||
return cl;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_netcall_postconfiguration(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
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_DISCONNECT]);
|
||||
*h = ngx_rtmp_netcall_disconnect;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
67
ngx_http_flv_module/ngx_rtmp_netcall_module.h
Normal file
67
ngx_http_flv_module/ngx_rtmp_netcall_module.h
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_NETCALL_H_INCLUDED_
|
||||
#define _NGX_RTMP_NETCALL_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
typedef ngx_chain_t * (*ngx_rtmp_netcall_create_pt)(ngx_rtmp_session_t *s,
|
||||
void *arg, ngx_pool_t *pool);
|
||||
typedef ngx_int_t (*ngx_rtmp_netcall_filter_pt)(ngx_chain_t *in);
|
||||
typedef ngx_int_t (*ngx_rtmp_netcall_sink_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t *in);
|
||||
typedef ngx_int_t (*ngx_rtmp_netcall_handle_pt)(ngx_rtmp_session_t *s,
|
||||
void *arg, ngx_chain_t *in);
|
||||
|
||||
#define NGX_RTMP_NETCALL_HTTP_GET 0
|
||||
#define NGX_RTMP_NETCALL_HTTP_POST 1
|
||||
|
||||
|
||||
/* If handle is NULL then netcall is created detached
|
||||
* which means it's completely independent of RTMP
|
||||
* session and its result is never visible to anyone.
|
||||
*
|
||||
* WARNING: It's not recommended to create non-detached
|
||||
* netcalls from disconect handlers. Netcall disconnect
|
||||
* handler which detaches active netcalls is executed
|
||||
* BEFORE your handler. It leads to a crash
|
||||
* after netcall connection is closed */
|
||||
typedef struct {
|
||||
ngx_url_t *url;
|
||||
ngx_rtmp_netcall_create_pt create;
|
||||
ngx_rtmp_netcall_filter_pt filter;
|
||||
ngx_rtmp_netcall_sink_pt sink;
|
||||
ngx_rtmp_netcall_handle_pt handle;
|
||||
void *arg;
|
||||
size_t argsize;
|
||||
} ngx_rtmp_netcall_init_t;
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_netcall_create(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_netcall_init_t *ci);
|
||||
|
||||
|
||||
/* HTTP handling */
|
||||
ngx_chain_t * ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s,
|
||||
ngx_pool_t *pool);
|
||||
ngx_chain_t * ngx_rtmp_netcall_http_format_request(ngx_int_t method,
|
||||
ngx_str_t *host, ngx_str_t *uri, ngx_chain_t *args, ngx_chain_t *body,
|
||||
ngx_pool_t *pool, ngx_str_t *content_type);
|
||||
ngx_chain_t * ngx_rtmp_netcall_http_skip_header(ngx_chain_t *in);
|
||||
|
||||
|
||||
/* Memcache handling */
|
||||
ngx_chain_t * ngx_rtmp_netcall_memcache_set(ngx_rtmp_session_t *s,
|
||||
ngx_pool_t *pool, ngx_str_t *key, ngx_str_t *value,
|
||||
ngx_uint_t flags, ngx_uint_t sec);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_NETCALL_H_INCLUDED_ */
|
||||
1768
ngx_http_flv_module/ngx_rtmp_notify_module.c
Normal file
1768
ngx_http_flv_module/ngx_rtmp_notify_module.c
Normal file
File diff suppressed because it is too large
Load Diff
820
ngx_http_flv_module/ngx_rtmp_parse.c
Normal file
820
ngx_http_flv_module/ngx_rtmp_parse.c
Normal file
@@ -0,0 +1,820 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
#define NGX_RTMP_PARSE_INVALID_REQUEST 11
|
||||
|
||||
|
||||
static uint32_t usual[] = {
|
||||
0xffffdbfe, /* 1111 1111 1111 1111 1101 1011 1111 1110 */
|
||||
|
||||
/* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
|
||||
0x7fff37d6, /* 0111 1111 1111 1111 0011 0111 1101 0110 */
|
||||
|
||||
/* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
|
||||
#if (NGX_WIN32)
|
||||
0xefffffff, /* 1110 1111 1111 1111 1111 1111 1111 1111 */
|
||||
#else
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
#endif
|
||||
|
||||
/* ~}| {zyx wvut srqp onml kjih gfed cba` */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
||||
};
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_parse_request_line(ngx_rtmp_session_t *s, ngx_buf_t *b)
|
||||
{
|
||||
u_char c, ch, *p;
|
||||
enum {
|
||||
sw_start = 0,
|
||||
sw_schema,
|
||||
sw_schema_slash,
|
||||
sw_schema_slash_slash,
|
||||
sw_host_start,
|
||||
sw_host,
|
||||
sw_host_end,
|
||||
sw_host_ip_literal,
|
||||
sw_port,
|
||||
sw_after_slash_in_uri,
|
||||
sw_check_uri,
|
||||
sw_uri
|
||||
} state;
|
||||
|
||||
state = sw_start;
|
||||
|
||||
for (p = b->pos; p < b->last; p++) {
|
||||
ch = *p;
|
||||
|
||||
switch (state) {
|
||||
|
||||
case sw_start:
|
||||
|
||||
s->schema_start = p;
|
||||
state = sw_schema;
|
||||
|
||||
/* fall through */
|
||||
|
||||
case sw_schema:
|
||||
|
||||
c = (u_char) (ch | 0x20);
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case ':':
|
||||
s->schema_end = p;
|
||||
state = sw_schema_slash;
|
||||
break;
|
||||
default:
|
||||
return NGX_RTMP_PARSE_INVALID_REQUEST;
|
||||
}
|
||||
break;
|
||||
|
||||
case sw_schema_slash:
|
||||
switch (ch) {
|
||||
case '/':
|
||||
state = sw_schema_slash_slash;
|
||||
break;
|
||||
default:
|
||||
return NGX_RTMP_PARSE_INVALID_REQUEST;
|
||||
}
|
||||
break;
|
||||
|
||||
case sw_schema_slash_slash:
|
||||
switch (ch) {
|
||||
case '/':
|
||||
state = sw_host_start;
|
||||
break;
|
||||
default:
|
||||
return NGX_RTMP_PARSE_INVALID_REQUEST;
|
||||
}
|
||||
break;
|
||||
|
||||
case sw_host_start:
|
||||
|
||||
s->host_start = p;
|
||||
|
||||
if (ch == '[') {
|
||||
state = sw_host_ip_literal;
|
||||
break;
|
||||
}
|
||||
|
||||
state = sw_host;
|
||||
|
||||
/* fall through */
|
||||
|
||||
case sw_host:
|
||||
|
||||
c = (u_char) (ch | 0x20);
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
break;
|
||||
}
|
||||
|
||||
if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') {
|
||||
break;
|
||||
}
|
||||
|
||||
/* fall through */
|
||||
|
||||
case sw_host_end:
|
||||
|
||||
s->host_end = p;
|
||||
|
||||
switch (ch) {
|
||||
case ':':
|
||||
s->port_start = p + 1;
|
||||
state = sw_port;
|
||||
break;
|
||||
case '/':
|
||||
s->uri_start = p;
|
||||
state = sw_after_slash_in_uri;
|
||||
break;
|
||||
default:
|
||||
return NGX_RTMP_PARSE_INVALID_REQUEST;
|
||||
}
|
||||
break;
|
||||
|
||||
case sw_host_ip_literal:
|
||||
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
break;
|
||||
}
|
||||
|
||||
c = (u_char) (ch | 0x20);
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case ':':
|
||||
break;
|
||||
case ']':
|
||||
state = sw_host_end;
|
||||
break;
|
||||
case '-':
|
||||
case '.':
|
||||
case '_':
|
||||
case '~':
|
||||
/* unreserved */
|
||||
break;
|
||||
case '!':
|
||||
case '$':
|
||||
case '&':
|
||||
case '\'':
|
||||
case '(':
|
||||
case ')':
|
||||
case '*':
|
||||
case '+':
|
||||
case ',':
|
||||
case ';':
|
||||
case '=':
|
||||
/* sub-delims */
|
||||
break;
|
||||
default:
|
||||
return NGX_RTMP_PARSE_INVALID_REQUEST;
|
||||
}
|
||||
break;
|
||||
|
||||
case sw_port:
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case '/':
|
||||
s->port_end = p;
|
||||
s->uri_start = p;
|
||||
state = sw_after_slash_in_uri;
|
||||
break;
|
||||
default:
|
||||
return NGX_RTMP_PARSE_INVALID_REQUEST;
|
||||
}
|
||||
break;
|
||||
|
||||
/* check "/.", "//", "%", and "\" (Win32) in URI */
|
||||
case sw_after_slash_in_uri:
|
||||
|
||||
if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
|
||||
state = sw_check_uri;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case '.':
|
||||
s->complex_uri = 1;
|
||||
state = sw_uri;
|
||||
break;
|
||||
case '%':
|
||||
s->quoted_uri = 1;
|
||||
state = sw_uri;
|
||||
break;
|
||||
case '/':
|
||||
s->complex_uri = 1;
|
||||
state = sw_uri;
|
||||
break;
|
||||
#if (NGX_WIN32)
|
||||
case '\\':
|
||||
s->complex_uri = 1;
|
||||
state = sw_uri;
|
||||
break;
|
||||
#endif
|
||||
case '?':
|
||||
s->args_start = p + 1;
|
||||
state = sw_uri;
|
||||
break;
|
||||
case '#':
|
||||
s->complex_uri = 1;
|
||||
state = sw_uri;
|
||||
break;
|
||||
case '+':
|
||||
s->plus_in_uri = 1;
|
||||
break;
|
||||
case '\0':
|
||||
return NGX_RTMP_PARSE_INVALID_REQUEST;
|
||||
default:
|
||||
state = sw_check_uri;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
/* check "/", "%" and "\" (Win32) in URI */
|
||||
case sw_check_uri:
|
||||
|
||||
if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case '/':
|
||||
state = sw_after_slash_in_uri;
|
||||
break;
|
||||
case '.':
|
||||
break;
|
||||
#if (NGX_WIN32)
|
||||
case '\\':
|
||||
s->complex_uri = 1;
|
||||
state = sw_after_slash_in_uri;
|
||||
break;
|
||||
#endif
|
||||
case '%':
|
||||
s->quoted_uri = 1;
|
||||
state = sw_uri;
|
||||
break;
|
||||
case '?':
|
||||
s->args_start = p + 1;
|
||||
state = sw_uri;
|
||||
break;
|
||||
case '#':
|
||||
s->complex_uri = 1;
|
||||
state = sw_uri;
|
||||
break;
|
||||
case '+':
|
||||
s->plus_in_uri = 1;
|
||||
break;
|
||||
case '\0':
|
||||
return NGX_RTMP_PARSE_INVALID_REQUEST;
|
||||
}
|
||||
break;
|
||||
|
||||
/* URI */
|
||||
case sw_uri:
|
||||
|
||||
if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case '#':
|
||||
s->complex_uri = 1;
|
||||
break;
|
||||
case '\0':
|
||||
return NGX_RTMP_PARSE_INVALID_REQUEST;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* end of request line */
|
||||
s->uri_end = p;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_process_request_uri(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
|
||||
if (s->args_start) {
|
||||
s->uri.len = s->args_start - 1 - s->uri_start;
|
||||
} else {
|
||||
s->uri.len = s->uri_end - s->uri_start;
|
||||
}
|
||||
|
||||
if (s->complex_uri || s->quoted_uri) {
|
||||
|
||||
s->uri.data = ngx_pnalloc(s->connection->pool, s->uri.len + 1);
|
||||
if (s->uri.data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
if (ngx_rtmp_parse_complex_uri(s, cscf->merge_slashes) != NGX_OK) {
|
||||
s->uri.len = 0;
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"client sent invalid request");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
} else {
|
||||
s->uri.data = s->uri_start;
|
||||
}
|
||||
|
||||
s->unparsed_uri.len = s->uri_end - s->uri_start;
|
||||
s->unparsed_uri.data = s->uri_start;
|
||||
|
||||
s->valid_unparsed_uri = s->space_in_uri ? 0 : 1;
|
||||
|
||||
if (s->args_start && s->uri_end > s->args_start) {
|
||||
s->args.len = s->uri_end - s->args_start;
|
||||
s->args.data = s->args_start;
|
||||
}
|
||||
|
||||
#if (NGX_WIN32)
|
||||
{
|
||||
u_char *p, *last;
|
||||
|
||||
p = s->uri.data;
|
||||
last = s->uri.data + s->uri.len;
|
||||
|
||||
while (p < last) {
|
||||
|
||||
if (*p++ == ':') {
|
||||
|
||||
/*
|
||||
* this check covers "::$data", "::$index_allocation" and
|
||||
* ":$i30:$index_allocation"
|
||||
*/
|
||||
|
||||
if (p < last && *p == '$') {
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"client sent unsafe win32 URI");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p = s->uri.data + s->uri.len - 1;
|
||||
|
||||
while (p > s->uri.data) {
|
||||
|
||||
if (*p == ' ') {
|
||||
p--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*p == '.') {
|
||||
p--;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (p != s->uri.data + s->uri.len - 1) {
|
||||
s->uri.len = p + 1 - s->uri.data;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"rtmp uri: \"%V\"", &s->uri);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"rtmp args: \"%V\"", &s->args);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_parse_complex_uri(ngx_rtmp_session_t *s, ngx_uint_t merge_slashes)
|
||||
{
|
||||
u_char c, ch, decoded, *p, *u;
|
||||
enum {
|
||||
sw_usual = 0,
|
||||
sw_slash,
|
||||
sw_dot,
|
||||
sw_dot_dot,
|
||||
sw_quoted,
|
||||
sw_quoted_second
|
||||
} state, quoted_state;
|
||||
|
||||
#if (NGX_SUPPRESS_WARN)
|
||||
decoded = '\0';
|
||||
quoted_state = sw_usual;
|
||||
#endif
|
||||
|
||||
state = sw_usual;
|
||||
p = s->uri_start;
|
||||
u = s->uri.data;
|
||||
s->args_start = NULL;
|
||||
|
||||
ch = *p++;
|
||||
|
||||
while (p <= s->uri_end) {
|
||||
|
||||
/*
|
||||
* we use "ch = *p++" inside the cycle, it is safe,
|
||||
* because after the URI there is a character: '\r'
|
||||
*/
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"s:%d in:'%Xd:%c'", state, ch, ch);
|
||||
|
||||
switch (state) {
|
||||
|
||||
case sw_usual:
|
||||
|
||||
if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
|
||||
*u++ = ch;
|
||||
ch = *p++;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
#if (NGX_WIN32)
|
||||
case '\\':
|
||||
if (u - 2 >= s->uri.data
|
||||
&& *(u - 1) == '.' && *(u - 2) != '.')
|
||||
{
|
||||
u--;
|
||||
}
|
||||
|
||||
if (p == s->uri_start + s->uri.len) {
|
||||
|
||||
/*
|
||||
* we omit the last "\" to cause redirect because
|
||||
* the browsers do not treat "\" as "/" in relative URL path
|
||||
*/
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
state = sw_slash;
|
||||
*u++ = '/';
|
||||
break;
|
||||
#endif
|
||||
case '/':
|
||||
#if (NGX_WIN32)
|
||||
if (u - 2 >= s->uri.data
|
||||
&& *(u - 1) == '.' && *(u - 2) != '.')
|
||||
{
|
||||
u--;
|
||||
}
|
||||
#endif
|
||||
state = sw_slash;
|
||||
*u++ = ch;
|
||||
break;
|
||||
case '%':
|
||||
quoted_state = state;
|
||||
state = sw_quoted;
|
||||
break;
|
||||
case '?':
|
||||
s->args_start = p;
|
||||
goto args;
|
||||
case '#':
|
||||
goto done;
|
||||
case '.':
|
||||
*u++ = ch;
|
||||
break;
|
||||
case '+':
|
||||
s->plus_in_uri = 1;
|
||||
|
||||
/* fall through */
|
||||
|
||||
default:
|
||||
*u++ = ch;
|
||||
break;
|
||||
}
|
||||
|
||||
ch = *p++;
|
||||
break;
|
||||
|
||||
case sw_slash:
|
||||
|
||||
if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
|
||||
state = sw_usual;
|
||||
*u++ = ch;
|
||||
ch = *p++;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
#if (NGX_WIN32)
|
||||
case '\\':
|
||||
break;
|
||||
#endif
|
||||
case '/':
|
||||
if (!merge_slashes) {
|
||||
*u++ = ch;
|
||||
}
|
||||
break;
|
||||
case '.':
|
||||
state = sw_dot;
|
||||
*u++ = ch;
|
||||
break;
|
||||
case '%':
|
||||
quoted_state = state;
|
||||
state = sw_quoted;
|
||||
break;
|
||||
case '?':
|
||||
s->args_start = p;
|
||||
goto args;
|
||||
case '#':
|
||||
goto done;
|
||||
case '+':
|
||||
s->plus_in_uri = 1;
|
||||
|
||||
/* fall through */
|
||||
|
||||
default:
|
||||
state = sw_usual;
|
||||
*u++ = ch;
|
||||
break;
|
||||
}
|
||||
|
||||
ch = *p++;
|
||||
break;
|
||||
|
||||
case sw_dot:
|
||||
|
||||
if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
|
||||
state = sw_usual;
|
||||
*u++ = ch;
|
||||
ch = *p++;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
#if (NGX_WIN32)
|
||||
case '\\':
|
||||
#endif
|
||||
case '/':
|
||||
state = sw_slash;
|
||||
u--;
|
||||
break;
|
||||
case '.':
|
||||
state = sw_dot_dot;
|
||||
*u++ = ch;
|
||||
break;
|
||||
case '%':
|
||||
quoted_state = state;
|
||||
state = sw_quoted;
|
||||
break;
|
||||
case '?':
|
||||
s->args_start = p;
|
||||
goto args;
|
||||
case '#':
|
||||
goto done;
|
||||
case '+':
|
||||
s->plus_in_uri = 1;
|
||||
|
||||
/* fall through */
|
||||
|
||||
default:
|
||||
state = sw_usual;
|
||||
*u++ = ch;
|
||||
break;
|
||||
}
|
||||
|
||||
ch = *p++;
|
||||
break;
|
||||
|
||||
case sw_dot_dot:
|
||||
|
||||
if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
|
||||
state = sw_usual;
|
||||
*u++ = ch;
|
||||
ch = *p++;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
#if (NGX_WIN32)
|
||||
case '\\':
|
||||
#endif
|
||||
case '/':
|
||||
state = sw_slash;
|
||||
u -= 5;
|
||||
for ( ;; ) {
|
||||
if (u < s->uri.data) {
|
||||
return NGX_RTMP_PARSE_INVALID_REQUEST;
|
||||
}
|
||||
if (*u == '/') {
|
||||
u++;
|
||||
break;
|
||||
}
|
||||
u--;
|
||||
}
|
||||
break;
|
||||
case '%':
|
||||
quoted_state = state;
|
||||
state = sw_quoted;
|
||||
break;
|
||||
case '?':
|
||||
s->args_start = p;
|
||||
goto args;
|
||||
case '#':
|
||||
goto done;
|
||||
case '+':
|
||||
s->plus_in_uri = 1;
|
||||
|
||||
/* fall through */
|
||||
|
||||
default:
|
||||
state = sw_usual;
|
||||
*u++ = ch;
|
||||
break;
|
||||
}
|
||||
|
||||
ch = *p++;
|
||||
break;
|
||||
|
||||
case sw_quoted:
|
||||
s->quoted_uri = 1;
|
||||
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
decoded = (u_char) (ch - '0');
|
||||
state = sw_quoted_second;
|
||||
ch = *p++;
|
||||
break;
|
||||
}
|
||||
|
||||
c = (u_char) (ch | 0x20);
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
decoded = (u_char) (c - 'a' + 10);
|
||||
state = sw_quoted_second;
|
||||
ch = *p++;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_RTMP_PARSE_INVALID_REQUEST;
|
||||
|
||||
case sw_quoted_second:
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
ch = (u_char) ((decoded << 4) + ch - '0');
|
||||
|
||||
if (ch == '%' || ch == '#') {
|
||||
state = sw_usual;
|
||||
*u++ = ch;
|
||||
ch = *p++;
|
||||
break;
|
||||
|
||||
} else if (ch == '\0') {
|
||||
return NGX_RTMP_PARSE_INVALID_REQUEST;
|
||||
}
|
||||
|
||||
state = quoted_state;
|
||||
break;
|
||||
}
|
||||
|
||||
c = (u_char) (ch | 0x20);
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
ch = (u_char) ((decoded << 4) + c - 'a' + 10);
|
||||
|
||||
if (ch == '?') {
|
||||
state = sw_usual;
|
||||
*u++ = ch;
|
||||
ch = *p++;
|
||||
break;
|
||||
|
||||
} else if (ch == '+') {
|
||||
s->plus_in_uri = 1;
|
||||
}
|
||||
|
||||
state = quoted_state;
|
||||
break;
|
||||
}
|
||||
|
||||
return NGX_RTMP_PARSE_INVALID_REQUEST;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
|
||||
s->uri.len = *u == CR ? (u - s->uri.data) : (u - s->uri.data + 1);
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
args:
|
||||
|
||||
while (p < s->uri_end) {
|
||||
if (*p++ != '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
s->args.len = p - 1 - s->args_start;
|
||||
s->args.data = s->args_start;
|
||||
s->args_start = NULL;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
s->uri.len = u - s->uri.data;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_process_request_line(ngx_rtmp_session_t *s, const u_char *name,
|
||||
const u_char *args, const u_char *cmd)
|
||||
{
|
||||
size_t rlen = 0;
|
||||
|
||||
s->stream.len = name ? ngx_strlen(name) : 0;
|
||||
if (s->stream.len) {
|
||||
s->stream.data = ngx_palloc(s->connection->pool, s->stream.len);
|
||||
if (s->stream.data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memcpy(s->stream.data, name, ngx_strlen(name));
|
||||
}
|
||||
|
||||
if (s->tc_url.data[s->tc_url.len - 1] == '/') {
|
||||
s->tc_url.len -= 1;
|
||||
}
|
||||
|
||||
rlen = s->tc_url.len;
|
||||
|
||||
if (s->stream.len) {
|
||||
rlen += 1 + s->stream.len;
|
||||
}
|
||||
|
||||
if (args && args[0]) {
|
||||
rlen += 1 + ngx_strlen(args);
|
||||
}
|
||||
|
||||
s->request_line = ngx_create_temp_buf(s->connection->pool, rlen + 1);
|
||||
if (s->request_line == NULL) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"%s: failed to ngx_pcalloc for request_line", cmd);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (s->stream.len) {
|
||||
if (args && args[0]) {
|
||||
*ngx_snprintf(s->request_line->pos, rlen + 1, "%V/%V?%s", &s->tc_url,
|
||||
&s->stream, args) = CR;
|
||||
} else {
|
||||
*ngx_snprintf(s->request_line->pos, rlen + 1, "%V/%V", &s->tc_url,
|
||||
&s->stream) = CR;
|
||||
}
|
||||
} else {
|
||||
if (args && args[0]) {
|
||||
*ngx_snprintf(s->request_line->pos, rlen + 1, "%V?%s", &s->tc_url,
|
||||
args) = CR;
|
||||
} else {
|
||||
*ngx_snprintf(s->request_line->pos, rlen + 1, "%V", &s->tc_url)
|
||||
= CR;
|
||||
}
|
||||
}
|
||||
|
||||
s->request_line->last += rlen;
|
||||
|
||||
if (ngx_rtmp_parse_request_line(s, s->request_line) != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"%s: invalid request line: '%s'", cmd, s->request_line->pos);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_process_request_uri(s) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
*s->request_line->last = 0;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
1284
ngx_http_flv_module/ngx_rtmp_play_module.c
Normal file
1284
ngx_http_flv_module/ngx_rtmp_play_module.c
Normal file
File diff suppressed because it is too large
Load Diff
93
ngx_http_flv_module/ngx_rtmp_play_module.h
Normal file
93
ngx_http_flv_module/ngx_rtmp_play_module.h
Normal file
@@ -0,0 +1,93 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_PLAY_H_INCLUDED_
|
||||
#define _NGX_RTMP_PLAY_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
#include "ngx_rtmp_cmd_module.h"
|
||||
|
||||
|
||||
typedef ngx_int_t (*ngx_rtmp_play_init_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f, ngx_int_t aindex, ngx_int_t vindex);
|
||||
typedef ngx_int_t (*ngx_rtmp_play_done_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f);
|
||||
typedef ngx_int_t (*ngx_rtmp_play_start_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f);
|
||||
typedef ngx_int_t (*ngx_rtmp_play_seek_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f, ngx_uint_t offs);
|
||||
typedef ngx_int_t (*ngx_rtmp_play_stop_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f);
|
||||
typedef ngx_int_t (*ngx_rtmp_play_send_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f, ngx_uint_t *ts);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t name;
|
||||
ngx_str_t pfx;
|
||||
ngx_str_t sfx;
|
||||
|
||||
ngx_rtmp_play_init_pt init;
|
||||
ngx_rtmp_play_done_pt done;
|
||||
ngx_rtmp_play_start_pt start;
|
||||
ngx_rtmp_play_seek_pt seek;
|
||||
ngx_rtmp_play_stop_pt stop;
|
||||
ngx_rtmp_play_send_pt send;
|
||||
} ngx_rtmp_play_fmt_t;
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_play_ctx_s ngx_rtmp_play_ctx_t;
|
||||
|
||||
|
||||
struct ngx_rtmp_play_ctx_s {
|
||||
ngx_rtmp_session_t *session;
|
||||
ngx_file_t file;
|
||||
ngx_rtmp_play_fmt_t *fmt;
|
||||
ngx_event_t send_evt;
|
||||
unsigned playing:1;
|
||||
unsigned opened:1;
|
||||
unsigned joined:1;
|
||||
ngx_uint_t ncrs;
|
||||
ngx_uint_t nheader;
|
||||
ngx_uint_t nbody;
|
||||
size_t pfx_size;
|
||||
ngx_str_t sfx;
|
||||
ngx_uint_t file_id;
|
||||
ngx_int_t aindex, vindex;
|
||||
ngx_uint_t nentry;
|
||||
ngx_uint_t post_seek;
|
||||
u_char name[NGX_RTMP_MAX_NAME];
|
||||
ngx_rtmp_play_ctx_t *next;
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t *root;
|
||||
ngx_url_t *url;
|
||||
} ngx_rtmp_play_entry_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t temp_path;
|
||||
ngx_str_t local_path;
|
||||
ngx_array_t entries; /* ngx_rtmp_play_entry_t * */
|
||||
ngx_uint_t nbuckets;
|
||||
ngx_rtmp_play_ctx_t **ctx;
|
||||
} ngx_rtmp_play_app_conf_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_array_t fmts; /* ngx_rtmp_play_fmt_t * */
|
||||
} ngx_rtmp_play_main_conf_t;
|
||||
|
||||
|
||||
extern ngx_module_t ngx_rtmp_play_module;
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_PLAY_H_INCLUDED_ */
|
||||
197
ngx_http_flv_module/ngx_rtmp_proxy_protocol.c
Normal file
197
ngx_http_flv_module/ngx_rtmp_proxy_protocol.c
Normal file
@@ -0,0 +1,197 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <nginx.h>
|
||||
#include "ngx_rtmp_proxy_protocol.h"
|
||||
|
||||
|
||||
static void ngx_rtmp_proxy_protocol_recv(ngx_event_t *rev);
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_proxy_protocol(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_event_t *rev;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = s->connection;
|
||||
rev = c->read;
|
||||
rev->handler = ngx_rtmp_proxy_protocol_recv;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->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->connection->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->connection->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);
|
||||
}
|
||||
19
ngx_http_flv_module/ngx_rtmp_proxy_protocol.h
Normal file
19
ngx_http_flv_module/ngx_rtmp_proxy_protocol.h
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_
|
||||
#define _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
void ngx_rtmp_proxy_protocol(ngx_rtmp_session_t *c);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_ */
|
||||
480
ngx_http_flv_module/ngx_rtmp_receive.c
Normal file
480
ngx_http_flv_module/ngx_rtmp_receive.c
Normal file
@@ -0,0 +1,480 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
#include "ngx_rtmp_amf.h"
|
||||
#include "ngx_rtmp_cmd_module.h"
|
||||
#include <string.h>
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
uint32_t val;
|
||||
uint8_t limit;
|
||||
|
||||
b = in->buf;
|
||||
|
||||
if (b->last - b->pos < 4) {
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"too small buffer for %d message: %d",
|
||||
(int)h->type, b->last - b->pos);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
val = ntohl(*(uint32_t *) b->pos);
|
||||
|
||||
switch(h->type) {
|
||||
case NGX_RTMP_MSG_CHUNK_SIZE:
|
||||
/* set chunk size =val */
|
||||
ngx_rtmp_set_chunk_size(s, val);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_MSG_ABORT:
|
||||
/* abort chunk stream =val */
|
||||
break;
|
||||
|
||||
case NGX_RTMP_MSG_ACK:
|
||||
/* receive ack with sequence number =val */
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive ack seq=%uD", val);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_MSG_ACK_SIZE:
|
||||
/* receive window size =val */
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive ack_size=%uD", val);
|
||||
s->ack_size = val;
|
||||
break;
|
||||
|
||||
case NGX_RTMP_MSG_BANDWIDTH:
|
||||
if (b->last - b->pos >= 5) {
|
||||
limit = *(uint8_t*)&b->pos[4];
|
||||
|
||||
(void)val;
|
||||
(void)limit;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive bandwidth=%uD limit=%d",
|
||||
val, (int)limit);
|
||||
|
||||
/* receive window size =val
|
||||
* && limit */
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_user_message_handler(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
uint16_t evt;
|
||||
uint32_t val;
|
||||
|
||||
b = in->buf;
|
||||
|
||||
if (b->last - b->pos < 6) {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"too small buffer for user message: %d",
|
||||
b->last - b->pos);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
evt = ntohs(*(uint16_t *) b->pos);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"RTMP recv user evt %s (%i)",
|
||||
ngx_rtmp_user_message_type(evt), (ngx_int_t) evt);
|
||||
|
||||
val = ntohl(*(uint32_t *) (b->pos + 2));
|
||||
|
||||
switch(evt) {
|
||||
case NGX_RTMP_USER_STREAM_BEGIN:
|
||||
{
|
||||
ngx_rtmp_stream_begin_t v;
|
||||
|
||||
v.msid = val;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive: stream_begin msid=%uD", v.msid);
|
||||
|
||||
return ngx_rtmp_stream_begin(s, &v);
|
||||
}
|
||||
|
||||
case NGX_RTMP_USER_STREAM_EOF:
|
||||
{
|
||||
ngx_rtmp_stream_eof_t v;
|
||||
|
||||
v.msid = val;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive: stream_eof msid=%uD", v.msid);
|
||||
|
||||
return ngx_rtmp_stream_eof(s, &v);
|
||||
}
|
||||
|
||||
case NGX_RTMP_USER_STREAM_DRY:
|
||||
{
|
||||
ngx_rtmp_stream_dry_t v;
|
||||
|
||||
v.msid = val;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive: stream_dry msid=%uD", v.msid);
|
||||
|
||||
return ngx_rtmp_stream_dry(s, &v);
|
||||
}
|
||||
|
||||
case NGX_RTMP_USER_SET_BUFLEN:
|
||||
{
|
||||
ngx_rtmp_set_buflen_t v;
|
||||
|
||||
v.msid = val;
|
||||
|
||||
if (b->last - b->pos < 10) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
v.buflen = ntohl(*(uint32_t *) (b->pos + 6));
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive: set_buflen msid=%uD buflen=%uD",
|
||||
v.msid, v.buflen);
|
||||
|
||||
/*TODO: move this to play module */
|
||||
s->buflen = v.buflen;
|
||||
|
||||
return ngx_rtmp_set_buflen(s, &v);
|
||||
}
|
||||
|
||||
case NGX_RTMP_USER_RECORDED:
|
||||
{
|
||||
ngx_rtmp_recorded_t v;
|
||||
|
||||
v.msid = val;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive: recorded msid=%uD", v.msid);
|
||||
|
||||
return ngx_rtmp_recorded(s, &v);
|
||||
}
|
||||
|
||||
case NGX_RTMP_USER_PING_REQUEST:
|
||||
return ngx_rtmp_send_ping_response(s, val);
|
||||
|
||||
case NGX_RTMP_USER_PING_RESPONSE:
|
||||
|
||||
/* val = incoming timestamp */
|
||||
|
||||
ngx_rtmp_reset_ping(s);
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
default:
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"unexpected user event: %i", (ngx_int_t) evt);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_fetch(ngx_chain_t **in, u_char *ret)
|
||||
{
|
||||
while (*in && (*in)->buf->pos >= (*in)->buf->last) {
|
||||
*in = (*in)->next;
|
||||
}
|
||||
|
||||
if (*in == NULL) {
|
||||
return NGX_DONE;
|
||||
}
|
||||
|
||||
*ret = *(*in)->buf->pos++;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_fetch_uint8(ngx_chain_t **in, uint8_t *ret)
|
||||
{
|
||||
return ngx_rtmp_fetch(in, (u_char *) ret);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_fetch_uint32(ngx_chain_t **in, uint32_t *ret, ngx_int_t n)
|
||||
{
|
||||
u_char r;
|
||||
ngx_int_t rc;
|
||||
uint32_t val;
|
||||
|
||||
*ret = 0;
|
||||
val = 0;
|
||||
|
||||
while (--n >= 0) {
|
||||
rc = ngx_rtmp_fetch(in, &r);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
val = (val << 8) | r;
|
||||
}
|
||||
|
||||
*ret = val;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_aggregate_message_handler(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
uint32_t base_time, timestamp, prev_size;
|
||||
size_t len;
|
||||
ngx_int_t first;
|
||||
u_char *last;
|
||||
ngx_int_t rc;
|
||||
ngx_buf_t *b;
|
||||
ngx_chain_t *cl, *next;
|
||||
ngx_rtmp_header_t ch;
|
||||
|
||||
ch = *h;
|
||||
|
||||
first = 1;
|
||||
base_time = 0;
|
||||
|
||||
while (in) {
|
||||
if (ngx_rtmp_fetch_uint8(&in, &ch.type) != NGX_OK) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_fetch_uint32(&in, &ch.mlen, 3) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_fetch_uint32(&in, ×tamp, 3) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_fetch_uint8(&in, (uint8_t *) ×tamp + 3) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_fetch_uint32(&in, &ch.msid, 3) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
base_time = timestamp;
|
||||
first = 0;
|
||||
}
|
||||
|
||||
ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"RTMP aggregate %s (%d) len=%uD time=%uD (+%D) msid=%uD",
|
||||
ngx_rtmp_message_type(ch.type),
|
||||
(ngx_int_t) ch.type, ch.mlen, ch.timestamp,
|
||||
timestamp - base_time, ch.msid);
|
||||
|
||||
/* limit chain */
|
||||
|
||||
len = 0;
|
||||
cl = in;
|
||||
while (cl) {
|
||||
b = cl->buf;
|
||||
len += (b->last - b->pos);
|
||||
if (len > ch.mlen) {
|
||||
break;
|
||||
}
|
||||
cl = cl->next;
|
||||
}
|
||||
|
||||
if (cl == NULL) {
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"RTMP error parsing aggregate");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
next = cl->next;
|
||||
cl->next = NULL;
|
||||
b = cl->buf;
|
||||
last = b->last;
|
||||
b->last -= (len - ch.mlen);
|
||||
|
||||
/* handle aggregated message */
|
||||
|
||||
ch.timestamp = h->timestamp + timestamp - base_time;
|
||||
|
||||
rc = ngx_rtmp_receive_message(s, &ch, in);
|
||||
|
||||
/* restore chain before checking the result */
|
||||
|
||||
in = cl;
|
||||
in->next = next;
|
||||
b->pos = b->last;
|
||||
b->last = last;
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* read 32-bit previous tag size */
|
||||
|
||||
if (ngx_rtmp_fetch_uint32(&in, &prev_size, 4) != NGX_OK) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"RTMP aggregate prev_size=%uD", prev_size);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_amf_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_amf_ctx_t act;
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
ngx_array_t *ch;
|
||||
ngx_rtmp_handler_pt *ph;
|
||||
ngx_chain_t *cl;
|
||||
ngx_int_t amf_len;
|
||||
size_t len, n;
|
||||
|
||||
static u_char func[128];
|
||||
|
||||
static ngx_rtmp_amf_elt_t elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
func, sizeof(func) },
|
||||
};
|
||||
|
||||
/* AMF command names come with string type, but shared object names
|
||||
* come without type */
|
||||
if (h->type == NGX_RTMP_MSG_AMF_SHARED ||
|
||||
h->type == NGX_RTMP_MSG_AMF3_SHARED)
|
||||
{
|
||||
elts[0].type |= NGX_RTMP_AMF_TYPELESS;
|
||||
} else {
|
||||
elts[0].type &= ~NGX_RTMP_AMF_TYPELESS;
|
||||
}
|
||||
|
||||
if ((h->type == NGX_RTMP_MSG_AMF3_SHARED ||
|
||||
h->type == NGX_RTMP_MSG_AMF3_META ||
|
||||
h->type == NGX_RTMP_MSG_AMF3_CMD)
|
||||
&& in->buf->last > in->buf->pos)
|
||||
{
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"AMF3 prefix: %ui", (ngx_int_t)*in->buf->pos);
|
||||
++in->buf->pos;
|
||||
}
|
||||
|
||||
cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
/*
|
||||
* work around the buggy option `-map` in FFmpeg, see:
|
||||
* https://trac.ffmpeg.org/ticket/10565
|
||||
*/
|
||||
if (in->buf->pos[0] == NGX_RTMP_AMF_NUMBER) {
|
||||
cl = in;
|
||||
amf_len = 0;
|
||||
|
||||
while (cl) {
|
||||
amf_len += cl->buf->last - cl->buf->pos;
|
||||
/* type: 1B, number payload: 8B */
|
||||
if (amf_len >= 9) {
|
||||
break;
|
||||
}
|
||||
|
||||
cl = cl->next;
|
||||
}
|
||||
|
||||
if (amf_len < 9) {
|
||||
ngx_log_error(NGX_LOG_WARN, s->connection->log, 0,
|
||||
"AMF malformed: type=%d, length=%D, ignored",
|
||||
NGX_RTMP_AMF_NUMBER, amf_len);
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* read AMF func name & transaction id */
|
||||
ngx_memzero(&act, sizeof(act));
|
||||
act.link = in;
|
||||
act.log = s->connection->log;
|
||||
memset(func, 0, sizeof(func));
|
||||
|
||||
if (ngx_rtmp_amf_read(&act, elts,
|
||||
sizeof(elts) / sizeof(elts[0])) != NGX_OK)
|
||||
{
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"AMF cmd failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* skip name */
|
||||
in = act.link;
|
||||
in->buf->pos += act.offset;
|
||||
|
||||
len = ngx_strlen(func);
|
||||
|
||||
ch = ngx_hash_find(&cmcf->amf_hash,
|
||||
ngx_hash_strlow(func, func, len), func, len);
|
||||
|
||||
if (ch && ch->nelts) {
|
||||
ph = ch->elts;
|
||||
for (n = 0; n < ch->nelts; ++n, ++ph) {
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"AMF func '%s' passed to handler %d/%d",
|
||||
func, n, ch->nelts);
|
||||
switch ((*ph)(s, h, in)) {
|
||||
case NGX_ERROR:
|
||||
return NGX_ERROR;
|
||||
case NGX_DONE:
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"AMF cmd '%s' no handler", func);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_receive_amf(ngx_rtmp_session_t *s, ngx_chain_t *in,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts)
|
||||
{
|
||||
ngx_rtmp_amf_ctx_t act;
|
||||
|
||||
ngx_memzero(&act, sizeof(act));
|
||||
act.link = in;
|
||||
act.log = s->connection->log;
|
||||
|
||||
return ngx_rtmp_amf_read(&act, elts, nelts);
|
||||
}
|
||||
1327
ngx_http_flv_module/ngx_rtmp_record_module.c
Normal file
1327
ngx_http_flv_module/ngx_rtmp_record_module.c
Normal file
File diff suppressed because it is too large
Load Diff
100
ngx_http_flv_module/ngx_rtmp_record_module.h
Normal file
100
ngx_http_flv_module/ngx_rtmp_record_module.h
Normal file
@@ -0,0 +1,100 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_RECORD_H_INCLUDED_
|
||||
#define _NGX_RTMP_RECORD_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
#define NGX_RTMP_RECORD_OFF 0x01
|
||||
#define NGX_RTMP_RECORD_AUDIO 0x02
|
||||
#define NGX_RTMP_RECORD_VIDEO 0x04
|
||||
#define NGX_RTMP_RECORD_KEYFRAMES 0x08
|
||||
#define NGX_RTMP_RECORD_MANUAL 0x10
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t id;
|
||||
ngx_uint_t flags;
|
||||
ngx_str_t path;
|
||||
size_t max_size;
|
||||
size_t max_frames;
|
||||
ngx_msec_t interval;
|
||||
ngx_str_t suffix;
|
||||
ngx_flag_t unique;
|
||||
ngx_flag_t append;
|
||||
ngx_flag_t lock_file;
|
||||
ngx_flag_t notify;
|
||||
ngx_url_t *url;
|
||||
|
||||
void **rec_conf;
|
||||
ngx_array_t rec; /* ngx_rtmp_record_app_conf_t * */
|
||||
} ngx_rtmp_record_app_conf_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_rtmp_record_app_conf_t *conf;
|
||||
ngx_file_t file;
|
||||
ngx_uint_t nframes;
|
||||
uint32_t epoch, time_shift;
|
||||
ngx_time_t last;
|
||||
time_t timestamp;
|
||||
unsigned failed:1;
|
||||
unsigned initialized:1;
|
||||
unsigned aac_header_sent:1;
|
||||
unsigned avc_header_sent:1;
|
||||
unsigned video_key_sent:1;
|
||||
unsigned audio:1;
|
||||
unsigned video:1;
|
||||
} ngx_rtmp_record_rec_ctx_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_array_t rec; /* ngx_rtmp_record_rec_ctx_t */
|
||||
u_char name[NGX_RTMP_MAX_NAME];
|
||||
u_char args[NGX_RTMP_MAX_ARGS];
|
||||
} ngx_rtmp_record_ctx_t;
|
||||
|
||||
|
||||
ngx_uint_t ngx_rtmp_record_find(ngx_rtmp_record_app_conf_t *racf,
|
||||
ngx_str_t *id);
|
||||
|
||||
|
||||
/* Manual recording control,
|
||||
* 'n' is record node index in config array.
|
||||
* Note: these functions allocate path in static buffer */
|
||||
|
||||
ngx_int_t ngx_rtmp_record_open(ngx_rtmp_session_t *s, ngx_uint_t n,
|
||||
ngx_str_t *path);
|
||||
ngx_int_t ngx_rtmp_record_close(ngx_rtmp_session_t *s, ngx_uint_t n,
|
||||
ngx_str_t *path);
|
||||
|
||||
void ngx_rtmp_record_get_path(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_record_rec_ctx_t *rctx, ngx_str_t *path);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t recorder;
|
||||
ngx_str_t path;
|
||||
} ngx_rtmp_record_done_t;
|
||||
|
||||
|
||||
typedef ngx_int_t (*ngx_rtmp_record_done_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_record_done_t *v);
|
||||
|
||||
|
||||
extern ngx_rtmp_record_done_pt ngx_rtmp_record_done;
|
||||
|
||||
|
||||
extern ngx_module_t ngx_rtmp_record_module;
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_RECORD_H_INCLUDED_ */
|
||||
1954
ngx_http_flv_module/ngx_rtmp_relay_module.c
Normal file
1954
ngx_http_flv_module/ngx_rtmp_relay_module.c
Normal file
File diff suppressed because it is too large
Load Diff
88
ngx_http_flv_module/ngx_rtmp_relay_module.h
Normal file
88
ngx_http_flv_module/ngx_rtmp_relay_module.h
Normal file
@@ -0,0 +1,88 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_RELAY_H_INCLUDED_
|
||||
#define _NGX_RTMP_RELAY_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_url_t url;
|
||||
ngx_str_t app;
|
||||
ngx_str_t name;
|
||||
ngx_str_t tc_url;
|
||||
ngx_str_t page_url;
|
||||
ngx_str_t swf_url;
|
||||
ngx_str_t flash_ver;
|
||||
ngx_str_t play_path;
|
||||
ngx_int_t live;
|
||||
ngx_int_t start;
|
||||
ngx_int_t stop;
|
||||
|
||||
void *tag; /* usually module reference */
|
||||
void *data; /* module-specific data */
|
||||
ngx_uint_t counter; /* mutable connection counter */
|
||||
} ngx_rtmp_relay_target_t;
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_relay_ctx_s ngx_rtmp_relay_ctx_t;
|
||||
|
||||
struct ngx_rtmp_relay_ctx_s {
|
||||
ngx_str_t server_name;
|
||||
ngx_str_t name;
|
||||
ngx_str_t url;
|
||||
ngx_log_t log;
|
||||
ngx_rtmp_session_t *session;
|
||||
ngx_rtmp_relay_ctx_t *publish;
|
||||
ngx_rtmp_relay_ctx_t *play;
|
||||
ngx_rtmp_relay_ctx_t *next;
|
||||
|
||||
ngx_str_t app;
|
||||
ngx_str_t tc_url;
|
||||
ngx_str_t page_url;
|
||||
ngx_str_t swf_url;
|
||||
ngx_str_t flash_ver;
|
||||
ngx_str_t play_path;
|
||||
ngx_int_t live;
|
||||
ngx_int_t start;
|
||||
ngx_int_t stop;
|
||||
|
||||
ngx_event_t push_evt;
|
||||
ngx_event_t *static_evt;
|
||||
void *tag;
|
||||
void *data;
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_array_t pulls; /* ngx_rtmp_relay_target_t * */
|
||||
ngx_array_t pushes; /* ngx_rtmp_relay_target_t * */
|
||||
ngx_array_t static_pulls; /* ngx_rtmp_relay_target_t * */
|
||||
ngx_array_t static_events; /* ngx_event_t * */
|
||||
ngx_log_t *log;
|
||||
ngx_uint_t nbuckets;
|
||||
ngx_msec_t buflen;
|
||||
ngx_flag_t session_relay;
|
||||
ngx_msec_t push_reconnect;
|
||||
ngx_msec_t pull_reconnect;
|
||||
ngx_rtmp_relay_ctx_t **ctx;
|
||||
} ngx_rtmp_relay_app_conf_t;
|
||||
|
||||
|
||||
extern ngx_module_t ngx_rtmp_relay_module;
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_relay_pull(ngx_rtmp_session_t *s, ngx_str_t *name,
|
||||
ngx_rtmp_relay_target_t *target);
|
||||
ngx_int_t ngx_rtmp_relay_push(ngx_rtmp_session_t *s, ngx_str_t *name,
|
||||
ngx_rtmp_relay_target_t *target);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_RELAY_H_INCLUDED_ */
|
||||
781
ngx_http_flv_module/ngx_rtmp_send.c
Normal file
781
ngx_http_flv_module/ngx_rtmp_send.c
Normal file
@@ -0,0 +1,781 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_http.h>
|
||||
#include "ngx_rtmp.h"
|
||||
#include "ngx_rtmp_amf.h"
|
||||
#include "ngx_rtmp_streams.h"
|
||||
#include "ngx_rtmp_cmd_module.h"
|
||||
|
||||
|
||||
#define NGX_RTMP_USER_START(s, tp) \
|
||||
ngx_rtmp_header_t __h; \
|
||||
ngx_chain_t *__l; \
|
||||
ngx_buf_t *__b; \
|
||||
ngx_rtmp_core_srv_conf_t *__cscf; \
|
||||
\
|
||||
__cscf = ngx_rtmp_get_module_srv_conf( \
|
||||
s, ngx_rtmp_core_module); \
|
||||
memset(&__h, 0, sizeof(__h)); \
|
||||
__h.type = tp; \
|
||||
__h.csid = 2; \
|
||||
__l = ngx_rtmp_alloc_shared_buf(__cscf); \
|
||||
if (__l == NULL) { \
|
||||
return NULL; \
|
||||
} \
|
||||
__b = __l->buf;
|
||||
|
||||
#define NGX_RTMP_UCTL_START(s, type, utype) \
|
||||
NGX_RTMP_USER_START(s, type); \
|
||||
*(__b->last++) = (u_char)((utype) >> 8); \
|
||||
*(__b->last++) = (u_char)(utype);
|
||||
|
||||
#define NGX_RTMP_USER_OUT1(v) \
|
||||
*(__b->last++) = (u_char) v;
|
||||
|
||||
#define NGX_RTMP_USER_OUT4(v) \
|
||||
*(__b->last++) = (u_char) (v >> 24); \
|
||||
*(__b->last++) = (u_char) (v >> 16); \
|
||||
*(__b->last++) = (u_char) (v >> 8); \
|
||||
*(__b->last++) = (u_char) v;
|
||||
|
||||
#define NGX_RTMP_USER_END(s) \
|
||||
ngx_rtmp_prepare_message(s, &__h, NULL, __l); \
|
||||
return __l;
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_send_shared_packet(ngx_rtmp_session_t *s, ngx_chain_t *cl)
|
||||
{
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
ngx_http_request_t *r;
|
||||
ngx_int_t rc;
|
||||
|
||||
if (cl == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
if (!s->relay) {
|
||||
/* rquest from http */
|
||||
r = s->data;
|
||||
if (r) {
|
||||
ngx_rtmp_free_shared_chain(cscf, cl);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
rc = ngx_rtmp_send_message(s, cl, 0);
|
||||
|
||||
ngx_rtmp_free_shared_chain(cscf, cl);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/* Protocol control messages */
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_chunk_size(ngx_rtmp_session_t *s, uint32_t chunk_size)
|
||||
{
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"chunk_size=%uD", chunk_size);
|
||||
|
||||
{
|
||||
NGX_RTMP_USER_START(s, NGX_RTMP_MSG_CHUNK_SIZE);
|
||||
|
||||
NGX_RTMP_USER_OUT4(chunk_size);
|
||||
|
||||
NGX_RTMP_USER_END(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_chunk_size(ngx_rtmp_session_t *s, uint32_t chunk_size)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_chunk_size(s, chunk_size));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_abort(ngx_rtmp_session_t *s, uint32_t csid)
|
||||
{
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: abort csid=%uD", csid);
|
||||
|
||||
{
|
||||
NGX_RTMP_USER_START(s, NGX_RTMP_MSG_ABORT);
|
||||
|
||||
NGX_RTMP_USER_OUT4(csid);
|
||||
|
||||
NGX_RTMP_USER_END(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_abort(ngx_rtmp_session_t *s, uint32_t csid)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_abort(s, csid));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_ack(ngx_rtmp_session_t *s, uint32_t seq)
|
||||
{
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: ack seq=%uD", seq);
|
||||
|
||||
{
|
||||
NGX_RTMP_USER_START(s, NGX_RTMP_MSG_ACK);
|
||||
|
||||
NGX_RTMP_USER_OUT4(seq);
|
||||
|
||||
NGX_RTMP_USER_END(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_ack(ngx_rtmp_session_t *s, uint32_t seq)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_ack(s, seq));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_ack_size(ngx_rtmp_session_t *s, uint32_t ack_size)
|
||||
{
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: ack_size=%uD", ack_size);
|
||||
|
||||
{
|
||||
NGX_RTMP_USER_START(s, NGX_RTMP_MSG_ACK_SIZE);
|
||||
|
||||
NGX_RTMP_USER_OUT4(ack_size);
|
||||
|
||||
NGX_RTMP_USER_END(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_ack_size(ngx_rtmp_session_t *s, uint32_t ack_size)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_ack_size(s, ack_size));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_bandwidth(ngx_rtmp_session_t *s, uint32_t ack_size,
|
||||
uint8_t limit_type)
|
||||
{
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: bandwidth ack_size=%uD limit=%d",
|
||||
ack_size, (int)limit_type);
|
||||
|
||||
{
|
||||
NGX_RTMP_USER_START(s, NGX_RTMP_MSG_BANDWIDTH);
|
||||
|
||||
NGX_RTMP_USER_OUT4(ack_size);
|
||||
NGX_RTMP_USER_OUT1(limit_type);
|
||||
|
||||
NGX_RTMP_USER_END(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_bandwidth(ngx_rtmp_session_t *s, uint32_t ack_size,
|
||||
uint8_t limit_type)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_bandwidth(s, ack_size, limit_type));
|
||||
}
|
||||
|
||||
|
||||
/* User control messages */
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_stream_begin(ngx_rtmp_session_t *s, uint32_t msid)
|
||||
{
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: stream_begin msid=%uD", msid);
|
||||
|
||||
{
|
||||
NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_BEGIN);
|
||||
|
||||
NGX_RTMP_USER_OUT4(msid);
|
||||
|
||||
NGX_RTMP_USER_END(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_stream_begin(ngx_rtmp_session_t *s, uint32_t msid)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_stream_begin(s, msid));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_stream_eof(ngx_rtmp_session_t *s, uint32_t msid)
|
||||
{
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: stream_end msid=%uD", msid);
|
||||
|
||||
{
|
||||
NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_EOF);
|
||||
|
||||
NGX_RTMP_USER_OUT4(msid);
|
||||
|
||||
NGX_RTMP_USER_END(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_stream_eof(ngx_rtmp_session_t *s, uint32_t msid)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_stream_eof(s, msid));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_stream_dry(ngx_rtmp_session_t *s, uint32_t msid)
|
||||
{
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: stream_dry msid=%uD", msid);
|
||||
|
||||
{
|
||||
NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_DRY);
|
||||
|
||||
NGX_RTMP_USER_OUT4(msid);
|
||||
|
||||
NGX_RTMP_USER_END(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_stream_dry(ngx_rtmp_session_t *s, uint32_t msid)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_stream_dry(s, msid));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_set_buflen(ngx_rtmp_session_t *s, uint32_t msid,
|
||||
uint32_t buflen_msec)
|
||||
{
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: set_buflen msid=%uD buflen=%uD",
|
||||
msid, buflen_msec);
|
||||
|
||||
{
|
||||
NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_SET_BUFLEN);
|
||||
|
||||
NGX_RTMP_USER_OUT4(msid);
|
||||
NGX_RTMP_USER_OUT4(buflen_msec);
|
||||
|
||||
NGX_RTMP_USER_END(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_set_buflen(ngx_rtmp_session_t *s, uint32_t msid,
|
||||
uint32_t buflen_msec)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_set_buflen(s, msid, buflen_msec));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_recorded(ngx_rtmp_session_t *s, uint32_t msid)
|
||||
{
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: recorded msid=%uD", msid);
|
||||
|
||||
{
|
||||
NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_RECORDED);
|
||||
|
||||
NGX_RTMP_USER_OUT4(msid);
|
||||
|
||||
NGX_RTMP_USER_END(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_recorded(ngx_rtmp_session_t *s, uint32_t msid)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_recorded(s, msid));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_ping_request(ngx_rtmp_session_t *s, uint32_t timestamp)
|
||||
{
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: ping_request timestamp=%uD", timestamp);
|
||||
|
||||
{
|
||||
NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_PING_REQUEST);
|
||||
|
||||
NGX_RTMP_USER_OUT4(timestamp);
|
||||
|
||||
NGX_RTMP_USER_END(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_ping_request(ngx_rtmp_session_t *s, uint32_t timestamp)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_ping_request(s, timestamp));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_ping_response(ngx_rtmp_session_t *s, uint32_t timestamp)
|
||||
{
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: ping_response timestamp=%uD", timestamp);
|
||||
|
||||
{
|
||||
NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_PING_RESPONSE);
|
||||
|
||||
NGX_RTMP_USER_OUT4(timestamp);
|
||||
|
||||
NGX_RTMP_USER_END(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_ping_response(ngx_rtmp_session_t *s, uint32_t timestamp)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_ping_response(s, timestamp));
|
||||
}
|
||||
|
||||
|
||||
static ngx_chain_t *
|
||||
ngx_rtmp_alloc_amf_buf(void *arg)
|
||||
{
|
||||
return ngx_rtmp_alloc_shared_buf((ngx_rtmp_core_srv_conf_t *)arg);
|
||||
}
|
||||
|
||||
|
||||
/* AMF sender */
|
||||
|
||||
/* NOTE: this function does not free shared bufs on error */
|
||||
ngx_int_t
|
||||
ngx_rtmp_append_amf(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t **first, ngx_chain_t **last,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts)
|
||||
{
|
||||
ngx_rtmp_amf_ctx_t act;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
ngx_int_t rc;
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
memset(&act, 0, sizeof(act));
|
||||
act.arg = cscf;
|
||||
act.alloc = ngx_rtmp_alloc_amf_buf;
|
||||
act.log = s->connection->log;
|
||||
|
||||
if (first) {
|
||||
act.first = *first;
|
||||
}
|
||||
|
||||
if (last) {
|
||||
act.link = *last;
|
||||
}
|
||||
|
||||
rc = ngx_rtmp_amf_write(&act, elts, nelts);
|
||||
|
||||
if (first) {
|
||||
*first = act.first;
|
||||
}
|
||||
|
||||
if (last) {
|
||||
*last = act.link;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts)
|
||||
{
|
||||
ngx_chain_t *first;
|
||||
ngx_int_t rc;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: amf nelts=%ui", nelts);
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
first = NULL;
|
||||
|
||||
rc = ngx_rtmp_append_amf(s, &first, NULL, elts, nelts);
|
||||
|
||||
if (rc != NGX_OK && first) {
|
||||
ngx_rtmp_free_shared_chain(cscf, first);
|
||||
first = NULL;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
ngx_rtmp_prepare_message(s, h, NULL, first);
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_amf(s, h, elts, nelts));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_status(ngx_rtmp_session_t *s, char *code, char* level,
|
||||
char *desc)
|
||||
{
|
||||
ngx_rtmp_header_t h;
|
||||
static double trans;
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_inf[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("level"),
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("code"),
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("description"),
|
||||
NULL, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"onStatus", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
out_inf,
|
||||
sizeof(out_inf) },
|
||||
};
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: status code='%s' level='%s' desc='%s'",
|
||||
code, level, desc);
|
||||
|
||||
out_inf[0].data = level;
|
||||
out_inf[1].data = code;
|
||||
out_inf[2].data = desc;
|
||||
|
||||
memset(&h, 0, sizeof(h));
|
||||
|
||||
h.type = NGX_RTMP_MSG_AMF_CMD;
|
||||
h.csid = NGX_RTMP_CSID_AMF;
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
|
||||
return ngx_rtmp_create_amf(s, &h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_status(ngx_rtmp_session_t *s, char *code, char* level, char *desc)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_status(s, code, level, desc));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_play_status(ngx_rtmp_session_t *s, char *code, char* level,
|
||||
ngx_uint_t duration, ngx_uint_t bytes)
|
||||
{
|
||||
ngx_rtmp_header_t h;
|
||||
static double dduration;
|
||||
static double dbytes;
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_inf[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("code"),
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("level"),
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("duration"),
|
||||
&dduration, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("bytes"),
|
||||
&dbytes, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"onPlayStatus", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
out_inf,
|
||||
sizeof(out_inf) },
|
||||
};
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"create: play_status code='%s' level='%s' "
|
||||
"duration=%ui bytes=%ui",
|
||||
code, level, duration, bytes);
|
||||
|
||||
out_inf[0].data = code;
|
||||
out_inf[1].data = level;
|
||||
|
||||
dduration = duration;
|
||||
dbytes = bytes;
|
||||
|
||||
memset(&h, 0, sizeof(h));
|
||||
|
||||
h.type = NGX_RTMP_MSG_AMF_META;
|
||||
h.csid = NGX_RTMP_CSID_AMF;
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
h.timestamp = duration;
|
||||
|
||||
return ngx_rtmp_create_amf(s, &h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_play_status(ngx_rtmp_session_t *s, char *code, char* level,
|
||||
ngx_uint_t duration, ngx_uint_t bytes)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_play_status(s, code, level, duration, bytes));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_fcpublish(ngx_rtmp_session_t *s, u_char *desc)
|
||||
{
|
||||
ngx_rtmp_header_t h;
|
||||
static double trans;
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_inf[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("level"),
|
||||
"status", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("code"),
|
||||
"NetStream.Publish.Start", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("description"),
|
||||
NULL, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"onFCPublish", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
out_inf,
|
||||
sizeof(out_inf) },
|
||||
};
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"create: fcpublish - set structure data");
|
||||
|
||||
out_inf[2].data = desc;
|
||||
trans = 0;
|
||||
|
||||
memset(&h, 0, sizeof(h));
|
||||
|
||||
h.type = NGX_RTMP_MSG_AMF_CMD;
|
||||
h.csid = NGX_RTMP_CSID_AMF;
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
|
||||
return ngx_rtmp_create_amf(s, &h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_fcpublish(ngx_rtmp_session_t *s, u_char *desc)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_fcpublish(s, desc));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_fcunpublish(ngx_rtmp_session_t *s, u_char *desc)
|
||||
{
|
||||
ngx_rtmp_header_t h;
|
||||
static double trans;
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_inf[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("level"),
|
||||
"status", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("code"),
|
||||
"NetStream.Unpublish.Success", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("description"),
|
||||
NULL, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"onFCUnpublish", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
out_inf,
|
||||
sizeof(out_inf) },
|
||||
};
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"create: fcunpublish - set structure data");
|
||||
|
||||
out_inf[2].data = desc;
|
||||
trans = 0;
|
||||
|
||||
memset(&h, 0, sizeof(h));
|
||||
|
||||
h.type = NGX_RTMP_MSG_AMF_CMD;
|
||||
h.csid = NGX_RTMP_CSID_AMF;
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
|
||||
return ngx_rtmp_create_amf(s, &h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_fcunpublish(ngx_rtmp_session_t *s, u_char *desc)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_fcunpublish(s, desc));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_sample_access(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_rtmp_header_t h;
|
||||
|
||||
static int access = 1;
|
||||
|
||||
static ngx_rtmp_amf_elt_t access_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"|RtmpSampleAccess", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_BOOLEAN,
|
||||
ngx_null_string,
|
||||
&access, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_BOOLEAN,
|
||||
ngx_null_string,
|
||||
&access, 0 },
|
||||
};
|
||||
|
||||
memset(&h, 0, sizeof(h));
|
||||
|
||||
h.type = NGX_RTMP_MSG_AMF_META;
|
||||
h.csid = NGX_RTMP_CSID_AMF;
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
|
||||
return ngx_rtmp_create_amf(s, &h, access_elts,
|
||||
sizeof(access_elts) / sizeof(access_elts[0]));
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_sample_access(ngx_rtmp_session_t *s)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_sample_access(s));
|
||||
}
|
||||
126
ngx_http_flv_module/ngx_rtmp_shared.c
Normal file
126
ngx_http_flv_module/ngx_rtmp_shared.c
Normal file
@@ -0,0 +1,126 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_alloc_shared_buf(ngx_rtmp_core_srv_conf_t *cscf)
|
||||
{
|
||||
u_char *p;
|
||||
ngx_chain_t *out;
|
||||
ngx_buf_t *b;
|
||||
size_t size;
|
||||
|
||||
if (cscf->free) {
|
||||
out = cscf->free;
|
||||
cscf->free = out->next;
|
||||
|
||||
} else {
|
||||
|
||||
size = cscf->chunk_size + NGX_RTMP_MAX_CHUNK_HEADER;
|
||||
|
||||
p = ngx_pcalloc(cscf->pool, NGX_RTMP_REFCOUNT_BYTES
|
||||
+ sizeof(ngx_chain_t)
|
||||
+ sizeof(ngx_buf_t)
|
||||
+ size);
|
||||
if (p == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p += NGX_RTMP_REFCOUNT_BYTES;
|
||||
out = (ngx_chain_t *)p;
|
||||
|
||||
p += sizeof(ngx_chain_t);
|
||||
out->buf = (ngx_buf_t *)p;
|
||||
|
||||
p += sizeof(ngx_buf_t);
|
||||
out->buf->start = p;
|
||||
out->buf->end = p + size;
|
||||
}
|
||||
|
||||
out->next = NULL;
|
||||
b = out->buf;
|
||||
b->pos = b->last = b->start + NGX_RTMP_MAX_CHUNK_HEADER;
|
||||
b->memory = 1;
|
||||
|
||||
/* buffer has refcount =1 when created! */
|
||||
ngx_rtmp_ref_set(out, 1);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_free_shared_chain(ngx_rtmp_core_srv_conf_t *cscf, ngx_chain_t *in)
|
||||
{
|
||||
ngx_chain_t *cl;
|
||||
|
||||
if (ngx_rtmp_ref_put(in)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (cl = in; ; cl = cl->next) {
|
||||
if (cl->next == NULL) {
|
||||
cl->next = cscf->free;
|
||||
cscf->free = in;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf,
|
||||
ngx_chain_t *head, ngx_chain_t *in)
|
||||
{
|
||||
ngx_chain_t *l, **ll;
|
||||
u_char *p;
|
||||
size_t size;
|
||||
|
||||
ll = &head;
|
||||
p = in->buf->pos;
|
||||
l = head;
|
||||
|
||||
if (l) {
|
||||
for(; l->next; l = l->next);
|
||||
ll = &l->next;
|
||||
}
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
if (l == NULL || l->buf->last == l->buf->end) {
|
||||
l = ngx_rtmp_alloc_shared_buf(cscf);
|
||||
if (l == NULL || l->buf == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
*ll = l;
|
||||
ll = &l->next;
|
||||
}
|
||||
|
||||
while (l->buf->end - l->buf->last >= in->buf->last - p) {
|
||||
l->buf->last = ngx_cpymem(l->buf->last, p,
|
||||
in->buf->last - p);
|
||||
in = in->next;
|
||||
if (in == NULL) {
|
||||
goto done;
|
||||
}
|
||||
p = in->buf->pos;
|
||||
}
|
||||
|
||||
size = l->buf->end - l->buf->last;
|
||||
l->buf->last = ngx_cpymem(l->buf->last, p, size);
|
||||
p += size;
|
||||
}
|
||||
|
||||
done:
|
||||
*ll = NULL;
|
||||
|
||||
return head;
|
||||
}
|
||||
1714
ngx_http_flv_module/ngx_rtmp_stat_module.c
Normal file
1714
ngx_http_flv_module/ngx_rtmp_stat_module.c
Normal file
File diff suppressed because it is too large
Load Diff
19
ngx_http_flv_module/ngx_rtmp_streams.h
Normal file
19
ngx_http_flv_module/ngx_rtmp_streams.h
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_STREAMS_H_INCLUDED_
|
||||
#define _NGX_RTMP_STREAMS_H_INCLUDED_
|
||||
|
||||
|
||||
#define NGX_RTMP_MSID 1
|
||||
|
||||
#define NGX_RTMP_CSID_AMF_INI 3
|
||||
#define NGX_RTMP_CSID_AMF 5
|
||||
#define NGX_RTMP_CSID_AUDIO 6
|
||||
#define NGX_RTMP_CSID_VIDEO 7
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_STREAMS_H_INCLUDED_ */
|
||||
1387
ngx_http_flv_module/ngx_rtmp_variables.c
Normal file
1387
ngx_http_flv_module/ngx_rtmp_variables.c
Normal file
File diff suppressed because it is too large
Load Diff
111
ngx_http_flv_module/ngx_rtmp_variables.h
Normal file
111
ngx_http_flv_module/ngx_rtmp_variables.h
Normal file
@@ -0,0 +1,111 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Igor Sysoev
|
||||
* Copyright (C) Nginx, Inc.
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_VARIABLES_H_INCLUDED_
|
||||
#define _NGX_RTMP_VARIABLES_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
|
||||
|
||||
typedef ngx_variable_value_t ngx_rtmp_variable_value_t;
|
||||
|
||||
#define ngx_rtmp_variable(v) { sizeof(v) - 1, 1, 0, 0, 0, (u_char *) v }
|
||||
|
||||
typedef struct ngx_rtmp_variable_s ngx_rtmp_variable_t;
|
||||
|
||||
typedef void (*ngx_rtmp_set_variable_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_variable_value_t *v, uintptr_t data);
|
||||
typedef ngx_int_t (*ngx_rtmp_get_variable_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_variable_value_t *v, uintptr_t data);
|
||||
|
||||
|
||||
#define NGX_RTMP_VAR_CHANGEABLE 1
|
||||
#define NGX_RTMP_VAR_NOCACHEABLE 2
|
||||
#define NGX_RTMP_VAR_INDEXED 4
|
||||
#define NGX_RTMP_VAR_NOHASH 8
|
||||
#define NGX_RTMP_VAR_WEAK 16
|
||||
#define NGX_RTMP_VAR_PREFIX 32
|
||||
|
||||
|
||||
struct ngx_rtmp_variable_s {
|
||||
ngx_str_t name; /* must be first to build the hash */
|
||||
ngx_rtmp_set_variable_pt set_handler;
|
||||
ngx_rtmp_get_variable_pt get_handler;
|
||||
uintptr_t data;
|
||||
ngx_uint_t flags;
|
||||
ngx_uint_t index;
|
||||
};
|
||||
|
||||
|
||||
ngx_rtmp_variable_t *ngx_rtmp_add_variable(ngx_conf_t *cf, ngx_str_t *name,
|
||||
ngx_uint_t flags);
|
||||
ngx_int_t ngx_rtmp_get_variable_index(ngx_conf_t *cf, ngx_str_t *name);
|
||||
ngx_rtmp_variable_value_t *ngx_rtmp_get_indexed_variable(ngx_rtmp_session_t *s,
|
||||
ngx_uint_t index);
|
||||
ngx_rtmp_variable_value_t *ngx_rtmp_get_flushed_variable(ngx_rtmp_session_t *s,
|
||||
ngx_uint_t index);
|
||||
|
||||
ngx_rtmp_variable_value_t *ngx_rtmp_get_variable(ngx_rtmp_session_t *s,
|
||||
ngx_str_t *name, ngx_uint_t key);
|
||||
|
||||
|
||||
#if (NGX_PCRE)
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t capture;
|
||||
ngx_int_t index;
|
||||
} ngx_rtmp_regex_variable_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_regex_t *regex;
|
||||
ngx_uint_t ncaptures;
|
||||
ngx_rtmp_regex_variable_t *variables;
|
||||
ngx_uint_t nvariables;
|
||||
ngx_str_t name;
|
||||
} ngx_rtmp_regex_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_rtmp_regex_t *regex;
|
||||
void *value;
|
||||
} ngx_rtmp_map_regex_t;
|
||||
|
||||
|
||||
ngx_rtmp_regex_t *ngx_rtmp_regex_compile(ngx_conf_t *cf,
|
||||
ngx_regex_compile_t *rc);
|
||||
ngx_int_t ngx_rtmp_regex_exec(ngx_rtmp_session_t *s, ngx_rtmp_regex_t *re,
|
||||
ngx_str_t *str);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_hash_combined_t hash;
|
||||
#if (NGX_PCRE)
|
||||
ngx_rtmp_map_regex_t *regex;
|
||||
ngx_uint_t nregex;
|
||||
#endif
|
||||
} ngx_rtmp_map_t;
|
||||
|
||||
|
||||
void *ngx_rtmp_map_find(ngx_rtmp_session_t *s, ngx_rtmp_map_t *map,
|
||||
ngx_str_t *match);
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_variables_add_core_vars(ngx_conf_t *cf);
|
||||
ngx_int_t ngx_rtmp_variables_init_vars(ngx_conf_t *cf);
|
||||
|
||||
|
||||
extern ngx_rtmp_variable_value_t ngx_rtmp_variable_null_value;
|
||||
extern ngx_rtmp_variable_value_t ngx_rtmp_variable_true_value;
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_VARIABLES_H_INCLUDED_ */
|
||||
16
ngx_http_flv_module/ngx_rtmp_version.h
Normal file
16
ngx_http_flv_module/ngx_rtmp_version.h
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
* Copyright (C) Winshining
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_VERSION_H_INCLUDED_
|
||||
#define _NGX_RTMP_VERSION_H_INCLUDED_
|
||||
|
||||
|
||||
#define nginx_rtmp_version 1002012
|
||||
#define NGINX_RTMP_VERSION "1.2.12"
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_VERSION_H_INCLUDED_ */
|
||||
BIN
ngx_http_flv_module/samples/flv.js.png
Normal file
BIN
ngx_http_flv_module/samples/flv.js.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
ngx_http_flv_module/samples/jwplayer_vlc.png
Normal file
BIN
ngx_http_flv_module/samples/jwplayer_vlc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
546
ngx_http_flv_module/stat.xsl
Normal file
546
ngx_http_flv_module/stat.xsl
Normal file
@@ -0,0 +1,546 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
|
||||
|
||||
<!--
|
||||
Copyright (C) Roman Arutyunyan
|
||||
Copyright (C) Winshining
|
||||
-->
|
||||
|
||||
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
|
||||
|
||||
<xsl:template match="/">
|
||||
<html>
|
||||
<head>
|
||||
<title>HTTP-FLV statistics</title>
|
||||
</head>
|
||||
<body>
|
||||
<xsl:apply-templates select="http-flv"/>
|
||||
<hr/>
|
||||
Generated by <a href='https://github.com/winshining/nginx-http-flv-module'>
|
||||
nginx-http-flv-module</a> <xsl:value-of select="/http-flv/nginx_http_flv_version"/>,
|
||||
<a href="http://nginx.org">nginx</a> <xsl:value-of select="/http-flv/nginx_version"/>,
|
||||
pid <xsl:value-of select="/http-flv/pid"/>,
|
||||
built <xsl:value-of select="/http-flv/built"/> <xsl:value-of select="/http-flv/compiler"/>
|
||||
</body>
|
||||
</html>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="http-flv">
|
||||
<table cellspacing="1" cellpadding="5">
|
||||
<tr bgcolor="#999999">
|
||||
<th>HTTP-FLV</th>
|
||||
<th>#clients</th>
|
||||
<th colspan="2">Recorders</th>
|
||||
<th colspan="2">Server</th>
|
||||
<th colspan="4">Video</th>
|
||||
<th colspan="4">Audio</th>
|
||||
<th>In bytes</th>
|
||||
<th>Out bytes</th>
|
||||
<th>In bits/s</th>
|
||||
<th>Out bits/s</th>
|
||||
<th>State</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">Accepted: <xsl:value-of select="naccepted"/></td>
|
||||
<th bgcolor="#999999">lists</th>
|
||||
<th bgcolor="#999999">#count</th>
|
||||
<th bgcolor="#999999">port</th>
|
||||
<th bgcolor="#999999">index</th>
|
||||
<th bgcolor="#999999">codec</th>
|
||||
<th bgcolor="#999999">bits/s</th>
|
||||
<th bgcolor="#999999">size</th>
|
||||
<th bgcolor="#999999">fps</th>
|
||||
<th bgcolor="#999999">codec</th>
|
||||
<th bgcolor="#999999">bits/s</th>
|
||||
<th bgcolor="#999999">freq</th>
|
||||
<th bgcolor="#999999">chan</th>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bytes_in"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bytes_out"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bw_in"/>
|
||||
<xsl:with-param name="bits" select="1"/>
|
||||
<xsl:with-param name="persec" select="1"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bw_out"/>
|
||||
<xsl:with-param name="bits" select="1"/>
|
||||
<xsl:with-param name="persec" select="1"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td/>
|
||||
<td>
|
||||
<xsl:call-template name="showtime">
|
||||
<xsl:with-param name="time" select="/http-flv/uptime * 1000"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
</tr>
|
||||
<xsl:apply-templates select="server"/>
|
||||
</table>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="server">
|
||||
<xsl:apply-templates select="application"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="application">
|
||||
<tr bgcolor="#999999">
|
||||
<td>
|
||||
<b><xsl:value-of select="name"/></b>
|
||||
</td>
|
||||
</tr>
|
||||
<xsl:apply-templates select="live"/>
|
||||
<xsl:apply-templates select="play"/>
|
||||
<xsl:apply-templates select="recorders"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="live">
|
||||
<tr bgcolor="#aaaaaa">
|
||||
<td>
|
||||
<i>live streams</i>
|
||||
</td>
|
||||
<td align="middle">
|
||||
<xsl:value-of select="nclients"/>
|
||||
</td>
|
||||
</tr>
|
||||
<xsl:apply-templates select="stream"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="play">
|
||||
<tr bgcolor="#aaaaaa">
|
||||
<td>
|
||||
<i>vod streams</i>
|
||||
</td>
|
||||
<td align="middle">
|
||||
<xsl:value-of select="nclients"/>
|
||||
</td>
|
||||
</tr>
|
||||
<xsl:apply-templates select="stream"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="recorders">
|
||||
<tr bgcolor="#aaaaaa">
|
||||
<td colspan="2">Recorders</td>
|
||||
<td>
|
||||
<a href="">
|
||||
<xsl:attribute name="onclick">
|
||||
var d=document.getElementById('<xsl:value-of select="../../port"/>-<xsl:value-of select="../../server_index"/>-<xsl:value-of select="../name"/>-recorders');
|
||||
d.style.display=d.style.display=='none'?'':'none';
|
||||
return false;
|
||||
</xsl:attribute>
|
||||
<i>config</i>
|
||||
</a>
|
||||
</td>
|
||||
<td align="middle">
|
||||
<xsl:value-of select="count"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="display:none">
|
||||
<xsl:attribute name="id"><xsl:value-of select="../../port"/>-<xsl:value-of select="../../server_index"/>-<xsl:value-of select="../name"/>-recorders</xsl:attribute>
|
||||
<td colspan="16">
|
||||
<table cellspacing="1" cellpadding="5">
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Path</th>
|
||||
<th>Suffix</th>
|
||||
<th>Flags</th>
|
||||
<th>Max Size</th>
|
||||
<th>Max Frames</th>
|
||||
<th>Interval</th>
|
||||
<th>Unique</th>
|
||||
<th>Append</th>
|
||||
<th>Lock File</th>
|
||||
<th>Notify</th>
|
||||
</tr>
|
||||
<xsl:apply-templates select="recorder"/>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="recorder">
|
||||
<tr bgcolor="#cccccc">
|
||||
<td>
|
||||
<xsl:value-of select="id"/>
|
||||
<xsl:if test="string-length(id) = 0">
|
||||
[DEFAULT]
|
||||
</xsl:if>
|
||||
</td>
|
||||
<td><xsl:value-of select="path"/></td>
|
||||
<td><xsl:value-of select="suffix"/></td>
|
||||
<td>
|
||||
<xsl:if test="flags/video">video<br/></xsl:if>
|
||||
<xsl:if test="flags/audio">audio<br/></xsl:if>
|
||||
<xsl:if test="flags/manual">manual</xsl:if>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="max_size"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td><xsl:value-of select="max_frames"/></td>
|
||||
<td>
|
||||
<xsl:call-template name="showinterval">
|
||||
<xsl:with-param name="interval" select="interval"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="binarystate">
|
||||
<xsl:with-param name="value" select="unique"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="binarystate">
|
||||
<xsl:with-param name="value" select="append"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="binarystate">
|
||||
<xsl:with-param name="value" select="lock_file"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="binarystate">
|
||||
<xsl:with-param name="value" select="notify"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="binarystate">
|
||||
<xsl:param name="value"/>
|
||||
<xsl:choose>
|
||||
<xsl:when test="$value">on</xsl:when>
|
||||
<xsl:otherwise>off</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="stream">
|
||||
<tr valign="top">
|
||||
<xsl:attribute name="bgcolor">
|
||||
<xsl:choose>
|
||||
<xsl:when test="active">#cccccc</xsl:when>
|
||||
<xsl:otherwise>#dddddd</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:attribute>
|
||||
<td>
|
||||
<a href="">
|
||||
<xsl:attribute name="onclick">
|
||||
var d=document.getElementById('<xsl:value-of select="../../../port"/>-<xsl:value-of select="../../../server_index"/>-<xsl:value-of select="../../name"/>-<xsl:value-of select="name"/>');
|
||||
d.style.display=d.style.display=='none'?'':'none';
|
||||
return false
|
||||
</xsl:attribute>
|
||||
<xsl:value-of select="name"/>
|
||||
<xsl:if test="string-length(name) = 0">
|
||||
[EMPTY]
|
||||
</xsl:if>
|
||||
</a>
|
||||
</td>
|
||||
<td align="middle"> <xsl:value-of select="nclients"/> </td>
|
||||
<td align="middle"> - </td>
|
||||
<td align="middle"> - </td>
|
||||
<td align="middle"> <xsl:value-of select="../../../port"/> </td>
|
||||
<td align="middle"> <xsl:value-of select="../../../server_index"/> </td>
|
||||
<td>
|
||||
<xsl:value-of select="meta/video/codec"/> <xsl:value-of select="meta/video/profile"/> <xsl:value-of select="meta/video/level"/>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bw_video"/>
|
||||
<xsl:with-param name="bits" select="1"/>
|
||||
<xsl:with-param name="persec" select="1"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:apply-templates select="meta/video/width"/>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:value-of select="meta/video/frame_rate"/>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:value-of select="meta/audio/codec"/> <xsl:value-of select="meta/audio/profile"/>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bw_audio"/>
|
||||
<xsl:with-param name="bits" select="1"/>
|
||||
<xsl:with-param name="persec" select="1"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:apply-templates select="meta/audio/sample_rate"/>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:value-of select="meta/audio/channels"/>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bytes_in"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bytes_out"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bw_in"/>
|
||||
<xsl:with-param name="bits" select="1"/>
|
||||
<xsl:with-param name="persec" select="1"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bw_out"/>
|
||||
<xsl:with-param name="bits" select="1"/>
|
||||
<xsl:with-param name="persec" select="1"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td><xsl:call-template name="streamstate"/></td>
|
||||
<td>
|
||||
<xsl:call-template name="showtime">
|
||||
<xsl:with-param name="time" select="time"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="display:none">
|
||||
<xsl:attribute name="id">
|
||||
<xsl:value-of select="../../../port"/>-<xsl:value-of select="../../../server_index"/>-<xsl:value-of select="../../name"/>-<xsl:value-of select="name"/>
|
||||
</xsl:attribute>
|
||||
<td colspan="16" ngcolor="#eeeeee">
|
||||
<table cellspacing="1" cellpadding="5">
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>State</th>
|
||||
<th>Address</th>
|
||||
<th>Flash version</th>
|
||||
<th>Page URL</th>
|
||||
<th>SWF URL</th>
|
||||
<th>Dropped</th>
|
||||
<th>Timestamp</th>
|
||||
<th>A-V</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
<xsl:apply-templates select="client"/>
|
||||
</table>
|
||||
<xsl:apply-templates select="records"/>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="records">
|
||||
<table cellspacing="1" cellpadding="5">
|
||||
<tr>
|
||||
<th>Recorder</th>
|
||||
<th>State</th>
|
||||
<th>Epoch</th>
|
||||
<th>Time Shift</th>
|
||||
<th>File Name</th>
|
||||
<th>Time</th>
|
||||
<th>Size</th>
|
||||
<th>Frames</th>
|
||||
</tr>
|
||||
<xsl:apply-templates select="record"/>
|
||||
</table>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="record">
|
||||
<tr>
|
||||
<xsl:attribute name="bgcolor">
|
||||
<xsl:choose>
|
||||
<xsl:when test="recording">#cccccc</xsl:when>
|
||||
<xsl:otherwise>#eeeeee</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:attribute>
|
||||
<td>
|
||||
<xsl:value-of select="recorder"/>
|
||||
<xsl:if test="string-length(recorder) = 0">
|
||||
[DEFAULT]
|
||||
</xsl:if>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:choose>
|
||||
<xsl:when test="recording">recording</xsl:when>
|
||||
<xsl:otherwise>idle</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</td>
|
||||
<td><xsl:value-of select="epoch"/></td>
|
||||
<td><xsl:value-of select="time_shift"/></td>
|
||||
<td><xsl:value-of select="file"/></td>
|
||||
<td>
|
||||
<xsl:call-template name="showtime">
|
||||
<xsl:with-param name="time" select="time * 1000"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="size"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td><xsl:value-of select="nframes"/></td>
|
||||
</tr>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="showtime">
|
||||
<xsl:param name="time"/>
|
||||
|
||||
<xsl:if test="$time > 0">
|
||||
<xsl:variable name="sec">
|
||||
<xsl:value-of select="floor($time div 1000)"/>
|
||||
</xsl:variable>
|
||||
|
||||
<xsl:if test="$sec >= 86400">
|
||||
<xsl:value-of select="floor($sec div 86400)"/>d
|
||||
</xsl:if>
|
||||
|
||||
<xsl:if test="$sec >= 3600">
|
||||
<xsl:value-of select="(floor($sec div 3600)) mod 24"/>h
|
||||
</xsl:if>
|
||||
|
||||
<xsl:if test="$sec >= 60">
|
||||
<xsl:value-of select="(floor($sec div 60)) mod 60"/>m
|
||||
</xsl:if>
|
||||
|
||||
<xsl:value-of select="$sec mod 60"/>s
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="showsize">
|
||||
<xsl:param name="size"/>
|
||||
<xsl:param name="bits" select="0" />
|
||||
<xsl:param name="persec" select="0" />
|
||||
<xsl:variable name="sizen">
|
||||
<xsl:value-of select="floor($size div 1024)"/>
|
||||
</xsl:variable>
|
||||
<xsl:choose>
|
||||
<xsl:when test="$sizen >= 1073741824">
|
||||
<xsl:value-of select="format-number($sizen div 1073741824,'#.###')"/> T</xsl:when>
|
||||
|
||||
<xsl:when test="$sizen >= 1048576">
|
||||
<xsl:value-of select="format-number($sizen div 1048576,'#.###')"/> G</xsl:when>
|
||||
|
||||
<xsl:when test="$sizen >= 1024">
|
||||
<xsl:value-of select="format-number($sizen div 1024,'#.##')"/> M</xsl:when>
|
||||
<xsl:when test="$sizen >= 0">
|
||||
<xsl:value-of select="$sizen"/> K</xsl:when>
|
||||
</xsl:choose>
|
||||
<xsl:if test="string-length($size) > 0">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$bits = 1">b</xsl:when>
|
||||
<xsl:otherwise>B</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
<xsl:if test="$persec = 1">/s</xsl:if>
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="showinterval">
|
||||
<xsl:param name="interval"/>
|
||||
|
||||
<xsl:if test="$interval > 0">
|
||||
<xsl:variable name="sec">
|
||||
<xsl:value-of select="floor($interval div 1000)"/>
|
||||
</xsl:variable>
|
||||
|
||||
<xsl:if test="$sec >= 86400">
|
||||
<xsl:value-of select="floor($sec div 86400)"/>d
|
||||
</xsl:if>
|
||||
|
||||
<xsl:if test="$sec >= 3600">
|
||||
<xsl:value-of select="(floor($sec div 3600)) mod 24"/>h
|
||||
</xsl:if>
|
||||
|
||||
<xsl:if test="$sec >= 60">
|
||||
<xsl:value-of select="(floor($sec div 60)) mod 60"/>m
|
||||
</xsl:if>
|
||||
|
||||
<xsl:if test="$sec < 60">
|
||||
<xsl:value-of select="$sec mod 60"/>s
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
|
||||
<xsl:if test="$interval = -1">Unset</xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="streamstate">
|
||||
<xsl:choose>
|
||||
<xsl:when test="active">active</xsl:when>
|
||||
<xsl:otherwise>idle</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<xsl:template name="clientstate">
|
||||
<xsl:choose>
|
||||
<xsl:when test="publishing">publishing</xsl:when>
|
||||
<xsl:otherwise>playing</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<xsl:template match="client">
|
||||
<tr>
|
||||
<xsl:attribute name="bgcolor">
|
||||
<xsl:choose>
|
||||
<xsl:when test="publishing">#cccccc</xsl:when>
|
||||
<xsl:otherwise>#eeeeee</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:attribute>
|
||||
<td><xsl:value-of select="id"/></td>
|
||||
<td><xsl:call-template name="clientstate"/></td>
|
||||
<td>
|
||||
<a target="_blank">
|
||||
<xsl:attribute name="href">
|
||||
http://apps.db.ripe.net/search/query.html?searchtext=<xsl:value-of select="address"/>
|
||||
</xsl:attribute>
|
||||
<xsl:attribute name="title">whois</xsl:attribute>
|
||||
<xsl:value-of select="address"/>
|
||||
</a>
|
||||
</td>
|
||||
<td><xsl:value-of select="flashver"/></td>
|
||||
<td>
|
||||
<a target="_blank">
|
||||
<xsl:attribute name="href">
|
||||
<xsl:value-of select="pageurl"/>
|
||||
</xsl:attribute>
|
||||
<xsl:value-of select="pageurl"/>
|
||||
</a>
|
||||
</td>
|
||||
<td><xsl:value-of select="swfurl"/></td>
|
||||
<td><xsl:value-of select="dropped"/></td>
|
||||
<td><xsl:value-of select="timestamp"/></td>
|
||||
<td><xsl:value-of select="avsync"/></td>
|
||||
<td>
|
||||
<xsl:call-template name="showtime">
|
||||
<xsl:with-param name="time" select="time"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="publishing">
|
||||
publishing
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="active">
|
||||
active
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="width">
|
||||
<xsl:value-of select="."/>x<xsl:value-of select="../height"/>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
11
ngx_http_flv_module/test/README.md
Normal file
11
ngx_http_flv_module/test/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# RTMP tests
|
||||
|
||||
nginx.conf is sample config for testing nginx-rtmp.
|
||||
Please update paths in it before using.
|
||||
|
||||
RTMP port: 1935, HTTP port: 8080
|
||||
|
||||
* http://localhost:8080/ - play myapp/mystream with JWPlayer
|
||||
* http://localhost:8080/record.html - capture myapp/mystream from webcam with old JWPlayer
|
||||
* http://localhost:8080/rtmp-publisher/player.html - play myapp/mystream with the test flash applet
|
||||
* http://localhost:8080/rtmp-publisher/publisher.html - capture myapp/mystream with the test flash applet
|
||||
1
ngx_http_flv_module/test/dump.sh
Normal file
1
ngx_http_flv_module/test/dump.sh
Normal file
@@ -0,0 +1 @@
|
||||
rtmpdump -v -r "rtmp://localhost/myapp/mystream"
|
||||
1
ngx_http_flv_module/test/ffstream.sh
Normal file
1
ngx_http_flv_module/test/ffstream.sh
Normal file
@@ -0,0 +1 @@
|
||||
ffmpeg -loglevel verbose -re -i ~/movie.avi -f flv rtmp://localhost/myapp/mystream
|
||||
66
ngx_http_flv_module/test/nginx.conf
Normal file
66
ngx_http_flv_module/test/nginx.conf
Normal file
@@ -0,0 +1,66 @@
|
||||
worker_processes 1;
|
||||
|
||||
error_log logs/error.log debug;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
|
||||
application myapp {
|
||||
live on;
|
||||
|
||||
#record keyframes;
|
||||
#record_path /tmp;
|
||||
#record_max_size 128K;
|
||||
#record_interval 30s;
|
||||
#record_suffix .this.is.flv;
|
||||
|
||||
#on_publish http://localhost:8080/publish;
|
||||
#on_play http://localhost:8080/play;
|
||||
#on_record_done http://localhost:8080/record_done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
http {
|
||||
server {
|
||||
listen 8080;
|
||||
|
||||
location /stat {
|
||||
rtmp_stat all;
|
||||
rtmp_stat_stylesheet stat.xsl;
|
||||
}
|
||||
|
||||
location /stat.xsl {
|
||||
root /path/to/nginx-rtmp-module/;
|
||||
}
|
||||
|
||||
location /control {
|
||||
rtmp_control all;
|
||||
}
|
||||
|
||||
#location /publish {
|
||||
# return 201;
|
||||
#}
|
||||
|
||||
#location /play {
|
||||
# return 202;
|
||||
#}
|
||||
|
||||
#location /record_done {
|
||||
# return 203;
|
||||
#}
|
||||
|
||||
location /rtmp-publisher {
|
||||
root /path/to/nginx-rtmp-module/test;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /path/to/nginx-rtmp-module/test/www;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
ngx_http_flv_module/test/play.sh
Normal file
1
ngx_http_flv_module/test/play.sh
Normal file
@@ -0,0 +1 @@
|
||||
ffplay -loglevel verbose "rtmp://localhost/myapp/mystream"
|
||||
15
ngx_http_flv_module/test/rtmp-publisher/README.md
Normal file
15
ngx_http_flv_module/test/rtmp-publisher/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# RTMP Publisher
|
||||
|
||||
Simple RTMP publisher.
|
||||
|
||||
Edit the following flashvars in publisher.html & player.html to suite your needs.
|
||||
|
||||
streamer: RTMP endpoint
|
||||
file: live stream name
|
||||
|
||||
## Compile
|
||||
|
||||
Install flex sdk http://www.adobe.com/devnet/flex/flex-sdk-download.html
|
||||
|
||||
mxmlc RtmpPublisher.mxml
|
||||
mxmlc RtmpPlayer.mxml
|
||||
69
ngx_http_flv_module/test/rtmp-publisher/RtmpPlayer.mxml
Normal file
69
ngx_http_flv_module/test/rtmp-publisher/RtmpPlayer.mxml
Normal file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
|
||||
xmlns:s="library://ns.adobe.com/flex/spark"
|
||||
xmlns:mx="library://ns.adobe.com/flex/mx"
|
||||
minWidth="500" minHeight="350" creationComplete="init()">
|
||||
|
||||
<fx:Script>
|
||||
<![CDATA[
|
||||
import mx.controls.Alert;
|
||||
import mx.core.FlexGlobals;
|
||||
import flash.display.StageDisplayState;
|
||||
import mx.managers.SystemManager;
|
||||
|
||||
private var streamer:String;
|
||||
private var file:String;
|
||||
|
||||
private function playButtonListener(event:MouseEvent):void {
|
||||
if(playButton.label == 'Play') {
|
||||
playButton.label = 'Stop';
|
||||
videoDisplay.source = streamer + "/" + file;
|
||||
videoDisplay.play();
|
||||
} else {
|
||||
playButton.label = 'Play';
|
||||
videoDisplay.source = "";
|
||||
//videoDisplay.close();
|
||||
}
|
||||
}
|
||||
|
||||
private function fullscreenButtonListener(event:MouseEvent):void {
|
||||
try {
|
||||
switch (systemManager.stage.displayState) {
|
||||
case StageDisplayState.FULL_SCREEN:
|
||||
stage.displayState = StageDisplayState.NORMAL;
|
||||
break;
|
||||
default:
|
||||
stage.displayState = StageDisplayState.FULL_SCREEN;
|
||||
break;
|
||||
}
|
||||
} catch (err:SecurityError) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private function init():void {
|
||||
videoDisplay.mx_internal::videoPlayer.bufferTime = 1;
|
||||
|
||||
streamer = FlexGlobals.topLevelApplication.parameters.streamer;
|
||||
if(streamer == null) {
|
||||
Alert.show('Missing flashvars: streamer');
|
||||
return;
|
||||
}
|
||||
|
||||
file = FlexGlobals.topLevelApplication.parameters.file;
|
||||
if(file == null) {
|
||||
Alert.show('Missing flashvars: file');
|
||||
return;
|
||||
}
|
||||
|
||||
playButton.addEventListener(MouseEvent.CLICK, playButtonListener);
|
||||
fullscreenButton.addEventListener(MouseEvent.CLICK, fullscreenButtonListener);
|
||||
}
|
||||
]]>
|
||||
</fx:Script>
|
||||
<s:BorderContainer x="0" y="0" width="100%" height="100%">
|
||||
<s:VideoDisplay width="100%" height="100%" id="videoDisplay"></s:VideoDisplay>
|
||||
<s:Button label="Play" id="playButton" horizontalCenter="0" bottom="10"></s:Button>
|
||||
<s:Button label="[ ]" id="fullscreenButton" right="10" bottom="10"></s:Button>
|
||||
</s:BorderContainer>
|
||||
</s:Application>
|
||||
BIN
ngx_http_flv_module/test/rtmp-publisher/RtmpPlayer.swf
Normal file
BIN
ngx_http_flv_module/test/rtmp-publisher/RtmpPlayer.swf
Normal file
Binary file not shown.
101
ngx_http_flv_module/test/rtmp-publisher/RtmpPlayerLight.mxml
Normal file
101
ngx_http_flv_module/test/rtmp-publisher/RtmpPlayerLight.mxml
Normal file
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
|
||||
xmlns:s="library://ns.adobe.com/flex/spark"
|
||||
xmlns:mx="library://ns.adobe.com/flex/mx"
|
||||
minWidth="500" minHeight="350" creationComplete="init()">
|
||||
|
||||
<fx:Script>
|
||||
<![CDATA[
|
||||
import mx.controls.Alert;
|
||||
import mx.core.FlexGlobals;
|
||||
import flash.display.StageDisplayState;
|
||||
import mx.managers.SystemManager;
|
||||
import org.osmf.events.MediaPlayerStateChangeEvent;
|
||||
import org.osmf.events.TimeEvent;
|
||||
import org.osmf.media.MediaPlayerState;
|
||||
|
||||
private var streamer:String;
|
||||
private var file:String;
|
||||
private var videoEventsDisabled:Boolean;
|
||||
private var previousVideoTime:Number;
|
||||
|
||||
private function fullscreenListener(event:MouseEvent):void {
|
||||
try {
|
||||
switch (stage.displayState) {
|
||||
case StageDisplayState.FULL_SCREEN:
|
||||
stage.displayState = StageDisplayState.NORMAL;
|
||||
break;
|
||||
default:
|
||||
stage.displayState = StageDisplayState.FULL_SCREEN;
|
||||
break;
|
||||
}
|
||||
} catch (err:SecurityError) {
|
||||
Alert.show('Fullsceen error: ' + err);
|
||||
}
|
||||
}
|
||||
|
||||
private function resetVideo():void {
|
||||
videoEventsDisabled = true;
|
||||
|
||||
try {
|
||||
videoDisplay.source = "";
|
||||
} catch (any:*) {}
|
||||
|
||||
setTimeout(resetVideoSource, 5000);
|
||||
}
|
||||
|
||||
private function resetVideoSource():void {
|
||||
videoEventsDisabled = false;
|
||||
previousVideoTime = NaN;
|
||||
videoDisplay.source = streamer + "/" + file;
|
||||
}
|
||||
|
||||
protected function stateChangeListener(event:MediaPlayerStateChangeEvent):void {
|
||||
if (videoEventsDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.state == MediaPlayerState.PLAYBACK_ERROR) {
|
||||
resetVideo();
|
||||
}
|
||||
}
|
||||
|
||||
protected function timeChangeListener(event:TimeEvent):void {
|
||||
if (videoEventsDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(event.time) && !isNaN(previousVideoTime)) {
|
||||
resetVideo();
|
||||
} else {
|
||||
previousVideoTime = event.time;
|
||||
}
|
||||
}
|
||||
|
||||
private function init():void {
|
||||
videoDisplay.mx_internal::videoPlayer.bufferTime = 1;
|
||||
|
||||
streamer = FlexGlobals.topLevelApplication.parameters.streamer;
|
||||
if(streamer == null) {
|
||||
Alert.show('Missing flashvars: streamer');
|
||||
return;
|
||||
}
|
||||
|
||||
file = FlexGlobals.topLevelApplication.parameters.file;
|
||||
if(file == null) {
|
||||
Alert.show('Missing flashvars: file');
|
||||
return;
|
||||
}
|
||||
|
||||
videoDisplay.addEventListener(MouseEvent.DOUBLE_CLICK, fullscreenListener);
|
||||
videoDisplay.addEventListener("MediaPlayerStateChange", stateChangeListener);
|
||||
videoDisplay.addEventListener("currentTimeChange", timeChangeListener);
|
||||
|
||||
resetVideoSource();
|
||||
}
|
||||
]]>
|
||||
</fx:Script>
|
||||
<s:BorderContainer x="0" y="0" width="100%" height="100%">
|
||||
<s:VideoDisplay doubleClickEnabled="true" width="100%" height="100%" id="videoDisplay"></s:VideoDisplay>
|
||||
</s:BorderContainer>
|
||||
</s:Application>
|
||||
BIN
ngx_http_flv_module/test/rtmp-publisher/RtmpPlayerLight.swf
Normal file
BIN
ngx_http_flv_module/test/rtmp-publisher/RtmpPlayerLight.swf
Normal file
Binary file not shown.
86
ngx_http_flv_module/test/rtmp-publisher/RtmpPublisher.mxml
Normal file
86
ngx_http_flv_module/test/rtmp-publisher/RtmpPublisher.mxml
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
|
||||
xmlns:s="library://ns.adobe.com/flex/spark"
|
||||
xmlns:mx="library://ns.adobe.com/flex/mx"
|
||||
minWidth="500" minHeight="350" creationComplete="init()">
|
||||
|
||||
<fx:Script>
|
||||
<![CDATA[
|
||||
import mx.controls.Alert;
|
||||
import mx.core.FlexGlobals;
|
||||
import mx.events.FlexEvent;
|
||||
import spark.skins.spark.PanelSkin;
|
||||
|
||||
private var streamer:String;
|
||||
private var file:String;
|
||||
private var camera:Camera;
|
||||
private var microphone:Microphone;
|
||||
private var connection:NetConnection;
|
||||
private var stream:NetStream;
|
||||
private var h264Settings:H264VideoStreamSettings;
|
||||
|
||||
private function publishButtonListener(event:MouseEvent):void {
|
||||
if(publishButton.label == 'Publish') {
|
||||
publishButton.label = 'Stop';
|
||||
connection = new NetConnection();
|
||||
connection.connect(streamer);
|
||||
connection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHander);
|
||||
} else {
|
||||
publishButton.label = 'Publish';
|
||||
stream.close();
|
||||
connection.close();
|
||||
stream = null;
|
||||
connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
private function netStatusHander(event:NetStatusEvent):void {
|
||||
switch(event.info.code) {
|
||||
case 'NetConnection.Connect.Success':
|
||||
stream = new NetStream(connection);
|
||||
stream.attachCamera(camera);
|
||||
stream.attachAudio(microphone);
|
||||
h264Settings = new H264VideoStreamSettings();
|
||||
h264Settings.setProfileLevel(H264Profile.BASELINE, H264Level.LEVEL_3_1);
|
||||
stream.videoStreamSettings = h264Settings;
|
||||
stream.publish(file, 'live');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function init():void {
|
||||
streamer = FlexGlobals.topLevelApplication.parameters.streamer;
|
||||
if(streamer == null) {
|
||||
Alert.show('Missing flashvars: streamer');
|
||||
return;
|
||||
}
|
||||
|
||||
file = FlexGlobals.topLevelApplication.parameters.file;
|
||||
if(file == null) {
|
||||
Alert.show('Missing flashvars: file');
|
||||
return;
|
||||
}
|
||||
|
||||
publishButton.addEventListener(MouseEvent.CLICK, publishButtonListener);
|
||||
|
||||
camera = Camera.getCamera();
|
||||
camera.setMode(640, 480, 30);
|
||||
camera.setQuality(131072, 70);
|
||||
|
||||
//videoDisplay.attachCamera(camera);
|
||||
var localCam:Video = new Video(640,480);
|
||||
localCam.attachCamera(camera);
|
||||
videoDisplay.addChild(localCam);
|
||||
|
||||
microphone = Microphone.getMicrophone();
|
||||
microphone.setSilenceLevel(0);
|
||||
microphone.codec = "Speex";
|
||||
microphone.encodeQuality = 6;
|
||||
}
|
||||
]]>
|
||||
</fx:Script>
|
||||
<s:BorderContainer x="0" y="0" width="100%" height="100%">
|
||||
<s:VideoDisplay width="100%" height="100%" id="videoDisplay"></s:VideoDisplay>
|
||||
<s:Button label="Publish" id="publishButton" horizontalCenter="0" bottom="10"></s:Button>
|
||||
</s:BorderContainer>
|
||||
</s:Application>
|
||||
BIN
ngx_http_flv_module/test/rtmp-publisher/RtmpPublisher.swf
Normal file
BIN
ngx_http_flv_module/test/rtmp-publisher/RtmpPublisher.swf
Normal file
Binary file not shown.
22
ngx_http_flv_module/test/rtmp-publisher/player.html
Normal file
22
ngx_http_flv_module/test/rtmp-publisher/player.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>RTMP Player</title>
|
||||
<script type="text/javascript" src="swfobject.js"></script>
|
||||
<script type="text/javascript">
|
||||
var flashVars = {
|
||||
streamer: 'rtmp://localhost/myapp',
|
||||
file:'mystream'
|
||||
};
|
||||
var params = {};
|
||||
params.allowfullscreen = "true";
|
||||
var attributes = {};
|
||||
swfobject.embedSWF("RtmpPlayer.swf", "rtmp-publisher", "640", "480", "9.0.0", null, flashVars, params, attributes);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="rtmp-publisher">
|
||||
<p>Flash not installed</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
19
ngx_http_flv_module/test/rtmp-publisher/publisher.html
Normal file
19
ngx_http_flv_module/test/rtmp-publisher/publisher.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>RTMP Publisher</title>
|
||||
<script type="text/javascript" src="swfobject.js"></script>
|
||||
<script type="text/javascript">
|
||||
var flashVars = {
|
||||
streamer: 'rtmp://localhost/myapp',
|
||||
file:'mystream'
|
||||
};
|
||||
swfobject.embedSWF("RtmpPublisher.swf", "rtmp-publisher", "640", "480", "9.0.0", null, flashVars);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="rtmp-publisher">
|
||||
<p>Flash not installed</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
4
ngx_http_flv_module/test/rtmp-publisher/swfobject.js
Normal file
4
ngx_http_flv_module/test/rtmp-publisher/swfobject.js
Normal file
File diff suppressed because one or more lines are too long
BIN
ngx_http_flv_module/test/www/bg.jpg
Normal file
BIN
ngx_http_flv_module/test/www/bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
19
ngx_http_flv_module/test/www/index.html
Normal file
19
ngx_http_flv_module/test/www/index.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<b>Play</b> | <a href="record.html">Record</a>
|
||||
<br/>
|
||||
<script type="text/javascript" src="/jwplayer/jwplayer.js"></script>
|
||||
|
||||
<div id="container">Loading the player ...</div>
|
||||
<script type="text/javascript">
|
||||
jwplayer("container").setup({
|
||||
sources: [
|
||||
{
|
||||
file: "rtmp://localhost/myapp?carg=1/mystream?sarg=2"
|
||||
}
|
||||
],
|
||||
image: "bg.jpg",
|
||||
autostart: false,
|
||||
width: 640,
|
||||
height: 480,
|
||||
primary: "flash"
|
||||
});
|
||||
</script>
|
||||
BIN
ngx_http_flv_module/test/www/jwplayer/jwplayer.flash.swf
Normal file
BIN
ngx_http_flv_module/test/www/jwplayer/jwplayer.flash.swf
Normal file
Binary file not shown.
71
ngx_http_flv_module/test/www/jwplayer/jwplayer.js
Normal file
71
ngx_http_flv_module/test/www/jwplayer/jwplayer.js
Normal file
@@ -0,0 +1,71 @@
|
||||
"undefined"==typeof jwplayer&&(jwplayer=function(d){if(jwplayer.api)return jwplayer.api.selectPlayer(d)},jwplayer.version="6.3.3242",jwplayer.vid=document.createElement("video"),jwplayer.audio=document.createElement("audio"),jwplayer.source=document.createElement("source"),function(d){function a(b){return function(){return e(b)}}var h=document,g=window,c=navigator,b=d.utils=function(){};b.exists=function(b){switch(typeof b){case "string":return 0<b.length;case "object":return null!==b;case "undefined":return!1}return!0};
|
||||
b.styleDimension=function(b){return b+(0<b.toString().indexOf("%")?"":"px")};b.getAbsolutePath=function(a,e){b.exists(e)||(e=h.location.href);if(b.exists(a)){var c;if(b.exists(a)){c=a.indexOf("://");var g=a.indexOf("?");c=0<c&&(0>g||g>c)}else c=void 0;if(c)return a;c=e.substring(0,e.indexOf("://")+3);var g=e.substring(c.length,e.indexOf("/",c.length+1)),k;0===a.indexOf("/")?k=a.split("/"):(k=e.split("?")[0],k=k.substring(c.length+g.length+1,k.lastIndexOf("/")),k=k.split("/").concat(a.split("/")));
|
||||
for(var f=[],d=0;d<k.length;d++)k[d]&&(b.exists(k[d])&&"."!=k[d])&&(".."==k[d]?f.pop():f.push(k[d]));return c+g+"/"+f.join("/")}};b.extend=function(){var a=b.extend.arguments;if(1<a.length){for(var e=1;e<a.length;e++)for(var c in a[e])try{b.exists(a[e][c])&&(a[0][c]=a[e][c])}catch(k){}return a[0]}return null};b.log=function(b,a){"undefined"!=typeof console&&"undefined"!=typeof console.log&&(a?console.log(b,a):console.log(b))};var e=b.userAgentMatch=function(b){return null!==c.userAgent.toLowerCase().match(b)};
|
||||
b.isIE=a(/msie/i);b.isFF=a(/firefox/i);b.isChrome=a(/chrome/i);b.isIOS=a(/iP(hone|ad|od)/i);b.isIPod=a(/iP(hone|od)/i);b.isIPad=a(/iPad/i);b.isSafari602=a(/Macintosh.*Mac OS X 10_8.*6\.0\.\d* Safari/i);b.isAndroid=function(b){return b?e(RegExp("android.*"+b,"i")):e(/android/i)};b.isMobile=function(){return b.isIOS()||b.isAndroid()};b.saveCookie=function(b,a){h.cookie="jwplayer."+b+"\x3d"+a+"; path\x3d/"};b.getCookies=function(){for(var b={},a=h.cookie.split("; "),e=0;e<a.length;e++){var c=a[e].split("\x3d");
|
||||
0==c[0].indexOf("jwplayer.")&&(b[c[0].substring(9,c[0].length)]=c[1])}return b};b.typeOf=function(b){var a=typeof b;return"object"===a?!b?"null":b instanceof Array?"array":a:a};b.translateEventResponse=function(a,e){var c=b.extend({},e);a==d.events.JWPLAYER_FULLSCREEN&&!c.fullscreen?(c.fullscreen="true"==c.message?!0:!1,delete c.message):"object"==typeof c.data?(c=b.extend(c,c.data),delete c.data):"object"==typeof c.metadata&&b.deepReplaceKeyName(c.metadata,["__dot__","__spc__","__dsh__","__default__"],
|
||||
["."," ","-","default"]);var k=["position","duration","offset"],g;for(g in k)c[k[g]]&&(c[k[g]]=Math.round(1E3*c[k[g]])/1E3);return c};b.flashVersion=function(){if(b.isAndroid())return 0;var a=c.plugins,e;try{if("undefined"!==a&&(e=a["Shockwave Flash"]))return parseInt(e.description.replace(/\D+(\d+)\..*/,"$1"))}catch(k){}if("undefined"!=typeof g.ActiveXObject)try{if(e=new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))return parseInt(e.GetVariable("$version").split(" ")[1].split(",")[0])}catch(d){}return 0};
|
||||
b.getScriptPath=function(b){for(var a=h.getElementsByTagName("script"),e=0;e<a.length;e++){var c=a[e].src;if(c&&0<=c.indexOf(b))return c.substr(0,c.indexOf(b))}return""};b.deepReplaceKeyName=function(b,a,e){switch(d.utils.typeOf(b)){case "array":for(var c=0;c<b.length;c++)b[c]=d.utils.deepReplaceKeyName(b[c],a,e);break;case "object":for(var k in b){var f;if(a instanceof Array&&e instanceof Array)if(a.length!=e.length)continue;else f=a;else f=[a];for(var g=k,c=0;c<f.length;c++)g=g.replace(RegExp(a[c],
|
||||
"g"),e[c]);b[g]=d.utils.deepReplaceKeyName(b[k],a,e);k!=g&&delete b[k]}}return b};var k=b.pluginPathType={ABSOLUTE:0,RELATIVE:1,CDN:2};b.getPluginPathType=function(a){if("string"==typeof a){a=a.split("?")[0];var e=a.indexOf("://");if(0<e)return k.ABSOLUTE;var c=a.indexOf("/");a=b.extension(a);return 0>e&&0>c&&(!a||!isNaN(a))?k.CDN:k.RELATIVE}};b.getPluginName=function(a){return a.replace(/^(.*\/)?([^-]*)-?.*\.(swf|js)$/,"$2")};b.getPluginVersion=function(a){return a.replace(/[^-]*-?([^\.]*).*$/,"$1")};
|
||||
b.isYouTube=function(a){return-1<a.indexOf("youtube.com")||-1<a.indexOf("youtu.be")};b.isRtmp=function(a,b){return 0==a.indexOf("rtmp")||"rtmp"==b};b.foreach=function(a,b){for(var e in a)a.hasOwnProperty(e)&&b(e)};b.isHTTPS=function(){return 0==g.location.href.indexOf("https")};b.repo=function(){var a="http://p.jwpcdn.com/"+d.version.split(/\W/).splice(0,2).join("/")+"/";try{b.isHTTPS()&&(a=a.replace("http://","https://ssl."))}catch(e){}return a}}(jwplayer),function(d){var a="video/",h={mp4:a+"mp4",
|
||||
vorbis:"audio/ogg",ogg:a+"ogg",webm:a+"webm",aac:"audio/mp4",mp3:"audio/mpeg",hls:"application/vnd.apple.mpegurl"},g={mp4:h.mp4,f4v:h.mp4,m4v:h.mp4,mov:h.mp4,m4a:h.aac,f4a:h.aac,aac:h.aac,mp3:h.mp3,ogv:h.ogg,ogg:h.vorbis,oga:h.vorbis,webm:h.webm,m3u8:h.hls,hls:h.hls},a="video",a={flv:a,f4v:a,mov:a,m4a:a,m4v:a,mp4:a,aac:a,f4a:a,mp3:"sound",smil:"rtmp",m3u8:"hls",hls:"hls"},c=d.extensionmap={},b;for(b in g)c[b]={html5:g[b]};for(b in a)c[b]||(c[b]={}),c[b].flash=a[b];c.types=h;c.mimeType=function(a){for(var b in h)if(h[b]==
|
||||
a)return b};c.extType=function(a){return c.mimeType(g[a])}}(jwplayer.utils),function(d){var a=d.loaderstatus={NEW:0,LOADING:1,ERROR:2,COMPLETE:3},h=document;d.scriptloader=function(g){function c(){e=a.ERROR;l.sendEvent(k.ERROR)}function b(){e=a.COMPLETE;l.sendEvent(k.COMPLETE)}var e=a.NEW,k=jwplayer.events,l=new k.eventdispatcher;d.extend(this,l);this.load=function(){var l=d.scriptloader.loaders[g];if(l&&(l.getStatus()==a.NEW||l.getStatus()==a.LOADING))l.addEventListener(k.ERROR,c),l.addEventListener(k.COMPLETE,
|
||||
b);else if(d.scriptloader.loaders[g]=this,e==a.NEW){e=a.LOADING;var n=h.createElement("script");n.addEventListener?(n.onload=b,n.onerror=c):n.readyState&&(n.onreadystatechange=function(){("loaded"==n.readyState||"complete"==n.readyState)&&b()});h.getElementsByTagName("head")[0].appendChild(n);n.src=g}};this.getStatus=function(){return e}};d.scriptloader.loaders={}}(jwplayer.utils),function(d){d.trim=function(a){return a.replace(/^\s*/,"").replace(/\s*$/,"")};d.pad=function(a,d,g){for(g||(g="0");a.length<
|
||||
d;)a=g+a;return a};d.xmlAttribute=function(a,d){for(var g=0;g<a.attributes.length;g++)if(a.attributes[g].name&&a.attributes[g].name.toLowerCase()==d.toLowerCase())return a.attributes[g].value.toString();return""};d.extension=function(a){if(!a||"rtmp"==a.substr(0,4))return"";a=a.substring(a.lastIndexOf("/")+1,a.length).split("?")[0].split("#")[0];if(-1<a.lastIndexOf("."))return a.substr(a.lastIndexOf(".")+1,a.length).toLowerCase()};d.stringToColor=function(a){a=a.replace(/(#|0x)?([0-9A-F]{3,6})$/gi,
|
||||
"$2");3==a.length&&(a=a.charAt(0)+a.charAt(0)+a.charAt(1)+a.charAt(1)+a.charAt(2)+a.charAt(2));return parseInt(a,16)}}(jwplayer.utils),function(d){d.key=function(a){var h,g,c;this.edition=function(){return c&&c.getTime()<(new Date).getTime()?"invalid":h};this.token=function(){return g};d.exists(a)||(a="");try{a=d.tea.decrypt(a,"36QXq4W@GSBV^teR");var b=a.split("/");(h=b[0])||(h="free");g=b[1];b[2]&&0<parseInt(b[2])&&(c=new Date,c.setTime(String(b[2])))}catch(e){h="invalid"}}}(jwplayer.utils),function(d){var a=
|
||||
d.tea={};a.encrypt=function(c,b){if(0==c.length)return"";var e=a.strToLongs(g.encode(c));1>=e.length&&(e[1]=0);for(var k=a.strToLongs(g.encode(b).slice(0,16)),l=e.length,d=e[l-1],n=e[0],q,j=Math.floor(6+52/l),f=0;0<j--;){f+=2654435769;q=f>>>2&3;for(var r=0;r<l;r++)n=e[(r+1)%l],d=(d>>>5^n<<2)+(n>>>3^d<<4)^(f^n)+(k[r&3^q]^d),d=e[r]+=d}e=a.longsToStr(e);return h.encode(e)};a.decrypt=function(c,b){if(0==c.length)return"";for(var e=a.strToLongs(h.decode(c)),k=a.strToLongs(g.encode(b).slice(0,16)),d=e.length,
|
||||
m=e[d-1],n=e[0],q,j=2654435769*Math.floor(6+52/d);0!=j;){q=j>>>2&3;for(var f=d-1;0<=f;f--)m=e[0<f?f-1:d-1],m=(m>>>5^n<<2)+(n>>>3^m<<4)^(j^n)+(k[f&3^q]^m),n=e[f]-=m;j-=2654435769}e=a.longsToStr(e);e=e.replace(/\0+$/,"");return g.decode(e)};a.strToLongs=function(a){for(var b=Array(Math.ceil(a.length/4)),e=0;e<b.length;e++)b[e]=a.charCodeAt(4*e)+(a.charCodeAt(4*e+1)<<8)+(a.charCodeAt(4*e+2)<<16)+(a.charCodeAt(4*e+3)<<24);return b};a.longsToStr=function(a){for(var b=Array(a.length),e=0;e<a.length;e++)b[e]=
|
||||
String.fromCharCode(a[e]&255,a[e]>>>8&255,a[e]>>>16&255,a[e]>>>24&255);return b.join("")};var h={code:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\x3d",encode:function(a,b){var e,k,d,m,n=[],q="",j,f,r=h.code;f=("undefined"==typeof b?0:b)?g.encode(a):a;j=f.length%3;if(0<j)for(;3>j++;)q+="\x3d",f+="\x00";for(j=0;j<f.length;j+=3)e=f.charCodeAt(j),k=f.charCodeAt(j+1),d=f.charCodeAt(j+2),m=e<<16|k<<8|d,e=m>>18&63,k=m>>12&63,d=m>>6&63,m&=63,n[j/3]=r.charAt(e)+r.charAt(k)+r.charAt(d)+
|
||||
r.charAt(m);n=n.join("");return n=n.slice(0,n.length-q.length)+q},decode:function(a,b){b="undefined"==typeof b?!1:b;var e,k,d,m,n,q=[],j,f=h.code;j=b?g.decode(a):a;for(var r=0;r<j.length;r+=4)e=f.indexOf(j.charAt(r)),k=f.indexOf(j.charAt(r+1)),m=f.indexOf(j.charAt(r+2)),n=f.indexOf(j.charAt(r+3)),d=e<<18|k<<12|m<<6|n,e=d>>>16&255,k=d>>>8&255,d&=255,q[r/4]=String.fromCharCode(e,k,d),64==n&&(q[r/4]=String.fromCharCode(e,k)),64==m&&(q[r/4]=String.fromCharCode(e));m=q.join("");return b?g.decode(m):m}},
|
||||
g={encode:function(a){a=a.replace(/[\u0080-\u07ff]/g,function(a){a=a.charCodeAt(0);return String.fromCharCode(192|a>>6,128|a&63)});return a=a.replace(/[\u0800-\uffff]/g,function(a){a=a.charCodeAt(0);return String.fromCharCode(224|a>>12,128|a>>6&63,128|a&63)})},decode:function(a){a=a.replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,function(a){a=(a.charCodeAt(0)&15)<<12|(a.charCodeAt(1)&63)<<6|a.charCodeAt(2)&63;return String.fromCharCode(a)});return a=a.replace(/[\u00c0-\u00df][\u0080-\u00bf]/g,
|
||||
function(a){a=(a.charCodeAt(0)&31)<<6|a.charCodeAt(1)&63;return String.fromCharCode(a)})}}}(jwplayer.utils),function(d){d.events={COMPLETE:"COMPLETE",ERROR:"ERROR",API_READY:"jwplayerAPIReady",JWPLAYER_READY:"jwplayerReady",JWPLAYER_FULLSCREEN:"jwplayerFullscreen",JWPLAYER_RESIZE:"jwplayerResize",JWPLAYER_ERROR:"jwplayerError",JWPLAYER_MEDIA_BEFOREPLAY:"jwplayerMediaBeforePlay",JWPLAYER_MEDIA_BEFORECOMPLETE:"jwplayerMediaBeforeComplete",JWPLAYER_COMPONENT_SHOW:"jwplayerComponentShow",JWPLAYER_COMPONENT_HIDE:"jwplayerComponentHide",
|
||||
JWPLAYER_MEDIA_BUFFER:"jwplayerMediaBuffer",JWPLAYER_MEDIA_BUFFER_FULL:"jwplayerMediaBufferFull",JWPLAYER_MEDIA_ERROR:"jwplayerMediaError",JWPLAYER_MEDIA_LOADED:"jwplayerMediaLoaded",JWPLAYER_MEDIA_COMPLETE:"jwplayerMediaComplete",JWPLAYER_MEDIA_SEEK:"jwplayerMediaSeek",JWPLAYER_MEDIA_TIME:"jwplayerMediaTime",JWPLAYER_MEDIA_VOLUME:"jwplayerMediaVolume",JWPLAYER_MEDIA_META:"jwplayerMediaMeta",JWPLAYER_MEDIA_MUTE:"jwplayerMediaMute",JWPLAYER_MEDIA_LEVELS:"jwplayerMediaLevels",JWPLAYER_MEDIA_LEVEL_CHANGED:"jwplayerMediaLevelChanged",
|
||||
JWPLAYER_CAPTIONS_CHANGED:"jwplayerCaptionsChanged",JWPLAYER_CAPTIONS_LIST:"jwplayerCaptionsList",JWPLAYER_PLAYER_STATE:"jwplayerPlayerState",state:{BUFFERING:"BUFFERING",IDLE:"IDLE",PAUSED:"PAUSED",PLAYING:"PLAYING"},JWPLAYER_PLAYLIST_LOADED:"jwplayerPlaylistLoaded",JWPLAYER_PLAYLIST_ITEM:"jwplayerPlaylistItem",JWPLAYER_PLAYLIST_COMPLETE:"jwplayerPlaylistComplete",JWPLAYER_DISPLAY_CLICK:"jwplayerViewClick",JWPLAYER_CONTROLS:"jwplayerViewControls",JWPLAYER_INSTREAM_CLICK:"jwplayerInstreamClicked",
|
||||
JWPLAYER_INSTREAM_DESTROYED:"jwplayerInstreamDestroyed"}}(jwplayer),function(d){var a=jwplayer.utils;d.eventdispatcher=function(d,g){var c,b;this.resetEventListeners=function(){c={};b=[]};this.resetEventListeners();this.addEventListener=function(b,k,d){try{a.exists(c[b])||(c[b]=[]),"string"==a.typeOf(k)&&(k=(new Function("return "+k))()),c[b].push({listener:k,count:d})}catch(g){a.log("error",g)}return!1};this.removeEventListener=function(b,d){if(c[b]){try{for(var g=0;g<c[b].length;g++)if(c[b][g].listener.toString()==
|
||||
d.toString()){c[b].splice(g,1);break}}catch(h){a.log("error",h)}return!1}};this.addGlobalListener=function(e,c){try{"string"==a.typeOf(e)&&(e=(new Function("return "+e))()),b.push({listener:e,count:c})}catch(d){a.log("error",d)}return!1};this.removeGlobalListener=function(e){if(e){try{for(var c=0;c<b.length;c++)if(b[c].listener.toString()==e.toString()){b.splice(c,1);break}}catch(d){a.log("error",d)}return!1}};this.sendEvent=function(e,k){a.exists(k)||(k={});a.extend(k,{id:d,version:jwplayer.version,
|
||||
type:e});g&&a.log(e,k);if("undefined"!=a.typeOf(c[e]))for(var l=0;l<c[e].length;l++){try{c[e][l].listener(k)}catch(m){a.log("There was an error while handling a listener: "+m.toString(),c[e][l].listener)}c[e][l]&&(1===c[e][l].count?delete c[e][l]:0<c[e][l].count&&(c[e][l].count-=1))}for(l=0;l<b.length;l++){try{b[l].listener(k)}catch(n){a.log("There was an error while handling a listener: "+n.toString(),b[l].listener)}b[l]&&(1===b[l].count?delete b[l]:0<b[l].count&&(b[l].count-=1))}}}}(jwplayer.events),
|
||||
function(d){var a={},h={};d.plugins=function(){};d.plugins.loadPlugins=function(g,c){h[g]=new d.plugins.pluginloader(new d.plugins.model(a),c);return h[g]};d.plugins.registerPlugin=function(g,c,b,e){var k=d.utils.getPluginName(g);a[k]||(a[k]=new d.plugins.plugin(g));a[k].registerPlugin(g,c,b,e)}}(jwplayer),function(d){d.plugins.model=function(a){this.addPlugin=function(h){var g=d.utils.getPluginName(h);a[g]||(a[g]=new d.plugins.plugin(h));return a[g]};this.getPlugins=function(){return a}}}(jwplayer),
|
||||
function(d){var a=jwplayer.utils,h=jwplayer.events;d.pluginmodes={FLASH:0,JAVASCRIPT:1,HYBRID:2};d.plugin=function(g){function c(){switch(a.getPluginPathType(g)){case a.pluginPathType.ABSOLUTE:return g;case a.pluginPathType.RELATIVE:return a.getAbsolutePath(g,window.location.href)}}function b(){q=setTimeout(function(){k=a.loaderstatus.COMPLETE;j.sendEvent(h.COMPLETE)},1E3)}function e(){k=a.loaderstatus.ERROR;j.sendEvent(h.ERROR)}var k=a.loaderstatus.NEW,l,m,n,q,j=new h.eventdispatcher;a.extend(this,
|
||||
j);this.load=function(){if(k==a.loaderstatus.NEW)if(0<g.lastIndexOf(".swf"))l=g,k=a.loaderstatus.COMPLETE,j.sendEvent(h.COMPLETE);else if(a.getPluginPathType(g)==a.pluginPathType.CDN)k=a.loaderstatus.COMPLETE,j.sendEvent(h.COMPLETE);else{k=a.loaderstatus.LOADING;var f=new a.scriptloader(c());f.addEventListener(h.COMPLETE,b);f.addEventListener(h.ERROR,e);f.load()}};this.registerPlugin=function(b,e,c,d){q&&(clearTimeout(q),q=void 0);n=e;c&&d?(l=d,m=c):"string"==typeof c?l=c:"function"==typeof c?m=c:
|
||||
!c&&!d&&(l=b);k=a.loaderstatus.COMPLETE;j.sendEvent(h.COMPLETE)};this.getStatus=function(){return k};this.getPluginName=function(){return a.getPluginName(g)};this.getFlashPath=function(){if(l)switch(a.getPluginPathType(l)){case a.pluginPathType.ABSOLUTE:return l;case a.pluginPathType.RELATIVE:return 0<g.lastIndexOf(".swf")?a.getAbsolutePath(l,window.location.href):a.getAbsolutePath(l,c())}return null};this.getJS=function(){return m};this.getTarget=function(){return n};this.getPluginmode=function(){if("undefined"!=
|
||||
typeof l&&"undefined"!=typeof m)return d.pluginmodes.HYBRID;if("undefined"!=typeof l)return d.pluginmodes.FLASH;if("undefined"!=typeof m)return d.pluginmodes.JAVASCRIPT};this.getNewInstance=function(a,b,e){return new m(a,b,e)};this.getURL=function(){return g}}}(jwplayer.plugins),function(d){var a=d.utils,h=d.events;d.plugins.pluginloader=function(g,c){function b(){m?j.sendEvent(h.ERROR,{message:n}):l||(l=!0,k=a.loaderstatus.COMPLETE,j.sendEvent(h.COMPLETE))}function e(){q||b();if(!l&&!m){var e=0,
|
||||
c=g.getPlugins(),f;for(f in q){var k=a.getPluginName(f),h=c[k],k=h.getJS(),j=h.getTarget(),h=h.getStatus();if(h==a.loaderstatus.LOADING||h==a.loaderstatus.NEW)e++;else if(k&&(!j||parseFloat(j)>parseFloat(d.version)))m=!0,n="Incompatible player version",b()}0==e&&b()}}var k=a.loaderstatus.NEW,l=!1,m=!1,n,q=c,j=new h.eventdispatcher;a.extend(this,j);this.setupPlugins=function(b,e,c){var f={length:0,plugins:{}},d=0,k={},h=g.getPlugins(),l;for(l in e.plugins){var j=a.getPluginName(l),m=h[j],B=m.getFlashPath(),
|
||||
n=m.getJS(),q=m.getURL();B&&(f.plugins[B]=a.extend({},e.plugins[l]),f.plugins[B].pluginmode=m.getPluginmode(),f.length++);try{if(n&&e.plugins&&e.plugins[q]){var v=document.createElement("div");v.id=b.id+"_"+j;v.style.position="absolute";v.style.top=0;v.style.zIndex=d+10;k[j]=m.getNewInstance(b,a.extend({},e.plugins[q]),v);d++;b.onReady(c(k[j],v,!0));b.onResize(c(k[j],v))}}catch(C){a.log("ERROR: Failed to load "+j+".")}}b.plugins=k;return f};this.load=function(){if(!(a.exists(c)&&"object"!=a.typeOf(c))){k=
|
||||
a.loaderstatus.LOADING;for(var b in c)if(a.exists(b)){var d=g.addPlugin(b);d.addEventListener(h.COMPLETE,e);d.addEventListener(h.ERROR,f)}d=g.getPlugins();for(b in d)d[b].load()}e()};var f=this.pluginFailed=function(){m||(m=!0,n="File not found",b())};this.getStatus=function(){return k}}}(jwplayer),function(d){d.playlist=function(a){var h=[];if("array"==d.utils.typeOf(a))for(var g=0;g<a.length;g++)h.push(new d.playlist.item(a[g]));else h.push(new d.playlist.item(a));return h}}(jwplayer),function(d){var a=
|
||||
d.item=function(h){var g=jwplayer.utils,c=g.extend({},a.defaults,h);c.tracks=g.exists(h.tracks)?h.tracks:[];0==c.sources.length&&(c.sources=[new d.source(c)]);for(var b=0;b<c.sources.length;b++){var e=c.sources[b]["default"];c.sources[b]["default"]=e?"true"==e.toString():!1;c.sources[b]=new d.source(c.sources[b])}if(c.captions&&!g.exists(h.tracks)){for(h=0;h<c.captions.length;h++)c.tracks.push(c.captions[h]);delete c.captions}for(b=0;b<c.tracks.length;b++)c.tracks[b]=new d.track(c.tracks[b]);return c};
|
||||
a.defaults={description:"",image:"",mediaid:"",title:"",sources:[],tracks:[]}}(jwplayer.playlist),function(d){var a=jwplayer.utils,h={file:void 0,label:void 0,type:void 0,"default":void 0};d.source=function(d){var c=a.extend({},h),b;for(b in h)a.exists(d[b])&&(c[b]=d[b],delete d[b]);c.type&&0<c.type.indexOf("/")&&(c.type=a.extensionmap.mimeType(c.type));"m3u8"==c.type&&(c.type="hls");"smil"==c.type&&(c.type="rtmp");return c}}(jwplayer.playlist),function(d){var a=jwplayer.utils,h={file:void 0,label:void 0,
|
||||
kind:"captions","default":!1};d.track=function(d){var c=a.extend({},h);d||(d={});for(var b in h)a.exists(d[b])&&(c[b]=d[b],delete d[b]);return c}}(jwplayer.playlist),function(d){var a=d.utils,h=d.events,g=document,c=d.embed=function(b){function e(a){l(n,p+a.message)}function k(){l(n,p+"No playable sources found")}function l(b,e){if(m.fallback){var c=b.style;c.backgroundColor="#000";c.color="#FFF";c.width=a.styleDimension(m.width);c.height=a.styleDimension(m.height);c.display="table";c.opacity=1;var c=
|
||||
document.createElement("p"),d=c.style;d.verticalAlign="middle";d.textAlign="center";d.display="table-cell";d.font="15px/20px Arial, Helvetica, sans-serif";c.innerHTML=e.replace(":",":\x3cbr\x3e");b.innerHTML="";b.appendChild(c)}}var m=new c.config(b.config),n,q,j,f=m.width,r=m.height,p="Error loading player: ",s=d.plugins.loadPlugins(b.id,m.plugins);m.fallbackDiv&&(j=m.fallbackDiv,delete m.fallbackDiv);m.id=b.id;q=g.getElementById(b.id);n=g.createElement("div");n.id=q.id;n.style.width=0<f.toString().indexOf("%")?
|
||||
f:f+"px";n.style.height=0<r.toString().indexOf("%")?r:r+"px";q.parentNode.replaceChild(n,q);d.embed.errorScreen=l;s.addEventListener(h.COMPLETE,function(){if("array"==a.typeOf(m.playlist)&&2>m.playlist.length&&(0==m.playlist.length||!m.playlist[0].sources||0==m.playlist[0].sources.length))k();else if(s.getStatus()==a.loaderstatus.COMPLETE){for(var d=0;d<m.modes.length;d++)if(m.modes[d].type&&c[m.modes[d].type]){var f=a.extend({},m),g=new c[m.modes[d].type](n,m.modes[d],f,s,b);if(g.supportsConfig()){g.addEventListener(h.ERROR,
|
||||
e);g.embed();d=b;f=f.events;g=void 0;for(g in f)"function"==typeof d[g]&&d[g].call(d,f[g]);return b}}m.fallback?(a.log("No suitable players found and fallback enabled"),new c.download(n,m,k)):(a.log("No suitable players found and fallback disabled"),n.parentNode.replaceChild(j,n))}});s.addEventListener(h.ERROR,function(a){l(n,"Could not load plugins: "+a.message)});s.load();return b}}(jwplayer),function(d){function a(a){if(a.playlist)for(var e=0;e<a.playlist.length;e++)a.playlist[e]=new c(a.playlist[e]);
|
||||
else{var e={},d;for(d in c.defaults)h(a,e,d);e.sources||(a.levels?(e.sources=a.levels,delete a.levels):(d={},h(a,d,"file"),h(a,d,"type"),e.sources=d.file?[d]:[]));a.playlist=[new c(e)]}}function h(a,e,c){g.exists(a[c])&&(e[c]=a[c],delete a[c])}var g=d.utils,c=d.playlist.item;(d.embed.config=function(b){var e={fallback:!0,height:270,primary:"html5",width:480,base:b.base?b.base:g.getScriptPath("jwplayer.js")};b=g.extend(e,d.defaults,b);var e={type:"html5",src:b.base+"jwplayer.html5.js"},c={type:"flash",
|
||||
src:b.base+"jwplayer.flash.swf"};b.modes="flash"==b.primary?[c,e]:[e,c];b.listbar&&(b.playlistsize=b.listbar.size,b.playlistposition=b.listbar.position);b.flashplayer&&(c.src=b.flashplayer);b.html5player&&(e.src=b.html5player);a(b);return b}).addConfig=function(b,c){a(c);return g.extend(b,c)}}(jwplayer),function(d){var a=d.utils,h=document;d.embed.download=function(d,c,b){function e(a,b){for(var c=h.querySelectorAll(a),e=0;e<c.length;e++)for(var d in b)c[e].style[d]=b[d]}function k(a,b,c){a=h.createElement(a);
|
||||
b&&(a.className="jwdownload"+b);c&&c.appendChild(a);return a}var l=a.extend({},c),m,n=l.width?l.width:480,q=l.height?l.height:320,j;c=c.logo?c.logo:{prefix:a.repo(),file:"logo.png",margin:10};var f,r;r=l.playlist;var p,s,l=["mp4","aac","mp3"];if(r&&r.length){p=r[0];s=p.sources;for(r=0;r<s.length;r++){var u=s[r],t=u.type?u.type:a.extensionmap.extType(a.extension(u.file));if(u.file)for(r in l)t==l[r]?(m=u.file,j=p.image):a.isYouTube(u.file)&&(f=u.file)}m?(b=m,d&&(m=k("a","display",d),k("div","icon",
|
||||
m),k("div","logo",m),b&&m.setAttribute("href",a.getAbsolutePath(b))),b="#"+d.id+" .jwdownload",d.style.width="",d.style.height="",e(b+"display",{width:a.styleDimension(Math.max(320,n)),height:a.styleDimension(Math.max(180,q)),background:"black center no-repeat "+(j?"url("+j+")":""),backgroundSize:"contain",position:"relative",border:"none",display:"block"}),e(b+"display div",{position:"absolute",width:"100%",height:"100%"}),e(b+"logo",{top:c.margin+"px",right:c.margin+"px",background:"top right no-repeat url("+
|
||||
c.prefix+c.file+")"}),e(b+"icon",{background:"center no-repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAgNJREFUeNrs28lqwkAYB/CZqNVDDj2r6FN41QeIy8Fe+gj6BL275Q08u9FbT8ZdwVfotSBYEPUkxFOoks4EKiJdaDuTjMn3wWBO0V/+sySR8SNSqVRKIR8qaXHkzlqS9jCfzzWcTCYp9hF5o+59sVjsiRzcegSckFzcjT+ruN80TeSlAjCAAXzdJSGPFXRpAAMYwACGZQkSdhG4WCzehMNhqV6vG6vVSrirKVEw66YoSqDb7cqlUilE8JjHd/y1MQefVzqdDmiaJpfLZWHgXMHn8F6vJ1cqlVAkEsGuAn83J4gAd2RZymQygX6/L1erVQt+9ZPWb+CDwcCC2zXGJaewl/DhcHhK3DVj+KfKZrMWvFarcYNLomAv4aPRSFZVlTlcSPA5fDweW/BoNIqFnKV53JvncjkLns/n/cLdS+92O7RYLLgsKfv9/t8XlDn4eDyiw+HA9Jyz2eyt0+kY2+3WFC5hluej0Ha7zQQq9PPwdDq1Et1sNsx/nFBgCqWJ8oAK1aUptNVqcYWewE4nahfU0YQnk4ntUEfGMIU2m01HoLaCKbTRaDgKtaVLk9tBYaBcE/6Artdr4RZ5TB6/dC+9iIe/WgAMYADDpAUJAxjAAAYwgGFZgoS/AtNNTF7Z2bL0BYPBV3Jw5xFwwWcYxgtBP5OkE8i9G7aWGOOCruvauwADALMLMEbKf4SdAAAAAElFTkSuQmCC)"})):
|
||||
f?(c=f,d=k("embed","",d),d.src="http://www.youtube.com/v/"+/v[=\/](\w*)|\/(\w+)$|^(\w+)$/i.exec(c).slice(1).join(""),d.type="application/x-shockwave-flash",d.width=n,d.height=q):b()}}}(jwplayer),function(d){var a=d.utils,h=d.events,g={};(d.embed.flash=function(c,b,e,k,l){function m(a,b,c){var e=document.createElement("param");e.setAttribute("name",b);e.setAttribute("value",c);a.appendChild(e)}function n(a,b,c){return function(){try{c&&document.getElementById(l.id+"_wrapper").appendChild(b);var e=
|
||||
document.getElementById(l.id).getPluginConfig("display");"function"==typeof a.resize&&a.resize(e.width,e.height);b.style.left=e.x;b.style.top=e.h}catch(d){}}}function q(b){if(!b)return{};var c={},e=[],d;for(d in b){var f=a.getPluginName(d),g=b[d];e.push(d);for(var k in g)c[f+"."+k]=g[k]}c.plugins=e.join(",");return c}var j=new d.events.eventdispatcher,f=a.flashVersion();a.extend(this,j);this.embed=function(){e.id=l.id;if(10>f)return j.sendEvent(h.ERROR,{message:"Flash version must be 10.0 or greater"}),
|
||||
!1;var d,p=a.extend({},e);c.id+"_wrapper"==c.parentNode.id?document.getElementById(c.id+"_wrapper"):(d=document.createElement("div"),d.id=c.id+"_wrapper",d.style.position="relative",d.style.width=a.styleDimension(p.width),d.style.height=a.styleDimension(p.height),c.parentNode.replaceChild(d,c),d.appendChild(c));d=k.setupPlugins(l,p,n);0<d.length?a.extend(p,q(d.plugins)):delete p.plugins;"undefined"!=typeof p["dock.position"]&&"false"==p["dock.position"].toString().toLowerCase()&&(p.dock=p["dock.position"],
|
||||
delete p["dock.position"]);d=p.wmode?p.wmode:p.height&&40>=p.height?"transparent":"opaque";for(var s="height width modes events primary base fallback volume".split(" "),u=0;u<s.length;u++)delete p[s[u]];var s=a.getCookies(),t;for(t in s)"undefined"==typeof p[t]&&(p[t]=s[t]);t=window.location.pathname.split("/");t.splice(t.length-1,1);t=t.join("/");p.base=t+"/";g[c.id]=p;a.isIE()?(p='\x3cobject classid\x3d"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" " width\x3d"100%" height\x3d"100%" id\x3d"'+c.id+
|
||||
'" name\x3d"'+c.id+'" tabindex\x3d0""\x3e',p+='\x3cparam name\x3d"movie" value\x3d"'+b.src+'"\x3e',p+='\x3cparam name\x3d"allowfullscreen" value\x3d"true"\x3e\x3cparam name\x3d"allowscriptaccess" value\x3d"always"\x3e',p+='\x3cparam name\x3d"seamlesstabbing" value\x3d"true"\x3e',p+='\x3cparam name\x3d"wmode" value\x3d"'+d+'"\x3e',p+='\x3cparam name\x3d"bgcolor" value\x3d"#000000"\x3e',p+="\x3c/object\x3e",c.outerHTML=p,p=document.getElementById(c.id)):(p=document.createElement("object"),p.setAttribute("type",
|
||||
"application/x-shockwave-flash"),p.setAttribute("data",b.src),p.setAttribute("width","100%"),p.setAttribute("height","100%"),p.setAttribute("bgcolor","#000000"),p.setAttribute("id",c.id),p.setAttribute("name",c.id),p.setAttribute("tabindex",0),m(p,"allowfullscreen","true"),m(p,"allowscriptaccess","always"),m(p,"seamlesstabbing","true"),m(p,"wmode",d),c.parentNode.replaceChild(p,c));l.container=p;l.setPlayer(p,"flash")};this.supportsConfig=function(){if(f)if(e){if("string"==a.typeOf(e.playlist))return!0;
|
||||
try{var b=e.playlist[0].sources;if("undefined"==typeof b)return!0;for(var c=0;c<b.length;c++){var d;if(d=b[c].file){var g=b[c].file,k=b[c].type;if(a.isYouTube(g)||a.isRtmp(g,k)||"hls"==k)d=!0;else{var h=a.extensionmap[k?k:a.extension(g)];d=!h?!1:!!h.flash}}if(d)return!0}}catch(j){}}else return!0;return!1}}).getVars=function(a){return g[a]}}(jwplayer),function(d){var a=d.utils,h=a.extensionmap,g=d.events;d.embed.html5=function(c,b,e,k,l){function m(a,b,d){return function(){try{var e=document.querySelector("#"+
|
||||
c.id+" .jwmain");d&&e.appendChild(b);"function"==typeof a.resize&&(a.resize(e.clientWidth,e.clientHeight),setTimeout(function(){a.resize(e.clientWidth,e.clientHeight)},400));b.left=e.style.left;b.top=e.style.top}catch(g){}}}function n(a){q.sendEvent(a.type,{message:"HTML5 player not found"})}var q=this,j=new g.eventdispatcher;a.extend(q,j);q.embed=function(){if(d.html5){k.setupPlugins(l,e,m);c.innerHTML="";var f=d.utils.extend({},e);delete f.volume;f=new d.html5.player(f);l.container=document.getElementById(l.id);
|
||||
l.setPlayer(f,"html5")}else f=new a.scriptloader(b.src),f.addEventListener(g.ERROR,n),f.addEventListener(g.COMPLETE,q.embed),f.load()};q.supportsConfig=function(){if(d.vid.canPlayType)try{if("string"==a.typeOf(e.playlist))return!0;for(var b=e.playlist[0].sources,c=0;c<b.length;c++){var g;var k=b[c].file,j=b[c].type;if(null!==navigator.userAgent.match(/BlackBerry/i)||a.isAndroid()&&("m3u"==a.extension(k)||"m3u8"==a.extension(k))||a.isRtmp(k,j))g=!1;else{var l=h[j?j:a.extension(k)],m;if(!l||l.flash&&
|
||||
!l.html5)m=!1;else{var n=l.html5,q=d.vid;if(n)try{m=q.canPlayType(n)?!0:!1}catch(z){m=!1}else m=!0}g=m}if(g)return!0}}catch(A){}return!1}}}(jwplayer),function(d){var a=d.embed,h=d.utils,g=h.extend(function(c){var b=h.repo(),e=c.config,g=e.plugins,l=e.analytics,m=b+"jwpsrv.js",n=b+"sharing.js",q=b+"related.js",j=b+"gapro.js",f=(new d.utils.key(d.key)).edition(),g=g?g:{};"ads"==f&&e.advertising&&(e.advertising.client.match(".js$|.swf$")?g[e.advertising.client]=e.advertising:g[b+e.advertising.client+
|
||||
("flash"==e.primary?".swf":".js")]=e.advertising);delete e.advertising;e.key=d.key;e.analytics&&(e.analytics.client&&e.analytics.client.match(".js$|.swf$"))&&(m=e.analytics.client);delete e.analytics;if("free"==f||!l||!1!==l.enabled)g[m]=l?l:{};delete g.sharing;delete g.related;if("premium"==f||"ads"==f)e.sharing&&(e.sharing.client&&e.sharing.client.match(".js$|.swf$")&&(n=e.sharing.client),g[n]=e.sharing),e.related&&(e.related.client&&e.related.client.match(".js$|.swf$")&&(q=e.related.client),g[q]=
|
||||
e.related),e.ga&&(e.ga.client&&e.ga.client.match(".js$|.swf$")&&(j=e.ga.client),g[j]=e.ga),e.skin&&(e.skin=e.skin.replace(/^(beelden|bekle|five|glow|modieus|roundster|stormtrooper|vapor)$/i,h.repo()+"skins/$1.xml").toLowerCase());e.plugins=g;return new a(c)},a);d.embed=g}(jwplayer),function(d){var a=[],h=d.utils,g=d.events,c=g.state,b=document,e=d.api=function(a){function l(a,b){return function(c){return b(a,c)}}function m(a,b){p[a]||(p[a]=[],q(g.JWPLAYER_PLAYER_STATE,function(b){var c=b.newstate;
|
||||
b=b.oldstate;if(c==a){var e=p[c];if(e)for(var d=0;d<e.length;d++)"function"==typeof e[d]&&e[d].call(this,{oldstate:b,newstate:c})}}));p[a].push(b);return f}function n(a,b){try{a.jwAddEventListener(b,'function(dat) { jwplayer("'+f.id+'").dispatchEvent("'+b+'", dat); }')}catch(c){h.log("Could not add internal listener")}}function q(a,b){r[a]||(r[a]=[],s&&u&&n(s,a));r[a].push(b);return f}function j(){if(u){for(var a=arguments[0],b=[],c=1;c<arguments.length;c++)b.push(arguments[c]);if("undefined"!=typeof s&&
|
||||
"function"==typeof s[a])switch(b.length){case 4:return s[a](b[0],b[1],b[2],b[3]);case 3:return s[a](b[0],b[1],b[2]);case 2:return s[a](b[0],b[1]);case 1:return s[a](b[0]);default:return s[a]()}return null}t.push(arguments)}var f=this,r={},p={},s=void 0,u=!1,t=[],w=void 0,x={},y={};f.container=a;f.id=a.id;f.getBuffer=function(){return j("jwGetBuffer")};f.getContainer=function(){return f.container};f.addButton=function(a,b,c,e){try{y[e]=c,j("jwDockAddButton",a,b,"jwplayer('"+f.id+"').callback('"+e+
|
||||
"')",e)}catch(d){h.log("Could not add dock button"+d.message)}};f.removeButton=function(a){j("jwDockRemoveButton",a)};f.callback=function(a){if(y[a])y[a]()};f.forceState=function(a){j("jwForceState",a);return f};f.releaseState=function(){return j("jwReleaseState")};f.getDuration=function(){return j("jwGetDuration")};f.getFullscreen=function(){return j("jwGetFullscreen")};f.getStretching=function(){return j("jwGetStretching")};f.getHeight=function(){return j("jwGetHeight")};f.getLockState=function(){return j("jwGetLockState")};
|
||||
f.getMeta=function(){return f.getItemMeta()};f.getMute=function(){return j("jwGetMute")};f.getPlaylist=function(){var a=j("jwGetPlaylist");"flash"==f.renderingMode&&h.deepReplaceKeyName(a,["__dot__","__spc__","__dsh__","__default__"],["."," ","-","default"]);return a};f.getPlaylistItem=function(a){h.exists(a)||(a=f.getCurrentItem());return f.getPlaylist()[a]};f.getPosition=function(){return j("jwGetPosition")};f.getRenderingMode=function(){return f.renderingMode};f.getState=function(){return j("jwGetState")};
|
||||
f.getVolume=function(){return j("jwGetVolume")};f.getWidth=function(){return j("jwGetWidth")};f.setFullscreen=function(a){h.exists(a)?j("jwSetFullscreen",a):j("jwSetFullscreen",!j("jwGetFullscreen"));return f};f.setStretching=function(a){j("jwSetStretching",a);return f};f.setMute=function(a){h.exists(a)?j("jwSetMute",a):j("jwSetMute",!j("jwGetMute"));return f};f.lock=function(){return f};f.unlock=function(){return f};f.load=function(a){j("jwLoad",a);return f};f.playlistItem=function(a){j("jwPlaylistItem",
|
||||
parseInt(a));return f};f.playlistPrev=function(){j("jwPlaylistPrev");return f};f.playlistNext=function(){j("jwPlaylistNext");return f};f.resize=function(a,c){if("flash"!=f.renderingMode)j("jwResize",a,c);else{var e=b.getElementById(f.id+"_wrapper");e&&(e.style.width=h.styleDimension(a),e.style.height=h.styleDimension(c))}return f};f.play=function(a){"undefined"==typeof a?(a=f.getState(),a==c.PLAYING||a==c.BUFFERING?j("jwPause"):j("jwPlay")):j("jwPlay",a);return f};f.pause=function(a){"undefined"==
|
||||
typeof a?(a=f.getState(),a==c.PLAYING||a==c.BUFFERING?j("jwPause"):j("jwPlay")):j("jwPause",a);return f};f.stop=function(){j("jwStop");return f};f.seek=function(a){j("jwSeek",a);return f};f.setVolume=function(a){j("jwSetVolume",a);return f};f.loadInstream=function(a,b){return w=new e.instream(this,s,a,b)};f.getQualityLevels=function(){return j("jwGetQualityLevels")};f.getCurrentQuality=function(){return j("jwGetCurrentQuality")};f.setCurrentQuality=function(a){j("jwSetCurrentQuality",a)};f.getCaptionsList=
|
||||
function(){return j("jwGetCaptionsList")};f.getCurrentCaptions=function(){return j("jwGetCurrentCaptions")};f.setCurrentCaptions=function(a){j("jwSetCurrentCaptions",a)};f.getControls=function(){return j("jwGetControls")};f.getSafeRegion=function(){return j("jwGetSafeRegion")};f.setControls=function(a){j("jwSetControls",a)};f.destroyPlayer=function(){j("jwPlayerDestroy")};var z={onBufferChange:g.JWPLAYER_MEDIA_BUFFER,onBufferFull:g.JWPLAYER_MEDIA_BUFFER_FULL,onError:g.JWPLAYER_ERROR,onFullscreen:g.JWPLAYER_FULLSCREEN,
|
||||
onMeta:g.JWPLAYER_MEDIA_META,onMute:g.JWPLAYER_MEDIA_MUTE,onPlaylist:g.JWPLAYER_PLAYLIST_LOADED,onPlaylistItem:g.JWPLAYER_PLAYLIST_ITEM,onPlaylistComplete:g.JWPLAYER_PLAYLIST_COMPLETE,onReady:g.API_READY,onResize:g.JWPLAYER_RESIZE,onComplete:g.JWPLAYER_MEDIA_COMPLETE,onSeek:g.JWPLAYER_MEDIA_SEEK,onTime:g.JWPLAYER_MEDIA_TIME,onVolume:g.JWPLAYER_MEDIA_VOLUME,onBeforePlay:g.JWPLAYER_MEDIA_BEFOREPLAY,onBeforeComplete:g.JWPLAYER_MEDIA_BEFORECOMPLETE,onDisplayClick:g.JWPLAYER_DISPLAY_CLICK,onControls:g.JWPLAYER_CONTROLS,
|
||||
onQualityLevels:g.JWPLAYER_MEDIA_LEVELS,onQualityChange:g.JWPLAYER_MEDIA_LEVEL_CHANGED,onCaptionsList:g.JWPLAYER_CAPTIONS_LIST,onCaptionsChange:g.JWPLAYER_CAPTIONS_CHANGED};h.foreach(z,function(a){f[a]=l(z[a],q)});var A={onBuffer:c.BUFFERING,onPause:c.PAUSED,onPlay:c.PLAYING,onIdle:c.IDLE};h.foreach(A,function(a){f[a]=l(A[a],m)});f.remove=function(){if(!u)throw"Cannot call remove() before player is ready";t=[];e.destroyPlayer(this.id)};f.setup=function(a){if(d.embed){var c=b.getElementById(f.id);
|
||||
c&&(a.fallbackDiv=c);c=f;t=[];e.destroyPlayer(c.id);c=d(f.id);c.config=a;return new d.embed(c)}return f};f.registerPlugin=function(a,b,c,e){d.plugins.registerPlugin(a,b,c,e)};f.setPlayer=function(a,b){s=a;f.renderingMode=b};f.detachMedia=function(){if("html5"==f.renderingMode)return j("jwDetachMedia")};f.attachMedia=function(a){if("html5"==f.renderingMode)return j("jwAttachMedia",a)};f.dispatchEvent=function(a,b){if(r[a])for(var c=h.translateEventResponse(a,b),e=0;e<r[a].length;e++)if("function"==
|
||||
typeof r[a][e])try{r[a][e].call(this,c)}catch(d){h.log("There was an error calling back an event handler")}};f.dispatchInstreamEvent=function(a){w&&w.dispatchEvent(a,arguments)};f.callInternal=j;f.playerReady=function(a){u=!0;s||f.setPlayer(b.getElementById(a.id));f.container=b.getElementById(f.id);h.foreach(r,function(a){n(s,a)});q(g.JWPLAYER_PLAYLIST_ITEM,function(){x={}});q(g.JWPLAYER_MEDIA_META,function(a){h.extend(x,a.metadata)});for(f.dispatchEvent(g.API_READY);0<t.length;)j.apply(this,t.shift())};
|
||||
f.getItemMeta=function(){return x};f.getCurrentItem=function(){return j("jwGetPlaylistIndex")};return f};e.selectPlayer=function(c){var d;h.exists(c)||(c=0);c.nodeType?d=c:"string"==typeof c&&(d=b.getElementById(c));return d?(c=e.playerById(d.id))?c:e.addPlayer(new e(d)):"number"==typeof c?a[c]:null};e.playerById=function(b){for(var c=0;c<a.length;c++)if(a[c].id==b)return a[c];return null};e.addPlayer=function(b){for(var c=0;c<a.length;c++)if(a[c]==b)return b;a.push(b);return b};e.destroyPlayer=function(c){for(var e=
|
||||
-1,d,g=0;g<a.length;g++)a[g].id==c&&(e=g,d=a[g]);0<=e&&(c=d.id,g=b.getElementById(c+("flash"==d.renderingMode?"_wrapper":"")),h.clearCss&&h.clearCss("#"+c),g&&("html5"==d.renderingMode&&d.destroyPlayer(),d=b.createElement("div"),d.id=c,g.parentNode.replaceChild(d,g)),a.splice(e,1));return null};d.playerReady=function(a){var b=d.api.playerById(a.id);b?b.playerReady(a):d.api.selectPlayer(a.id).playerReady(a)}}(jwplayer),function(d){d.api.instream=function(a,d,g,c){this.play=function(a){d.jwInstreamPlay(a)};
|
||||
this.pause=function(a){d.jwInstreamPause(a)};this.destroy=function(){d.jwInstreamDestroy()};a.callInternal("jwLoadInstream",g,c?c:{})}}(jwplayer),function(d){var a=d.api,h=a.selectPlayer;a.selectPlayer=function(a){return(a=h(a))?a:{registerPlugin:function(a,b,e){d.plugins.registerPlugin(a,b,e)}}}}(jwplayer));
|
||||
BIN
ngx_http_flv_module/test/www/jwplayer_old/player.swf
Normal file
BIN
ngx_http_flv_module/test/www/jwplayer_old/player.swf
Normal file
Binary file not shown.
5
ngx_http_flv_module/test/www/jwplayer_old/swfobject.js
Normal file
5
ngx_http_flv_module/test/www/jwplayer_old/swfobject.js
Normal file
File diff suppressed because one or more lines are too long
39
ngx_http_flv_module/test/www/record.html
Normal file
39
ngx_http_flv_module/test/www/record.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<a href="index.html">Play</a> | <b>Record</b>
|
||||
<br/>
|
||||
<script src="jwplayer_old/swfobject.js"></script>
|
||||
<script type="text/javascript">
|
||||
var flashvars =
|
||||
{
|
||||
'streamer': 'rtmp://localhost/myapp',
|
||||
'file': 'mystream',
|
||||
'type': 'camera',
|
||||
'controlbar': 'bottom',
|
||||
'stretching': 'none',
|
||||
'frontcolor': '86C29D', // text & icons (green)
|
||||
'backcolor': '849BC1', // playlist background (blue)
|
||||
'lightcolor': 'C286BA', // selected text/track highlight (pink)
|
||||
'screencolor': 'FFFFFF', // screen background (black)
|
||||
'id': 'playerID',
|
||||
'autostart': 'true'
|
||||
};
|
||||
|
||||
var params =
|
||||
{
|
||||
'allowfullscreen': 'true',
|
||||
'allowscriptaccess': 'always',
|
||||
'bgcolor': '#FFFFFF'
|
||||
};
|
||||
|
||||
var attributes =
|
||||
{
|
||||
'id': 'playerID',
|
||||
'name': 'playerID'
|
||||
};
|
||||
swfobject.embedSWF('jwplayer_old/player.swf', 'player', '320', '260', '9.0.124', false, flashvars, params, attributes);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="playercontainer" class="playercontainer"><a id="player" class="player" href="http://get.adobe.com/flashplayer/">Get the Adobe Flash Player to see this video.</a></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user