RTMP 流的相关配置体验

2021-08-26 2513点热度 2人点赞

心血来潮,想和朋友一起看电影,但是电影比较不符合法律法规,所以迟迟想不好在哪里看。想着没了大平台,我们还能自己直播给朋友看么,还是得有一套自己的实现方式。


1 、 RTMP 流的历史今天

实时消息传输协议(RTMP)最初由 Macromedia 开发,后被 Adobe 接手并广泛应用于 Flash 播放器时代的音视频传输。它以 TCP 长连接为基础,能够在客户端与服务器之间持续、低延迟地传输音视频数据,一度成为直播领域的绝对标准。时至今日,尽管 Flash 已经退出浏览器舞台,RTMP 仍牢牢占据 “推流” 环节的统治地位——绝大多数的编码器和推流软件(包括 OBS)都将其作为默认上传协议。

而基于 RTMP 推上来流后,流的分发又分为好多种方式,从早期的纯 RTMP 分发,到 HLS+M3U8 的高延迟+高兼容+大并发方案,又到移动端 HTTP-FLV 的低延迟+客户端方案,再到 WebRTC 的实时互动直播方案。每种方案都有其特色,也都有其缺陷,取长补短是当下直播分发方案的真实实现。

同样的,早期的直播分发方案异常朴素:一台服务器运行 Nginx 并挂载 RTMP 模块,配合 ffmpeg 进行转码或转推,就能将一路输入流分发给少量观众观看。随后出现了专业的流媒体服务器(如 SRS 、 Nginx-RTMP 、 Wowza),它们支持更复杂的协议转换(RTMP 转 HTTP-FLV 、 HLS 、 WebRTC 等),并逐步引入边缘节点、 CDN 回源等概念,支撑起高并发。


2 、 Nginx 的 RTMP 体验

如果你只是想最快的办法让别人一对一看到你的音视频流,当然是找个远程串流了。

为了理解串流,先利用 Nginx 的 RTMP 模块快速搭一个中转,用 ffmpeg 推流,用 VLC 看流。全程 RTMP 不进行转换。


2.1 、创建基本的 rtmp 流分发的配置

Nginx 的 RTMP 组件默认是 nginx-rtmp-module [ 链接 ]

这款组件是大多数发行版都有现成的包,一般来说不需要编译

apt-get install libnginx-mod-rtmp

安装好后,即可在 nginx.conf 中增加 rtmp{} 相关字段

通常 Nginx 的站点配置都是 include 进主配置文件中,所以 rtmp 也一样可以如此操作

在 nginx.conf 配置文件中,与 http{} 同级的地方之前插入 rtmp 配置

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
......
}

http {
......
}

rtmp{
include /etc/nginx/rtmp.d/*.conf;
}

然后我们用此配置路径,创建 /etc/nginx/rtmp.d/ 文件夹,并在其中添加相关服务配置 default.conf

server {
listen 1935; # RTMP 默认端口
# chunk_size 4096; # RTMP 块大小
# max_streams 128; # 最大并行 RTMP 流 数量
# drop_idle_publisher 10s; #推流者停止推流后断开链接时间

application live {
live on; # 开启直播模式
record off; # 不保存录像到磁盘
# allow publish all;
allow publish 192.168.1.0/24;
allow play all;
}
}

此时,我们就可以重新加载 nginx 配置

nginx -s reload

然后,使用相关推流软件,为服务器推送 rtmp 流。

ffmpeg -re -i input.mp4 -c copy -f flv rtmp://server_ip/live/stream

但是在推送之前,我们不要忘记打开防火墙

firewall-cmd --add-port=1935/tcp 

推送 rtmp 流的路径是根据 application 后面的 应用名 ( live ) 决定的,同时受限于 allow publish / deny publish 参数

rtmp://server.address:1935/live/stream

# RTMP 地址
rtmp://domain_ip:port/live/stream

domain_ip 服务地址
port 服务端口(默认 1935/TCP)
live 定义的应用名 (Nginx)
stream RTMP 流名称(串流密钥)

而查看 rtmp 流的路径和推送的路径一致,但受限于 allow play / deny play 参数。

rtmp 流默认不在本地保存,所以收到的流的配置完全取决于推送方的推流配置


2.2 、 RTMP 模块的 HLS 配置

现代的服务器当然支持多终端了,而不同系统的终端千奇百怪,其中适配最好的还是 HLS

当然 HLS 的缺点也很明显,延迟高(5-20s)且服务器需要有缓存文件。

HLS 使用 m3u8 + ts 文件的方式调度,m3u8 里是一个指针指向了对应的 ts 视频片段

而 HLS 配置的方式也很简单,只需要在原有的 RTMP 基础上增加少许配置即可同时使用。

相关参数配置可以参考 [ 链接 ]

application hls_live {
    live on;
    # interleave on;    # 音视频流使用相同 RTMP 块传输
    # wait_key on;    # 在播放时使视频流从 关键帧 开始播放

    hls on;
    hls_path /tmp/hls;
    hls_fragment 1300ms;
    hls_max_fragment 1800ms;
    hls_playlist_length 3900ms;
    hls_fragment_slicing aligned;

    # pull rtmp://    #从远程服务器拉取流并显示
    # push rtmp://    #从本地服务器推送流至目标
}

但与 RTMP 不同,我们还需要额外增加一个 http 站点以供 HLS/HTTP 方式访问

这里创建的是 http://localhost/ 这个虚拟站点,站点的根路径为 Nginx 自带的示例页面

站点的 live 路径指向了 rtmp 的 hls 缓存路径,并配置了允许跨域调用

server {
    listen 80;
    listen [::]:80;
    server_name localhost;
    access_log /var/log/nginx/lives.log;
    error_log /var/log/nginx/lives-error.log;

    location / {
        root html;
        index index.html;
    }

    location /lives {
        alias /tmp/hls;
        expires -1;
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Cache-Control' 'no-cache';
    }

}

配置好后,依然重启 Nginx,重启后可以使用 HLS/HTTP 的方式访问。

推流的终端的目标推送地址没有变化。

rtmp://server.address:1935/live 

HLS/HTTP 查看流的地址是一个文件,如果直接用浏览器打开会进行下载

http://localhost:80/lives/livestream.m3u8

注意这里有三个名词参数,live 对应 rtmp 的配置,lives 对应 http 的配置,而 livestream 对应 推流者的 串流密钥
(如为空则会出现 /tmp/hls/.m3u8 的文件)


2.3 、 RTMP 模块的 多终端流分发 配置

如果有多个终端同时从服务器取流,而服务器上传上来的流只有一份,这时会出现冲突。

对于专门的流服务器,他们会复制多份流并传输。在 Nginx 中有同样的配置方式

编辑 nginx.conf ,在 rtmp {} 同级的位置上添加全局参数

rtmp_auto_push on;
rtmp_auto_push_reconnect 1s;
rtmp_socket_dir /tmp/rtmp/;

添加好后,同样的重新加载 Nginx 配置

nginx -s reload

这里会自动创建 /tmp/rtmp/ 文件夹,并且里面会生成 等同 worker_processes 数量的 socket 文件。

此时再访问 rtmp,会发现多终端不会因为互相抢占而卡顿了。


2.4 、类似的同款 RTMP 组件

Nginx 还有另一款 RTMP 组件,额外还支持了 FLV,名字是 nginx-http-flv-module [ 链接 ]

相比 rtmp-module ,flv-module 额外增加了许多命令参数用于优化性能

但是由于 flv-module 并不能直接合并到 apt-get 安装的 nginx 里面,需要编译

而编译使用官方的 Nginx -V 参数,会出现 __DATA_TIME__ 错误,所以最终没有采用


3 、 SRS 的 RTMP 体验

SRS 是一款优秀的 流分发 整体解决方案,所以当我们想了解更多时,可以体验一下 SRS 的解决方案 [ 链接 ]


3.1 、编译使用和了解 SRS 的配置

在 Ubuntu 20 中,直接下载并编译 SRS 4 是一切正常的,而 SRS 3 会出现一些问题(有些组件被彻底替换了)

(吐槽:SRS 为了搞中文圈子,把 Github 上的 WIKI 弄得稀烂,还不更新)
(吐槽 2:SRS 整个包搞得一团麻,全是快捷方式根本摘不清,编译完居然还得留着不能删了)

首先按照 Wiki 留下的仅剩的几句话,编译安装(遇到任何问题,只能一律谷歌去补所需要的包)

cd /opt/
git clone -b 4.0release https://gitee.com/ossrs/srs.git
cd /opt/srs/trunk
./configure
make

编译完成后,我们重点关注几个文件

/opt/srs/trunk/etc/init.d/srs      # 主启动文件 (重要)
/opt/srs/trunk/usr/lib/systemd/system/srs.service        # Systemd 服务文件,对应上方主启动文件使用
/opt/srs/trunk/objs/srs        # 程序文件
/opt/srs/trunk/conf/srs.conf        # 配置文件
/opt/srs/trunk/objs/srs.log        # 日志文件
/opt/srs/trunk/objs/srs.pid        # 进程 ID 文件

这几个文件之中,互相指向,所以当我们要改动相关路径时,务必同步配置文件的信息。

然后我们可以运行服务器了,手动启动的命令如下

/opt/srs/trunk/objs/srs -c /opt/srs/trunk/conf/srs.conf

但是实际上我们完全可以通过 主启动文件 进行管理。复制一个主启动文件,并修改五个选项配置

ROOT="/opt/srs/trunk/"
APP="/opt/srs/trunk/objs/srs"
CONFIG="/opt/srs/trunk/conf/srs.conf"
DEFAULT_PID_FILE='/opt/srs/trunk/objs/srs.pid'
DEFAULT_LOG_FILE='/opt/srs/trunk/objs/srs.log'

修改完毕后,直接运行即可(systemd 服务文件也可以直接拷贝过去并修改指向即可)

bash srs start

此时你就可以直接通过访问 http://localhost:8080/ 进行管理操作了。

然后分享时的链接如下,直接打开就能看。

http://localhost/players/srs_player.html?autostart=true&app=live&stream=livestream.flv&server=localhost&port=80&vhost=localhost&schema=http

如果涉及 SRS 配置文件,在测试过程中我生成了一份使用的配置文件,记录在此(改动多处文件位置)

listen 1935;
max_connections 1000;
srs_log_tank file;
srs_log_file /opt/srs/log/srs.log;
pid /opt/srs/run/srs.pid;
daemon on;

http_api {
    enabled off;
    listen 1985;
    raw_api {
        enabled off;
        allow_reload on;
        allow_query on;
        allow_update on;
    }
}

http_server {
    enabled on;
    listen 8080;
    dir /opt/srs/objs/nginx/html;
}

vhost localhost {
    tcp_nodelay     on;
    min_latency     on;

    play {
        gop_cache       off;
        queue_length    10;
        mw_latency      100;
    }
    
    publish {
        mr off;
    }

    hls {
        enabled on;
    }

    http_remux {
        enabled on;
        mount [vhost]/[app]/[stream].flv;
    }

    enabled on;
}

vhost __defaultVhost__ {
    hls {
        enabled         on;
    }
    http_remux {
        enabled     on;
        mount       [vhost]/[app]/[stream].flv;
    }
}

ff_log_dir /dev/null;

如果你需要从你原本的 Nginx 中代理 8080 端口,则需要在 原本的 Nginx 增加下面的代理站点配置。请注意安全性问题

server {
    listen 80;
    listen [::]:80;
    server_name localhost;
    access_log /var/log/nginx/lives.log;
    error_log /var/log/nginx/lives-error.log;

    location / {
        proxy_pass http://localhost:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
    }

}

3.2 、使用 Docker 部署和便捷的方式实施 srs 并实现整体流程

本文以搭建一套 5 人以内的 1080P60 私有直播系统为主旨,实现到最终成品。

运行框架如下所示

OBS    通过 RTMP 向内网 IP 的 1935 端口推流 
SRS 接收推流,将 RTMP 协议实时转换为 HTTP-FLV,监听在 127.0.0.1:8080,一切文件路径均在 /opt/srs 下
Nginx 作为唯一对外入口,提供 HTTPS 加密、统一密码认证、并发限制,并将外网请求代理到 SRS 的内部 HTTP-FLV 接口
Html5 利用 mpegts.js 拉取 HTTP-FLV 流,在浏览器中低延时播放,同时通过 Base64 混淆和子资源完整性(SRI)防止流地址被爬

3.2.1 、安装 SRS

首先是 SRS,先获取源,然后提取 SRS 的配置文件

# 拉取 srs 镜像 
docker pull ossrs/srs:6

# 国外速度慢就考虑换源
# docker pull ossrs/srs:6
# docker pull registry.cn-hangzhou.aliyuncs.com/ossrs/srs:6
# 查看当前版本镜像的配置文件 
docker run --rm ossrs/srs:6 cat /usr/local/srs/conf/docker.conf > /opt/srs/srs.conf

# main config for srs.
# @see full.conf for detail config.

listen 1935;
max_connections 1000;
srs_log_tank console;
daemon off;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
rtc_server {
enabled on;
listen 8000; # UDP port
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate
candidate $CANDIDATE;
}
vhost __defaultVhost__ {
hls {
enabled on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
rtc {
enabled on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtmp-to-rtc
rtmp_to_rtc off;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtc-to-rtmp
rtc_to_rtmp off;
}
}

导出好配置文件后,我们就可以修改 SRS 的配置文件,并传递给 docker 内部

# main config for srs.
# @see full.conf for detail config.

listen 1935;
# 根据会话数修改,建议两倍预期会话数
max_connections 1000;
# 阻止日志保存,但一定要调试清楚后再操作
srs_log_tank console;
# docker 内禁止守护进程化,避免崩溃后无法自行重启
daemon off;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
# 开启 SRT 协议,端口 10080/UDP,带固定密码 3BzTDc0F65UO1Cqd
srt_server {
enabled on;
listen 10080;
maxbw 100000000;
connect_timeout 4000;
peerlatency 300;
recvlatency 300;
passphrase 3BzTDc0F65UO1Cqd;
pbkeylen 16;
rtc_server {
# 关闭 WebRTC,不考虑这个功能组
enabled off;
listen 8000; # UDP port
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate
candidate $CANDIDATE;
}
vhost __defaultVhost__ {
# 关闭 WebRTC,不考虑这个功能组
min_latency on;

# 关闭 HLS 和 M3U8 以及切片
hls {
enabled off;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
# 开启 SRT 协议
srt {
enabled on;
srt_to_rtmp on;
}
# 关闭 WebRTC,不考虑这个功能组
rtc {
enabled off;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtmp-to-rtc
rtmp_to_rtc off;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtc-to-rtmp
rtc_to_rtmp off;
}

play{
# 开启 GOP 缓存,首屏打开立刻就有关键帧播放
gop_cache on;
# 缓存帧数,用户访问时强制推送,在 60fps 下 120 帧=2s
gop_cache_max_frames 120;
queue_length 5000;
}

dvr {
# 禁用录制
enabled off;
}
}

此时外部文件以准备齐全,可以使用手动命令启动 SRS 了
(tmpfs 参数的意图是解决 HLS 反复写入到硬盘的问题,直接移动到内存中)

docker run -d --restart=yes \
--name srs6_live \
--log-driver none \
--tmpfs /usr/local/srs/objs/nginx/html:rw,noexec,nosuid,size=512m \
-p 192.168.1.2:1935:1935 \
-p 10080:10080/udp \
-p 127.0.0.1:8080:8080 \
-p 127.0.0.1:1985:1985 \
-v /opt/srs/srs.conf:/usr/local/srs/conf/docker.conf \
ossrs/srs:6

如果启动顺利没有问题,则可以准备一个启动脚本了
附带给 Nginx 创建好密码文件

#!/bin/bash
# =======================================================================
# SRS6 局域网专属启动脚本
# =======================================================================

CONTAINER_NAME="srs6_live"
IMAGE_NAME="registry.cn-hangzhou.aliyuncs.com/ossrs/srs:6"
LAN_IP="192.168.1.2"
PASSWORD="$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16)"

echo "========================================="
echo " 1. 检查并清空历史存留的 SRS 程序..."
echo "========================================="
if [ "$(docker ps -aq -f name=^${CONTAINER_NAME}$)" ]; then
echo "发现历史残留容器 [${CONTAINER_NAME}],正在强制删除..."
docker rm -f ${CONTAINER_NAME}
echo "历史容器已清理干净。"
else
echo "未发现残留容器,保持环境洁净。"
fi

echo -e "\n========================================="
echo " 2. 正在同步最新的阿里云 SRS6 镜像..."
echo "========================================="
docker pull ${IMAGE_NAME}

echo -e "\n========================================="
echo " 3. 正在部署并启动全新的 SRS6 容器..."
echo "========================================="

#【安全策略调整说明】:
# 1. --restart=no : 明确禁止 Docker 在开机或重启时自动运行此容器。
# 2. -p 192.168.1.2:1935:1935 : 极其关键!强制 RTMP 服务仅监听局域网 IP,公网无法触及。
# 3. --log-driver none : 不保留任何日志,确保数据仅在内存中。
# 4. --tmpfs : 将 HLS off 参数不生效 进而无法关闭产生的本地读写 IO 转移到内存中。
docker run -d --restart=no \
--name ${CONTAINER_NAME} \
--log-driver none \
--tmpfs /usr/local/srs/objs/nginx/html:rw,noexec,nosuid,size=512m \
-p ${LAN_IP}:1935:1935 \
-p 127.0.0.1:8080:8080 \
-p 127.0.0.1:1985:1985 \
-v /opt/srs/srs.conf:/usr/local/srs/conf/docker.conf \
${IMAGE_NAME}

echo -e "\n========================================="
echo " 4. 正在重新生成密码... user / $(date +%Y%m%d) "
echo "========================================="

htpasswd -b /opt/srs/.htpasswd user "$PASSWORD"

# 验证启动状态
if [ $? -eq 0 ]; then
echo "-----------------------------------------"
echo " SRS6 容器已成功在后台运转!"
echo " 安全限制:推流仅在本地局域网同网段可用。"
echo " 本地 OBS 推流地址: rtmp://${LAN_IP}/live/ + mystream"
echo " 用户: user 密码: ${PASSWORD} "
echo "-----------------------------------------"
else
echo " 启动失败,请检查 /opt/srs/srs.conf 配置文件。"
fi

对应的关闭脚本也需要一个,同样也会顺便清理密码,不需要的话可以单独摘出去

#!/bin/bash
# =======================================================================
# SRS6 一键停止与自动销毁脚本
# =======================================================================

CONTAINER_NAME="srs6_live"

echo "========================================="
echo " 正在准备停止并自动销毁 SRS6 容器..."
echo "========================================="

if [ "$(docker ps -aq -f name=^${CONTAINER_NAME}$)" ]; then
echo "正在强制移除后台运行的容器 [${CONTAINER_NAME}]..."
docker rm -f ${CONTAINER_NAME}
echo " 容器已成功销毁,系统资源已完全释放!"
else
echo " 未发现名为 [${CONTAINER_NAME}] 的活动容器,无需处理。"
fi
echo "========================================="
echo " 准备清空密码并重启 Nginx 断开会话..."
echo "========================================="
htpasswd -cb /opt/srs/.htpasswd user "$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16)"
systemctl restart nginx.service
echo "========================================="

上述启停一切顺利后,配置文件先搁置在一旁,SRS 已经能开始实现数据转换了,这时候记得开放防火墙端口

# 添加防火墙端口,RTMP 1935/TCP,SRT 10080/UDP
firewall-cmd --add-port=1935/tcp --permanent
firewall-cmd --add-port=10080/udp --permanent
firewall-cmd --reload

# 查看 docker 运行的日志
docker logs srs6_live 2>&1

3.2.2 、配置 Nginx 和 SRS 联动

Nginx 部分,首先有点基础配置就行,主要目的是卸载证书,然后开放端口,以及负责反代

先准备所需要的文件和文件夹

域名              your.domain.name 
源网站的根目录 /opt/live
SRS 所在路径 /opt/srs/html
SSL 证书 /opt/srs/cert/fullchain.pem /opt/srs/cert/privkey.pem

然后制作 Nginx 的配置文件,可以参考如下所示

server {
listen 443 ssl;
listen [::]:443 ssl;
listen 443 quic;
listen [::]:443 quic;
http2 on;
http3 on;
server_name your.domain.name;
# 注意:这个路径是原本网站的
root /opt/live;
ssl_certificate /opt/srs/cert/fullchain.pem;
ssl_certificate_key /opt/srs/cert/privkey.pem;
access_log /var/log/nginx/live.log;
error_log /var/log/nginx/live-error.log;

# 启用 QUIC 标识头
add_header Alt-Svc 'h3=":443"; ma=86400';

# ...... 省略原始站点配置

# SRS RTMP LIVE
location /live_room/ {
# 注意:子路径必须使用 alias,不能用 root!
# 这样 Nginx 才会去 /var/www/live_page/ 目录下直接寻找 index.html
alias /opt/srs/html/;
index index.html;

# 简易密码鉴权:只保护直播间,不影响别的(上下认证信息和名称一致)
auth_basic "Private Live Gateway";
auth_basic_user_file /opt/srs/.htpasswd;

# HTTP/3 QUIC 广播(仅允许本站访问,独立作用域置顶,防止被覆盖)
add_header Alt-Svc 'h3=":443"; ma=86400';
add_header X-Frame-Options "SAMEORIGIN";
}

# 2. 直播流底层数据反向代理(SRS 的标准流路径)
location /live/ {
proxy_pass http://127.0.0.1:8080/live/;
proxy_http_version 1.1;

# 极速拉流调优:彻底关闭 Nginx 缓存,消灭几秒的死延时
proxy_buffering off;
proxy_cache off;

# 避免代理透传超时
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;

# 协议升级支持
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

# 透传真实 IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# 🔒 核心流保护:防止懂技术的人绕过网页直接用 VLC 输入 /live/mystream.flv 偷流(上下认证信息和名称一致)
auth_basic "Private Live Gateway";
auth_basic_user_file /opt/srs/.htpasswd;

# ⚡ 同步广播 HTTP/3 QUIC
add_header Alt-Svc 'h3=":443"; ma=86400';
}
}

SRS 的启动脚本会自带这个密码文件,如果不方便当然可以选择手动创建

# 生成密码文件,用户 demo,密码 123456(演示用)
htpasswd -cb /opt/srs/.htpasswd demo 123456
# 基于已生成文件创建第二用户(演示用)
htpasswd -b /opt/srs/.htpasswd test 123456
# 基于已生成文件修改第二用户,密码交互式输入(演示用)
htpasswd /opt/srs/.htpasswd test

然后是 OSB 的推流,注意你推流选择什么档位,别人看就是什么档位的码率分辨率刷新率

设置 - 直播 - 自定义
服务器 rtmp://192.168.1.2/live
推流码 mystream
(等效 rtmp://192.168.1.2/live/mystream)
SRT 推流服务器 srt://192.168.1.2:10080?streamid=#!::r=live/mystream,m=publish&passphrase=3BzTDc0F65UO1Cqd&pbkeylen=16

设置 - 输出 - 输出模式高级 - 直播
CoreAudio AAC + Nvidia Nvenc HEVC + 固定码率 CBR + 音轨 1
CoreAudio AAC + Nvidia Nvenc H264 + 固定码率 CBR + 音轨 1 (如果别人浏览器只能软解就得切这个模式了)

两步完成后,你就可以在 /opt/srs/html/ 放一个 index.html 文件,开始测试访问了。

# 制作 index.html 文件 
# 注意如果网络加载很慢,可以考虑提前下载好 mpegts.min.js 文件在本地同目录下存放

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SRS + Nginx 极简流测试</title>
<script src="https://cdn.jsdelivr.net/npm/mpegts.js@latest/dist/mpegts.min.js"></script>
<style>
body {
font-family: -apple-system, sans-serif;
background: #f0f2f5;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 50px;
}
.player-box {
width: 100%;
max-width: 800px;
background: #000;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
video {
width: 100%;
display: block;
aspect-ratio: 16 / 9;
}
.tips {
margin-top: 20px;
color: #666;
font-size: 14px;
text-align: center;
line-height: 1.6;
}
</style>
</head>
<body>

<h2>SRS + Nginx 信号流测试</h2>

<div class="player-box">
<video id="videoPlayer" controls></video>
</div>

<div class="tips">
<p><b> 测试说明:</b> 请先用文本编辑器打开本网页,修改 JS 代码中的 <b>streamUrl</b> 地址。</p>
<p> 配置完成后,打开网页并<b> 手动点击视频中央的播放按钮</b>,即可测试画面分流与音频是否正常。</p>
</div>

<script>
document.addEventListener('DOMContentLoaded', function () {
if (typeof mpegts !== 'undefined' && mpegts.isSupported()) {
var videoElement = document.getElementById('videoPlayer');

// 🛠️ 新手配置重点:在此处修改为你实际的 Nginx 反代或 SRS 的 HTTP-FLV 拉流路径
// 例如相对路径 '/live/mystream.flv',或绝对路径 'http://你的服务器 IP: 端口/live/mystream.flv'
var streamUrl = window.location.origin + '/live/mystream.flv';

console.log('正在拉取测试流:', streamUrl);

var player = mpegts.createPlayer({
type: 'flv',
url: streamUrl,
isLive: true,
hasAudio: true, // 开启音频支持
hasVideo: true // 开启视频支持
}, {
enableStashBuffer: false // 关闭前置缓冲区,追求最低延迟,方便新手观察推拉流同步性
});

player.attachMediaElement(videoElement);
player.load();

// 异常捕获:方便新手在 F12 控制台排查因跨域 (CORS) 或 404 引发的拉流失败
player.on(mpegts.Events.ERROR, function (type, details, info) {
console.error('拉流发生错误,请检查 Nginx/SRS 配置或流是否未推成功:', type, details);
});

} else {
alert('当前浏览器不支持 H5 mpegts.js 播放,请尝试更换 Chrome/Edge 浏览器。');
}
});
</script>

</body>
</html>

当一切测试好后,音视频双轨都正常访问和浏览,就可以在这个基础上进行优化了

HTML+CSS+JS 的文件是相对简单的,你可以自行修改和处理添加以下常用功能

1. 高可用与 “自愈” 机制(最核心的底层升级)

极简版只管 “把流拉起来”,一旦网络波动或者手机锁屏,流就死了。你的现行版加入了一整套自动化运维逻辑:

看门狗硬自愈: 引入了 setInterval 巡检。如果发现视频在播放状态,但内部时间轴(currentTime)卡死超过 10 秒,立刻强行重建链路。

切屏防假死(Visibility API): 针对手机端退后台、锁屏再返回时容易导致内存锁死和黑屏的痛点,监听了页面的可见性状态,在返回时延迟 100ms 彻底销毁旧实例并重新拉流。

控制栏防误触: 拦截了非控制条区域的点击事件,防止用户在点击画面或者调整声音时,意外把直播给暂停了。


2. 播放内核与拉流参数微调
极简版参数极为保守,而现行版针对 mpegts.js 进行了深度榨干:

防累积延迟: 启用了缓存区(enableStashBuffer: true)以及追帧赶片逻辑(Chasing),允许播放器在落后时 “快进” 追上主播。

性能优化: 开启了 enableWorker,将流解码工作放进 Web Worker 异步线程,避免阻塞浏览器主线程卡顿。

秒开策略: 极简版靠用户手动点播放;现行版采用 “JS 层面赋予静音(muted = true)强行破开浏览器原生拦截实现无交互秒开,再通过界面提示用户手动开声音” 的行业熟水做法。


3. 架构设计与安全性
工程化分离: 从单文件拆分为标准的 index.html 、 style.css 、 live.js 三文件结构,方便后续长期维护。

暗号路由(安全混淆): 剔除明文 URL,将 Nginx 的反代拉流路径通过 Base64 密文(L2xpdmUvbXlzdHJlYW0uZmx2)进行混淆,并在前端用 atob() 运行时解密,防止流地址直接暴露被轻易爬取。

4. UI 视觉与 “手术刀式” 多端适配

极简版只是一个居中的黑框,现行版拥有极高的视觉完成度:

智能昼夜系统: 自动根据北京时间判定当前是白天还是黑夜,无缝切换两套 CSS 变量颜色,并动态修改页面标题。

黄金比例锁死: CSS 采用了双向等比例收缩逻辑(max-width: min(1280px, calc(65vh * 16 / 9))),无论在大显示器还是矮屏幕(如 F12 压迫)下,视频永远维持 16:9 且不超出视口。

移动端防溢出防换行: 针对手机窄屏做了媒体查询(Media Query),文字自动缩减字号,状态栏开启自然折行(white-space: normal),确保在任何型号的手机上界面都不错位、不溢出。
常用参数变动如下所示

// Nginx 暗号路由 Base64 密文
// echo -n "/live/mystream.flv" | base64
var cipherText = "L2xpdmUvbXlzdHJlYW0uZmx2";
var streamUrl = window.location.origin + atob(cipherText);

// 参数精准纠偏与对齐
player = mpegts.createPlayer({
type: 'flv',
url: streamUrl,
isLive: true,
hasAudio: true,
hasVideo: true
}, {
enableStashBuffer: true,
liveBufferLatencyMaxDuration: 7, // 最大安全缓冲区秒长度(堆积超量就判断延迟)
liveBufferLatencyMinDuration: 4, // 最小安全缓冲区秒长度(用光缓冲就体验卡屏)
liveBufferLatencyChasing: false, // 停用追帧赶片逻辑(堆积超量就倍速直到恢复到缓冲区间)
enableWorker: true, // 多线程解码
lazyLoad: true, // 开启懒加载清理缓冲区
lazyLoadMaxDuration: 8, // 本地缓冲区延迟秒最大值(堆积超量就删帧跳秒)
lazyLoadRecoverDuration: 4, // 本地缓冲区延迟秒安全值(跳秒剩下的秒数,中间全跳)
seekType: 'range'
});

4 、体验感想

RTMP 吃带宽,服务器带宽不够根本跑不快

SRT 是 UDP ,自带加密,天然支持 HEVC 等协议

HLS 高延迟,但多平台兼容性好

HLV 低延迟,但多平台兼容性差

WebAPI 更低延迟,但需要限定公网 IP 。

玩玩就好,真正好多人想一起看电影,还得走视频会议的方式更妥帖。

如果 SRS 的 WebAPI 要是支持 Turn 服务器 就好了。


5 、参考链接

Enabling Video Streaming for Remote Learning with NGINX and NGINX Plus [ 链接 ]

How to setup Nginx+RTMP live stream server [ 链接 ]

StarryVoid

Have a good time