Addition Features: Custom Homepage and WHEP player
1. DVR record apply support regex, for usage: please enter
config file -dvr_apply and start regex with /^xxx$/
2. Add custom homepage for player, enter the url
localhost:8080/players/homepage.html to access
3. Add WHEP player, enter the url localhost:8080/players/whep_itp.html
4. Convert Files Format from CRLF to LF (Windows to Linux)
* Known Issues:
1. There is a unsmooth swup between hompage and player page, Swup.js
will be used to fix this issue in the future.
This commit is contained in:
parent
64bbb5adcc
commit
d887d2b867
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -1,14 +1,13 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
title: ""
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
!!! Before submitting a new bug report, please ensure you have searched for any existing bugs. Duplicate issues or
|
||||
questions that are overly simple or already addressed in the documentation will be removed without any
|
||||
!!! Before submitting a new bug report, please ensure you have searched for any existing bugs. Duplicate issues or
|
||||
questions that are overly simple or already addressed in the documentation will be removed without any
|
||||
response.
|
||||
|
||||
**Describe the bug**
|
||||
|
|
@ -19,6 +18,7 @@ Desribe your SRS Server version here.
|
|||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
|
|
|
|||
5
.github/ISSUE_TEMPLATE/feature_request.md
vendored
5
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -1,10 +1,9 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
title: ""
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
!!! Before submitting a new feature request, please ensure you have searched for any existing features. Duplicate issues or questions that are overly simple or already addressed in the documentation will be removed without any response.
|
||||
|
|
|
|||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'cpp' ]
|
||||
language: ["cpp"]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
|
|
|||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
|
|
@ -434,35 +434,35 @@ jobs:
|
|||
name: Release ${{ env.SRS_TAG }}
|
||||
body: |
|
||||
If you would like to support SRS, please consider contributing to our [OpenCollective](https://opencollective.com/srs-server).
|
||||
|
||||
|
||||
[${{ github.sha }}](https://github.com/ossrs/srs/commit/${{ github.sha }})
|
||||
${{ github.event.head_commit.message }}
|
||||
|
||||
|
||||
## Resource
|
||||
* Source: ${{ env.SRS_SOURCE_MD5 }} [${{ env.SRS_SOURCE_TAR }}](https://github.com/ossrs/srs/releases/download/${{ env.SRS_TAG }}/${{ env.SRS_SOURCE_TAR }})
|
||||
* Binary: ${{ env.SRS_PACKAGE_MD5 }} [${{ env.SRS_PACKAGE_ZIP }}](https://github.com/ossrs/srs/releases/download/${{ env.SRS_TAG }}/${{ env.SRS_PACKAGE_ZIP }})
|
||||
* Binary: ${{ env.SRS_CYGWIN_MD5 }} [${{ env.SRS_CYGWIN_TAR }}](https://github.com/ossrs/srs/releases/download/${{ env.SRS_TAG }}/${{ env.SRS_CYGWIN_TAR }})
|
||||
|
||||
|
||||
## Resource Mirror: gitee.com
|
||||
* Source: ${{ env.SRS_SOURCE_MD5 }} [${{ env.SRS_SOURCE_TAR }}](https://gitee.com/ossrs/srs/releases/download/${{ env.SRS_TAG }}/${{ env.SRS_SOURCE_TAR }})
|
||||
* Binary: ${{ env.SRS_PACKAGE_MD5 }} [${{ env.SRS_PACKAGE_ZIP }}](https://gitee.com/ossrs/srs/releases/download/${{ env.SRS_TAG }}/${{ env.SRS_PACKAGE_ZIP }})
|
||||
* Binary: ${{ env.SRS_CYGWIN_MD5 }} [${{ env.SRS_CYGWIN_TAR }}](https://gitee.com/ossrs/srs/releases/download/${{ env.SRS_TAG }}/${{ env.SRS_CYGWIN_TAR }})
|
||||
|
||||
|
||||
## Docker
|
||||
* [docker pull ossrs/srs:${{ env.SRS_MAJOR }}](https://ossrs.io/lts/en-us/docs/v7/doc/getting-started)
|
||||
* [docker pull ossrs/srs:${{ env.SRS_TAG }}](https://ossrs.io/lts/en-us/docs/v7/doc/getting-started)
|
||||
* [docker pull ossrs/srs:${{ env.SRS_XYZ }}](https://ossrs.io/lts/en-us/docs/v7/doc/getting-started)
|
||||
|
||||
|
||||
## Docker Mirror: aliyun.com
|
||||
* [docker pull registry.cn-hangzhou.aliyuncs.com/ossrs/srs:${{ env.SRS_MAJOR }}](https://ossrs.net/lts/zh-cn/docs/v7/doc/getting-started)
|
||||
* [docker pull registry.cn-hangzhou.aliyuncs.com/ossrs/srs:${{ env.SRS_TAG }}](https://ossrs.net/lts/zh-cn/docs/v7/doc/getting-started)
|
||||
* [docker pull registry.cn-hangzhou.aliyuncs.com/ossrs/srs:${{ env.SRS_XYZ }}](https://ossrs.net/lts/zh-cn/docs/v7/doc/getting-started)
|
||||
|
||||
|
||||
## Doc: ossrs.io
|
||||
* [Getting Started](https://ossrs.io/lts/en-us/docs/v7/doc/getting-started)
|
||||
* [Wiki home](https://ossrs.io/lts/en-us/docs/v7/doc/introduction)
|
||||
* [FAQ](https://ossrs.io/lts/en-us/faq), [Features](https://github.com/ossrs/srs/blob/${{ github.sha }}/trunk/doc/Features.md#features) or [ChangeLogs](https://github.com/ossrs/srs/blob/${{ github.sha }}/trunk/doc/CHANGELOG.md#changelog)
|
||||
|
||||
|
||||
## Doc: ossrs.net
|
||||
* [快速入门](https://ossrs.net/lts/zh-cn/docs/v7/doc/getting-started)
|
||||
* [中文Wiki首页](https://ossrs.net/lts/zh-cn/docs/v7/doc/introduction)
|
||||
|
|
|
|||
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
|
|
@ -325,4 +325,3 @@ jobs:
|
|||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- run: echo 'All done'
|
||||
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -42,4 +42,3 @@ cmake-build-debug
|
|||
/trunk/ide/srs_clion/srs
|
||||
/trunk/ide/srs_clion/Testing/
|
||||
/trunk/ide/vscode-build
|
||||
|
||||
|
|
|
|||
2
.vscode/README.md
vendored
2
.vscode/README.md
vendored
|
|
@ -15,7 +15,7 @@ Run commmand `> CMake: Configure` to configure the project.
|
|||
|
||||
> Note: You can press `Ctrl+R`, then type `CMake: Configure` then select `Clang` as the toolchain.
|
||||
|
||||
> Note: The `settings.json` is used to configure the cmake. It will use `${workspaceFolder}/trunk/ide/srs_clion/CMakeLists.txt`
|
||||
> Note: The `settings.json` is used to configure the cmake. It will use `${workspaceFolder}/trunk/ide/srs_clion/CMakeLists.txt`
|
||||
> and `${workspaceFolder}/trunk/ide/vscode-build` as the source file and build directory.
|
||||
|
||||
Click the `Run > Run Without Debugging` button to start the server.
|
||||
|
|
|
|||
68
.vscode/launch.json
vendored
68
.vscode/launch.json
vendored
|
|
@ -1,36 +1,36 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch SRS",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/trunk/ide/vscode-build/srs",
|
||||
"args": ["-c", "conf/console.conf"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/trunk",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "lldb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"name": "Launch SRS",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/trunk/ide/vscode-build/srs",
|
||||
"args": ["-c", "conf/console.conf"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/trunk",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "lldb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"preLaunchTask": "build",
|
||||
"logging": {
|
||||
"engineLogging": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Launch srs-proxy",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"cwd": "${workspaceFolder}/proxy",
|
||||
"program": "${workspaceFolder}/proxy"
|
||||
}
|
||||
]
|
||||
}
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"preLaunchTask": "build",
|
||||
"logging": {
|
||||
"engineLogging": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Launch srs-proxy",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"cwd": "${workspaceFolder}/proxy",
|
||||
"program": "${workspaceFolder}/proxy"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
43
.vscode/settings.json
vendored
43
.vscode/settings.json
vendored
|
|
@ -1,23 +1,24 @@
|
|||
{
|
||||
"cmake.sourceDirectory": "${workspaceFolder}/trunk/ide/srs_clion",
|
||||
"cmake.buildDirectory": "${workspaceFolder}/trunk/ide/vscode-build",
|
||||
"cmake.configureOnOpen": false,
|
||||
"files.associations": {
|
||||
"vector": "cpp",
|
||||
"__hash_table": "cpp",
|
||||
"__split_buffer": "cpp",
|
||||
"__tree": "cpp",
|
||||
"array": "cpp",
|
||||
"bitset": "cpp",
|
||||
"deque": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"list": "cpp",
|
||||
"map": "cpp",
|
||||
"queue": "cpp",
|
||||
"set": "cpp",
|
||||
"stack": "cpp",
|
||||
"string": "cpp",
|
||||
"string_view": "cpp",
|
||||
"unordered_map": "cpp"
|
||||
}
|
||||
"cmake.sourceDirectory": "${workspaceFolder}/trunk/ide/srs_clion",
|
||||
"cmake.buildDirectory": "${workspaceFolder}/trunk/ide/vscode-build",
|
||||
"cmake.configureOnOpen": false,
|
||||
"files.associations": {
|
||||
"vector": "cpp",
|
||||
"__hash_table": "cpp",
|
||||
"__split_buffer": "cpp",
|
||||
"__tree": "cpp",
|
||||
"array": "cpp",
|
||||
"bitset": "cpp",
|
||||
"deque": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"list": "cpp",
|
||||
"map": "cpp",
|
||||
"queue": "cpp",
|
||||
"set": "cpp",
|
||||
"stack": "cpp",
|
||||
"string": "cpp",
|
||||
"string_view": "cpp",
|
||||
"unordered_map": "cpp"
|
||||
},
|
||||
"files.eol": "\n"
|
||||
}
|
||||
|
|
|
|||
31
.vscode/tasks.json
vendored
31
.vscode/tasks.json
vendored
|
|
@ -1,17 +1,16 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"type": "shell",
|
||||
"command": "cmake --build ${workspaceFolder}/trunk/ide/vscode-build",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": ["$gcc"],
|
||||
"detail": "Build SRS by cmake."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"type": "shell",
|
||||
"command": "cmake --build ${workspaceFolder}/trunk/ide/vscode-build",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": ["$gcc"],
|
||||
"detail": "Build SRS by cmake."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
Welome to contribute to SRS!
|
||||
|
||||
1. Please start from fixing some [Issues: good first issue](https://github.com/ossrs/srs/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
|
||||
1. Please [setup your email](https://ossrs.io/lts/en-us/how-to-file-pr#setup-your-email) before contributing, this is important.
|
||||
1. Then follow the [guide](https://ossrs.io/lts/en-us/how-to-file-pr) or [贡献代码](https://ossrs.net/lts/zh-cn/how-to-file-pr) to file a PR.
|
||||
1. We will review your PR ASAP. Since our bandwidth is limited, we appreciate your patience.
|
||||
|
||||
If achieve [50 commits](https://github.com/ossrs/srs/graphs/contributors), you will be [TOC of SRS](https://github.com/ossrs/srs/blob/develop/trunk/AUTHORS.md#toc).
|
||||
|
||||
* The update of this file MUST be approved by [Winlin](https://github.com/winlinvip) and 3+ [TOC](https://github.com/ossrs/srs/blob/develop/trunk/AUTHORS.md#toc).
|
||||
* Each [PullRequest](https://github.com/ossrs/srs/pulls) MUST be approved by 2+ [TOC](https://github.com/ossrs/srs/blob/develop/trunk/AUTHORS.md#toc) or [Developers](https://github.com/ossrs/srs/blob/develop/trunk/AUTHORS.md#developers).
|
||||
* The name of TOC will be listed at [README](https://github.com/ossrs/srs#authors) forever.
|
||||
|
||||
All [contributors](https://github.com/ossrs/srs/blob/develop/trunk/AUTHORS.md#contributors) are listed in SRS authors.
|
||||
|
||||
274
README.md
274
README.md
|
|
@ -1,35 +1,11 @@
|
|||
# SRS(Simple Realtime Server)
|
||||
# NPL ITP Intranet Livestream Server
|
||||
|
||||

|
||||
[](https://github.com/ossrs/srs/actions?query=workflow%3ACodeQL+branch%3Adevelop)
|
||||
[](https://github.com/ossrs/srs/actions/workflows/release.yml?query=workflow%3ARelease)
|
||||
[](https://github.com/ossrs/srs/actions?query=workflow%3ATest+branch%3Adevelop)
|
||||
[](https://app.codecov.io/gh/ossrs/srs/tree/develop)
|
||||
[](https://ossrs.net/lts/zh-cn/contact#discussion)
|
||||
[](https://twitter.com/srs_server)
|
||||
[](https://www.youtube.com/@srs_server)
|
||||
[](https://discord.gg/yZ4BnPmHAd)
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fossrs%2Fsrs?ref=badge_small)
|
||||
[](https://stackoverflow.com/questions/tagged/simple-realtime-server)
|
||||
[](https://opencollective.com/srs-server)
|
||||
[](https://hub.docker.com/r/ossrs/srs/tags)
|
||||
> This is a fork of SRS Service with additional features provided that enhance the experience of livestream in NPL.
|
||||
> **COPYRIGHT (C) 2024-2025 Jason Yang Jiepeng, Jasper, HyperKNF, Hayden, NPL ITP Team, PLK, NPL**
|
||||
|
||||
SRS/6.0 ([Hang](https://ossrs.io/lts/en-us/product#release-60)) is a simple, high-efficiency, and real-time video server,
|
||||
supporting RTMP/WebRTC/HLS/HTTP-FLV/SRT/MPEG-DASH/GB28181, Linux/Windows/macOS, X86_64/ARMv7/AARCH64/M1/RISCV/LOONGARCH/MIPS,
|
||||
and essential [features](trunk/doc/Features.md#features).
|
||||
## Usage (Simple Version)
|
||||
|
||||
[](https://ossrs.net/wiki/images/SRS-SingleNode-4.0-hd.png)
|
||||
|
||||
> Note: For more details on the single-node architecture for SRS, please visit the following [link](https://www.figma.com/file/333POxVznQ8Wz1Rxlppn36/SRS-4.0-Server-Arch).
|
||||
|
||||
SRS is licenced under [MIT](https://github.com/ossrs/srs/blob/develop/LICENSE), and some third-party libraries are
|
||||
distributed under their [licenses](https://ossrs.io/lts/en-us/license).
|
||||
|
||||
<a name="product"></a> <a name="usage-docker"></a>
|
||||
|
||||
## Usage
|
||||
|
||||
Please check the Getting Started guide in [English](https://ossrs.io/lts/en-us/docs/v5/doc/getting-started)
|
||||
Please check the Getting Started guide in [English](https://ossrs.io/lts/en-us/docs/v5/doc/getting-started)
|
||||
or [Chinese](https://ossrs.net/lts/zh-cn/docs/v5/doc/getting-started). We highly recommend using SRS with docker:
|
||||
|
||||
```bash
|
||||
|
|
@ -48,158 +24,150 @@ ffmpeg -re -i ./doc/source.flv -c copy -f flv -y rtmp://localhost/live/livestrea
|
|||
|
||||
Alternatively, stream by [OBS](https://obsproject.com/download) using the following configuration:
|
||||
|
||||
* Service: `Custom`
|
||||
* Server: `rtmp://localhost/live`
|
||||
* Stream Key: `livestream`
|
||||
- Service: `Custom`
|
||||
- Server: `rtmp://localhost/live`
|
||||
- Stream Key: `livestream`
|
||||
|
||||
Play the following streams using media players:
|
||||
|
||||
* To play an RTMP stream with URL `rtmp://localhost/live/livestream` on [VLC player](https://www.videolan.org/), open the player, go to Media > Open Network Stream, enter the URL and click Play.
|
||||
* You can play HTTP-FLV stream URL [http://localhost:8080/live/livestream.flv](http://localhost:8080/players/srs_player.html?autostart=true&stream=livestream.flv) on a webpage using the srs-player, an HTML5-based player.
|
||||
* Use srs-player for playing HLS stream with URL [http://localhost:8080/live/livestream.m3u8](http://localhost:8080/players/srs_player.html?autostart=true&stream=livestream.m3u8).
|
||||
- To play an RTMP stream with URL `rtmp://localhost/live/livestream` on [VLC player](https://www.videolan.org/), open the player, go to Media > Open Network Stream, enter the URL and click Play.
|
||||
- You can play HTTP-FLV stream URL [http://localhost:8080/live/livestream.flv](http://localhost:8080/players/srs_player.html?autostart=true&stream=livestream.flv) on a webpage using the srs-player, an HTML5-based player.
|
||||
- Use srs-player for playing HLS stream with URL [http://localhost:8080/live/livestream.m3u8](http://localhost:8080/players/srs_player.html?autostart=true&stream=livestream.m3u8).
|
||||
|
||||
If you'd like to use WebRTC, convert RTMP to WebRTC, or convert WebRTC to RTMP, please check out
|
||||
the wiki documentation in either [English](https://ossrs.io/lts/en-us/docs/v5/doc/getting-started#webrtc) or
|
||||
If you'd like to use WebRTC, convert RTMP to WebRTC, or convert WebRTC to RTMP, please check out
|
||||
the wiki documentation in either [English](https://ossrs.io/lts/en-us/docs/v5/doc/getting-started#webrtc) or
|
||||
[Chinese](https://ossrs.net/lts/zh-cn/docs/v5/doc/getting-started#webrtc).
|
||||
|
||||
To learn more about RTMP, HLS, HTTP-FLV, SRT, MPEG-DASH, WebRTC protocols, clustering,
|
||||
HTTP API, DVR, and transcoding, please check the documents in [English](https://ossrs.io)
|
||||
To learn more about RTMP, HLS, HTTP-FLV, SRT, MPEG-DASH, WebRTC protocols, clustering,
|
||||
HTTP API, DVR, and transcoding, please check the documents in [English](https://ossrs.io)
|
||||
or [Chinese](https://ossrs.net).
|
||||
|
||||
## Sponsor
|
||||
## Complete User Guideline
|
||||
|
||||
Would you like additional assistance from us? By becoming a sponsor or backer of SRS, we can provide you
|
||||
with the support you need:
|
||||
### Features
|
||||
|
||||
* Backer: $5 per month, online text chat support through Discord.
|
||||
* Sponsor: $100 per month, online text chat plus online meeting support.
|
||||
Please read [FEATURES](trunk/doc/Features.md#features).
|
||||
|
||||
Please visit [OpenCollective](https://opencollective.com/srs-server) to become a backer or sponsor, and send
|
||||
us a direct message on [Discord](https://discord.gg/yZ4BnPmHAd). We are currently providing support to the
|
||||
developers listed below:
|
||||
### Changelog
|
||||
|
||||
[](https://opencollective.com/srs-server)
|
||||
Please read [CHANGELOG](trunk/doc/CHANGELOG.md#changelog).
|
||||
|
||||
At SRS, our goal is to create a free, open-source community that helps developers all over the world
|
||||
build high-quality streaming and RTC platforms for their businesses.
|
||||
### Performance
|
||||
|
||||
<a name="authors"></a>
|
||||
Please read [PERFORMANCE](trunk/doc/PERFORMANCE.md#performance).
|
||||
|
||||
## Contributing
|
||||
### Architecture
|
||||
|
||||
The [authors](trunk/AUTHORS.md#authors), [TOC(Technical Oversight Committee)](trunk/AUTHORS.md#toc),
|
||||
and [contributors](trunk/AUTHORS.md#contributors) are listed [here](trunk/AUTHORS.md). The TOC members
|
||||
Please read [ARCHITECTURE](trunk/doc/Architecture.md#architecture).
|
||||
|
||||
### Ports
|
||||
|
||||
Please read [PORTS](trunk/doc/Resources.md#ports).
|
||||
|
||||
### APIs
|
||||
|
||||
Please read [APIS](trunk/doc/Resources.md#apis).
|
||||
|
||||
### Mirrors
|
||||
|
||||
Please read [MIRRORS](trunk/doc/Resources.md#mirrors).
|
||||
|
||||
### Dockers
|
||||
|
||||
Please read [DOCKERS](trunk/doc/Dockers.md).
|
||||
|
||||
## SRS(Simple Realtime Server)
|
||||
|
||||
**Here is a short appendix introduce the official package of SRS Simple Realtime Server:**
|
||||
|
||||
SRS/6.0 is a simple, high-efficiency, and real-time video server,
|
||||
supporting RTMP/WebRTC/HLS/HTTP-FLV/SRT/MPEG-DASH/GB28181, Linux/Windows/macOS, X86_64/ARMv7/AARCH64/M1/RISCV/LOONGARCH/MIPS,
|
||||
and essential [features](trunk/doc/Features.md#features).
|
||||
|
||||
[](https://ossrs.net/wiki/images/SRS-SingleNode-4.0-hd.png)
|
||||
|
||||
> Note: For more details on the single-node architecture for SRS, please visit the following [link](https://www.figma.com/file/333POxVznQ8Wz1Rxlppn36/SRS-4.0-Server-Arch).
|
||||
|
||||
SRS is licenced under [MIT](https://github.com/ossrs/srs/blob/develop/LICENSE), and some third-party libraries are distributed under their [licenses](https://ossrs.io/lts/en-us/license).
|
||||
|
||||
### Contribution to official repo of SRS
|
||||
|
||||
The [authors](trunk/AUTHORS.md#authors), [TOC(Technical Oversight Committee)](trunk/AUTHORS.md#toc),
|
||||
and [contributors](trunk/AUTHORS.md#contributors) are listed [here](trunk/AUTHORS.md). The TOC members
|
||||
who made significant contributions and maintained parts of SRS are listed below:
|
||||
|
||||
* [Winlin](https://github.com/winlinvip): Founder of the project, focusing on ST and Issues/PR. Responsible for architecture and maintenance.
|
||||
* [ZhaoWenjie](https://github.com/wenjiegit): One of the earliest contributors, focusing on HDS and Windows. Has expertise in client technology.
|
||||
* [ShiWei](https://github.com/runner365): Specializes in SRT and H.265, maintaining SRT and FLV patches for FFmpeg. An expert in codecs and FFmpeg.
|
||||
* [XiaoZhihong](https://github.com/xiaozhihong): Concentrates on WebRTC/QUIC and SRT, with expertise in network QoS. Contributed to ARM on ST and was the original contributor for WebRTC.
|
||||
* [WuPengqiang](https://github.com/Bepartofyou): Focused on H.265, initially contributed to the FFmpeg module in SRS for transcoding AAC with OPUS for WebRTC.
|
||||
* [XiaLixin](https://github.com/xialixin): Specializes in GB28181, with expertise in live streaming and WebRTC.
|
||||
* [LiPeng](https://github.com/lipeng19811218): Concentrates on WebRTC and contributes to memory management and smart pointers.
|
||||
* [ChenGuanghua](https://github.com/chen-guanghua): Focused on WebRTC/QoS and introduced the Asan toolchain to SRS.
|
||||
* [ChenHaibo](https://github.com/duiniuluantanqin): Specializes in GB28181 and HTTP API, contributing to patches for FFmpeg with WHIP.
|
||||
* [ZhangJunqin](https://github.com/chundonglinlin): Focused on H.265, Prometheus Exporter, and API module.
|
||||
- [Winlin](https://github.com/winlinvip): Founder of the project, focusing on ST and Issues/PR. Responsible for architecture and maintenance.
|
||||
- [ZhaoWenjie](https://github.com/wenjiegit): One of the earliest contributors, focusing on HDS and Windows. Has expertise in client technology.
|
||||
- [ShiWei](https://github.com/runner365): Specializes in SRT and H.265, maintaining SRT and FLV patches for FFmpeg. An expert in codecs and FFmpeg.
|
||||
- [XiaoZhihong](https://github.com/xiaozhihong): Concentrates on WebRTC/QUIC and SRT, with expertise in network QoS. Contributed to ARM on ST and was the original contributor for WebRTC.
|
||||
- [WuPengqiang](https://github.com/Bepartofyou): Focused on H.265, initially contributed to the FFmpeg module in SRS for transcoding AAC with OPUS for WebRTC.
|
||||
- [XiaLixin](https://github.com/xialixin): Specializes in GB28181, with expertise in live streaming and WebRTC.
|
||||
- [LiPeng](https://github.com/lipeng19811218): Concentrates on WebRTC and contributes to memory management and smart pointers.
|
||||
- [ChenGuanghua](https://github.com/chen-guanghua): Focused on WebRTC/QoS and introduced the Asan toolchain to SRS.
|
||||
- [ChenHaibo](https://github.com/duiniuluantanqin): Specializes in GB28181 and HTTP API, contributing to patches for FFmpeg with WHIP.
|
||||
- [ZhangJunqin](https://github.com/chundonglinlin): Focused on H.265, Prometheus Exporter, and API module.
|
||||
|
||||
A huge `THANK YOU` goes out to:
|
||||
|
||||
* All the [contributors](trunk/AUTHORS.md#contributors) of SRS.
|
||||
* All the friends of SRS who gave [big support](https://ossrs.net/lts/zh-cn/product).
|
||||
* [Genes](http://sourceforge.net/users/genes), [Mabbott](http://sourceforge.net/users/mabbott), and [Michael Talyanksy](https://github.com/michaeltalyansky) for making and sharing [State Threads](https://github.com/ossrs/state-threads/tree/srs).
|
||||
- All the [contributors](trunk/AUTHORS.md#contributors) of SRS.
|
||||
- All the friends of SRS who gave [big support](https://ossrs.net/lts/zh-cn/product).
|
||||
- [Genes](http://sourceforge.net/users/genes), [Mabbott](http://sourceforge.net/users/mabbott), and [Michael Talyanksy](https://github.com/michaeltalyansky) for making and sharing [State Threads](https://github.com/ossrs/state-threads/tree/srs).
|
||||
|
||||
We're really thankful to everyone in the community for helping us find bugs and improve the project.
|
||||
We're really thankful to everyone in the community for helping us find bugs and improve the project.
|
||||
To stay in touch and keep helping our community, please check out this [guide](https://github.com/ossrs/srs/contribute).
|
||||
|
||||
## LICENSE
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fossrs%2Fsrs?ref=badge_small)
|
||||
NPL ITP Intranet Livestream Server is a fork of SRS and also livenced under [MIT](https://github.com/PLK-NPL-ITP/livestream_server/blob/develop/LICENSE)
|
||||
|
||||
SRS is licenced under [MIT](https://github.com/ossrs/srs/blob/develop/LICENSE), and some third-party libraries are
|
||||
distributed under their [licenses](https://ossrs.io/lts/en-us/license).
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fossrs%2Fsrs?ref=badge_large)
|
||||
SRS is licenced under [MIT](https://github.com/ossrs/srs/blob/develop/LICENSE), and some third-party libraries are distributed under their [licenses](https://ossrs.io/lts/en-us/license).
|
||||
|
||||
## Releases
|
||||
|
||||
* 2025-05-03, [Release v6.0-a2](https://github.com/ossrs/srs/releases/tag/v6.0-a2), v6.0-a2, 6.0 alpha2, v6.0.165, 169712 lines.
|
||||
* 2024-09-01, [Release v6.0-a1](https://github.com/ossrs/srs/releases/tag/v6.0-a1), v6.0-a1, 6.0 alpha1, v6.0.155, 169636 lines.
|
||||
* 2024-07-27, [Release v6.0-a0](https://github.com/ossrs/srs/releases/tag/v6.0-a0), v6.0-a0, 6.0 alpha0, v6.0.145, 169259 lines.
|
||||
* 2024-07-04, [Release v6.0-d6](https://github.com/ossrs/srs/releases/tag/v6.0-d6), v6.0-d6, 6.0 dev6, v6.0.134, 168904 lines.
|
||||
* 2024-06-15, [Release v6.0-d5](https://github.com/ossrs/srs/releases/tag/v6.0-d5), v6.0-d5, 6.0 dev5, v6.0.129, 168454 lines.
|
||||
* 2024-02-15, [Release v6.0-d4](https://github.com/ossrs/srs/releases/tag/v6.0-d4), v6.0-d4, 6.0 dev4, v6.0.113, 167695 lines.
|
||||
* 2023-11-19, [Release v6.0-d3](https://github.com/ossrs/srs/releases/tag/v6.0-d3), v6.0-d3, 6.0 dev3, v6.0.101, 167560 lines.
|
||||
* 2023-09-28, [Release v6.0-d2](https://github.com/ossrs/srs/releases/tag/v6.0-d2), v6.0-d2, 6.0 dev2, v6.0.85, 167509 lines.
|
||||
* 2023-08-31, [Release v6.0-d1](https://github.com/ossrs/srs/releases/tag/v6.0-d1), v6.0-d1, 6.0 dev1, v6.0.72, 167135 lines.
|
||||
* 2023-07-09, [Release v6.0-d0](https://github.com/ossrs/srs/releases/tag/v6.0-d0), v6.0-d0, 6.0 dev0, v6.0.59, 166739 lines.
|
||||
* 2024-06-15, [Release v5.0-r3](https://github.com/ossrs/srs/releases/tag/v5.0-r3), v5.0-r3, 5.0 release3, v5.0.213, 163585 lines.
|
||||
* 2024-04-03, [Release v5.0-r2](https://github.com/ossrs/srs/releases/tag/v5.0-r2), v5.0-r2, 5.0 release2, v5.0.210, 163515 lines.
|
||||
* 2024-02-15, [Release v5.0-r1](https://github.com/ossrs/srs/releases/tag/v5.0-r1), v5.0-r1, 5.0 release1, v5.0.208, 163441 lines.
|
||||
* 2023-12-30, [Release v5.0-r0](https://github.com/ossrs/srs/releases/tag/v5.0-r0), v5.0-r0, 5.0 release0, v5.0.205, 163363 lines.
|
||||
* 2023-11-19, [Release v5.0-b7](https://github.com/ossrs/srs/releases/tag/v5.0-b7), v5.0-b7, 5.0 beta7, v5.0.200, 163305 lines.
|
||||
* 2023-10-25, [Release v5.0-b6](https://github.com/ossrs/srs/releases/tag/v5.0-b6), v5.0-b6, 5.0 beta6, v5.0.195, 163303 lines.
|
||||
* 2023-09-28, [Release v5.0-b5](https://github.com/ossrs/srs/releases/tag/v5.0-b5), v5.0-b5, 5.0 beta5, v5.0.185, 163254 lines.
|
||||
* 2023-08-31, [Release v5.0-b4](https://github.com/ossrs/srs/releases/tag/v5.0-b4), v5.0-b4, 5.0 beta4, v5.0.176, 162919 lines.
|
||||
* 2023-08-02, [Release v5.0-b3](https://github.com/ossrs/srs/releases/tag/v5.0-b3), v5.0-b3, 5.0 beta3, v5.0.170, 162704 lines.
|
||||
* 2023-07-09, [Release v5.0-b2](https://github.com/ossrs/srs/releases/tag/v5.0-b2), v5.0-b2, 5.0 beta2, v5.0.166, 162520 lines.
|
||||
* 2023-06-11, [Release v5.0-b1](https://github.com/ossrs/srs/releases/tag/v5.0-b1), v5.0-b1, 5.0 beta1, v5.0.157, 162494 lines.
|
||||
* 2023-05-14, [Release v5.0-b0](https://github.com/ossrs/srs/releases/tag/v5.0-b0), v5.0-b0, 5.0 beta0, v5.0.155, 162600 lines.
|
||||
* 2023-03-23, [Release v5.0-a5](https://github.com/ossrs/srs/releases/tag/v5.0-a5), v5.0-a5, 5.0 alpha5, v5.0.148, 162066 lines.
|
||||
* 2023-02-12, [Release v5.0-a4](https://github.com/ossrs/srs/releases/tag/v5.0-a4), v5.0-a4, 5.0 alpha4, v5.0.141, 161897 lines.
|
||||
* 2023-01-02, [Release v5.0-a3](https://github.com/ossrs/srs/releases/tag/v5.0-a3), v5.0-a3, 5.0 alpha3, v5.0.128, 161327 lines.
|
||||
* 2022-12-18, [Release v5.0-a2](https://github.com/ossrs/srs/releases/tag/v5.0-a2), v5.0-a2, 5.0 alpha2, v5.0.112, 161233 lines.
|
||||
* 2022-12-01, [Release v5.0-a1](https://github.com/ossrs/srs/releases/tag/v5.0-a1), v5.0-a1, 5.0 alpha1, v5.0.100, 160817 lines.
|
||||
* 2022-11-25, [Release v5.0-a0](https://github.com/ossrs/srs/releases/tag/v5.0-a0), v5.0-a0, 5.0 alpha0, v5.0.98, 159813 lines.
|
||||
* 2022-11-22, Release [v4.0-r4](https://github.com/ossrs/srs/releases/tag/v4.0-r4), v4.0-r4, 4.0 release4, v4.0.268, 145482 lines.
|
||||
* 2022-09-16, Release [v4.0-r3](https://github.com/ossrs/srs/releases/tag/v4.0-r3), v4.0-r3, 4.0 release3, v4.0.265, 145328 lines.
|
||||
* 2022-08-24, Release [v4.0-r2](https://github.com/ossrs/srs/releases/tag/v4.0-r2), v4.0-r2, 4.0 release2, v4.0.257, 144890 lines.
|
||||
* 2022-06-29, Release [v4.0-r1](https://github.com/ossrs/srs/releases/tag/v4.0-r1), v4.0-r1, 4.0 release1, v4.0.253, 144680 lines.
|
||||
* 2022-06-11, Release [v4.0-r0](https://github.com/ossrs/srs/releases/tag/v4.0-r0), v4.0-r0, 4.0 release0, v4.0.252, 144680 lines.
|
||||
* 2020-06-27, [Release v3.0-r0](https://github.com/ossrs/srs/releases/tag/v3.0-r0), 3.0 release0, 3.0.141, 122674 lines.
|
||||
* 2020-02-02, [Release v3.0-b0](https://github.com/ossrs/srs/releases/tag/v3.0-b0), 3.0 beta0, 3.0.112, 121709 lines.
|
||||
* 2019-10-04, [Release v3.0-a0](https://github.com/ossrs/srs/releases/tag/v3.0-a0), 3.0 alpha0, 3.0.56, 107946 lines.
|
||||
* 2017-03-03, [Release v2.0-r0](https://github.com/ossrs/srs/releases/tag/v2.0-r0), 2.0 release0, 2.0.234, 86373 lines.
|
||||
* 2016-08-06, [Release v2.0-b0](https://github.com/ossrs/srs/releases/tag/v2.0-b0), 2.0 beta0, 2.0.210, 89704 lines.
|
||||
* 2015-08-23, [Release v2.0-a0](https://github.com/ossrs/srs/releases/tag/v2.0-a0), 2.0 alpha0, 2.0.185, 89022 lines.
|
||||
* 2014-12-05, [Release v1.0-r0](https://github.com/ossrs/srs/releases/tag/v1.0-r0), all bug fixed, 1.0.10, 59391 lines.
|
||||
* 2014-10-09, [Release v0.9.8](https://github.com/ossrs/srs/releases/tag/v0.9.8), all bug fixed, 1.0.0, 59316 lines.
|
||||
* 2014-04-07, [Release v0.9.1](https://github.com/ossrs/srs/releases/tag/v0.9.1), live streaming. 30000 lines.
|
||||
* 2013-10-23, [Release v0.1.0](https://github.com/ossrs/srs/releases/tag/v0.1.0), rtmp. 8287 lines.
|
||||
* 2013-10-17, Created.
|
||||
|
||||
## Features
|
||||
|
||||
Please read [FEATURES](trunk/doc/Features.md#features).
|
||||
|
||||
<a name="history"></a> <a name="change-logs"></a>
|
||||
## Changelog
|
||||
|
||||
Please read [CHANGELOG](trunk/doc/CHANGELOG.md#changelog).
|
||||
|
||||
## Performance
|
||||
|
||||
Please read [PERFORMANCE](trunk/doc/PERFORMANCE.md#performance).
|
||||
|
||||
## Architecture
|
||||
|
||||
Please read [ARCHITECTURE](trunk/doc/Architecture.md#architecture).
|
||||
|
||||
## Ports
|
||||
|
||||
Please read [PORTS](trunk/doc/Resources.md#ports).
|
||||
|
||||
## APIs
|
||||
|
||||
Please read [APIS](trunk/doc/Resources.md#apis).
|
||||
|
||||
## Mirrors
|
||||
|
||||
Please read [MIRRORS](trunk/doc/Resources.md#mirrors).
|
||||
|
||||
## Dockers
|
||||
|
||||
Please read [DOCKERS](trunk/doc/Dockers.md).
|
||||
|
||||
Beijing, 2013.10<br/>
|
||||
Winlin
|
||||
Bold text is the release from the contribution of NPL ITP Development Group!
|
||||
|
||||
- **2025-05-13, [Release v6.0.166.2b](), v6.**
|
||||
- 2025-05-03, [Release v6.0-a2](https://github.com/ossrs/srs/releases/tag/v6.0-a2), v6.0-a2, 6.0 alpha2, v6.0.165, 169712 lines.
|
||||
- 2024-09-01, [Release v6.0-a1](https://github.com/ossrs/srs/releases/tag/v6.0-a1), v6.0-a1, 6.0 alpha1, v6.0.155, 169636 lines.
|
||||
- 2024-07-27, [Release v6.0-a0](https://github.com/ossrs/srs/releases/tag/v6.0-a0), v6.0-a0, 6.0 alpha0, v6.0.145, 169259 lines.
|
||||
- 2024-07-04, [Release v6.0-d6](https://github.com/ossrs/srs/releases/tag/v6.0-d6), v6.0-d6, 6.0 dev6, v6.0.134, 168904 lines.
|
||||
- 2024-06-15, [Release v6.0-d5](https://github.com/ossrs/srs/releases/tag/v6.0-d5), v6.0-d5, 6.0 dev5, v6.0.129, 168454 lines.
|
||||
- 2024-02-15, [Release v6.0-d4](https://github.com/ossrs/srs/releases/tag/v6.0-d4), v6.0-d4, 6.0 dev4, v6.0.113, 167695 lines.
|
||||
- 2023-11-19, [Release v6.0-d3](https://github.com/ossrs/srs/releases/tag/v6.0-d3), v6.0-d3, 6.0 dev3, v6.0.101, 167560 lines.
|
||||
- 2023-09-28, [Release v6.0-d2](https://github.com/ossrs/srs/releases/tag/v6.0-d2), v6.0-d2, 6.0 dev2, v6.0.85, 167509 lines.
|
||||
- 2023-08-31, [Release v6.0-d1](https://github.com/ossrs/srs/releases/tag/v6.0-d1), v6.0-d1, 6.0 dev1, v6.0.72, 167135 lines.
|
||||
- 2023-07-09, [Release v6.0-d0](https://github.com/ossrs/srs/releases/tag/v6.0-d0), v6.0-d0, 6.0 dev0, v6.0.59, 166739 lines.
|
||||
- 2024-06-15, [Release v5.0-r3](https://github.com/ossrs/srs/releases/tag/v5.0-r3), v5.0-r3, 5.0 release3, v5.0.213, 163585 lines.
|
||||
- 2024-04-03, [Release v5.0-r2](https://github.com/ossrs/srs/releases/tag/v5.0-r2), v5.0-r2, 5.0 release2, v5.0.210, 163515 lines.
|
||||
- 2024-02-15, [Release v5.0-r1](https://github.com/ossrs/srs/releases/tag/v5.0-r1), v5.0-r1, 5.0 release1, v5.0.208, 163441 lines.
|
||||
- 2023-12-30, [Release v5.0-r0](https://github.com/ossrs/srs/releases/tag/v5.0-r0), v5.0-r0, 5.0 release0, v5.0.205, 163363 lines.
|
||||
- 2023-11-19, [Release v5.0-b7](https://github.com/ossrs/srs/releases/tag/v5.0-b7), v5.0-b7, 5.0 beta7, v5.0.200, 163305 lines.
|
||||
- 2023-10-25, [Release v5.0-b6](https://github.com/ossrs/srs/releases/tag/v5.0-b6), v5.0-b6, 5.0 beta6, v5.0.195, 163303 lines.
|
||||
- 2023-09-28, [Release v5.0-b5](https://github.com/ossrs/srs/releases/tag/v5.0-b5), v5.0-b5, 5.0 beta5, v5.0.185, 163254 lines.
|
||||
- 2023-08-31, [Release v5.0-b4](https://github.com/ossrs/srs/releases/tag/v5.0-b4), v5.0-b4, 5.0 beta4, v5.0.176, 162919 lines.
|
||||
- 2023-08-02, [Release v5.0-b3](https://github.com/ossrs/srs/releases/tag/v5.0-b3), v5.0-b3, 5.0 beta3, v5.0.170, 162704 lines.
|
||||
- 2023-07-09, [Release v5.0-b2](https://github.com/ossrs/srs/releases/tag/v5.0-b2), v5.0-b2, 5.0 beta2, v5.0.166, 162520 lines.
|
||||
- 2023-06-11, [Release v5.0-b1](https://github.com/ossrs/srs/releases/tag/v5.0-b1), v5.0-b1, 5.0 beta1, v5.0.157, 162494 lines.
|
||||
- 2023-05-14, [Release v5.0-b0](https://github.com/ossrs/srs/releases/tag/v5.0-b0), v5.0-b0, 5.0 beta0, v5.0.155, 162600 lines.
|
||||
- 2023-03-23, [Release v5.0-a5](https://github.com/ossrs/srs/releases/tag/v5.0-a5), v5.0-a5, 5.0 alpha5, v5.0.148, 162066 lines.
|
||||
- 2023-02-12, [Release v5.0-a4](https://github.com/ossrs/srs/releases/tag/v5.0-a4), v5.0-a4, 5.0 alpha4, v5.0.141, 161897 lines.
|
||||
- 2023-01-02, [Release v5.0-a3](https://github.com/ossrs/srs/releases/tag/v5.0-a3), v5.0-a3, 5.0 alpha3, v5.0.128, 161327 lines.
|
||||
- 2022-12-18, [Release v5.0-a2](https://github.com/ossrs/srs/releases/tag/v5.0-a2), v5.0-a2, 5.0 alpha2, v5.0.112, 161233 lines.
|
||||
- 2022-12-01, [Release v5.0-a1](https://github.com/ossrs/srs/releases/tag/v5.0-a1), v5.0-a1, 5.0 alpha1, v5.0.100, 160817 lines.
|
||||
- 2022-11-25, [Release v5.0-a0](https://github.com/ossrs/srs/releases/tag/v5.0-a0), v5.0-a0, 5.0 alpha0, v5.0.98, 159813 lines.
|
||||
- 2022-11-22, Release [v4.0-r4](https://github.com/ossrs/srs/releases/tag/v4.0-r4), v4.0-r4, 4.0 release4, v4.0.268, 145482 lines.
|
||||
- 2022-09-16, Release [v4.0-r3](https://github.com/ossrs/srs/releases/tag/v4.0-r3), v4.0-r3, 4.0 release3, v4.0.265, 145328 lines.
|
||||
- 2022-08-24, Release [v4.0-r2](https://github.com/ossrs/srs/releases/tag/v4.0-r2), v4.0-r2, 4.0 release2, v4.0.257, 144890 lines.
|
||||
- 2022-06-29, Release [v4.0-r1](https://github.com/ossrs/srs/releases/tag/v4.0-r1), v4.0-r1, 4.0 release1, v4.0.253, 144680 lines.
|
||||
- 2022-06-11, Release [v4.0-r0](https://github.com/ossrs/srs/releases/tag/v4.0-r0), v4.0-r0, 4.0 release0, v4.0.252, 144680 lines.
|
||||
- 2020-06-27, [Release v3.0-r0](https://github.com/ossrs/srs/releases/tag/v3.0-r0), 3.0 release0, 3.0.141, 122674 lines.
|
||||
- 2020-02-02, [Release v3.0-b0](https://github.com/ossrs/srs/releases/tag/v3.0-b0), 3.0 beta0, 3.0.112, 121709 lines.
|
||||
- 2019-10-04, [Release v3.0-a0](https://github.com/ossrs/srs/releases/tag/v3.0-a0), 3.0 alpha0, 3.0.56, 107946 lines.
|
||||
- 2017-03-03, [Release v2.0-r0](https://github.com/ossrs/srs/releases/tag/v2.0-r0), 2.0 release0, 2.0.234, 86373 lines.
|
||||
- 2016-08-06, [Release v2.0-b0](https://github.com/ossrs/srs/releases/tag/v2.0-b0), 2.0 beta0, 2.0.210, 89704 lines.
|
||||
- 2015-08-23, [Release v2.0-a0](https://github.com/ossrs/srs/releases/tag/v2.0-a0), 2.0 alpha0, 2.0.185, 89022 lines.
|
||||
- 2014-12-05, [Release v1.0-r0](https://github.com/ossrs/srs/releases/tag/v1.0-r0), all bug fixed, 1.0.10, 59391 lines.
|
||||
- 2014-10-09, [Release v0.9.8](https://github.com/ossrs/srs/releases/tag/v0.9.8), all bug fixed, 1.0.0, 59316 lines.
|
||||
- 2014-04-07, [Release v0.9.1](https://github.com/ossrs/srs/releases/tag/v0.9.1), live streaming. 30000 lines.
|
||||
- 2013-10-23, [Release v0.1.0](https://github.com/ossrs/srs/releases/tag/v0.1.0), rtmp. 8287 lines.
|
||||
- 2013-10-17, Created.
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
# Security Policy
|
||||
|
||||
We appreciate work to discover security vulnerabilities, but as SRS is entirely volunteer driven,
|
||||
so we might not be able to respond to issues in time.
|
||||
|
||||
Please click [here](https://github.com/ossrs/srs/security/advisories) to report a vulnerability.
|
||||
|
||||
|
||||
28
docker-compose.yaml
Normal file
28
docker-compose.yaml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
name: OSSR-SRS
|
||||
|
||||
services:
|
||||
srs:
|
||||
container_name: SRS-Streaming-Server
|
||||
# image: ossrs/srs:v6.0.166
|
||||
# image: itprefects/srs:v6.0.166.1h
|
||||
image: itprefects/srs:v6.0.166.2b
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- SRS_DAEMON=off
|
||||
- SRS_IN_DOCKER=on
|
||||
- CANDIDATE=127.0.0.1
|
||||
working_dir: /usr/local/srs
|
||||
ports:
|
||||
- "10080:10080/udp"
|
||||
- "1935:1935"
|
||||
- "1985:1985"
|
||||
- "1990:1990"
|
||||
- "5060:5060"
|
||||
- "8000:8000/udp"
|
||||
- "8080:8080"
|
||||
- "8088:8088"
|
||||
- "9000:9000"
|
||||
volumes:
|
||||
- ./docker_data/config:/usr/local/srs/conf
|
||||
- ./docker_data/DVR_Record:/usr/local/srs/DVR_Record
|
||||
- ./docker_data/objects:/usr/local/srs/objs
|
||||
27
package-lock.json
generated
Normal file
27
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "livestream_server",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"prettier": "^3.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://mirrors.huaweicloud.com/repository/npm/prettier/-/prettier-3.5.3.tgz",
|
||||
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
package.json
Normal file
5
package.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"prettier": "^3.5.3"
|
||||
}
|
||||
}
|
||||
173
trunk/AUTHORS.md
173
trunk/AUTHORS.md
|
|
@ -1,173 +0,0 @@
|
|||
## AUTHORS
|
||||
|
||||
The authors and administrators of SRS, ordered by first contribution:
|
||||
|
||||
- `Winlin<winlinvip@gmail.com>`
|
||||
- `XiaoZhihong<hondaxiao@tencent.com>`
|
||||
|
||||
## TOC
|
||||
|
||||
The TOC(Technical Oversight Committee), ordered by first contribution:
|
||||
|
||||
- `Winlin<winlinvip@gmail.com>`
|
||||
- `ZhaoWenjie<zhaowenjie@tal.com>`
|
||||
- `ShiWei<xiaoq_bj@126.com>`
|
||||
- `XiaoZhihong<hondaxiao@tencent.com>`
|
||||
- `WuPengqiang<pengqiang.wpq@alibaba-inc.com>`
|
||||
- `XiaLixin<xialixin@kanzhun.com>`
|
||||
- `LiPeng<lipeng19811218@gmail.com>`
|
||||
- `ChenGuanghua<jinxue.cgh@alibaba-inc.com>`
|
||||
- `ChenHaibo<nmgchenhaibo@foxmail.com>`
|
||||
- `ZhangJunqin<chundonglinlin@126.com>`
|
||||
|
||||
## CONTRIBUTORS
|
||||
|
||||
CONTRIBUTORS ordered by first contribution.
|
||||
|
||||
* `Winlin<winlin@vip.126.com>`
|
||||
* `Winlin<winterserver@126.com>`
|
||||
* `Winlin<chengli.ycl@alibaba-inc.com>`
|
||||
* `ZhaoWenjie<zhaowenjie@tal.com>`
|
||||
* `ZhaoWenjie<740936897@qq.com>`
|
||||
* `ZhaoWenjie<wenjie.zhao@chinacache.com>`
|
||||
* `xiangcheng.liu<liuxc0116@foxmail.com>`
|
||||
* `naijia.liu<youngcow@youngcow.net>`
|
||||
* `alcoholyi<alcoholyi@qq.com>`
|
||||
* `byteman<wangchen2011@gmail.com>`
|
||||
* `chad.wang<chad.wang.cn@gmail.com>`
|
||||
* `suhetao<suhetao@gmail.com>`
|
||||
* `Johnny<fengjihu@163.com>`
|
||||
* `karthikeyan<keyanmca@gmail.com>`
|
||||
* `StevenLiu<lq@chinaffmpeg.org>`
|
||||
* `StevenLiu<lingjiujianke@gmail.com>`
|
||||
* `StevenLiu<liuqi05@kuaishou.com>`
|
||||
* `zhengfl<zhengfl_1989@126.com>`
|
||||
* `tufang14<breadbean1449@gmail.com>`
|
||||
* `allspace<allspace@gmail.com>`
|
||||
* `niesongsong<nie950@gmail.com>`
|
||||
* `rudeb0t<nimrod@themanxgroup.tw>`
|
||||
* `CallMeNP<np.liamg@gmail.com>`
|
||||
* `synote<synote@qq.com>`
|
||||
* `lovecat<littlefawn@163.com>`
|
||||
* `panda1986<542638787@qq.com>`
|
||||
* `YueHonghui<hongf.yue@hotmail.com>`
|
||||
* `ThomasDreibholz<dreibh@simula.no>`
|
||||
* `JuntaoLiu<juntliu@gmail.com>`
|
||||
* `RocFang<fangpeng1986@gmail.com>`
|
||||
* `MakarovYaroslav<yaroslav.makarov.97@mail.ru>`
|
||||
* `MirkoVelic<mvelic@inoxx.net>`
|
||||
* `HuiZhang(huzhang2)<huzhang2@cisco.com>`
|
||||
* `OtterWa<simpleotter23@gmail.com>`
|
||||
* `walkermi<172192667@qq.com>`
|
||||
* `haofz<fuzhuang.hao@vhall.com>`
|
||||
* `ME_Kun_Han<hanvskun@hotmail.com>`
|
||||
* `ljx0305<ljx0305@gmail.com>`
|
||||
* `cenxinwei<censhanhe@163.com>`
|
||||
* `StarBrilliant<m13253@hotmail.com>`
|
||||
* `xubin<xubin@chnvideo.com>`
|
||||
* `intliang<yintiliang@gmail.com>`
|
||||
* `flowerwrong<sysuyangkang@gmail.com>`
|
||||
* `YLX<568414379@qq.com>`
|
||||
* `J<guotaojiang@qq.com>`
|
||||
* `Harlan<hailiang@gvrcraft.com>`
|
||||
* `hankun<hankun@bravovcloud.com>`
|
||||
* `JonathanBarratt<jonathan.barratt@gmail.com>`
|
||||
* `KeeganH<keeganwharris@gmail.com>`
|
||||
* `liuxc0116<liuxc0116@gmail.com>`
|
||||
* `ChengdongZhang<lmajzcd@sina.com>`
|
||||
* `lovacat<lovecat@china.sina.com>`
|
||||
* `qiang.li<qiang.li@verycdn.com.cn>`
|
||||
* `HungMingWu<u9089000@gmail.com>`
|
||||
* `Himer<xishizhaohua@qq.com>`
|
||||
* `XiaLixin<xialixin@kanzhun.com>`
|
||||
* `XiaLixin<68469352@qq.com>`
|
||||
* `XiaLixin<xlx0625@163.com>`
|
||||
* `XiaLixin<xialx@yuntongxun.com>`
|
||||
* `alphonsetai<tyh_123@163.com>`
|
||||
* `Michael.Ma<wnpllr@gmail.com>`
|
||||
* `lam2003<linmin3@yy.com>`
|
||||
* `ShiWei<shiwei05@kuaishou.com>`
|
||||
* `ShiWei<shi.weibd@hotmail.com>`
|
||||
* `ShiWei<xiaoq_bj@126.com>`
|
||||
* `XiaofengWang<wasphin@gmail.com>`
|
||||
* `XiaoZhihong<hondaxiao@tencent.com>`
|
||||
* `XiaoZhihong<xiaozhihong8@gmail.com>`
|
||||
* `XiaoZhihong<xiaozhihong@huya.com>`
|
||||
* `yanghuiwen<cainiaodj@qq.com>`
|
||||
* `WuPengqiang<309554135@qq.com>`
|
||||
* `WuPengqiang<pengqiang.wpq@alibaba-inc.com>`
|
||||
* `l<22312935+lam2003@users.noreply.github.com>`
|
||||
* `xfalcon<x-falcon@users.noreply.github.com>`
|
||||
* `ChenGuanghua<jinxue.cgh@alibaba-inc.com>`
|
||||
* `ChenGuanghua<chengh_math@126.com>`
|
||||
* `LiPeng<mozhan.lp@alibaba-inc.com>`
|
||||
* `LiPeng<lipeng19811218@gmail.com>`
|
||||
* `yajun18<yajun18@staff.sina.com.cn>`
|
||||
* `liulichuan<liulichuan@kuaishou.com>`
|
||||
* `yapingcat<caoyapingneu@163.com>`
|
||||
* `chenchengbin<chenchengbin@yy.com>`
|
||||
* `ChenHaibo<495810242@qq.com>`
|
||||
* `ChenHaibo<nmgchenhaibo@foxmail.com>`
|
||||
* `jasongwq<jasongwq@gmail.com>`
|
||||
* `yinjiaoyuan<yinjiaoyuan@163.com>`
|
||||
* `PieerePi<pihuibin@hotmail.com>`
|
||||
* `JesseXi<jesse.jinjin@wo.cn>`
|
||||
* `PieerePi<40780488+PieerePi@users.noreply.github.com>`
|
||||
* `ghostsf<ghost_sf@163.com>`
|
||||
* `xbpeng121<53243357+xbpeng121@users.noreply.github.com>`
|
||||
* `Johzzy<hellojinqiang@gmail.com>`
|
||||
* `stone<bluestn@163.com>`
|
||||
* `cfw11<34058899+cfw11@users.noreply.github.com>`
|
||||
* `Hung-YiChen<gaod.chen@gmail.com>`
|
||||
* `long<liyalong12345@126.com>`
|
||||
* `matthew1838<77285055+matthew1838@users.noreply.github.com>`
|
||||
* `rise<rise.worlds@outlook.com>`
|
||||
* `pyw<PYW1@users.noreply.github.com>`
|
||||
* `MatheusMacabu<macabu@users.noreply.github.com>`
|
||||
* `Alex.CR<xiaoq_bj@126.com>`
|
||||
* `Zhouxiaojun2008<279686030@qq.com>`
|
||||
* `Zhouxiaojun2008 <31402829+zhouxiaojun2008@users.noreply.github.com>`
|
||||
* `Pengfei.ma<pengfei.ma.chn@outlook.com>`
|
||||
* `Pengfei.ma<52305649+mapengfei53@users.noreply.github.com>`
|
||||
* `ZhangJunqin<zhangjunqin@jd.com>`
|
||||
* `ZhangJunqin<chundonglinlin@126.com>`
|
||||
* `AbrarAhmed<13979201+abrar71@users.noreply.github.com>`
|
||||
* `BoringWednesday<2651933495@qq.com>`
|
||||
* `liuxiaoliang<37902979+liuxiaoliang8@users.noreply.github.com>`
|
||||
* `qingfuliao<45161976+qingfuliao@users.noreply.github.com>`
|
||||
* `along<alongL@users.noreply.github.com>`
|
||||
* `akanchi<chenpx718@gmail.com>`
|
||||
* `HuachaoMao<huachaomao@gmail.com>`
|
||||
* `James<jiankai.wang@opensight.cn>`
|
||||
* `lvndry<monga.4landry@gmail.com>`
|
||||
* `马文武<mwenwu@kitegogo.com>`
|
||||
* `PurpleGrape<purplegrape4@gmail.com>`
|
||||
* `xmedia-systems<xmedia.systems@gmail.com>`
|
||||
* `MatinZadehDolatabad<zadehdolatabad@gmail.com>`
|
||||
* `zozobreakzou<zozobreak@163.com>`
|
||||
* `mingo-wu<57790355+mingo-wu1@users.noreply.github.com>`
|
||||
* `CommanderRoot<CommanderRoot@users.noreply.github.com>`
|
||||
* `dev-clavis<declavis.company@gmail.com>`
|
||||
* `everything411<everything411@163.com>`
|
||||
* `faicker<faicker.mo@gmail.com>`
|
||||
* `Matthew<matthewgao@gmail.com>`
|
||||
* `simon1tan1<107955289+simon1tan1@users.noreply.github.com>`
|
||||
* `feng<308276366@qq.com>`
|
||||
* `wangzhen<wrennywang@hotmail.com>`
|
||||
* `yashwardhan-jyani<100014271+yashwardhan-jyani@users.noreply.github.com>`
|
||||
* `qyt<486179@qq.com>`
|
||||
* `Kazuo<hagihara@miharu.co.jp>`
|
||||
* `MarkCao<industriousonesoft@gmail.com>`
|
||||
* `Mr.Li<iskyman@163.com>`
|
||||
* `Loken<lokenetwork@users.noreply.github.com>`
|
||||
* `JacobSu<suzp1984@gmail.com>`
|
||||
* `terrencetang2023<tangshaoteng@163.com>`
|
||||
* `jb-alvarado<2212056+jb-alvarado@users.noreply.github.com>`
|
||||
* `Jay<39650910+w41203208@users.noreply.github.com>`
|
||||
* `Arjen10<47875127+Arjen10@users.noreply.github.com>`
|
||||
* `Lukas<89481353+Lukas-Kaufmann@users.noreply.github.com>`
|
||||
* `VampireAchao<achao1441470436@gmail.com>`
|
||||
* `Laurentiu<laurfb@gmail.com>`
|
||||
* `Bahamut<retamia@gmail.com>`
|
||||
* `MarcOlzheim<zlo@zlo.nu>`
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
AUTHORS.md
|
||||
|
|
@ -7,7 +7,6 @@ The changelog for SRS.
|
|||
<a name="v7-changes"></a>
|
||||
|
||||
## SRS 7.0 Changelog
|
||||
* v7.0, 2025-05-13, Merge [#4289](https://github.com/ossrs/srs/pull/4289): rtmp2rtc: Support HEVC. v7.0.33 (#4289)
|
||||
* v7.0, 2025-04-30, Merge [#4308](https://github.com/ossrs/srs/pull/4308): Fix memory leaks from errors skipping resource release. v7.0.32 (#4308)
|
||||
* v7.0, 2025-04-26, Merge [#4292](https://github.com/ossrs/srs/pull/4309): Support custom deleter for SrsUniquePtr. v7.0.31 (#4309)
|
||||
* v7.0, 2025-03-21, Merge [#4292](https://github.com/ossrs/srs/pull/4292): Typo: "forked" process in log output. v7.0.30 (#4292)
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
source.200kbps.768x320.flv
|
||||
1
trunk/doc/source.flv
Normal file
1
trunk/doc/source.flv
Normal file
|
|
@ -0,0 +1 @@
|
|||
source.200kbps.768x320.flv
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../favicon.ico
|
||||
1
trunk/research/api-server/static-dir/favicon.ico
Normal file
1
trunk/research/api-server/static-dir/favicon.ico
Normal file
|
|
@ -0,0 +1 @@
|
|||
../../favicon.ico
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../index.html
|
||||
1
trunk/research/api-server/static-dir/index.html
Normal file
1
trunk/research/api-server/static-dir/index.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
../../index.html
|
||||
6159
trunk/research/players/css/bootstrap.min.css
vendored
6159
trunk/research/players/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
135
trunk/research/players/css/homepage_main.css
Normal file
135
trunk/research/players/css/homepage_main.css
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 2rem;
|
||||
background: white;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
.logo img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
background: #4285f4;
|
||||
color: white;
|
||||
padding: 0.8rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 24px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-button:hover {
|
||||
background: #357abd;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 4rem 2rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #202124;
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
color: #5f6368;
|
||||
margin-bottom: 3rem;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.join-container {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.code-box {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* gap: 30px; */
|
||||
position: relative;
|
||||
width: 250px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#code-input {
|
||||
padding: 10px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
color: #000000;
|
||||
font-size: 1em;
|
||||
transition: 0.5s;
|
||||
}
|
||||
#code-span {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
padding: 0 10px;
|
||||
pointer-events: none;
|
||||
font-size: 1em;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
text-transform: uppercase;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
#code-input:valid ~ #code-span,
|
||||
#code-input:focus ~ #code-span {
|
||||
color: #4285f4;
|
||||
transform: translateX(-15px) translateY(-18px) scale(0.7);
|
||||
/* font-size: 0.65em; */
|
||||
padding: 0 10px;
|
||||
background: white;
|
||||
border-left: 3px solid #4285f4;
|
||||
border-right: 3px solid #4285f4;
|
||||
/* letter-spacing: .2em; */
|
||||
}
|
||||
#code-input:valid, #code-input:focus {
|
||||
border: 3px solid #4285f4;
|
||||
}
|
||||
|
||||
.join-button {
|
||||
background: linear-gradient(135deg, #4285f4, #34a853);
|
||||
color: white;
|
||||
padding: 1rem 2rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.join-button:hover {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.join-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
309
trunk/research/players/css/whep_main.css
Normal file
309
trunk/research/players/css/whep_main.css
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
/*=============== VARIABLES CSS ===============*/
|
||||
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&display=swap');
|
||||
|
||||
:root {
|
||||
/*========== Colors ==========*/
|
||||
/*Color mode HSL(hue, saturation, lightness)*/
|
||||
--background-color: rgb(255, 255, 255);
|
||||
--preloader-background-color: rgb(255, 255, 255);
|
||||
--preloader-text-color: hsl(210, 30%, 10%);
|
||||
--secondary-color: hsl(0, 0%, 65%);
|
||||
|
||||
/*========== Font and typography ==========*/
|
||||
/*.5rem = 8px | 1rem = 16px ...*/
|
||||
--body-font: "Quicksand", sans-serif;
|
||||
--second-font: Serif;
|
||||
--h3-font-size: 1rem;
|
||||
|
||||
/*========== z index ==========*/
|
||||
--z-tooltip: 10;
|
||||
--z-fixed: 100;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#rtc_media_player {
|
||||
max-width: 95%;
|
||||
max-height: 95vh;
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
background: #000000;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
*,
|
||||
*::after,
|
||||
*::before {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
.form {
|
||||
max-width: 60rem;
|
||||
}
|
||||
|
||||
.form__radios {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.form__radio {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
column-gap: 2rem;
|
||||
row-gap: .8rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.form__radio p {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.form__inputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .8rem;
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
.form__inputs input,
|
||||
.form__radios input {
|
||||
margin-bottom: 1rem;
|
||||
background-color: #556;
|
||||
border: none;
|
||||
padding: 1rem 1.5rem;
|
||||
color: inherit;
|
||||
outline: transparent;
|
||||
}
|
||||
|
||||
.form__buttons {
|
||||
margin-top: 4rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 2fr));
|
||||
gap: 1rem 2rem;
|
||||
}
|
||||
|
||||
.toast .btn {
|
||||
background-color: #778;
|
||||
color: inherit;
|
||||
border: none;
|
||||
padding: 1rem 1.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #556;
|
||||
}
|
||||
|
||||
.btn--success {
|
||||
background-color: rgb(21, 173, 89);
|
||||
}
|
||||
|
||||
.btn--success:hover {
|
||||
background-color: rgb(12, 134, 67);
|
||||
}
|
||||
|
||||
.btn--error {
|
||||
background-color: rgb(199, 29, 66);
|
||||
}
|
||||
|
||||
.btn--error:hover {
|
||||
background-color: rgb(158, 16, 47);
|
||||
}
|
||||
|
||||
.btn--warning {
|
||||
background-color: rgb(232, 172, 21);
|
||||
color: #222226;
|
||||
}
|
||||
|
||||
.btn--warning:hover {
|
||||
background-color: rgb(199, 141, 15);
|
||||
}
|
||||
|
||||
|
||||
/* THE ACTUAL TOAST NOTIFICATIONS */
|
||||
.toast {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 0.7rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.toast__message {
|
||||
position: relative;
|
||||
color: #EEE;
|
||||
background-color: #334;
|
||||
width: 25rem;
|
||||
max-width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 1.1rem;
|
||||
align-items: center;
|
||||
font-size: 0.85rem;
|
||||
border-left: 3px solid #EEE;
|
||||
animation: toast 6s cubic-bezier(0.075, 0.82, 0.165, 1) forwards;
|
||||
}
|
||||
|
||||
.toast__icon {
|
||||
background-color: rgb(21, 173, 89);
|
||||
width: 1.95rem;
|
||||
height: 1.95rem;
|
||||
border-radius: 50%;
|
||||
color: #222226;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.toast__icon svg {
|
||||
height: 1.3rem;
|
||||
width: 1.3rem;
|
||||
}
|
||||
|
||||
.toast__buttons {
|
||||
display: grid;
|
||||
/* flex-direction: row; */
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.toast__buttons button {
|
||||
background-color: rgba(255, 255, 255, .1);
|
||||
border: none;
|
||||
color: inherit;
|
||||
padding: .5rem 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toast__buttons button:hover {
|
||||
background-color: rgba(255, 255, 255, .2);
|
||||
}
|
||||
|
||||
.toast__close {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 2rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 100;
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/* ERROR MESSAGES */
|
||||
.toast__message--error {
|
||||
background-color: #334;
|
||||
background-image: linear-gradient(to right,
|
||||
rgba(136, 7, 35, 0.3),
|
||||
rgba(136, 7, 35, 0) 30%);
|
||||
border-left: 3px solid rgb(199, 29, 66);
|
||||
}
|
||||
|
||||
.toast__message--error .toast__icon {
|
||||
background-color: rgb(199, 29, 66);
|
||||
}
|
||||
|
||||
.toast__message--error .toast__icon svg {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.toast__message--error .toast__heading {
|
||||
color: rgb(199, 29, 66);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
/* WARNING MESSAGES */
|
||||
.toast__message--warning {
|
||||
background-color: #334;
|
||||
background-image: linear-gradient(to right,
|
||||
rgba(145, 93, 10, 0.3),
|
||||
rgba(145, 93, 10, 0) 30%);
|
||||
border-left: 3px solid rgb(232, 172, 21);
|
||||
}
|
||||
|
||||
.toast__message--warning .toast__icon {
|
||||
background-color: rgb(232, 172, 21);
|
||||
}
|
||||
|
||||
.toast__message--warning .toast__icon svg {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.toast__message--warning .toast__heading {
|
||||
color: rgb(232, 172, 21);
|
||||
margin: 0;
|
||||
}
|
||||
.toast__message p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
/* SUCCESS MESSAGES */
|
||||
.toast__message--success {
|
||||
background-color: #334;
|
||||
background-image: linear-gradient(to right,
|
||||
rgba(12, 100, 52, 0.3),
|
||||
rgba(12, 100, 52, 0) 30%);
|
||||
border-left: 3px solid rgb(21, 173, 89);
|
||||
}
|
||||
|
||||
.toast__message--success .toast__icon {
|
||||
background-color: rgb(21, 173, 89);
|
||||
}
|
||||
|
||||
.toast__message--success .toast__icon svg {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.toast__message--success .toast__heading {
|
||||
color: rgb(21, 173, 89);
|
||||
margin-top: 0.8rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@keyframes toast {
|
||||
0% {
|
||||
transform: translateY(150%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
10%,
|
||||
90% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
330
trunk/research/players/homepage.html
Normal file
330
trunk/research/players/homepage.html
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NPL ITP Intranet Livestream Channel</title>
|
||||
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="css/homepage_main.css" />
|
||||
|
||||
<script type="text/javascript" src="js/anime.js"></script>
|
||||
<script type="text/javascript" src="js/jquery-1.12.2.min.js"></script>
|
||||
<script type="text/javascript" src="js/adapter-7.4.0.min.js"></script>
|
||||
<script type="text/javascript" src="js/srs.sdk.js"></script>
|
||||
<script type="text/javascript" src="js/winlin.utility.js"></script>
|
||||
<script type="text/javascript" src="js/srs.page.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="preloader">
|
||||
<h2 class="ml13 ml13-1">NPL ITP</h2>
|
||||
<h2 class="ml13 ml13-2">Intranet Livestream Channel</h2>
|
||||
<h3 id="status">Connecting to the SRS Server ...</h3>
|
||||
<div id="loader">Loading...</div>
|
||||
</div>
|
||||
<style>
|
||||
/*=============== PRELOADER STYLE ===============*/
|
||||
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&display=swap');
|
||||
|
||||
:root {
|
||||
/*========== Colors ==========*/
|
||||
/*Color mode HSL(hue, saturation, lightness)*/
|
||||
--background-color: rgb(255, 255, 255);
|
||||
--preloader-background-color: rgb(255, 255, 255);
|
||||
--preloader-text-color: hsl(210, 30%, 10%);
|
||||
--secondary-color: hsl(0, 0%, 65%);
|
||||
|
||||
/*========== Font and typography ==========*/
|
||||
/*.5rem = 8px | 1rem = 16px ...*/
|
||||
--body-font: "Quicksand", sans-serif;
|
||||
--second-font: Serif;
|
||||
--h3-font-size: 1rem;
|
||||
|
||||
/*========== z index ==========*/
|
||||
--z-tooltip: 10;
|
||||
--z-fixed: 100;
|
||||
}
|
||||
|
||||
@media (max-width: 650px) {
|
||||
.ml13 {
|
||||
font-size: 3.3rem !important;
|
||||
/* Adjust this value as needed */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 410px) {
|
||||
.ml13 {
|
||||
font-size: 2.2rem !important;
|
||||
/* Adjust this value as needed */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 310px) {
|
||||
.ml13 {
|
||||
font-size: 1.5rem !important;
|
||||
/* Adjust this value as needed */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.preloader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* Tailwind 'gap-4' is 1rem */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
/* 'h-screen' is 100% of the viewport height */
|
||||
background-color: var(--preloader-background-color);
|
||||
z-index: 1100;
|
||||
/* 'z-[1100]' sets the z-index */
|
||||
transform: translateY(0);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#preload_logo {
|
||||
width: 90%;
|
||||
margin-bottom: -2rem;
|
||||
/* Tailwind 'mb-4' is 1rem */
|
||||
}
|
||||
|
||||
.ml13 {
|
||||
font-size: 5rem;
|
||||
/* text-transform: uppercase; */
|
||||
color: var(--preloader-text-color);
|
||||
letter-spacing: -1px;
|
||||
font-weight: 1000;
|
||||
font-family: var(--second-font);
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ml13 .word {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ml13 .letter {
|
||||
display: inline-block;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.preloader #status {
|
||||
font-size: 2.2rem;
|
||||
color: var(--secondary-color);
|
||||
font-family: var(--body-font);
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
margin-top: 3rem;
|
||||
/* Tailwind 'mt-8' is 2rem */
|
||||
}
|
||||
|
||||
#loader {
|
||||
width: 250px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 5%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-family: helvetica, arial, sans-serif;
|
||||
font-size: 1rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 900;
|
||||
color: var(--preloader-text-color);
|
||||
letter-spacing: 0.2em;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background: var(--preloader-text-color);
|
||||
position: absolute;
|
||||
animation: load 0.7s infinite alternate ease-in-out;
|
||||
}
|
||||
|
||||
&::before {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load {
|
||||
0% {
|
||||
left: 0;
|
||||
height: 30px;
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
50% {
|
||||
height: 8px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 235px;
|
||||
height: 30px;
|
||||
width: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
/*=============== PRELOADER ANIMATION ===============*/
|
||||
for (i = 1; i <= 2; i++) {
|
||||
var textWrapper = document.querySelector('.ml13.ml13-' + i);
|
||||
console.log(textWrapper);
|
||||
// Split text into words
|
||||
var words = textWrapper.textContent.trim().split(' ');
|
||||
|
||||
// Clear the existing content
|
||||
textWrapper.innerHTML = '';
|
||||
|
||||
// Wrap each word and its letters in spans
|
||||
words.forEach(function (word) {
|
||||
var wordSpan = document.createElement('span');
|
||||
wordSpan.classList.add('word');
|
||||
wordSpan.innerHTML = word.replace(/\S/g, "<span class='letter'>$&</span>");
|
||||
textWrapper.appendChild(wordSpan);
|
||||
textWrapper.appendChild(document.createTextNode(' ')); // Add space between words
|
||||
});
|
||||
}
|
||||
var animation = anime.timeline({ loop: true })
|
||||
.add({
|
||||
targets: '.ml13 .letter',
|
||||
translateY: [40, 0],
|
||||
translateZ: 0,
|
||||
opacity: [0, 1],
|
||||
filter: ['blur(5px)', 'blur(0px)'], // Starting from blurred to unblurred
|
||||
easing: "easeOutExpo",
|
||||
duration: 1400,
|
||||
delay: (el, i) => 300 + 30 * i,
|
||||
}).add({
|
||||
targets: '.ml13 .letter',
|
||||
translateY: [0, -40],
|
||||
opacity: [1, 0],
|
||||
filter: ['blur(0px)', 'blur(5px)'], // Ending from unblurred to blurred
|
||||
easing: "easeInExpo",
|
||||
duration: 1200,
|
||||
delay: (el, i) => 100 + 30 * i,
|
||||
changeComplete: onLoopComplete
|
||||
});
|
||||
|
||||
countdownEnd = false;
|
||||
refeshHtml = false;
|
||||
function onLoopComplete() {
|
||||
if (countdownEnd) {
|
||||
if (refeshHtml) {
|
||||
// Emit an event to notify that the preloader is hidden
|
||||
const event = new Event('preloaderHidden');
|
||||
document.dispatchEvent(event);
|
||||
} else {
|
||||
hidePreloader(); // Call hidePreloader after the animation completes
|
||||
animation.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showPreloader() {
|
||||
countdownEnd = false;
|
||||
var preloader = document.querySelector('.preloader');
|
||||
// 重置preloader位置和透明度
|
||||
preloader.style.transform = 'translateY(100%)';
|
||||
preloader.style.opacity = '1';
|
||||
preloader.style.display = 'flex';
|
||||
|
||||
// 从下至上动画
|
||||
anime({
|
||||
targets: preloader,
|
||||
translateY: ['100%', '0%'],
|
||||
duration: 2000,
|
||||
easing: 'easeOutExpo',
|
||||
complete: function () {
|
||||
// 动画完成后开始循环动画
|
||||
animation.play();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.style.overflowY = 'hidden';
|
||||
}
|
||||
|
||||
|
||||
function hidePreloader() {
|
||||
var preloader = document.querySelector('.preloader');
|
||||
anime({
|
||||
targets: preloader,
|
||||
translateY: ['0%', '100%'],
|
||||
duration: 2000,
|
||||
easing: 'easeOutExpo',
|
||||
complete: function () {
|
||||
preloader.style.display = 'none'; // Hide the preloader after animation
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<nav>
|
||||
<div class="logo">
|
||||
<div class="logo-box">
|
||||
<img src="./img/school_logo.png" alt="Logo">
|
||||
</div>
|
||||
<span>NPL ITP Intranet Channel</span>
|
||||
</div>
|
||||
<button class="nav-button">首页</button>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<div class="hero-section">
|
||||
<h1>内联网人人都可以用的超低延迟直播服务</h1>
|
||||
<p class="subtitle">借助SRS服务和WebRTC连接协议,随时观看直播</p>
|
||||
|
||||
<div class="join-container">
|
||||
<div class="code-box">
|
||||
<input type="text" id="code-input" required="required">
|
||||
<span id="code-span">Enter the Stream Code</span>
|
||||
</div>
|
||||
<button class="join-button">Enter</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
document.querySelector('.join-button').addEventListener('click', function () {
|
||||
var url = "whep_itp.html?vhost=__defaultVhost__&app=live&server=localhost&port=8080&autostart=true&schema=http&stream=" + document.querySelector('#code-input').value;
|
||||
console.log(url)
|
||||
showPreloader();
|
||||
history.pushState({}, '', url);
|
||||
fetch(url)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
// 添加离开动画
|
||||
countdownEnd = true;
|
||||
refeshHtml = true;
|
||||
document.addEventListener('preloaderHidden', function () {
|
||||
window.location.href = url;
|
||||
});
|
||||
});
|
||||
});
|
||||
window.addEventListener('load', function () {
|
||||
countdownEnd = true;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
trunk/research/players/img/school_logo.png
Normal file
BIN
trunk/research/players/img/school_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
BIN
trunk/research/players/img/school_logo_text.png
Normal file
BIN
trunk/research/players/img/school_logo_text.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 139 KiB |
8
trunk/research/players/js/anime.js
Normal file
8
trunk/research/players/js/anime.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -129,7 +129,7 @@ function build_default_whip_whep_url(query, apiPath) {
|
|||
var vhost = (!query.vhost)? window.location.hostname:query.vhost;
|
||||
var app = (!query.app)? "live":query.app;
|
||||
var stream = (!query.stream)? "livestream":query.stream;
|
||||
var api = ':' + (query.api || (window.location.protocol === 'http:' ? '1985' : '1990'));
|
||||
var api = ':' + (query.api || (query.schema === 'http' ? '1985' : '1990'));
|
||||
const realApiPath = query.path || apiPath;
|
||||
|
||||
var queries = [];
|
||||
|
|
@ -141,7 +141,9 @@ function build_default_whip_whep_url(query, apiPath) {
|
|||
}
|
||||
queries = user_extra_params(query, queries, true);
|
||||
|
||||
var uri = window.location.protocol + "//" + server + api + realApiPath + "?app=" + app + "&stream=" + stream + "&" + queries.join('&');
|
||||
schema = query.schema || window.location.protocol.slice(0, -1);
|
||||
|
||||
var uri = schema + "://" + server + api + realApiPath + "?app=" + app + "&stream=" + stream + "&" + queries.join('&');
|
||||
while (uri.lastIndexOf("?") === uri.length - 1) {
|
||||
uri = uri.slice(0, uri.length - 1);
|
||||
}
|
||||
|
|
@ -180,3 +182,7 @@ function srs_init_whep(id, query) {
|
|||
update_nav();
|
||||
$(id).val(build_default_whip_whep_url(query, '/rtc/v1/whep/'));
|
||||
}
|
||||
|
||||
function srs_get_whep(query) {
|
||||
return build_default_whip_whep_url(query, '/rtc/v1/whep/');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -574,7 +574,7 @@ function SrsRtcWhipWhepAsync() {
|
|||
userStream.getTracks().forEach(function (track) {
|
||||
self.pc.addTrack(track);
|
||||
// Notify about local track when stream is ok.
|
||||
self.ontrack && self.ontrack({track: track});
|
||||
// self.ontrack && self.ontrack({track: track});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -602,6 +602,13 @@ function SrsRtcWhipWhepAsync() {
|
|||
return self.__internal.parseId(url, offer.sdp, answer);
|
||||
};
|
||||
|
||||
// 新增回调函数
|
||||
self.onconnected = null; // 连接成功回调
|
||||
self.onfirstvideo = null; // 收到第一个视频包回调
|
||||
self.oninactivevideo = null; // 视频流中断回调
|
||||
self.onvideoresume = null; // 视频流恢复回调
|
||||
self.onconnectionlost = null; // 连接永久丢失回调
|
||||
|
||||
// See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
|
||||
// @url The WebRTC url to play with, for example:
|
||||
// http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream
|
||||
|
|
@ -614,6 +621,19 @@ function SrsRtcWhipWhepAsync() {
|
|||
|
||||
if (!options?.videoOnly) self.pc.addTransceiver("audio", {direction: "recvonly"});
|
||||
if (!options?.audioOnly) self.pc.addTransceiver("video", {direction: "recvonly"});
|
||||
|
||||
if (options.onconnected) self.onconnected = options.onconnected;
|
||||
if (options.onfirstvideo) self.onfirstvideo = options.onfirstvideo;
|
||||
if (options.oninactivevideo) self.oninactivevideo = options.oninactivevideo;
|
||||
if (options.onvideoresume) self.onvideoresume = options.onvideoresume;
|
||||
if (options.onconnectionlost) self.onconnectionlost = options.onconnectionlost;
|
||||
|
||||
// 监听ICE连接状态
|
||||
self.pc.oniceconnectionstatechange = function () {
|
||||
if (self.pc.iceConnectionState === 'connected') {
|
||||
self.onconnected && self.onconnected();
|
||||
}
|
||||
};
|
||||
|
||||
var offer = await self.pc.createOffer();
|
||||
await self.pc.setLocalDescription(offer);
|
||||
|
|
@ -641,15 +661,114 @@ function SrsRtcWhipWhepAsync() {
|
|||
|
||||
// Close the publisher.
|
||||
self.close = function () {
|
||||
self.pc && self.pc.close();
|
||||
self.pc = null;
|
||||
// 清理所有计时器
|
||||
clearInterval(self.__internal.checkInterval);
|
||||
clearTimeout(self.__internal.inactiveTimer);
|
||||
|
||||
// 重置状态
|
||||
self.__internal.checkInterval = null;
|
||||
self.__internal.inactiveTimer = null;
|
||||
self.__internal.videoState = 'closed';
|
||||
|
||||
// 关闭PeerConnection
|
||||
if (self.pc) {
|
||||
self.pc.close();
|
||||
self.pc = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleVideoState = {
|
||||
init: async () => {
|
||||
const stats = await self.pc.getStats(self.__internal.currentTrack);
|
||||
stats.forEach(report => {
|
||||
if (report.type === 'inbound-rtp' && report.kind === 'video') {
|
||||
if (report.bytesReceived > 0) {
|
||||
self.__internal.videoState = 'active';
|
||||
self.onfirstvideo && self.onfirstvideo();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 检测中状态
|
||||
checking: async () => {
|
||||
const stats = await self.pc.getStats(self.__internal.currentTrack);
|
||||
stats.forEach(report => {
|
||||
if (report.type === 'inbound-rtp' && report.kind === 'video') {
|
||||
if (report.bytesReceived > self.__internal.lastBytes) {
|
||||
self.__internal.videoState = 'active';
|
||||
self.__internal.lastBytes = report.bytesReceived;
|
||||
self.__internal.lastActiveTime = Date.now();
|
||||
// self.onfirstvideo && self.onfirstvideo();
|
||||
self.onvideoresume && self.onvideoresume();
|
||||
console.log('🎬 视频流恢复');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
active: async () => {
|
||||
const stats = await self.pc.getStats(self.__internal.currentTrack);
|
||||
let bytesUpdated = false;
|
||||
|
||||
stats.forEach(report => {
|
||||
if (report.type === 'inbound-rtp' && report.kind === 'video') {
|
||||
const currentBytes = report.bytesReceived;
|
||||
if (currentBytes > self.__internal.lastBytes) {
|
||||
self.__internal.lastBytes = currentBytes;
|
||||
self.__internal.lastActiveTime = Date.now();
|
||||
bytesUpdated = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!bytesUpdated && Date.now() - self.__internal.lastActiveTime > 3000) {
|
||||
self.__internal.videoState = 'inactive';
|
||||
self.oninactivevideo && self.oninactivevideo();
|
||||
|
||||
// 启动15秒断开倒计时
|
||||
self.__internal.inactiveTimer = setTimeout(() => {
|
||||
self.onconnectionlost && self.onconnectionlost();
|
||||
self.close();
|
||||
}, 10000);
|
||||
}
|
||||
},
|
||||
|
||||
inactive: async () => {
|
||||
const stats = await self.pc.getStats(self.__internal.currentTrack);
|
||||
stats.forEach(report => {
|
||||
if (report.type === 'inbound-rtp' && report.kind === 'video') {
|
||||
if (report.bytesReceived > self.__internal.lastBytes) {
|
||||
// 清除断开计时器
|
||||
clearTimeout(self.__internal.inactiveTimer);
|
||||
self.__internal.inactiveTimer = null;
|
||||
|
||||
self.__internal.videoState = 'checking';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// The callback when got local stream.
|
||||
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
|
||||
self.ontrack = function (event) {
|
||||
// Add track to stream of SDK.
|
||||
self.stream.addTrack(event.track);
|
||||
|
||||
if (event.track.kind === 'video') {
|
||||
self.__internal.currentTrack = event.track;
|
||||
|
||||
// 启动状态检测引擎
|
||||
self.__internal.checkInterval = setInterval(() => {
|
||||
if (!self.__internal.currentTrack) return;
|
||||
|
||||
// 执行状态机转换
|
||||
handleVideoState[self.__internal.videoState]?.();
|
||||
}, 1000); // 每秒检测一次
|
||||
|
||||
// 初始状态转换
|
||||
self.__internal.videoState = 'init';
|
||||
}
|
||||
};
|
||||
|
||||
self.pc = new RTCPeerConnection(null);
|
||||
|
|
@ -661,6 +780,13 @@ function SrsRtcWhipWhepAsync() {
|
|||
|
||||
// Internal APIs.
|
||||
self.__internal = {
|
||||
videoState: 'init',
|
||||
checkInterval: null,
|
||||
inactiveTimer: null, // 新增断开计时器
|
||||
lastBytes: 0,
|
||||
lastActiveTime: 0,
|
||||
currentTrack: null,
|
||||
|
||||
parseId: (url, offer, answer) => {
|
||||
let sessionid = offer.substr(offer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);
|
||||
sessionid = sessionid.substr(0, sessionid.indexOf('\n') - 1) + ':';
|
||||
|
|
|
|||
66
trunk/research/players/js/whep_main.js
Normal file
66
trunk/research/players/js/whep_main.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
const btns = document.querySelectorAll('.btn');
|
||||
const toastContainer = document.querySelector('.toast');
|
||||
const buttonYes = document.querySelector('#buttonsYes');
|
||||
const buttonNo = document.querySelector('#buttonsNo');
|
||||
const buttonsContainer = document.querySelector('#buttonsDiv');
|
||||
|
||||
const options = {
|
||||
regular: {
|
||||
heading: 'This is a Regular notification',
|
||||
paragraph: 'This toast message notifies you of something. Whatever that may be.',
|
||||
svg: ''
|
||||
},
|
||||
success: {
|
||||
heading: 'This is a Success notification',
|
||||
paragraph: 'This toast message notifies you of all your great successes. You rock!',
|
||||
svg: '<div class="toast__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="m9 20.42l-6.21-6.21l2.83-2.83L9 14.77l9.88-9.89l2.83 2.83z"/></svg></div>'
|
||||
},
|
||||
error: {
|
||||
heading: 'This is an Error notification',
|
||||
paragraph: 'This toast message notifies you of an Error. Don\'t worry it\'s probably not your fault.',
|
||||
svg: '<div class="toast__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="currentColor" d="M15.1 3.1L12.9.9L8 5.9L3.1.9L.9 3.1l5 4.9l-5 4.9l2.2 2.2l4.9-5l4.9 5l2.2-2.2l-5-4.9z"/></svg></div>'
|
||||
},
|
||||
warning: {
|
||||
heading: 'This is a Warning notification',
|
||||
paragraph: 'This toast message notifies you of a Warning. Who knows what might have happened.',
|
||||
svg: '<div class="toast__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M11 14V5h2v9zm0 5v-2h2v2z"/></svg></div>'
|
||||
}
|
||||
}
|
||||
|
||||
function createToast({type='regular', buttons='false', close='true', heading='', paragraph='', button1='', button2=''}) {
|
||||
const html = `
|
||||
<div class="toast__message toast__message--${type}">
|
||||
${options[type].svg}
|
||||
<div class="toast__text">
|
||||
<h3 class="toast__heading">
|
||||
${heading ? heading : options[type].heading}
|
||||
</h3>
|
||||
<p>
|
||||
${paragraph ? paragraph : options[type].paragraph}
|
||||
</p>
|
||||
</div>
|
||||
${close === 'true' ? `<span class="toast__close">×</span>` : ''}
|
||||
|
||||
</div>
|
||||
`;
|
||||
toastContainer.insertAdjacentHTML('beforeend', html);
|
||||
|
||||
const toast = toastContainer.lastElementChild;
|
||||
toast.addEventListener('animationend', () => toast.remove());
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
document.querySelector('#heading').value = '';
|
||||
document.querySelector('#paragraph').value = '';
|
||||
}
|
||||
|
||||
btns.forEach(btn => btn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const close = document.querySelector('input[name=close]:checked').value;
|
||||
const heading = document.querySelector('#heading').value;
|
||||
const paragraph = document.querySelector('#paragraph').value;
|
||||
resetForm();
|
||||
createToast({type: this.value, close, heading, paragraph});
|
||||
}));
|
||||
|
||||
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>SRS</title>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body{
|
||||
padding-top: 30px;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/>
|
||||
<script type="text/javascript" src="js/jquery-1.12.2.min.js"></script>
|
||||
<script type="text/javascript" src="js/adapter-7.4.0.min.js"></script>
|
||||
<script type="text/javascript" src="js/srs.sdk.js"></script>
|
||||
<script type="text/javascript" src="js/winlin.utility.js"></script>
|
||||
<script type="text/javascript" src="js/srs.page.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<img style="width: 0px; height: 0px;" src='//ossrs.net/gif/v1/sls.gif?site=ossrs.net&path=/player/rtcpublisher'/>
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<a class="brand" href="https://github.com/ossrs/srs" target="_blank">SRS</a>
|
||||
<div class="nav-collapse collapse">
|
||||
<ul class="nav">
|
||||
<li><a id="nav_srs_player" href="srs_player.html">LivePlayer</a></li>
|
||||
<!--<li><a id="nav_rtc_player" href="rtc_player.html">RTC播放器</a></li>-->
|
||||
<!--<li><a id="nav_rtc_publisher" href="rtc_publisher.html">RTC推流</a></li>-->
|
||||
<li><a id="nav_whip" href="whip.html">WHIP</a></li>
|
||||
<li class="active"><a id="nav_whep" href="whip.html">WHEP</a></li>
|
||||
<li><a href="http://ossrs.net/srs.release/releases/app.html">iOS/Andriod</a></li>
|
||||
<!--<li><a id="nav_srs_publisher" href="srs_publisher.html">SRS编码器</a></li>-->
|
||||
<!--<li><a id="nav_srs_chat" href="srs_chat.html">SRS会议</a></li>-->
|
||||
<!--<li><a id="nav_srs_bwt" href="srs_bwt.html">SRS测网速</a></li>-->
|
||||
<!--<li><a id="nav_vlc" href="vlc.html">VLC播放器</a></li>-->
|
||||
<!--<li><a id="nav_gb28181" href="srs_gb28181.html">GB28181</a></li>-->
|
||||
<!--<li>
|
||||
<a href="https://github.com/ossrs/srs">
|
||||
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/ossrs/srs?style=social">
|
||||
</a>
|
||||
</li>-->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div id="main_info" class="alert alert-info fade in">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
<strong><span>Usage:</span></strong> <span>Enter the WebRTC WHEP URL and click "Play" to start playing.</span>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
URL:
|
||||
<input type="text" id="txt_url" class="input-xxlarge" value="">
|
||||
<button class="btn btn-primary" id="btn_play">Play</button>
|
||||
</div>
|
||||
|
||||
<p></p>
|
||||
<video id="rtc_media_player" controls autoplay></video>
|
||||
|
||||
<p></p>
|
||||
<div class="form-inline">
|
||||
Controls:
|
||||
<label>
|
||||
<input type="checkbox" id="ch_videoonly" style="margin-bottom: 8px"> Video Only
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" id="ch_audioonly" style="margin-bottom: 8px"> Audio Only
|
||||
</label>
|
||||
</div>
|
||||
|
||||
SessionID: <span id='sessionid'></span>
|
||||
|
||||
<p></p>
|
||||
Simulator: <a href='#' id='simulator-drop'>Drop</a>
|
||||
|
||||
<footer>
|
||||
<p></p>
|
||||
<p><a href="https://github.com/ossrs/srs">SRS Team © 2020</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
var sdk = null; // Global handler to do cleanup when republishing.
|
||||
var startPlay = function() {
|
||||
$('#rtc_media_player').show();
|
||||
|
||||
// Close PC when user replay.
|
||||
if (sdk) {
|
||||
sdk.close();
|
||||
}
|
||||
sdk = new SrsRtcWhipWhepAsync();
|
||||
|
||||
// User should set the stream when publish is done, @see https://webrtc.org/getting-started/media-devices
|
||||
// However SRS SDK provides a consist API like https://webrtc.org/getting-started/remote-streams
|
||||
$('#rtc_media_player').prop('srcObject', sdk.stream);
|
||||
// Optional callback, SDK will add track to stream.
|
||||
// sdk.ontrack = function (event) { console.log('Got track', event); sdk.stream.addTrack(event.track); };
|
||||
|
||||
// For example: webrtc://r.ossrs.net/live/livestream
|
||||
var url = $("#txt_url").val();
|
||||
sdk.play(url, {
|
||||
videoOnly: $('#ch_videoonly').prop('checked'),
|
||||
audioOnly: $('#ch_audioonly').prop('checked'),
|
||||
}).then(function(session){
|
||||
$('#sessionid').html(session.sessionid);
|
||||
$('#simulator-drop').attr('href', session.simulator + '?drop=1&username=' + session.sessionid);
|
||||
}).catch(function (reason) {
|
||||
sdk.close();
|
||||
$('#rtc_media_player').hide();
|
||||
console.error(reason);
|
||||
});
|
||||
};
|
||||
|
||||
$('#rtc_media_player').hide();
|
||||
var query = parse_query_string();
|
||||
srs_init_whep("#txt_url", query);
|
||||
|
||||
$("#btn_play").click(startPlay);
|
||||
// Never play util windows loaded @see https://github.com/ossrs/srs/issues/2732
|
||||
if (query.autostart === 'true') {
|
||||
$('#rtc_media_player').prop('muted', true);
|
||||
console.warn('For autostart, we should mute it, see https://www.jianshu.com/p/c3c6944eed5a ' +
|
||||
'or https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#audiovideo_elements');
|
||||
window.addEventListener("load", function(){ startPlay(); });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
371
trunk/research/players/whep_itp.html
Normal file
371
trunk/research/players/whep_itp.html
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NPL ITP Intranet Livestream Channel</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="css/whep_main.css"/>
|
||||
|
||||
<script type="text/javascript" src="js/anime.js"></script>
|
||||
<script type="text/javascript" src="js/jquery-1.12.2.min.js"></script>
|
||||
<script type="text/javascript" src="js/adapter-7.4.0.min.js"></script>
|
||||
<script type="text/javascript" src="js/srs.sdk.js"></script>
|
||||
<script type="text/javascript" src="js/winlin.utility.js"></script>
|
||||
<script type="text/javascript" src="js/srs.page.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="preloader">
|
||||
<h2 class="ml13 ml13-1">NPL ITP</h2>
|
||||
<h2 class="ml13 ml13-2">Intranet Livestream Channel</h2>
|
||||
<h3 id="status">Connecting to the SRS Server ...</h3>
|
||||
<div id="loader">Loading...</div>
|
||||
</div>
|
||||
<style>
|
||||
/*=============== PRELOADER STYLE ===============*/
|
||||
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&display=swap');
|
||||
:root {
|
||||
/*========== Colors ==========*/
|
||||
/*Color mode HSL(hue, saturation, lightness)*/
|
||||
--background-color: rgb(255, 255, 255);
|
||||
--preloader-background-color: rgb(255, 255, 255);
|
||||
--preloader-text-color: hsl(210, 30%, 10%);
|
||||
--secondary-color: hsl(0, 0%, 65%);
|
||||
|
||||
/*========== Font and typography ==========*/
|
||||
/*.5rem = 8px | 1rem = 16px ...*/
|
||||
--body-font: "Quicksand", sans-serif;
|
||||
--second-font: Serif;
|
||||
--h3-font-size: 1rem;
|
||||
|
||||
/*========== z index ==========*/
|
||||
--z-tooltip: 10;
|
||||
--z-fixed: 100;
|
||||
}
|
||||
|
||||
@media (max-width: 650px) {
|
||||
.ml13 {
|
||||
font-size: 3.3rem !important;
|
||||
/* Adjust this value as needed */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 410px) {
|
||||
.ml13 {
|
||||
font-size: 2.2rem !important;
|
||||
/* Adjust this value as needed */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 310px) {
|
||||
.ml13 {
|
||||
font-size: 1.5rem !important;
|
||||
/* Adjust this value as needed */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.preloader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* Tailwind 'gap-4' is 1rem */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
/* 'h-screen' is 100% of the viewport height */
|
||||
background-color: var(--preloader-background-color);
|
||||
z-index: 1100;
|
||||
/* 'z-[1100]' sets the z-index */
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
#preload_logo {
|
||||
width: 90%;
|
||||
margin-bottom: -2rem;
|
||||
/* Tailwind 'mb-4' is 1rem */
|
||||
}
|
||||
|
||||
.ml13 {
|
||||
font-size: 5rem;
|
||||
/* text-transform: uppercase; */
|
||||
color: var(--preloader-text-color);
|
||||
letter-spacing: -1px;
|
||||
font-weight: 1000;
|
||||
font-family: var(--second-font);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ml13 .word {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ml13 .letter {
|
||||
display: inline-block;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.preloader #status {
|
||||
font-size: 2.2rem;
|
||||
color: var(--secondary-color);
|
||||
font-family: var(--body-font);
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
margin-top: 3rem;
|
||||
/* Tailwind 'mt-8' is 2rem */
|
||||
}
|
||||
|
||||
#loader {
|
||||
width: 250px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 5%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-family: helvetica, arial, sans-serif;
|
||||
font-size: 1rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 900;
|
||||
color: var(--preloader-text-color);
|
||||
letter-spacing: 0.2em;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background: var(--preloader-text-color);
|
||||
position: absolute;
|
||||
animation: load 0.7s infinite alternate ease-in-out;
|
||||
}
|
||||
|
||||
&::before {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load {
|
||||
0% {
|
||||
left: 0;
|
||||
height: 30px;
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
50% {
|
||||
height: 8px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 235px;
|
||||
height: 30px;
|
||||
width: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
/*=============== PRELOADER ANIMATION ===============*/
|
||||
for (i = 1; i <= 2; i++) {
|
||||
var textWrapper = document.querySelector('.ml13.ml13-' + i);
|
||||
console.log(textWrapper);
|
||||
// Split text into words
|
||||
var words = textWrapper.textContent.trim().split(' ');
|
||||
|
||||
// Clear the existing content
|
||||
textWrapper.innerHTML = '';
|
||||
|
||||
// Wrap each word and its letters in spans
|
||||
words.forEach(function (word) {
|
||||
var wordSpan = document.createElement('span');
|
||||
wordSpan.classList.add('word');
|
||||
wordSpan.innerHTML = word.replace(/\S/g, "<span class='letter'>$&</span>");
|
||||
textWrapper.appendChild(wordSpan);
|
||||
textWrapper.appendChild(document.createTextNode(' ')); // Add space between words
|
||||
});
|
||||
}
|
||||
var animation = anime.timeline({ loop: true })
|
||||
.add({
|
||||
targets: '.ml13 .letter',
|
||||
translateY: [40, 0],
|
||||
translateZ: 0,
|
||||
opacity: [0, 1],
|
||||
filter: ['blur(5px)', 'blur(0px)'], // Starting from blurred to unblurred
|
||||
easing: "easeOutExpo",
|
||||
duration: 1400,
|
||||
delay: (el, i) => 300 + 30 * i,
|
||||
}).add({
|
||||
targets: '.ml13 .letter',
|
||||
translateY: [0, -40],
|
||||
opacity: [1, 0],
|
||||
filter: ['blur(0px)', 'blur(5px)'], // Ending from unblurred to blurred
|
||||
easing: "easeInExpo",
|
||||
duration: 1200,
|
||||
delay: (el, i) => 100 + 30 * i,
|
||||
changeComplete: onLoopComplete
|
||||
});
|
||||
|
||||
countdownEnd = false;
|
||||
refeshHtml = false;
|
||||
function onLoopComplete() {
|
||||
if (countdownEnd) {
|
||||
if (refeshHtml) {
|
||||
// Emit an event to notify that the preloader is hidden
|
||||
const event = new Event('preloaderHidden');
|
||||
document.dispatchEvent(event);
|
||||
} else {
|
||||
hidePreloader(); // Call hidePreloader after the animation completes
|
||||
animation.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showPreloader() {
|
||||
countdownEnd = false;
|
||||
var preloader = document.querySelector('.preloader');
|
||||
// 重置preloader位置和透明度
|
||||
preloader.style.transform = 'translateY(100%)';
|
||||
preloader.style.opacity = '1';
|
||||
preloader.style.display = 'flex';
|
||||
|
||||
// 从下至上动画
|
||||
anime({
|
||||
targets: preloader,
|
||||
translateY: ['100%', '0%'],
|
||||
duration: 2000,
|
||||
easing: 'easeOutExpo',
|
||||
complete: function () {
|
||||
// 动画完成后开始循环动画
|
||||
animation.play();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.style.overflowY = 'hidden';
|
||||
}
|
||||
|
||||
|
||||
function hidePreloader() {
|
||||
var preloader = document.querySelector('.preloader');
|
||||
anime({
|
||||
targets: preloader,
|
||||
translateY: ['0%', '100%'],
|
||||
duration: 2000,
|
||||
easing: 'easeOutExpo',
|
||||
complete: function () {
|
||||
preloader.style.display = 'none'; // Hide the preloader after animation
|
||||
|
||||
var mediaPlayer = document.querySelector('#rtc_media_player');
|
||||
if (mediaPlayer && mediaPlayer.muted) {
|
||||
console.log('#rtc_media_player is muted');
|
||||
createToast({
|
||||
type: 'warning',
|
||||
heading: 'Livestream is Muted',
|
||||
paragraph: 'Due to the browser policy, the livestream is muted by default. Please unmute it to hear the sound.',
|
||||
close: 'true'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="toast"></div>
|
||||
<video id="rtc_media_player" controls autoplay></video>
|
||||
|
||||
<script type="text/javascript" src="js/whep_main.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
var sdk = null; // Global handler to do cleanup when republishing.
|
||||
var startPlay = function() {
|
||||
$('#rtc_media_player').show();
|
||||
|
||||
// Close PC when user replay.
|
||||
if (sdk) {
|
||||
sdk.close();
|
||||
}
|
||||
sdk = new SrsRtcWhipWhepAsync();
|
||||
|
||||
// User should set the stream when publish is done, @see https://webrtc.org/getting-started/media-devices
|
||||
// However SRS SDK provides a consist API like https://webrtc.org/getting-started/remote-streams
|
||||
$('#rtc_media_player').prop('srcObject', sdk.stream);
|
||||
// Optional callback, SDK will add track to stream.
|
||||
// sdk.ontrack = function (event) { console.log('Got track', event); sdk.stream.addTrack(event.track); };
|
||||
|
||||
// For example: webrtc://r.ossrs.net/live/livestream
|
||||
var query = parse_query_string();
|
||||
|
||||
sdk.play(srs_get_whep(query), {
|
||||
videoOnly: $('#ch_videoonly').prop('checked'),
|
||||
audioOnly: $('#ch_audioonly').prop('checked'),
|
||||
onconnected: function () {
|
||||
console.log('🚀 WebRTC连接成功');
|
||||
document.getElementById('status').innerHTML = 'Waiting for stream package ...';
|
||||
},
|
||||
onfirstvideo: function () {
|
||||
console.log('🎬 收到第一个视频数据包');
|
||||
document.getElementById('status').innerHTML = 'Prepared to stream now!';
|
||||
countdownEnd = true;
|
||||
},
|
||||
oninactivevideo: function () {
|
||||
console.log('⚠️ 视频流中断超过3秒');
|
||||
showPreloader();
|
||||
document.getElementById('status').innerHTML = 'Lost Connection or End of stream. Waiting for resume ...';
|
||||
},
|
||||
onconnectionlost: function () {
|
||||
console.log('⛔ 连接永久丢失')
|
||||
document.getElementById('status').innerHTML = 'End of stream. Redirect to Home Page ...';
|
||||
directHomepage();
|
||||
},
|
||||
onvideoresume: function () {
|
||||
console.log('🎥 视频流恢复');
|
||||
document.getElementById('status').innerHTML = 'Reconnected to stream now!';
|
||||
countdownEnd = true;
|
||||
},
|
||||
}).catch(function (reason) {
|
||||
sdk.close();
|
||||
console.error(reason);
|
||||
document.getElementById('status').innerHTML = 'Error in WebRTC Connection. Redirect to Home Page ...';
|
||||
directHomepage();
|
||||
});
|
||||
};
|
||||
|
||||
function directHomepage() {
|
||||
url = "./homepage.html"
|
||||
fetch(url)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
// 添加离开动画
|
||||
countdownEnd = true;
|
||||
refeshHtml = true;
|
||||
document.addEventListener('preloaderHidden', function () {
|
||||
window.location.href = url;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Never play util windows loaded @see https://github.com/ossrs/srs/issues/2732
|
||||
$('#rtc_media_player').prop('muted', true);
|
||||
console.warn('For autostart, we should mute it, see https://www.jianshu.com/p/c3c6944eed5a ' +
|
||||
'or https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#audiovideo_elements');
|
||||
window.addEventListener("load", function(){ startPlay(); });
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
using namespace std;
|
||||
|
||||
#include <srs_kernel_error.hpp>
|
||||
|
|
@ -333,12 +334,30 @@ bool srs_config_apply_filter(SrsConfDirective* dvr_apply, SrsRequest* req)
|
|||
if (args.size() == 1 && dvr_apply->arg0() == "all") {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
string id = req->app + "/" + req->stream;
|
||||
if (std::find(args.begin(), args.end(), id) != args.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
for (const string& pattern : args) {
|
||||
// 如果模式以/开始和结束,则视为正则表达式
|
||||
if (pattern.size() >= 2 && pattern.front() == '/' && pattern.back() == '/') {
|
||||
try {
|
||||
std::regex re(pattern.substr(1, pattern.size() - 2));
|
||||
if (std::regex_match(id, re)) {
|
||||
return true;
|
||||
}
|
||||
} catch (const std::regex_error&) {
|
||||
// 正则表达式无效,跳过这条规则
|
||||
continue;
|
||||
}
|
||||
} else if (pattern == id) {
|
||||
// 普通字符串匹配
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <map>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
|
||||
#include <srs_app_reload.hpp>
|
||||
#include <srs_app_async_call.hpp>
|
||||
|
|
|
|||
|
|
@ -1068,7 +1068,7 @@ srs_error_t SrsHlsController::write_video(SrsVideoFrame* frame, int64_t dts)
|
|||
|
||||
// Refresh the codec ASAP.
|
||||
if (muxer->latest_vcodec() != frame->vcodec()->id) {
|
||||
srs_trace("HLS: Switch video codec %d(%s) to %d(%s)", muxer->latest_vcodec(), srs_video_codec_id2str(muxer->latest_vcodec()).c_str(),
|
||||
srs_trace("HLS: Switch video codec %d(%s) to %d(%s)", muxer->latest_acodec(), srs_video_codec_id2str(muxer->latest_vcodec()).c_str(),
|
||||
frame->vcodec()->id, srs_video_codec_id2str(frame->vcodec()->id).c_str());
|
||||
muxer->set_latest_vcodec(frame->vcodec()->id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2598,51 +2598,6 @@ bool srs_sdp_has_h264_profile(const SrsSdp& sdp, const string& profile)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool srs_sdp_has_h265_profile(const SrsMediaPayloadType& payload_type, const string& profile)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
if (payload_type.format_specific_param_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
H265SpecificParam h265_param;
|
||||
if ((err = srs_parse_h265_fmtp(payload_type.format_specific_param_, h265_param)) != srs_success) {
|
||||
srs_error_reset(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (h265_param.profile_id == profile) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool srs_sdp_has_h265_profile(const SrsSdp& sdp, const string& profile)
|
||||
{
|
||||
for (size_t i = 0; i < sdp.media_descs_.size(); ++i) {
|
||||
const SrsMediaDesc& desc = sdp.media_descs_[i];
|
||||
if (!desc.is_video()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<SrsMediaPayloadType> payloads = desc.find_media_with_encoding_name("H265");
|
||||
if (payloads.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (std::vector<SrsMediaPayloadType>::iterator it = payloads.begin(); it != payloads.end(); ++it) {
|
||||
const SrsMediaPayloadType& payload_type = *it;
|
||||
if (srs_sdp_has_h265_profile(payload_type, profile)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
srs_error_t SrsRtcConnection::negotiate_publish_capability(SrsRtcUserConfig* ruc, SrsRtcSourceDescription* stream_desc)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
|
@ -3086,6 +3041,8 @@ srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRtcUserConfig* ruc, s
|
|||
|
||||
bool nack_enabled = _srs_config->get_rtc_nack_enabled(req->vhost);
|
||||
bool twcc_enabled = _srs_config->get_rtc_twcc_enabled(req->vhost);
|
||||
// TODO: FIME: Should check packetization-mode=1 also.
|
||||
bool has_42e01f = srs_sdp_has_h264_profile(remote_sdp, "42e01f");
|
||||
|
||||
SrsSharedPtr<SrsRtcSource> source;
|
||||
if ((err = _srs_rtc_sources->fetch_or_create(req, source)) != srs_success) {
|
||||
|
|
@ -3126,87 +3083,56 @@ srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRtcUserConfig* ruc, s
|
|||
|
||||
remote_payload = payloads.at(0);
|
||||
track_descs = source->get_track_desc("audio", "opus");
|
||||
} else if (remote_media_desc.is_video() && ruc->codec_ == "av1") {
|
||||
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("AV1");
|
||||
if (payloads.empty()) {
|
||||
// Be compatible with the Chrome M96, still check the AV1X encoding name
|
||||
// @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166
|
||||
payloads = remote_media_desc.find_media_with_encoding_name("AV1X");
|
||||
}
|
||||
if (payloads.empty()) {
|
||||
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid AV1 payload type");
|
||||
}
|
||||
|
||||
remote_payload = payloads.at(0);
|
||||
track_descs = source->get_track_desc("video", "AV1");
|
||||
if (track_descs.empty()) {
|
||||
// Be compatible with the Chrome M96, still check the AV1X encoding name
|
||||
// @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166
|
||||
track_descs = source->get_track_desc("video", "AV1X");
|
||||
}
|
||||
} else if (remote_media_desc.is_video() && ruc->codec_ == "hevc") {
|
||||
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H265");
|
||||
if (payloads.empty()) {
|
||||
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found h265 payload type");
|
||||
}
|
||||
|
||||
remote_payload = payloads.at(0);
|
||||
|
||||
// TODO: FIXME: pick up a profile for HEVC.
|
||||
// @see https://www.rfc-editor.org/rfc/rfc7798#section-7.2.1
|
||||
|
||||
track_descs = source->get_track_desc("video", "H265");
|
||||
} else if (remote_media_desc.is_video()) {
|
||||
std::string prefer_codec = ruc->codec_;
|
||||
if (prefer_codec.empty()) {
|
||||
// Get the source codec if not specified.
|
||||
std::vector<SrsRtcTrackDescription*> track_descs = source->get_track_desc("video", "");
|
||||
if (!track_descs.empty()) {
|
||||
std::string codec_name = track_descs.at(0)->media_->name_;
|
||||
std::transform(codec_name.begin(), codec_name.end(), codec_name.begin(), ::tolower);
|
||||
if (codec_name == "h265") {
|
||||
prefer_codec = "hevc";
|
||||
} else {
|
||||
prefer_codec = codec_name;
|
||||
}
|
||||
} else {
|
||||
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no video track in source");
|
||||
}
|
||||
// TODO: check opus format specific param
|
||||
vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H264");
|
||||
if (payloads.empty()) {
|
||||
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found h264 payload type");
|
||||
}
|
||||
|
||||
if (prefer_codec == "av1") {
|
||||
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("AV1");
|
||||
if (payloads.empty()) {
|
||||
// Be compatible with the Chrome M96, still check the AV1X encoding name
|
||||
// @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166
|
||||
payloads = remote_media_desc.find_media_with_encoding_name("AV1X");
|
||||
}
|
||||
if (payloads.empty()) {
|
||||
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid AV1 payload type");
|
||||
}
|
||||
|
||||
remote_payload = payloads.at(0);
|
||||
track_descs = source->get_track_desc("video", "AV1");
|
||||
if (track_descs.empty()) {
|
||||
// Be compatible with the Chrome M96, still check the AV1X encoding name
|
||||
// @see https://bugs.chromium.org/p/webrtc/issues/detail?id=13166
|
||||
track_descs = source->get_track_desc("video", "AV1X");
|
||||
}
|
||||
} else if (prefer_codec == "hevc") {
|
||||
std::vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H265");
|
||||
if (payloads.empty()) {
|
||||
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found h265 payload type");
|
||||
}
|
||||
|
||||
// @see https://www.rfc-editor.org/rfc/rfc7798#section-7.2.1
|
||||
bool has_main_profile = srs_sdp_has_h265_profile(remote_sdp, "1");
|
||||
remote_payload = payloads.at(0);
|
||||
|
||||
for (int j = 0; j < (int)payloads.size(); j++) {
|
||||
const SrsMediaPayloadType& payload = payloads.at(j);
|
||||
|
||||
// For H.265, we only check if profile-id=1 (Main Profile)
|
||||
// Format example: level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST
|
||||
if (!has_main_profile || srs_sdp_has_h265_profile(payload, "1")) {
|
||||
remote_payload = payload;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
track_descs = source->get_track_desc("video", "H265");
|
||||
} else {
|
||||
vector<SrsMediaPayloadType> payloads = remote_media_desc.find_media_with_encoding_name("H264");
|
||||
if (payloads.empty()) {
|
||||
return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found h264 payload type");
|
||||
}
|
||||
remote_payload = payloads.at(0);
|
||||
for (int j = 0; j < (int)payloads.size(); j++) {
|
||||
const SrsMediaPayloadType& payload = payloads.at(j);
|
||||
|
||||
// If exists 42e01f profile, choose it; otherwise, use the first payload.
|
||||
// TODO: FIME: Should check packetization-mode=1 also.
|
||||
bool has_42e01f = srs_sdp_has_h264_profile(remote_sdp, "42e01f");
|
||||
|
||||
remote_payload = payloads.at(0);
|
||||
for (int j = 0; j < (int)payloads.size(); j++) {
|
||||
const SrsMediaPayloadType& payload = payloads.at(j);
|
||||
|
||||
// If exists 42e01f profile, choose it; otherwise, use the first payload.
|
||||
// TODO: FIME: Should check packetization-mode=1 also.
|
||||
if (!has_42e01f || srs_sdp_has_h264_profile(payload, "42e01f")) {
|
||||
remote_payload = payload;
|
||||
break;
|
||||
}
|
||||
if (!has_42e01f || srs_sdp_has_h264_profile(payload, "42e01f")) {
|
||||
remote_payload = payload;
|
||||
break;
|
||||
}
|
||||
|
||||
track_descs = source->get_track_desc("video", "H264");
|
||||
}
|
||||
|
||||
track_descs = source->get_track_desc("video", "H264");
|
||||
}
|
||||
|
||||
for (int j = 0; j < (int)track_descs.size(); ++j) {
|
||||
|
|
@ -3312,11 +3238,7 @@ void video_track_generate_play_offer(SrsRtcTrackDescription* track, string mid,
|
|||
|
||||
SrsVideoPayload* payload = (SrsVideoPayload*)track->media_;
|
||||
|
||||
if (payload->name_ == "H265") {
|
||||
local_media_desc.payload_types_.push_back(payload->generate_media_payload_type_h265());
|
||||
} else {
|
||||
local_media_desc.payload_types_.push_back(payload->generate_media_payload_type());
|
||||
}
|
||||
local_media_desc.payload_types_.push_back(payload->generate_media_payload_type());
|
||||
|
||||
if (track->red_) {
|
||||
SrsRedPayload* red_payload = (SrsRedPayload*)track->red_;
|
||||
|
|
|
|||
|
|
@ -92,42 +92,6 @@ srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264
|
|||
return err;
|
||||
}
|
||||
|
||||
srs_error_t srs_parse_h265_fmtp(const std::string& fmtp, H265SpecificParam& h265_param)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
std::vector<std::string> vec = srs_string_split(fmtp, ";");
|
||||
for (size_t i = 0; i < vec.size(); ++i) {
|
||||
std::vector<std::string> kv = srs_string_split(vec[i], "=");
|
||||
if (kv.size() != 2) continue;
|
||||
|
||||
if (kv[0] == "level-id") {
|
||||
h265_param.level_id = kv[1];
|
||||
} else if (kv[0] == "profile-id") {
|
||||
h265_param.profile_id = kv[1];
|
||||
} else if (kv[0] == "tier-flag") {
|
||||
h265_param.tier_flag = kv[1];
|
||||
} else if (kv[0] == "tx-mode") {
|
||||
h265_param.tx_mode = kv[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (h265_param.level_id.empty()) {
|
||||
return srs_error_new(ERROR_RTC_SDP_DECODE, "no h265 param: level-id");
|
||||
}
|
||||
if (h265_param.profile_id.empty()) {
|
||||
return srs_error_new(ERROR_RTC_SDP_DECODE, "no h265 param: profile-id");
|
||||
}
|
||||
if (h265_param.tier_flag.empty()) {
|
||||
return srs_error_new(ERROR_RTC_SDP_DECODE, "no h265 param: tier-flag");
|
||||
}
|
||||
if (h265_param.tx_mode.empty()) {
|
||||
return srs_error_new(ERROR_RTC_SDP_DECODE, "no h265 param: tx-mode");
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
SrsSessionInfo::SrsSessionInfo()
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,16 +97,8 @@ struct H264SpecificParam
|
|||
std::string level_asymmerty_allow;
|
||||
};
|
||||
|
||||
struct H265SpecificParam
|
||||
{
|
||||
std::string level_id;
|
||||
std::string profile_id;
|
||||
std::string tier_flag;
|
||||
std::string tx_mode;
|
||||
};
|
||||
|
||||
extern srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264_param);
|
||||
extern srs_error_t srs_parse_h265_fmtp(const std::string& fmtp, H265SpecificParam& h265_param);
|
||||
|
||||
class SrsMediaPayloadType
|
||||
{
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -785,15 +785,7 @@ std::vector<SrsRtcTrackDescription*> SrsRtcSource::get_track_desc(std::string ty
|
|||
if (type == "video") {
|
||||
std::vector<SrsRtcTrackDescription*>::iterator it = stream_desc_->video_track_descs_.begin();
|
||||
while (it != stream_desc_->video_track_descs_.end() ){
|
||||
if (media_name.empty()) {
|
||||
track_descs.push_back(*it);
|
||||
} else {
|
||||
string name = (*it)->media_->name_;
|
||||
std::transform(name.begin(), name.end(), name.begin(), static_cast<int(*)(int)>(std::toupper));
|
||||
if (name == media_name) {
|
||||
track_descs.push_back(*it);
|
||||
}
|
||||
}
|
||||
track_descs.push_back(*it);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
|
@ -1082,15 +1074,10 @@ srs_error_t SrsRtcRtpBuilder::on_video(SrsSharedPtrMessage* msg)
|
|||
return err;
|
||||
}
|
||||
|
||||
// support video codec: h264/h265
|
||||
SrsVideoCodecId vcodec = format->vcodec->id;
|
||||
if (vcodec != SrsVideoCodecIdAVC && vcodec != SrsVideoCodecIdHEVC) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// WebRTC does NOT support HEVC.
|
||||
#ifdef SRS_H265
|
||||
if ((err = bridge_->update_codec(vcodec)) != srs_success) {
|
||||
return srs_error_wrap(err, "update codec");
|
||||
if (format->vcodec->id == SrsVideoCodecIdHEVC) {
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -1174,16 +1161,10 @@ srs_error_t SrsRtcRtpBuilder::filter(SrsSharedPtrMessage* msg, SrsFormat* format
|
|||
|
||||
// Because RTC does not support B-frame, so we will drop them.
|
||||
// TODO: Drop B-frame in better way, which not cause picture corruption.
|
||||
if (!keep_bframe) {
|
||||
bool is_b_frame = false;
|
||||
if (format->vcodec->id == SrsVideoCodecIdAVC) {
|
||||
if ((err = SrsVideoFrame::parse_avc_bframe(sample, is_b_frame)) != srs_success) {
|
||||
return srs_error_wrap(err, "parse bframe");
|
||||
}
|
||||
} else if (format->vcodec->id == SrsVideoCodecIdHEVC) {
|
||||
if ((err = SrsVideoFrame::parse_hevc_bframe(sample, format, is_b_frame)) != srs_success) {
|
||||
return srs_error_wrap(err, "parse bframe");
|
||||
}
|
||||
if (!keep_bframe && format->vcodec->id == SrsVideoCodecIdAVC) {
|
||||
bool is_b_frame;
|
||||
if ((err = SrsVideoFrame::parse_avc_b_frame(sample, is_b_frame)) != srs_success) {
|
||||
return srs_error_wrap(err, "parse bframe");
|
||||
}
|
||||
if (is_b_frame) {
|
||||
continue;
|
||||
|
|
@ -1205,60 +1186,53 @@ srs_error_t SrsRtcRtpBuilder::package_stap_a(SrsSharedPtrMessage* msg, SrsRtpPac
|
|||
return err;
|
||||
}
|
||||
|
||||
// Note that the sps/pps may change, so we should copy it.
|
||||
const vector<char>& sps = format->vcodec->sequenceParameterSetNALUnit;
|
||||
const vector<char>& pps = format->vcodec->pictureParameterSetNALUnit;
|
||||
if (sps.empty() || pps.empty()) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "sps/pps empty");
|
||||
}
|
||||
|
||||
pkt->header.set_payload_type(video_payload_type_);
|
||||
pkt->header.set_ssrc(video_ssrc_);
|
||||
pkt->frame_type = SrsFrameTypeVideo;
|
||||
pkt->nalu_type = (SrsAvcNaluType)kStapA;
|
||||
pkt->header.set_marker(false);
|
||||
pkt->header.set_sequence(video_sequence++);
|
||||
pkt->header.set_timestamp(msg->timestamp * 90);
|
||||
|
||||
ISrsRtpPayloader* stap = NULL;
|
||||
vector<vector<char>*> params;
|
||||
int size = 0;
|
||||
if (format->vcodec->id == SrsVideoCodecIdHEVC) {
|
||||
for (size_t i = 0; i < format->vcodec->hevc_dec_conf_record_.nalu_vec.size(); i++) {
|
||||
if (format->vcodec->hevc_dec_conf_record_.nalu_vec[i].nal_unit_type == SrsHevcNaluType_VPS
|
||||
|| format->vcodec->hevc_dec_conf_record_.nalu_vec[i].nal_unit_type == SrsHevcNaluType_SPS
|
||||
|| format->vcodec->hevc_dec_conf_record_.nalu_vec[i].nal_unit_type == SrsHevcNaluType_PPS) {
|
||||
vector<char>& nalu = (vector<char>&)format->vcodec->hevc_dec_conf_record_.nalu_vec[i].nal_data_vec[0].nal_unit_data;
|
||||
params.push_back(&nalu);
|
||||
size += format->vcodec->hevc_dec_conf_record_.nalu_vec[i].nal_data_vec[0].nal_unit_length;
|
||||
}
|
||||
}
|
||||
SrsRtpSTAPPayload* stap = new SrsRtpSTAPPayload();
|
||||
pkt->set_payload(stap, SrsRtspPacketPayloadTypeSTAP);
|
||||
|
||||
stap = new SrsRtpSTAPPayloadHevc();
|
||||
pkt->set_payload(stap, SrsRtspPacketPayloadTypeSTAPHevc);
|
||||
pkt->nalu_type = kStapHevc;
|
||||
} else if (format->vcodec->id == SrsVideoCodecIdAVC) {
|
||||
params.push_back(&format->vcodec->sequenceParameterSetNALUnit);
|
||||
params.push_back(&format->vcodec->pictureParameterSetNALUnit);
|
||||
size = format->vcodec->sequenceParameterSetNALUnit.size() + format->vcodec->pictureParameterSetNALUnit.size();
|
||||
uint8_t header = sps[0];
|
||||
stap->nri = (SrsAvcNaluType)header;
|
||||
|
||||
stap = new SrsRtpSTAPPayload();
|
||||
pkt->set_payload(stap, SrsRtspPacketPayloadTypeSTAP);
|
||||
pkt->nalu_type = kStapA;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "vps/sps/pps empty");
|
||||
}
|
||||
// Copy the SPS/PPS bytes, because it may change.
|
||||
int size = (int)(sps.size() + pps.size());
|
||||
char* payload = pkt->wrap(size);
|
||||
|
||||
for (vector<vector<char>*>::iterator it = params.begin(); it != params.end(); ++it) {
|
||||
vector<char>* param = *it;
|
||||
if (true) {
|
||||
SrsSample* sample = new SrsSample();
|
||||
sample->bytes = payload;
|
||||
sample->size = param->size();
|
||||
if (format->vcodec->id == SrsVideoCodecIdHEVC) {
|
||||
static_cast<SrsRtpSTAPPayloadHevc*>(stap)->nalus.push_back(sample);
|
||||
} else {
|
||||
static_cast<SrsRtpSTAPPayload*>(stap)->nalus.push_back(sample);
|
||||
}
|
||||
sample->size = (int)sps.size();
|
||||
stap->nalus.push_back(sample);
|
||||
|
||||
memcpy(payload, (char*)param->data(), param->size());
|
||||
payload += (int)param->size();
|
||||
memcpy(payload, (char*)&sps[0], sps.size());
|
||||
payload += (int)sps.size();
|
||||
}
|
||||
|
||||
if (true) {
|
||||
SrsSample* sample = new SrsSample();
|
||||
sample->bytes = payload;
|
||||
sample->size = (int)pps.size();
|
||||
stap->nalus.push_back(sample);
|
||||
|
||||
memcpy(payload, (char*)&pps[0], pps.size());
|
||||
payload += (int)pps.size();
|
||||
}
|
||||
|
||||
srs_info("RTC STAP-A seq=%u, sps %d, pps %d bytes", pkt->header.get_sequence(), sps.size(), pps.size());
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -1266,14 +1240,8 @@ srs_error_t SrsRtcRtpBuilder::package_nalus(SrsSharedPtrMessage* msg, const vect
|
|||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
SrsFormat* format = meta->vsh_format();
|
||||
if (!format || !format->vcodec) {
|
||||
return err;
|
||||
}
|
||||
bool is_hevc = format->vcodec->id == SrsVideoCodecIdHEVC;
|
||||
|
||||
SrsRtpRawNALUs* raw_raw = new SrsRtpRawNALUs();
|
||||
uint8_t first_nalu_type = 0;
|
||||
SrsAvcNaluType first_nalu_type = SrsAvcNaluTypeReserved;
|
||||
|
||||
for (int i = 0; i < (int)samples.size(); i++) {
|
||||
SrsSample* sample = samples[i];
|
||||
|
|
@ -1282,8 +1250,8 @@ srs_error_t SrsRtcRtpBuilder::package_nalus(SrsSharedPtrMessage* msg, const vect
|
|||
continue;
|
||||
}
|
||||
|
||||
if (first_nalu_type == 0) {
|
||||
first_nalu_type = is_hevc ? uint8_t(SrsHevcNaluTypeParse(sample->bytes[0])) : uint8_t(SrsAvcNaluTypeParse(sample->bytes[0]));
|
||||
if (first_nalu_type == SrsAvcNaluTypeReserved) {
|
||||
first_nalu_type = SrsAvcNaluType((uint8_t)(sample->bytes[0] & kNalTypeMask));
|
||||
}
|
||||
|
||||
raw_raw->push_back(sample->copy());
|
||||
|
|
@ -1304,7 +1272,7 @@ srs_error_t SrsRtcRtpBuilder::package_nalus(SrsSharedPtrMessage* msg, const vect
|
|||
pkt->header.set_payload_type(video_payload_type_);
|
||||
pkt->header.set_ssrc(video_ssrc_);
|
||||
pkt->frame_type = SrsFrameTypeVideo;
|
||||
pkt->nalu_type = first_nalu_type;
|
||||
pkt->nalu_type = (SrsAvcNaluType)first_nalu_type;
|
||||
pkt->header.set_sequence(video_sequence++);
|
||||
pkt->header.set_timestamp(msg->timestamp * 90);
|
||||
pkt->set_payload(raw_raw, SrsRtspPacketPayloadTypeNALU);
|
||||
|
|
@ -1314,54 +1282,40 @@ srs_error_t SrsRtcRtpBuilder::package_nalus(SrsSharedPtrMessage* msg, const vect
|
|||
// because more than one RTP packet will refer to it.
|
||||
SrsUniquePtr<SrsRtpRawNALUs> raw(raw_raw);
|
||||
|
||||
int header_size = is_hevc ? SrsHevcNaluHeaderSize : SrsAvcNaluHeaderSize;
|
||||
|
||||
// Package NALUs in FU-A RTP packets.
|
||||
int fu_payload_size = kRtpMaxPayloadSize;
|
||||
|
||||
// The first byte is store in FU-A header.
|
||||
uint8_t header = raw->skip_bytes(header_size);
|
||||
|
||||
int nb_left = nn_bytes - header_size;
|
||||
uint8_t header = raw->skip_first_byte();
|
||||
uint8_t nal_type = header & kNalTypeMask;
|
||||
int nb_left = nn_bytes - 1;
|
||||
|
||||
int num_of_packet = 1 + (nn_bytes - 1) / fu_payload_size;
|
||||
for (int i = 0; i < num_of_packet; ++i) {
|
||||
int packet_size = srs_min(nb_left, fu_payload_size);
|
||||
|
||||
SrsRtpFUAPayload* fua = new SrsRtpFUAPayload();
|
||||
if ((err = raw->read_samples(fua->nalus, packet_size)) != srs_success) {
|
||||
srs_freep(fua);
|
||||
return srs_error_wrap(err, "read samples %d bytes, left %d, total %d", packet_size, nb_left, nn_bytes);
|
||||
}
|
||||
|
||||
SrsRtpPacket* pkt = new SrsRtpPacket();
|
||||
pkts.push_back(pkt);
|
||||
|
||||
pkt->header.set_payload_type(video_payload_type_);
|
||||
pkt->header.set_ssrc(video_ssrc_);
|
||||
pkt->frame_type = SrsFrameTypeVideo;
|
||||
pkt->nalu_type = kFuA;
|
||||
pkt->nalu_type = (SrsAvcNaluType)kFuA;
|
||||
pkt->header.set_sequence(video_sequence++);
|
||||
pkt->header.set_timestamp(msg->timestamp * 90);
|
||||
|
||||
if (is_hevc) {
|
||||
SrsRtpFUAPayloadHevc* fua = new SrsRtpFUAPayloadHevc();
|
||||
if ((err = raw->read_samples(fua->nalus, packet_size)) != srs_success) {
|
||||
srs_freep(fua);
|
||||
return srs_error_wrap(err, "read hevc samples %d bytes, left %d, total %d", packet_size, nb_left, nn_bytes);
|
||||
}
|
||||
fua->nalu_type = SrsHevcNaluTypeParse(header);
|
||||
fua->start = bool(i == 0);
|
||||
fua->end = bool(i == num_of_packet - 1);
|
||||
|
||||
pkt->set_payload(fua, SrsRtspPacketPayloadTypeFUAHevc);
|
||||
} else {
|
||||
SrsRtpFUAPayload* fua = new SrsRtpFUAPayload();
|
||||
if ((err = raw->read_samples(fua->nalus, packet_size)) != srs_success) {
|
||||
srs_freep(fua);
|
||||
return srs_error_wrap(err, "read samples %d bytes, left %d, total %d", packet_size, nb_left, nn_bytes);
|
||||
}
|
||||
fua->nalu_type = SrsAvcNaluTypeParse(header);
|
||||
fua->start = bool(i == 0);
|
||||
fua->end = bool(i == num_of_packet - 1);
|
||||
|
||||
pkt->set_payload(fua, SrsRtspPacketPayloadTypeFUA);
|
||||
}
|
||||
fua->nri = (SrsAvcNaluType)header;
|
||||
fua->nalu_type = (SrsAvcNaluType)nal_type;
|
||||
fua->start = bool(i == 0);
|
||||
fua->end = bool(i == num_of_packet - 1);
|
||||
|
||||
pkt->set_payload(fua, SrsRtspPacketPayloadTypeFUA);
|
||||
pkt->wrap(msg);
|
||||
|
||||
nb_left -= packet_size;
|
||||
|
|
@ -1400,19 +1354,11 @@ srs_error_t SrsRtcRtpBuilder::package_fu_a(SrsSharedPtrMessage* msg, SrsSample*
|
|||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
SrsFormat* format = meta->vsh_format();
|
||||
if (!format || !format->vcodec) {
|
||||
return err;
|
||||
}
|
||||
|
||||
bool is_hevc = format->vcodec->id == SrsVideoCodecIdHEVC;
|
||||
int header_size = is_hevc ? SrsHevcNaluHeaderSize : SrsAvcNaluHeaderSize;
|
||||
srs_assert(sample->size >= header_size);
|
||||
|
||||
char* p = sample->bytes + header_size;
|
||||
int nb_left = sample->size - header_size;
|
||||
char* p = sample->bytes + 1;
|
||||
int nb_left = sample->size - 1;
|
||||
uint8_t header = sample->bytes[0];
|
||||
|
||||
uint8_t nal_type = header & kNalTypeMask;
|
||||
|
||||
int num_of_packet = 1 + (nb_left - 1) / fu_payload_size;
|
||||
for (int i = 0; i < num_of_packet; ++i) {
|
||||
int packet_size = srs_min(nb_left, fu_payload_size);
|
||||
|
|
@ -1425,32 +1371,17 @@ srs_error_t SrsRtcRtpBuilder::package_fu_a(SrsSharedPtrMessage* msg, SrsSample*
|
|||
pkt->frame_type = SrsFrameTypeVideo;
|
||||
pkt->header.set_sequence(video_sequence++);
|
||||
pkt->header.set_timestamp(msg->timestamp * 90);
|
||||
pkt->nalu_type = is_hevc ? kFuHevc : kFuA;
|
||||
|
||||
if (is_hevc) {
|
||||
// H265 FU-A header
|
||||
SrsRtpFUAPayloadHevc2* fua = new SrsRtpFUAPayloadHevc2();
|
||||
pkt->set_payload(fua, SrsRtspPacketPayloadTypeFUAHevc2);
|
||||
SrsRtpFUAPayload2* fua = new SrsRtpFUAPayload2();
|
||||
pkt->set_payload(fua, SrsRtspPacketPayloadTypeFUA2);
|
||||
|
||||
fua->nalu_type = SrsHevcNaluTypeParse(header);
|
||||
fua->start = bool(i == 0);
|
||||
fua->end = bool(i == num_of_packet - 1);
|
||||
fua->nri = (SrsAvcNaluType)header;
|
||||
fua->nalu_type = (SrsAvcNaluType)nal_type;
|
||||
fua->start = bool(i == 0);
|
||||
fua->end = bool(i == num_of_packet - 1);
|
||||
|
||||
fua->payload = p;
|
||||
fua->size = packet_size;
|
||||
} else {
|
||||
// H264 FU-A header
|
||||
SrsRtpFUAPayload2* fua = new SrsRtpFUAPayload2();
|
||||
pkt->set_payload(fua, SrsRtspPacketPayloadTypeFUA2);
|
||||
|
||||
fua->nri = (SrsAvcNaluType)header;
|
||||
fua->nalu_type = SrsAvcNaluTypeParse(header);
|
||||
fua->start = bool(i == 0);
|
||||
fua->end = bool(i == num_of_packet - 1);
|
||||
|
||||
fua->payload = p;
|
||||
fua->size = packet_size;
|
||||
}
|
||||
fua->payload = p;
|
||||
fua->size = packet_size;
|
||||
|
||||
pkt->wrap(msg);
|
||||
|
||||
|
|
@ -2134,7 +2065,6 @@ SrsVideoPayload* SrsVideoPayload::copy()
|
|||
cp->sample_ = sample_;
|
||||
cp->rtcp_fbs_ = rtcp_fbs_;
|
||||
cp->h264_param_ = h264_param_;
|
||||
cp->h265_param_ = h265_param_;
|
||||
|
||||
return cp;
|
||||
}
|
||||
|
|
@ -2148,55 +2078,14 @@ SrsMediaPayloadType SrsVideoPayload::generate_media_payload_type()
|
|||
media_payload_type.rtcp_fb_ = rtcp_fbs_;
|
||||
|
||||
std::ostringstream format_specific_param;
|
||||
bool has_param = false;
|
||||
|
||||
if (!h264_param_.level_asymmerty_allow.empty()) {
|
||||
format_specific_param << "level-asymmetry-allowed=" << h264_param_.level_asymmerty_allow;
|
||||
has_param = true;
|
||||
}
|
||||
if (!h264_param_.packetization_mode.empty()) {
|
||||
if (has_param) format_specific_param << ";";
|
||||
format_specific_param << "packetization-mode=" << h264_param_.packetization_mode;
|
||||
has_param = true;
|
||||
format_specific_param << ";packetization-mode=" << h264_param_.packetization_mode;
|
||||
}
|
||||
if (!h264_param_.profile_level_id.empty()) {
|
||||
if (has_param) format_specific_param << ";";
|
||||
format_specific_param << "profile-level-id=" << h264_param_.profile_level_id;
|
||||
}
|
||||
|
||||
media_payload_type.format_specific_param_ = format_specific_param.str();
|
||||
|
||||
return media_payload_type;
|
||||
}
|
||||
|
||||
SrsMediaPayloadType SrsVideoPayload::generate_media_payload_type_h265()
|
||||
{
|
||||
SrsMediaPayloadType media_payload_type(pt_);
|
||||
|
||||
media_payload_type.encoding_name_ = name_;
|
||||
media_payload_type.clock_rate_ = sample_;
|
||||
media_payload_type.rtcp_fb_ = rtcp_fbs_;
|
||||
|
||||
std::ostringstream format_specific_param;
|
||||
bool has_param = false;
|
||||
|
||||
if (!h265_param_.level_id.empty()) {
|
||||
format_specific_param << "level-id=" << h265_param_.level_id;
|
||||
has_param = true;
|
||||
}
|
||||
if (!h265_param_.profile_id.empty()) {
|
||||
if (has_param) format_specific_param << ";";
|
||||
format_specific_param << "profile-id=" << h265_param_.profile_id;
|
||||
has_param = true;
|
||||
}
|
||||
if (!h265_param_.tier_flag.empty()) {
|
||||
if (has_param) format_specific_param << ";";
|
||||
format_specific_param << "tier-flag=" << h265_param_.tier_flag;
|
||||
has_param = true;
|
||||
}
|
||||
if (!h265_param_.tx_mode.empty()) {
|
||||
if (has_param) format_specific_param << ";";
|
||||
format_specific_param << "tx-mode=" << h265_param_.tx_mode;
|
||||
format_specific_param << ";profile-level-id=" << h264_param_.profile_level_id;
|
||||
}
|
||||
|
||||
media_payload_type.format_specific_param_ = format_specific_param.str();
|
||||
|
|
@ -2241,31 +2130,6 @@ srs_error_t SrsVideoPayload::set_h264_param_desc(std::string fmtp)
|
|||
return err;
|
||||
}
|
||||
|
||||
// level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST
|
||||
srs_error_t SrsVideoPayload::set_h265_param_desc(std::string fmtp)
|
||||
{
|
||||
std::vector<std::string> attributes = split_str(fmtp, ";");
|
||||
for (size_t i = 0; i < attributes.size(); ++i) {
|
||||
std::string attribute = attributes.at(i);
|
||||
std::vector<std::string> kv = split_str(attribute, "=");
|
||||
if (kv.size() != 2) {
|
||||
return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid h265 param=%s", attribute.c_str());
|
||||
}
|
||||
if (kv[0] == "level-id") {
|
||||
h265_param_.level_id = kv[1];
|
||||
} else if (kv[0] == "profile-id") {
|
||||
h265_param_.profile_id = kv[1];
|
||||
} else if (kv[0] == "tier-flag") {
|
||||
h265_param_.tier_flag = kv[1];
|
||||
} else if (kv[0] == "tx-mode") {
|
||||
h265_param_.tx_mode = kv[1];
|
||||
} else {
|
||||
return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid h265 param=%s", kv[0].c_str());
|
||||
}
|
||||
}
|
||||
return srs_success;
|
||||
}
|
||||
|
||||
SrsAudioPayload::SrsAudioPayload()
|
||||
{
|
||||
channel_ = 0;
|
||||
|
|
@ -2858,7 +2722,7 @@ void SrsRtcVideoRecvTrack::on_before_decode_payload(SrsRtpPacket* pkt, SrsBuffer
|
|||
}
|
||||
|
||||
uint8_t v = (uint8_t)(buf->head()[0] & kNalTypeMask);
|
||||
pkt->nalu_type = v;
|
||||
pkt->nalu_type = SrsAvcNaluType(v);
|
||||
|
||||
if (v == kStapA) {
|
||||
*ppayload = new SrsRtpSTAPPayload();
|
||||
|
|
|
|||
|
|
@ -396,7 +396,6 @@ class SrsVideoPayload : public SrsCodecPayload
|
|||
{
|
||||
public:
|
||||
H264SpecificParam h264_param_;
|
||||
H265SpecificParam h265_param_;
|
||||
|
||||
public:
|
||||
SrsVideoPayload();
|
||||
|
|
@ -405,10 +404,8 @@ public:
|
|||
public:
|
||||
virtual SrsVideoPayload* copy();
|
||||
virtual SrsMediaPayloadType generate_media_payload_type();
|
||||
virtual SrsMediaPayloadType generate_media_payload_type_h265();
|
||||
public:
|
||||
srs_error_t set_h264_param_desc(std::string fmtp);
|
||||
srs_error_t set_h265_param_desc(std::string fmtp);
|
||||
};
|
||||
|
||||
// TODO: FIXME: Rename it.
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ SrsFrameToRtcBridge::SrsFrameToRtcBridge(SrsSharedPtr<SrsRtcSource> source)
|
|||
|
||||
// video track ssrc
|
||||
if (true) {
|
||||
std::vector<SrsRtcTrackDescription*> descs = source->get_track_desc("video", "");
|
||||
std::vector<SrsRtcTrackDescription*> descs = source->get_track_desc("video", "H264");
|
||||
if (!descs.empty()) {
|
||||
video_ssrc = descs.at(0)->ssrc_;
|
||||
}
|
||||
|
|
@ -95,8 +95,6 @@ SrsFrameToRtcBridge::SrsFrameToRtcBridge(SrsSharedPtr<SrsRtcSource> source)
|
|||
|
||||
rtp_builder_ = new SrsRtcRtpBuilder(this, audio_ssrc, audio_payload_type, video_ssrc, video_payload_type);
|
||||
#endif
|
||||
|
||||
video_codec_id_ = SrsVideoCodecIdReserved;
|
||||
}
|
||||
|
||||
SrsFrameToRtcBridge::~SrsFrameToRtcBridge()
|
||||
|
|
@ -157,39 +155,6 @@ srs_error_t SrsFrameToRtcBridge::on_rtp(SrsRtpPacket* pkt)
|
|||
{
|
||||
return source_->on_rtp(pkt);
|
||||
}
|
||||
|
||||
srs_error_t SrsFrameToRtcBridge::update_codec(SrsVideoCodecId id)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
if (video_codec_id_ == id) {
|
||||
return err;
|
||||
}
|
||||
|
||||
std::vector<SrsRtcTrackDescription*> video_track_descs = source_->get_track_desc("video", "");
|
||||
if (video_track_descs.empty()) {
|
||||
return srs_error_new(ERROR_RTC_NO_TRACK, "no track found for conversion");
|
||||
}
|
||||
|
||||
SrsRtcTrackDescription* video_track_desc = video_track_descs.at(0);
|
||||
SrsVideoPayload* video_payload = (SrsVideoPayload*)video_track_desc->media_;
|
||||
|
||||
if (id == SrsVideoCodecIdHEVC) {
|
||||
video_payload->name_ = "H265";
|
||||
video_payload->set_h265_param_desc("level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST");
|
||||
} else {
|
||||
video_payload->name_ = "H264";
|
||||
video_payload->set_h264_param_desc("level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f");
|
||||
}
|
||||
|
||||
srs_trace("RTC: Switch video codec %d(%s) to %d(%s)", video_codec_id_, srs_video_codec_id2str(video_codec_id_).c_str(),
|
||||
id, srs_video_codec_id2str(id).c_str());
|
||||
|
||||
video_codec_id_ = id;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
SrsCompositeBridge::SrsCompositeBridge()
|
||||
|
|
|
|||
|
|
@ -65,8 +65,6 @@ private:
|
|||
#if defined(SRS_FFMPEG_FIT)
|
||||
SrsRtcRtpBuilder* rtp_builder_;
|
||||
#endif
|
||||
private:
|
||||
SrsVideoCodecId video_codec_id_;
|
||||
public:
|
||||
SrsFrameToRtcBridge(SrsSharedPtr<SrsRtcSource> source);
|
||||
virtual ~SrsFrameToRtcBridge();
|
||||
|
|
@ -76,7 +74,6 @@ public:
|
|||
virtual void on_unpublish();
|
||||
virtual srs_error_t on_frame(SrsSharedPtrMessage* frame);
|
||||
srs_error_t on_rtp(SrsRtpPacket* pkt);
|
||||
srs_error_t update_codec(SrsVideoCodecId id);
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@
|
|||
|
||||
#define VERSION_MAJOR 7
|
||||
#define VERSION_MINOR 0
|
||||
#define VERSION_REVISION 33
|
||||
#define VERSION_REVISION 32
|
||||
|
||||
#endif
|
||||
|
|
@ -676,7 +676,7 @@ srs_error_t SrsVideoFrame::add_sample(char* bytes, int size)
|
|||
|
||||
// By default, use AVC(H.264) to parse NALU.
|
||||
// For video, parse the nalu type, set the IDR flag.
|
||||
SrsAvcNaluType nal_unit_type = SrsAvcNaluTypeParse(bytes[0]);
|
||||
SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(bytes[0] & 0x1f);
|
||||
|
||||
if (nal_unit_type == SrsAvcNaluTypeIDR) {
|
||||
has_idr = true;
|
||||
|
|
@ -703,25 +703,29 @@ srs_error_t SrsVideoFrame::parse_avc_nalu_type(const SrsSample* sample, SrsAvcNa
|
|||
srs_error_t err = srs_success;
|
||||
|
||||
if (sample == NULL || sample->size < 1) {
|
||||
return srs_error_new(ERROR_NALU_EMPTY, "empty nalu");
|
||||
return srs_error_new(ERROR_AVC_NALU_EMPTY, "empty nalu");
|
||||
}
|
||||
|
||||
uint8_t header = sample->bytes[0];
|
||||
avc_nalu_type = SrsAvcNaluTypeParse(header);
|
||||
avc_nalu_type = (SrsAvcNaluType)(header & kNalTypeMask);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
srs_error_t SrsVideoFrame::parse_avc_bframe(const SrsSample* sample, bool& is_b_frame)
|
||||
srs_error_t SrsVideoFrame::parse_avc_b_frame(const SrsSample* sample, bool& is_b_frame)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
if (sample == NULL || sample->size < 1) {
|
||||
return srs_error_new(ERROR_AVC_NALU_EMPTY, "empty nalu");
|
||||
}
|
||||
|
||||
SrsAvcNaluType nalu_type;
|
||||
if ((err = parse_avc_nalu_type(sample, nalu_type)) != srs_success) {
|
||||
return srs_error_wrap(err, "parse avc nalu type error");
|
||||
}
|
||||
|
||||
if (nalu_type == SrsAvcNaluTypeIDR || nalu_type == SrsAvcNaluTypeSPS || nalu_type == SrsAvcNaluTypePPS) {
|
||||
if (nalu_type != SrsAvcNaluTypeNonIDR && nalu_type != SrsAvcNaluTypeDataPartitionA && nalu_type != SrsAvcNaluTypeIDR) {
|
||||
is_b_frame = false;
|
||||
return err;
|
||||
}
|
||||
|
|
@ -751,87 +755,6 @@ srs_error_t SrsVideoFrame::parse_avc_bframe(const SrsSample* sample, bool& is_b_
|
|||
return err;
|
||||
}
|
||||
|
||||
srs_error_t SrsVideoFrame::parse_hevc_nalu_type(const SrsSample* sample, SrsHevcNaluType& hevc_nalu_type)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
if (sample == NULL || sample->size < 1) {
|
||||
return srs_error_new(ERROR_NALU_EMPTY, "empty hevc nalu");
|
||||
}
|
||||
|
||||
uint8_t header = sample->bytes[0];
|
||||
hevc_nalu_type = SrsHevcNaluTypeParse(header);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
srs_error_t SrsVideoFrame::parse_hevc_bframe(const SrsSample* sample, SrsFormat *format, bool& is_b_frame)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
SrsHevcNaluType nalu_type;
|
||||
if ((err = parse_hevc_nalu_type(sample, nalu_type)) != srs_success) {
|
||||
return srs_error_wrap(err, "parse hevc nalu type error");
|
||||
}
|
||||
|
||||
if (nalu_type > SrsHevcNaluType_CODED_SLICE_TFD) {
|
||||
is_b_frame = false;
|
||||
return err;
|
||||
}
|
||||
|
||||
SrsUniquePtr<SrsBuffer> stream(new SrsBuffer(sample->bytes, sample->size));
|
||||
stream->skip(2);
|
||||
|
||||
// @see 7.3.6.1 General slice segment header syntax
|
||||
// @doc ITU-T-H.265-2021.pdf, page 66.
|
||||
SrsBitBuffer bs(stream.get());
|
||||
|
||||
uint8_t first_slice_segment_in_pic_flag = bs.read_bit();
|
||||
|
||||
uint32_t slice_pic_parameter_set_id;
|
||||
if ((err = bs.read_bits_ue(slice_pic_parameter_set_id)) != srs_success) {
|
||||
return srs_error_wrap(err, "read slice pic parameter set id");
|
||||
}
|
||||
|
||||
if (slice_pic_parameter_set_id >= SrsHevcMax_PPS_COUNT) {
|
||||
return srs_error_new(ERROR_HEVC_DECODE_ERROR, "slice pic parameter set id out of range: %d", slice_pic_parameter_set_id);
|
||||
}
|
||||
|
||||
SrsHevcRbspPps *pps = &(format->vcodec->hevc_dec_conf_record_.pps_table[slice_pic_parameter_set_id]);
|
||||
if (!pps) {
|
||||
return srs_error_new(ERROR_HEVC_DECODE_ERROR, "pps not found");
|
||||
}
|
||||
|
||||
uint8_t dependent_slice_segment_flag = 0;
|
||||
if (!first_slice_segment_in_pic_flag) {
|
||||
if (pps->dependent_slice_segments_enabled_flag) {
|
||||
dependent_slice_segment_flag = bs.read_bit();
|
||||
}
|
||||
}
|
||||
|
||||
if (dependent_slice_segment_flag) {
|
||||
return srs_error_new(ERROR_HEVC_DECODE_ERROR, "dependent slice segment flag is not supported");
|
||||
}
|
||||
|
||||
for (int i = 0; i < pps->num_extra_slice_header_bits; i++) {
|
||||
bs.skip_bits(1);
|
||||
}
|
||||
|
||||
uint32_t slice_type;
|
||||
if ((err = bs.read_bits_ue(slice_type)) != srs_success) {
|
||||
return srs_error_wrap(err, "read slice type");
|
||||
}
|
||||
|
||||
is_b_frame = slice_type == SrsHevcSliceTypeB;
|
||||
if (is_b_frame) {
|
||||
srs_verbose("nalu_type=%d, slice type=%d", nalu_type, slice_type);
|
||||
}
|
||||
|
||||
// no need to evaluate the rest
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
SrsFormat::SrsFormat()
|
||||
{
|
||||
acodec = NULL;
|
||||
|
|
@ -2340,7 +2263,7 @@ srs_error_t SrsFormat::avc_demux_sps()
|
|||
// 7.4.1 NAL unit semantics
|
||||
// ISO_IEC_14496-10-AVC-2012.pdf, page 61.
|
||||
// nal_unit_type specifies the type of RBSP data structure contained in the NAL unit as specified in Table 7-1.
|
||||
SrsAvcNaluType nal_unit_type = SrsAvcNaluTypeParse(nutv);
|
||||
SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(nutv & 0x1f);
|
||||
if (nal_unit_type != 7) {
|
||||
return srs_error_new(ERROR_HLS_DECODE_ERROR, "for sps, nal_unit_type shall be equal to 7");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,6 @@
|
|||
|
||||
class SrsBuffer;
|
||||
class SrsBitBuffer;
|
||||
class SrsFormat;
|
||||
|
||||
// @see: https://datatracker.ietf.org/doc/html/rfc6184#section-1.3
|
||||
const int SrsAvcNaluHeaderSize = 1;
|
||||
// @see: https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4
|
||||
const int SrsHevcNaluHeaderSize = 2;
|
||||
|
||||
/**
|
||||
* The video codec id.
|
||||
|
|
@ -427,8 +421,6 @@ enum SrsAvcNaluType
|
|||
// Coded slice extension slice_layer_extension_rbsp( )
|
||||
SrsAvcNaluTypeCodedSliceExt = 20,
|
||||
};
|
||||
// @see https://datatracker.ietf.org/doc/html/rfc6184#section-1.3
|
||||
#define SrsAvcNaluTypeParse(code) (SrsAvcNaluType)(code & 0x1F)
|
||||
std::string srs_avc_nalu2str(SrsAvcNaluType nalu_type);
|
||||
|
||||
#ifdef SRS_H265
|
||||
|
|
@ -504,19 +496,8 @@ enum SrsHevcNaluType {
|
|||
SrsHevcNaluType_UNSPECIFIED_63,
|
||||
SrsHevcNaluType_INVALID,
|
||||
};
|
||||
// @see https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4
|
||||
#define SrsHevcNaluTypeParse(code) (SrsHevcNaluType)((code & 0x7E) >> 1)
|
||||
|
||||
/**
|
||||
* @see Table 7-7 – Name association to slice_type
|
||||
* @doc ITU-T-H.265-2021.pdf, page 116.
|
||||
*/
|
||||
enum SrsHevcSliceType {
|
||||
SrsHevcSliceTypeB = 0,
|
||||
SrsHevcSliceTypeP = 1,
|
||||
SrsHevcSliceTypeI = 2,
|
||||
};
|
||||
|
||||
struct SrsHevcNalData {
|
||||
uint16_t nal_unit_length;
|
||||
std::vector<uint8_t> nal_unit_data;
|
||||
|
|
@ -1339,10 +1320,7 @@ public:
|
|||
virtual SrsVideoCodecConfig* vcodec();
|
||||
public:
|
||||
static srs_error_t parse_avc_nalu_type(const SrsSample* sample, SrsAvcNaluType& avc_nalu_type);
|
||||
static srs_error_t parse_avc_bframe(const SrsSample* sample, bool& is_b_frame);
|
||||
|
||||
static srs_error_t parse_hevc_nalu_type(const SrsSample* sample, SrsHevcNaluType& hevc_nalu_type);
|
||||
static srs_error_t parse_hevc_bframe(const SrsSample* sample, SrsFormat* format, bool& is_b_frame);
|
||||
static srs_error_t parse_avc_b_frame(const SrsSample* sample, bool& is_b_frame);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@
|
|||
XX(ERROR_HEVC_DECODE_ERROR , 3099, "HevcDecode", "HEVC decode av stream failed") \
|
||||
XX(ERROR_MP4_HVCC_CHANGE , 3100, "Mp4HvcCChange", "MP4 does not support video HvcC change") \
|
||||
XX(ERROR_HEVC_API_NO_PREFIXED , 3101, "HevcAnnexbPrefix", "No annexb prefix for HEVC decoder") \
|
||||
XX(ERROR_NALU_EMPTY , 3102, "NaluEmpty", "NALU is empty")
|
||||
XX(ERROR_AVC_NALU_EMPTY , 3102, "AvcNaluEmpty", "AVC NALU is empty")
|
||||
|
||||
/**************************************************/
|
||||
/* HTTP/StreamConverter protocol error. */
|
||||
|
|
|
|||
|
|
@ -758,7 +758,7 @@ SrsRtpPacket::SrsRtpPacket()
|
|||
shared_buffer_ = NULL;
|
||||
actual_buffer_size_ = 0;
|
||||
|
||||
nalu_type = 0;
|
||||
nalu_type = SrsAvcNaluTypeReserved;
|
||||
frame_type = SrsFrameTypeReserved;
|
||||
cached_payload_size = 0;
|
||||
decode_handler = NULL;
|
||||
|
|
@ -961,23 +961,6 @@ bool SrsRtpPacket::is_keyframe()
|
|||
if((SrsAvcNaluTypeIDR == nalu_type) || (SrsAvcNaluTypeSPS == nalu_type) || (SrsAvcNaluTypePPS == nalu_type)) {
|
||||
return true;
|
||||
}
|
||||
#ifdef SRS_H265
|
||||
if(nalu_type == kStapHevc) {
|
||||
SrsRtpSTAPPayloadHevc* stap_payload = dynamic_cast<SrsRtpSTAPPayloadHevc*>(payload_);
|
||||
if(NULL != stap_payload->get_vps() || NULL != stap_payload->get_sps() || NULL != stap_payload->get_pps()) {
|
||||
return true;
|
||||
}
|
||||
} else if(nalu_type == kFuHevc) {
|
||||
SrsRtpFUAPayloadHevc2* fua_payload = dynamic_cast<SrsRtpFUAPayloadHevc2*>(payload_);
|
||||
if(fua_payload->nalu_type >= SrsHevcNaluType_CODED_SLICE_BLA && fua_payload->nalu_type <= SrsHevcNaluType_RESERVED_23) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if((SrsHevcNaluType_VPS == nalu_type) || (SrsHevcNaluType_SPS == nalu_type) || (SrsHevcNaluType_PPS == nalu_type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -1081,10 +1064,10 @@ void SrsRtpRawNALUs::push_back(SrsSample* sample)
|
|||
nalus.push_back(sample);
|
||||
}
|
||||
|
||||
uint8_t SrsRtpRawNALUs::skip_bytes(int count)
|
||||
uint8_t SrsRtpRawNALUs::skip_first_byte()
|
||||
{
|
||||
srs_assert (cursor >= 0 && nn_bytes > 0 && cursor + count < nn_bytes);
|
||||
cursor += count;
|
||||
srs_assert (cursor >= 0 && nn_bytes > 0 && cursor < nn_bytes);
|
||||
cursor++;
|
||||
return uint8_t(nalus[0]->bytes[0]);
|
||||
}
|
||||
|
||||
|
|
@ -1211,7 +1194,7 @@ SrsSample* SrsRtpSTAPPayload::get_sps()
|
|||
continue;
|
||||
}
|
||||
|
||||
SrsAvcNaluType nalu_type = SrsAvcNaluTypeParse(p->bytes[0]);
|
||||
SrsAvcNaluType nalu_type = (SrsAvcNaluType)(p->bytes[0] & kNalTypeMask);
|
||||
if (nalu_type == SrsAvcNaluTypeSPS) {
|
||||
return p;
|
||||
}
|
||||
|
|
@ -1229,7 +1212,7 @@ SrsSample* SrsRtpSTAPPayload::get_pps()
|
|||
continue;
|
||||
}
|
||||
|
||||
SrsAvcNaluType nalu_type = SrsAvcNaluTypeParse(p->bytes[0]);
|
||||
SrsAvcNaluType nalu_type = (SrsAvcNaluType)(p->bytes[0] & kNalTypeMask);
|
||||
if (nalu_type == SrsAvcNaluTypePPS) {
|
||||
return p;
|
||||
}
|
||||
|
|
@ -1415,7 +1398,7 @@ srs_error_t SrsRtpFUAPayload::decode(SrsBuffer* buf)
|
|||
v = buf->read_1bytes();
|
||||
start = v & kStart;
|
||||
end = v & kEnd;
|
||||
nalu_type = SrsAvcNaluTypeParse(v);
|
||||
nalu_type = SrsAvcNaluType(v & kNalTypeMask);
|
||||
|
||||
if (!buf->require(1)) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1);
|
||||
|
|
@ -1516,7 +1499,7 @@ srs_error_t SrsRtpFUAPayload2::decode(SrsBuffer* buf)
|
|||
v = buf->read_1bytes();
|
||||
start = v & kStart;
|
||||
end = v & kEnd;
|
||||
nalu_type = SrsAvcNaluTypeParse(v);
|
||||
nalu_type = SrsAvcNaluType(v & kNalTypeMask);
|
||||
|
||||
if (!buf->require(1)) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1);
|
||||
|
|
@ -1542,362 +1525,3 @@ ISrsRtpPayloader* SrsRtpFUAPayload2::copy()
|
|||
|
||||
return cp;
|
||||
}
|
||||
|
||||
SrsRtpSTAPPayloadHevc::SrsRtpSTAPPayloadHevc()
|
||||
{
|
||||
++_srs_pps_objs_rothers->sugar;
|
||||
}
|
||||
|
||||
SrsRtpSTAPPayloadHevc::~SrsRtpSTAPPayloadHevc()
|
||||
{
|
||||
int nn_nalus = (int)nalus.size();
|
||||
for (int i = 0; i < nn_nalus; i++) {
|
||||
SrsSample* p = nalus[i];
|
||||
srs_freep(p);
|
||||
}
|
||||
}
|
||||
|
||||
SrsSample* SrsRtpSTAPPayloadHevc::get_vps()
|
||||
{
|
||||
int nn_nalus = (int)nalus.size();
|
||||
for (int i = 0; i < nn_nalus; i++) {
|
||||
SrsSample* p = nalus[i];
|
||||
if (!p || !p->size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SrsHevcNaluType nalu_type = SrsHevcNaluTypeParse(p->bytes[0]);
|
||||
if (nalu_type == SrsHevcNaluType_VPS) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SrsSample* SrsRtpSTAPPayloadHevc::get_sps()
|
||||
{
|
||||
int nn_nalus = (int)nalus.size();
|
||||
for (int i = 0; i < nn_nalus; i++) {
|
||||
SrsSample* p = nalus[i];
|
||||
if (!p || !p->size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SrsHevcNaluType nalu_type = SrsHevcNaluTypeParse(p->bytes[0]);
|
||||
if (nalu_type == SrsHevcNaluType_SPS) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SrsSample* SrsRtpSTAPPayloadHevc::get_pps()
|
||||
{
|
||||
int nn_nalus = (int)nalus.size();
|
||||
for (int i = 0; i < nn_nalus; i++) {
|
||||
SrsSample* p = nalus[i];
|
||||
if (!p || !p->size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SrsHevcNaluType nalu_type = SrsHevcNaluTypeParse(p->bytes[0]);
|
||||
if (nalu_type == SrsHevcNaluType_PPS) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t SrsRtpSTAPPayloadHevc::nb_bytes()
|
||||
{
|
||||
int size = 2;
|
||||
|
||||
int nn_nalus = (int)nalus.size();
|
||||
for (int i = 0; i < nn_nalus; i++) {
|
||||
SrsSample* p = nalus[i];
|
||||
size += 2 + p->size;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
srs_error_t SrsRtpSTAPPayloadHevc::encode(SrsBuffer* buf)
|
||||
{
|
||||
if (!buf->require(2)) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2);
|
||||
}
|
||||
|
||||
// STAP header, RTP payload format for aggregation packets
|
||||
// @see https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2
|
||||
buf->write_1bytes(kStapHevc << 1);
|
||||
buf->write_1bytes(1);
|
||||
|
||||
// NALUs.
|
||||
int nn_nalus = (int)nalus.size();
|
||||
for (int i = 0; i < nn_nalus; i++) {
|
||||
SrsSample* p = nalus[i];
|
||||
|
||||
if (!buf->require(2 + p->size)) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2 + p->size);
|
||||
}
|
||||
|
||||
buf->write_2bytes(p->size);
|
||||
buf->write_bytes(p->bytes, p->size);
|
||||
}
|
||||
|
||||
return srs_success;
|
||||
}
|
||||
|
||||
srs_error_t SrsRtpSTAPPayloadHevc::decode(SrsBuffer* buf)
|
||||
{
|
||||
if (!buf->require(2)) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2);
|
||||
}
|
||||
|
||||
// STAP header, RTP payload format for aggregation packets
|
||||
// @see https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2
|
||||
uint8_t v = buf->read_1bytes();
|
||||
buf->skip(1);
|
||||
|
||||
// forbidden_zero_bit shoul be zero.
|
||||
// @see https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2
|
||||
uint8_t f = (v & 0x80);
|
||||
if (f == 0x80) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "forbidden_zero_bit should be zero");
|
||||
}
|
||||
|
||||
// NALUs.
|
||||
while (!buf->empty()) {
|
||||
if (!buf->require(2)) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2);
|
||||
}
|
||||
|
||||
int size = buf->read_2bytes();
|
||||
if (!buf->require(size)) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", size);
|
||||
}
|
||||
|
||||
SrsSample* sample = new SrsSample();
|
||||
sample->bytes = buf->head();
|
||||
sample->size = size;
|
||||
buf->skip(size);
|
||||
|
||||
nalus.push_back(sample);
|
||||
}
|
||||
|
||||
return srs_success;
|
||||
}
|
||||
|
||||
ISrsRtpPayloader* SrsRtpSTAPPayloadHevc::copy()
|
||||
{
|
||||
SrsRtpSTAPPayloadHevc* cp = new SrsRtpSTAPPayloadHevc();
|
||||
|
||||
int nn_nalus = (int)nalus.size();
|
||||
for (int i = 0; i < nn_nalus; i++) {
|
||||
SrsSample* p = nalus[i];
|
||||
cp->nalus.push_back(p->copy());
|
||||
}
|
||||
|
||||
return cp;
|
||||
}
|
||||
|
||||
SrsRtpFUAPayloadHevc::SrsRtpFUAPayloadHevc()
|
||||
{
|
||||
start = end = false;
|
||||
nalu_type = (SrsHevcNaluType)0;
|
||||
|
||||
++_srs_pps_objs_rothers->sugar;
|
||||
}
|
||||
|
||||
SrsRtpFUAPayloadHevc::~SrsRtpFUAPayloadHevc()
|
||||
{
|
||||
int nn_nalus = (int)nalus.size();
|
||||
for (int i = 0; i < nn_nalus; i++) {
|
||||
SrsSample* p = nalus[i];
|
||||
srs_freep(p);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t SrsRtpFUAPayloadHevc::nb_bytes()
|
||||
{
|
||||
int size = 3;
|
||||
|
||||
int nn_nalus = (int)nalus.size();
|
||||
for (int i = 0; i < nn_nalus; i++) {
|
||||
SrsSample* p = nalus[i];
|
||||
size += p->size;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
srs_error_t SrsRtpFUAPayloadHevc::encode(SrsBuffer* buf)
|
||||
{
|
||||
if (!buf->require(3)) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 3);
|
||||
}
|
||||
|
||||
// PayloadHdr, @see: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3
|
||||
buf->write_1bytes(kFuHevc << 1);
|
||||
buf->write_1bytes(1);
|
||||
|
||||
// FU header, @see https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3
|
||||
uint8_t fu_header = (start ? kStart : 0) | (end ? kEnd : 0);
|
||||
fu_header |= nalu_type;
|
||||
buf->write_1bytes(fu_header);
|
||||
|
||||
int nn_nalus = (int)nalus.size();
|
||||
for (int i = 0; i < nn_nalus; i++) {
|
||||
SrsSample* p = nalus[i];
|
||||
|
||||
if (!buf->require(p->size)) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", p->size);
|
||||
}
|
||||
|
||||
buf->write_bytes(p->bytes, p->size);
|
||||
}
|
||||
|
||||
return srs_success;
|
||||
}
|
||||
|
||||
srs_error_t SrsRtpFUAPayloadHevc::decode(SrsBuffer* buf)
|
||||
{
|
||||
if (!buf->require(3)) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 3);
|
||||
}
|
||||
|
||||
// skip PayloadHdr, 2 bytes
|
||||
buf->skip(2);
|
||||
|
||||
uint8_t fu_header = buf->read_1bytes();
|
||||
start = fu_header & kStart;
|
||||
end = fu_header & kEnd;
|
||||
nalu_type = SrsHevcNaluType(fu_header & 0x3F);
|
||||
if (!buf->require(1)) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1);
|
||||
}
|
||||
|
||||
SrsSample* sample = new SrsSample();
|
||||
sample->bytes = buf->head();
|
||||
sample->size = buf->left();
|
||||
buf->skip(sample->size);
|
||||
|
||||
nalus.push_back(sample);
|
||||
|
||||
return srs_success;
|
||||
}
|
||||
|
||||
ISrsRtpPayloader* SrsRtpFUAPayloadHevc::copy()
|
||||
{
|
||||
SrsRtpFUAPayloadHevc* cp = new SrsRtpFUAPayloadHevc();
|
||||
|
||||
cp->start = start;
|
||||
cp->end = end;
|
||||
cp->nalu_type = nalu_type;
|
||||
|
||||
int nn_nalus = (int)nalus.size();
|
||||
for (int i = 0; i < nn_nalus; i++) {
|
||||
SrsSample* p = nalus[i];
|
||||
cp->nalus.push_back(p->copy());
|
||||
}
|
||||
|
||||
return cp;
|
||||
}
|
||||
|
||||
SrsRtpFUAPayloadHevc2::SrsRtpFUAPayloadHevc2()
|
||||
{
|
||||
start = end = false;
|
||||
nalu_type = (SrsHevcNaluType)0;
|
||||
|
||||
payload = NULL;
|
||||
size = 0;
|
||||
|
||||
++_srs_pps_objs_rfua->sugar;
|
||||
}
|
||||
|
||||
SrsRtpFUAPayloadHevc2::~SrsRtpFUAPayloadHevc2()
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t SrsRtpFUAPayloadHevc2::nb_bytes()
|
||||
{
|
||||
// PayloadHdr(2) + FU header(1)
|
||||
return 3 + size;
|
||||
}
|
||||
|
||||
srs_error_t SrsRtpFUAPayloadHevc2::encode(SrsBuffer* buf)
|
||||
{
|
||||
if (!buf->require(3 + size)) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 3 + size);
|
||||
}
|
||||
|
||||
// Fast encoding.
|
||||
char* p = buf->head();
|
||||
|
||||
// PayloadHdr, @see: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3
|
||||
/*
|
||||
* create the HEVC payload header and transmit the buffer as fragmentation units (FU)
|
||||
*
|
||||
* 0 1
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* |F| Type | LayerId | TID |
|
||||
* +-------------+-----------------+
|
||||
*
|
||||
* F = 0
|
||||
* Type = 49 (fragmentation unit (FU))
|
||||
* LayerId = 0
|
||||
* TID = 1
|
||||
*/
|
||||
*p++ = kFuHevc << 1;
|
||||
*p++ = 1;
|
||||
|
||||
// FU header, @see https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3
|
||||
uint8_t fu_header = (start ? kStart : 0) | (end ? kEnd : 0);
|
||||
fu_header |= nalu_type;
|
||||
*p++ = fu_header;
|
||||
|
||||
memcpy(p, payload, size);
|
||||
|
||||
// Consume bytes.
|
||||
buf->skip(3 + size);
|
||||
|
||||
return srs_success;
|
||||
}
|
||||
|
||||
srs_error_t SrsRtpFUAPayloadHevc2::decode(SrsBuffer* buf)
|
||||
{
|
||||
if (!buf->require(3)) {
|
||||
return srs_error_new(ERROR_RTC_RTP_MUXER, "requires 3 bytes");
|
||||
}
|
||||
|
||||
// skip PayloadHdr, 2 bytes
|
||||
buf->skip(2);
|
||||
|
||||
uint8_t fu_header = buf->read_1bytes();
|
||||
start = fu_header & kStart;
|
||||
end = fu_header & kEnd;
|
||||
nalu_type = SrsHevcNaluType(fu_header & 0x3F);
|
||||
|
||||
payload = buf->head();
|
||||
size = buf->left();
|
||||
buf->skip(size);
|
||||
|
||||
return srs_success;
|
||||
}
|
||||
|
||||
ISrsRtpPayloader* SrsRtpFUAPayloadHevc2::copy()
|
||||
{
|
||||
SrsRtpFUAPayloadHevc2* cp = new SrsRtpFUAPayloadHevc2();
|
||||
|
||||
cp->start = start;
|
||||
cp->end = end;
|
||||
cp->nalu_type = nalu_type;
|
||||
cp->payload = payload;
|
||||
cp->size = size;
|
||||
|
||||
return cp;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,14 +29,10 @@ const uint8_t kNalTypeMask = 0x1F;
|
|||
|
||||
// @see: https://tools.ietf.org/html/rfc6184#section-5.2
|
||||
const uint8_t kStapA = 24;
|
||||
|
||||
// @see: https://tools.ietf.org/html/rfc6184#section-5.2
|
||||
const uint8_t kFuA = 28;
|
||||
|
||||
// @see: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2
|
||||
const uint8_t kStapHevc = 48;
|
||||
// @see: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3
|
||||
const uint8_t kFuHevc = 49;
|
||||
|
||||
// @see: https://tools.ietf.org/html/rfc6184#section-5.8
|
||||
const uint8_t kStart = 0x80; // Fu-header start bit
|
||||
const uint8_t kEnd = 0x40; // Fu-header end bit
|
||||
|
|
@ -257,12 +253,9 @@ enum SrsRtspPacketPayloadType
|
|||
{
|
||||
SrsRtspPacketPayloadTypeRaw,
|
||||
SrsRtspPacketPayloadTypeFUA2,
|
||||
SrsRtspPacketPayloadTypeFUAHevc2,
|
||||
SrsRtspPacketPayloadTypeFUA,
|
||||
SrsRtspPacketPayloadTypeFUAHevc,
|
||||
SrsRtspPacketPayloadTypeNALU,
|
||||
SrsRtspPacketPayloadTypeSTAP,
|
||||
SrsRtspPacketPayloadTypeSTAPHevc,
|
||||
SrsRtspPacketPayloadTypeUnknown,
|
||||
};
|
||||
|
||||
|
|
@ -296,7 +289,7 @@ private:
|
|||
// Helper fields.
|
||||
public:
|
||||
// The first byte as nalu type, for video decoder only.
|
||||
uint8_t nalu_type;
|
||||
SrsAvcNaluType nalu_type;
|
||||
// The frame type, for RTMP bridge or SFU source.
|
||||
SrsFrameType frame_type;
|
||||
// Fast cache for performance.
|
||||
|
|
@ -383,7 +376,7 @@ public:
|
|||
public:
|
||||
void push_back(SrsSample* sample);
|
||||
public:
|
||||
uint8_t skip_bytes(int count);
|
||||
uint8_t skip_first_byte();
|
||||
// We will manage the returned samples, if user want to manage it, please copy it.
|
||||
srs_error_t read_samples(std::vector<SrsSample*>& samples, int packet_size);
|
||||
// interface ISrsRtpPayloader
|
||||
|
|
@ -467,68 +460,4 @@ public:
|
|||
virtual ISrsRtpPayloader* copy();
|
||||
};
|
||||
|
||||
class SrsRtpSTAPPayloadHevc : public ISrsRtpPayloader
|
||||
{
|
||||
public:
|
||||
// The NALU samples, we will manage the samples.
|
||||
// @remark We only refer to the memory, user must free its bytes.
|
||||
std::vector<SrsSample*> nalus;
|
||||
public:
|
||||
SrsRtpSTAPPayloadHevc();
|
||||
virtual ~SrsRtpSTAPPayloadHevc();
|
||||
public:
|
||||
SrsSample* get_vps();
|
||||
SrsSample* get_sps();
|
||||
SrsSample* get_pps();
|
||||
// interface ISrsRtpPayloader
|
||||
public:
|
||||
virtual uint64_t nb_bytes();
|
||||
virtual srs_error_t encode(SrsBuffer* buf);
|
||||
virtual srs_error_t decode(SrsBuffer* buf);
|
||||
virtual ISrsRtpPayloader* copy();
|
||||
};
|
||||
|
||||
// FU, for one NALU with multiple fragments.
|
||||
// With more than one payload for HEVC.
|
||||
class SrsRtpFUAPayloadHevc : public ISrsRtpPayloader
|
||||
{
|
||||
public:
|
||||
// The FUA header.
|
||||
bool start;
|
||||
bool end;
|
||||
SrsHevcNaluType nalu_type;
|
||||
// The NALU samples, we manage the samples.
|
||||
// @remark We only refer to the memory, user must free its bytes.
|
||||
std::vector<SrsSample*> nalus;
|
||||
public:
|
||||
SrsRtpFUAPayloadHevc();
|
||||
virtual ~SrsRtpFUAPayloadHevc();
|
||||
// interface ISrsRtpPayloader
|
||||
public:
|
||||
virtual uint64_t nb_bytes();
|
||||
virtual srs_error_t encode(SrsBuffer* buf);
|
||||
virtual srs_error_t decode(SrsBuffer* buf);
|
||||
virtual ISrsRtpPayloader* copy();
|
||||
};
|
||||
|
||||
// FU, for one NALU with multiple fragments.
|
||||
// With only one payload for HEVC.
|
||||
class SrsRtpFUAPayloadHevc2 : public ISrsRtpPayloader
|
||||
{
|
||||
public:
|
||||
bool start;
|
||||
bool end;
|
||||
SrsHevcNaluType nalu_type;
|
||||
char* payload;
|
||||
int size;
|
||||
public:
|
||||
SrsRtpFUAPayloadHevc2();
|
||||
virtual ~SrsRtpFUAPayloadHevc2();
|
||||
public:
|
||||
virtual uint64_t nb_bytes();
|
||||
virtual srs_error_t encode(SrsBuffer* buf);
|
||||
virtual srs_error_t decode(SrsBuffer* buf);
|
||||
virtual ISrsRtpPayloader* copy();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -3567,204 +3567,6 @@ VOID TEST(KernelCodecTest, AVFrameNoConfig)
|
|||
HELPER_EXPECT_SUCCESS(f.add_sample((char*)"\x05", 1));
|
||||
}
|
||||
}
|
||||
VOID TEST(KernelCodecTest, VideoFrameH264)
|
||||
{
|
||||
srs_error_t err;
|
||||
|
||||
if (true) {
|
||||
// I Frame
|
||||
uint8_t data[] = {0x05, 0x00, 0x00, 0x00};
|
||||
SrsSample sample((char*)data, sizeof(data));
|
||||
|
||||
SrsAvcNaluType nalu_type = SrsAvcNaluTypeForbidden;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_nalu_type(&sample, nalu_type));
|
||||
EXPECT_EQ(nalu_type, SrsAvcNaluTypeIDR);
|
||||
|
||||
// P Frame
|
||||
uint8_t data2[] = {0x01, 0x00, 0x00, 0x00};
|
||||
SrsSample sample2((char*)data2, sizeof(data2));
|
||||
|
||||
nalu_type = SrsAvcNaluTypeForbidden;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_nalu_type(&sample2, nalu_type));
|
||||
EXPECT_EQ(nalu_type, SrsAvcNaluTypeNonIDR);
|
||||
|
||||
// SPS
|
||||
uint8_t data3[] = {0x07, 0x00, 0x00, 0x00};
|
||||
SrsSample sample3((char*)data3, sizeof(data3));
|
||||
|
||||
nalu_type = SrsAvcNaluTypeForbidden;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_nalu_type(&sample3, nalu_type));
|
||||
EXPECT_EQ(nalu_type, SrsAvcNaluTypeSPS);
|
||||
|
||||
// PPS
|
||||
uint8_t data4[] = {0x08, 0x00, 0x00, 0x00};
|
||||
SrsSample sample4((char*)data4, sizeof(data4));
|
||||
|
||||
nalu_type = SrsAvcNaluTypeForbidden;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_nalu_type(&sample4, nalu_type));
|
||||
EXPECT_EQ(nalu_type, SrsAvcNaluTypePPS);
|
||||
|
||||
// Empty Sample
|
||||
SrsSample empty_sample(NULL, 0);
|
||||
HELPER_EXPECT_FAILED(SrsVideoFrame::parse_avc_nalu_type(&empty_sample, nalu_type));
|
||||
}
|
||||
|
||||
if (true) {
|
||||
// B Frame, slice_type=1(B Frame)
|
||||
uint8_t data[] = {0x01, 0xA8, 0x00, 0x00};
|
||||
SrsSample sample((char*)data, sizeof(data));
|
||||
|
||||
bool is_b_frame = false;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_bframe(&sample, is_b_frame));
|
||||
EXPECT_TRUE(is_b_frame);
|
||||
|
||||
// Non-B Frame, slice_type=0(P Frame)
|
||||
uint8_t data2[] = {0x01, 0x88, 0x00, 0x00};
|
||||
SrsSample sample2((char*)data2, sizeof(data2));
|
||||
|
||||
is_b_frame = true;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_bframe(&sample2, is_b_frame));
|
||||
EXPECT_FALSE(is_b_frame);
|
||||
|
||||
// SPS
|
||||
uint8_t data3[] = {0x07, 0xA8, 0x00, 0x00};
|
||||
SrsSample sample3((char*)data3, sizeof(data3));
|
||||
|
||||
is_b_frame = true;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_bframe(&sample3, is_b_frame));
|
||||
EXPECT_FALSE(is_b_frame);
|
||||
|
||||
// PPS
|
||||
uint8_t data4[] = {0x08, 0xA8, 0x00, 0x00};
|
||||
SrsSample sample4((char*)data4, sizeof(data4));
|
||||
|
||||
is_b_frame = true;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_bframe(&sample4, is_b_frame));
|
||||
EXPECT_FALSE(is_b_frame);
|
||||
|
||||
// IDR
|
||||
uint8_t data5[] = {0x05, 0xA8, 0x00, 0x00};
|
||||
SrsSample sample5((char*)data5, sizeof(data5));
|
||||
|
||||
is_b_frame = true;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_avc_bframe(&sample5, is_b_frame));
|
||||
EXPECT_FALSE(is_b_frame);
|
||||
|
||||
// Empty Sample
|
||||
SrsSample empty_sample(NULL, 0);
|
||||
HELPER_EXPECT_FAILED(SrsVideoFrame::parse_avc_bframe(&empty_sample, is_b_frame));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SRS_H265
|
||||
VOID TEST(KernelCodecTest, VideoFrameH265)
|
||||
{
|
||||
srs_error_t err;
|
||||
|
||||
if (true) {
|
||||
// I Frame
|
||||
uint8_t data[] = {0x26, 0x01, 0x00, 0x00};
|
||||
SrsSample sample((char*)data, sizeof(data));
|
||||
|
||||
SrsHevcNaluType nalu_type = SrsHevcNaluType_INVALID;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_nalu_type(&sample, nalu_type));
|
||||
EXPECT_EQ(nalu_type, SrsHevcNaluType_CODED_SLICE_IDR);
|
||||
|
||||
// P Frame
|
||||
uint8_t data2[] = {0x02, 0x01, 0x00, 0x00};
|
||||
SrsSample sample2((char*)data2, sizeof(data2));
|
||||
|
||||
nalu_type = SrsHevcNaluType_INVALID;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_nalu_type(&sample2, nalu_type));
|
||||
EXPECT_EQ(nalu_type, SrsHevcNaluType_CODED_SLICE_TRAIL_R);
|
||||
|
||||
// VPS
|
||||
uint8_t data3[] = {0x40, 0x01, 0x00, 0x00};
|
||||
SrsSample sample3((char*)data3, sizeof(data3));
|
||||
|
||||
nalu_type = SrsHevcNaluType_INVALID;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_nalu_type(&sample3, nalu_type));
|
||||
EXPECT_EQ(nalu_type, SrsHevcNaluType_VPS);
|
||||
|
||||
// SPS
|
||||
uint8_t data4[] = {0x42, 0x01, 0x00, 0x00};
|
||||
SrsSample sample4((char*)data4, sizeof(data4));
|
||||
|
||||
nalu_type = SrsHevcNaluType_INVALID;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_nalu_type(&sample4, nalu_type));
|
||||
EXPECT_EQ(nalu_type, SrsHevcNaluType_SPS);
|
||||
|
||||
// PPS
|
||||
uint8_t data5[] = {0x44, 0x01, 0x00, 0x00};
|
||||
SrsSample sample5((char*)data5, sizeof(data5));
|
||||
|
||||
nalu_type = SrsHevcNaluType_INVALID;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_nalu_type(&sample5, nalu_type));
|
||||
EXPECT_EQ(nalu_type, SrsHevcNaluType_PPS);
|
||||
|
||||
// Empty Sample
|
||||
SrsSample empty_sample(NULL, 0);
|
||||
HELPER_EXPECT_FAILED(SrsVideoFrame::parse_hevc_nalu_type(&empty_sample, nalu_type));
|
||||
}
|
||||
|
||||
if (true) {
|
||||
SrsFormat format;
|
||||
HELPER_EXPECT_SUCCESS(format.initialize());
|
||||
|
||||
// B Frame, slice_type=0(B Frame)
|
||||
uint8_t data[] = {0x02, 0x01, 0xE0, 0x44};
|
||||
SrsSample sample((char*)data, sizeof(data));
|
||||
|
||||
bool is_b_frame = false;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_bframe(&sample, &format, is_b_frame));
|
||||
EXPECT_TRUE(is_b_frame);
|
||||
|
||||
// Non-B Frame, slice_type=1(P Frame)
|
||||
uint8_t data2[] = {0x02, 0x01, 0xD0, 0x30};
|
||||
SrsSample sample2((char*)data2, sizeof(data2));
|
||||
|
||||
is_b_frame = true;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_bframe(&sample2, &format, is_b_frame));
|
||||
EXPECT_FALSE(is_b_frame);
|
||||
|
||||
// VPS
|
||||
uint8_t data3[] = {0x40, 0x01, 0xE0, 0x44};
|
||||
SrsSample sample3((char*)data3, sizeof(data3));
|
||||
|
||||
is_b_frame = true;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_bframe(&sample3, &format, is_b_frame));
|
||||
EXPECT_FALSE(is_b_frame);
|
||||
|
||||
// SPS
|
||||
uint8_t data4[] = {0x42, 0x01, 0xE0, 0x44};
|
||||
SrsSample sample4((char*)data4, sizeof(data4));
|
||||
|
||||
is_b_frame = true;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_bframe(&sample4, &format, is_b_frame));
|
||||
EXPECT_FALSE(is_b_frame);
|
||||
|
||||
// PPS
|
||||
uint8_t data5[] = {0x44, 0x01, 0xE0, 0x44};
|
||||
SrsSample sample5((char*)data5, sizeof(data5));
|
||||
|
||||
is_b_frame = true;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_bframe(&sample5, &format, is_b_frame));
|
||||
EXPECT_FALSE(is_b_frame);
|
||||
|
||||
// IDR
|
||||
uint8_t data6[] = {0x26, 0x01, 0xE0, 0x44};
|
||||
SrsSample sample6((char*)data6, sizeof(data6));
|
||||
|
||||
is_b_frame = true;
|
||||
HELPER_EXPECT_SUCCESS(SrsVideoFrame::parse_hevc_bframe(&sample6, &format, is_b_frame));
|
||||
EXPECT_FALSE(is_b_frame);
|
||||
|
||||
// Empty Sample
|
||||
SrsSample empty_sample(NULL, 0);
|
||||
HELPER_EXPECT_FAILED(SrsVideoFrame::parse_hevc_bframe(&empty_sample, &format, is_b_frame));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
VOID TEST(KernelCodecTest, IsSequenceHeaderSpecial)
|
||||
{
|
||||
|
|
@ -4419,7 +4221,7 @@ VOID TEST(KernelCodecTest, HevcVideoFormat)
|
|||
if (true) {
|
||||
SrsFormat f;
|
||||
HELPER_EXPECT_SUCCESS(f.initialize());
|
||||
|
||||
|
||||
// firstly demux sequence header
|
||||
HELPER_EXPECT_SUCCESS(f.on_video(0, (char*)ext_vps_sps_pps, sizeof(ext_vps_sps_pps)));
|
||||
EXPECT_EQ(1, f.video->frame_type);
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ VOID TEST(KernelRTCTest, RtpSTAPPayloadException)
|
|||
SrsAvcNaluType nalu_type = SrsAvcNaluTypeReserved;
|
||||
// Try to parse the NALU type for video decoder.
|
||||
if (!buf.empty()) {
|
||||
nalu_type = SrsAvcNaluTypeParse(buf.head()[0]);
|
||||
nalu_type = SrsAvcNaluType((uint8_t)(buf.head()[0] & kNalTypeMask));
|
||||
}
|
||||
|
||||
EXPECT_TRUE(nalu_type == kStapA);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user