SRS+flv.js打造兼容性较高的直播、点播平台

时间:2021-02-06 18:28:58

*************************************************************************************************************************************************

注意:强烈建议使用srs3.0,srs2.0存在的问题(回调,跨域)等问题不需要修改源码,而且可以修改生成mp4视频。

回调:

# SRS推流开始,结束
def live_publish(request):
# 添加磁盘检测
if not mounted():
return HttpResponse(1)
params = str(request.body, encoding="utf-8")
object = json.loads(params)
l_uuid = object.get('stream')
live = Live.objects.get(uuid=l_uuid)
live.status = 'living'
live.save()
return HttpResponse(0)

跨域:

  没测,一直用nginx代理

MP4:

  后期打算直接生成mp4,替换之前的flv

*************************************************************************************************************************************************

1、公司之前用的是:

直播:rtmp+jwplayer

点播:h5(mp4文件)

弊端:兼容性差,貌似跟系统版本,浏览器,浏览器版本都有关。还有就是rtmp推流生成的文件是flv格式,需要转码成mp4才能点播。

2、SRS+flv.js的优点:

当然是兼容性大大提高了,在pc端谷歌,火狐都可以播放,手机端火狐可以,谷歌不行,其他没测。

3、上图,看看效果:

SRS+flv.js打造兼容性较高的直播、点播平台

SRS+flv.js打造兼容性较高的直播、点播平台

样式什么的没添加,官方的demon 直接copy过来。

4、flv.js下载,构建:

Github:https://github.com/Bilibili/flv.js

解压后进入mater:

构建:

npm install
npm install -g gulp
gulp release

在dist下生成了我们需要的js

flv.html:

<!DOCTYPE html>
<html> <head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"> </head> <body>
<script src="flv.min.js"></script>
<video id="videoElement"></video>
<script>
if (flvjs.isSupported()) {
var videoElement = document.getElementById('videoElement');
var flvPlayer = flvjs.createPlayer({
type: 'flv',
url: 'http://192.168.2.192/live/video.flv'
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
}
</script> </body> </html>

type可以是mp4,flv。url的类型要对应,可以是服务器的文件,也可以是rtmp推流的临时文件。

在这一步可以测试下点播是否正常,文件应该放在http服务器下以http协议访问,不能是文件形式访问。http服务器可以是nginx,python,tomcat等都可以

5、如果上一步成功,接下来就是搭建SRS服务器了

Github : https://github.com/ossrs/srs/wiki/v2_CN_SampleHttpFlv

这篇文章介绍的比较详细,下面是简单记录步骤:

假定你已经下载并编译好了SRS,可以参考:SRS服务器搭建,ffmpeg 本地推流

首先复制conf中的http.flv.live.conf为my.http.flv.live.conf,内容:

# the config for srs to remux rtmp to flv live stream.
# @see https://github.com/ossrs/srs/wiki/v2_CN_DeliveryHttpStream
# @see full.conf for detail config. listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 80;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
hstrs on;
} dvr {
# https://github.com/ossrs/srs/wiki/v2_CN_DVR enabled on;
dvr_path ./objs/nginx/html/[app]/[stream].flv;
dvr_plan session;
dvr_duration 30;
dvr_wait_keyframe on;
time_jitter full; } }

这里该了http的服务端口为80,添加了保存rtmp流文件的配置,指定存储路径./objs/nginx/html/[app]/[stream].flv。

启动SRS:

./objs/srs -c conf/my.http.flv.live.conf

接下来就是推流了。

假定你安装了ffmpeg。

ffmpeg -re -i /root/Videos/video.flv -c copy -f flv rtmp://192.168.2.192/live/video

如果推流成功那就可以在VLC中播放rtmp://192.168.2.192/live/video了,这样之前的html中的url就是:http://192.168.2.192/live/video.flv,

把之前的html/js copy到SRS的/objs/nginx/html/ 下,访问 http://ip/flv.html(这时的http服务由SRS提供,和之前的不一样) ,注意ip要和html中的ip一致,否则会报跨域的错。

至此整个直播点播服务的雏形就搭建成功!

6、添加回调

希望在开始推流的时候srs请求python服务,修改资源的状态为正在直播,推流结束是再次回调,请求python服务,修改状态为停止直播

srs的配置:

# the config for srs to remux rtmp to flv live stream.
# @see https://github.com/ossrs/srs/wiki/v2_CN_DeliveryHttpStream
# @see full.conf for detail config. listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console; http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
} vhost __defaultVhost__ {
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
hstrs on;
} dvr {
# https://github.com/ossrs/srs/wiki/v2_CN_DVR enabled on;
dvr_path ./objs/nginx/html/[app]/[stream].flv;
dvr_plan session;
dvr_duration 30;
dvr_wait_keyframe on;
time_jitter full; } http_hooks {
enabled on;
on_publish http://localhost:8000/on_publish/;
on_unpublish http://localhost:8000/on_unpublish/; } }

注意: on_publish的ip需要根据netstat -pantu 判断,看看监听在哪个地址,比如127.0.0.1:8000,那么就应该保持一致,

按理说写localhost也应该可以,在终端用crul localhos:8000 也是可以访问,但是回调时报错:

[2017-11-30 03:08:22.478][error][20398][220][11] dns resolve server error, ip empty. ret=1029(Resource temporarily unavailable)
[2017-11-30 03:08:22.478][warn][20398][220][11] http client failed, server=localhost, port=8000, timeout=30000000, ret=1029
[2017-11-30 03:08:22.478][warn][20398][220][11] http connect server failed. ret=1029
[2017-11-30 03:08:22.478][error][20398][220][11] http post on_publish uri failed. client_id=220, url=http://localhost:8000/on_publish/, request={"action":"on_publish","client_id":220,"ip":"192.168.2.151","vhost":"__defaultVhost__","app":"live","tcUrl":"rtmp://192.168.2.134:1935/live","stream":"f345f5b0d34a11e78008365426bed70e"}, response=, code=-147690992, ret=1029(Resource temporarily unavailable)
[2017-11-30 03:08:22.478][error][20398][220][11] hook client on_publish failed. url=http://localhost:8000/on_publish/, ret=1029(Resource temporarily unavailable)
[2017-11-30 03:08:22.478][error][20398][220][11] http hook on_publish failed. ret=1029(Resource temporarily unavailable)
[2017-11-30 03:08:22.478][error][20398][220][11] stream service cycle failed. ret=1029(Resource temporarily unavailable)

所以还是保持一致的好。

另外:uwsgi并不对外提供访问服务,只由nginx转发,所以服务不要监听在0.0.0.0:8000,更不要写内网ip如192.168.2.111这样的,应为不确定下次启动ip不变。

所以uwsgi最好还是监听在127.0.0.1:8000。

python:

# SRS推流开始,结束
def live_publish(request):
params = str(request.body, encoding="utf-8")
object = json.loads(params)
l_uuid = object.get('stream')
live = Live.objects.get(uuid=l_uuid)
live.status = 'living'
live.save()
return HttpResponse(0) def live_unpublish(request):
params = str(request.body, encoding="utf-8")
object = json.loads(params)
l_uuid = object.get('stream')
live = Live.objects.get(uuid=l_uuid)
live.status = 'stop'
live.save()
return HttpResponse(0)

官方文档的说明:

https://github.com/ossrs/srs/wiki/v2_CN_HTTPCallback

根据:an int value specifies the error code(0 corresponding to success)

大概是说要返回一个0,不过我尝试各种返回值0,“0“,{”code”:0}...都没用

一返回srs就报错:empty response

不知道为毛srs接收不到。

没办法,改源码:

在srs/trunk/src/app/srs_app_http_hooks.cpp

找到报错的位置:

    // should never be empty.
res = SRS_HTTP_RESPONSE_OK;
if (res.empty()) {
ret = ERROR_HTTP_DATA_INVALID;
srs_error("invalid empty response. ret=%d", ret);
return ret;
}

在进入判断前先赋值:res = SRS_HTTP_RESPONSE_OK;

然后重新编译安装。

还可以打包传服务器上用。

./scripts/package.sh --x86-x64

当然这只是权宜之计,因为我不需要判断用户的权限来滤用户,所以不用控制response返回值,希望日后搞明白说明原因导致。

如果有读者知道原因,还请告知,谢谢。

7、视频编辑

剪切:

ffmpeg -ss 0:1:30 -t 0:0:20 -i input.avi -vcodec copy -acodec copy output.avi

-ss 开始时间,-t 持续时间

提取图片:

ffmpeg –i test.avi –r 1 –f image2 image-%3d.jpeg

封装:

ffmpeg –i video_file –i audio_file –vcodec copy –acodec copy output_file

flv快速添加关键帧(为了拖动播放):

yamdi -i tmp.flv -o 51e714ded33a11e7889a365426bed70e.flv

8、压力测试

~/Downloads/flazr-0.7-RC2# ./client.sh rtmp://192.168.2.134:1935/live/a54b2dceda5911e7a5b1365426bed70e -load 200

查看srs服务器的网卡信息:

ethtool eth0

查看 srs服务器的流量:

iftop

9、转了一圈回到原点

前段时间用以上方案搭建的直播点播系统测试结果还是比较满意的

笔记本(百兆网卡)网线直连开发板(千兆网卡):

子码流(100-150并发)主码流(10-20)

笔记本(千兆网卡)网线直连开发板(千兆网卡):

子码流(没测,不过不会超过1000,srs中有最大连接数设置)主码流(100-200)(目标就是支持100人在线观看)

但是前两天用录播主机推流到开发板,出现视频流畅,声音卡顿的现象。

之前怀疑过网络不畅通,视频源有问题,录播主机有问题,flv.js。

后来发现是flv.js在处理某些视频流,或视频文件(直播http-flv,点播xx.flv)会发生以上现象。

但是直接用VLC客户端播放没有声音卡顿的现象。

没办法,只好改回JWPlayer(播放rtmp),Video(播放mp4)

这就需要将推流的临时文件xxx.flv重新封装成xxx.mp4。

希望flv.js的后续版本可以解决这样的问题。

10、转机

测试几天发现只有录播设备播放文件通道的时候声音会卡顿,直接将其他电脑的音视频接入没有卡顿,但是随着播放时间加长,会出现声音延迟的现象。

SRS+flv.js打造兼容性较高的直播、点播平台

11、环境和部署脚本:

├── CentOS-Base.repo
├── ffmpeg-3.4
├── install.sh
├── my.http.flv.live.conf
├── nginx-1.12.2
├── nohup.out
├── Python-3.5.0
├── run.sh
├── SRS-Ubuntu12-armv7cpu-2.0.243
├── stop.sh
├── touch
├── touch.conf
├── touch.ini
└── yamdi-1.9

install.sh:

#!/bin/bash

install_list='system python srs nginx deploy env ffmpeg yamdi'
#install_list='ffmpeg yamdi'
if [[ ${install_list} =~ system ]]
then
#替换yum源,更新系统
yum_path='/etc/yum.repos.d/'
for file in ${yum_path}*
do
end_str=${file:0-3}
if [ "${end_str}" != 'bak' ]
then
mv $file ${file}.bak
fi
done
cp CentOS-Base.repo ${yum_path}
yum update #安装基本工具
yum install net-tools
yum install nc
else
echo '>>>pass system'
fi if [[ ${install_list} =~ "python" ]]
then
#Python3.5安装
cd Python-3.5.0/
./configure
make
make install
pip3 install uwsgi
cd ..
else
echo '>>>pass python'
fi if [[ ${install_list} =~ "nginx" ]]
then
#nginx 安装
yum -y install zlib zlib-devel openssl openssl--devel pcre pcre-devel
cd nginx-1.12.2/
./configure
make
make install
cd ..
else
echo '>>>pass nginx'
fi if [[ ${install_list} =~ "srs" ]]
then
#srs 安装
yum install redhat-lsb -y
cd SRS-Ubuntu12-armv7cpu-2.0.243/
./INSTALL
cd ..
else
echo '>>>pass srs'
fi if [[ ${install_list} =~ "ffmpeg" ]]
then
#ffmpeg 安装
cd ffmpeg-3.4/
#./configure
make
make install
cd ..
else
echo '>>>pass ffmpeg'
fi if [[ ${install_list} =~ "yamdi" ]]
then
#yamdi 安装
cd yamdi-1.9/
make
make install
cd ..
else
echo '>>>pass yamdi'
fi if [[ ${install_list} =~ "deploy" ]]
then
#部署项目
mkdir /opt/script/
cp my.http.flv.live.conf /usr/local/srs/conf/
cp touch.conf /usr/local/nginx/conf/
cp touch.ini /opt/script/
cp touch /opt/
mkdir /usr/local/nginx/html/images/
cp touch/tmp/* /usr/local/nginx/html/images/
else
echo '>>>pass deploy'
fi if [[ ${install_list} =~ "env" ]]
then
#安装项目依赖
pip3 install django==1.9.8
pip3 install xadmin
pip3 install future
pip3 install django_crispy_forms
pip3 install django-formtools
pip3 install httplib2
pip3 install six
pip3 install django_import_export
pip3 install django-cors-headers
pip3 install django-pure-pagination yum install python-devel zlib-devel libjpeg-turbo-devel -y
pip3 install Pillow else
echo '>>>pass env' fi

run.sh:

#!/bin/bash

#启动项目
#touch
pkill -9 uwsgi
cd /opt/
uwsgi --ini script/touch.ini &
chmod 766 /opt/script/touchrnb.sock #nginx
pkill -9 nginx
cd /usr/local/nginx/
./sbin/nginx -c conf/touch.conf & #srs
pkill -9 srs
cd /usr/local/srs/
./objs/srs -c conf/my.http.flv.live.conf > /dev/null &

stop.sh:

#!/bin/bash

#停止项目
#nginx
pkill -9 nginx
#srs
pkill -9 srs
#touch
pkill -9 uwsgi

开机启动:

编辑/etc/rc.d/rc.local,添加run.sh脚步路径

12、文件瘦身

strip   objs/srs

(arm版本:arm-hisiv300-linux-strip)

可以从7-8兆减到2-3兆

13、srs跨域

之前是nginx代理服务器和srs在同一台机器上,但是公司考虑到嵌入式板的性能问题,需要提供更换直播服务器的功能,为了避免修改nginx配置的问题,所以

直播服务器地址由后台配置,存到数据库,而不使用nginx代理。但是这样就有跨域的问题了,我使用的srs版本为2.0,目前解决跨域的方法是修改源码。

参考:https://github.com/ossrs/srs/issues/1002

修改 src/app/srs_app_http_stream.cpp

在486行添加 w->header()->set("Access-Control-Allow-Origin", "*");

重新编译安装即可

14、集群和负载均衡

集群很简单参考:https://github.com/ossrs/srs/wiki/v3_CN_SampleHttpFlvCluster

负载均衡:

如果集群较大推荐CDN,如果小集群可以用nginx

值得一提的是srs在接收nginx的转发请求时不是用的相对路径

nginx配置文件:

worker_processes  4;

events {
worker_connections 1024;
} http { include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
gzip on;
client_max_body_size 4096m; upstream localhost{
ip_hash;
server 127.0.0.1:8080;
server 192.168.2.127:8080;
} server {
listen 80;
server_name 192.168.2.192 ;
charset utf-8; location / {
add_header 'Access-Control-Allow-Origin' '*';
proxy_pass http://127.0.0.1:8000/;
} # 指定静态文件路径
location /static/ {
alias /root/GitClient/touch/static_all/;
index index.html index.htm;
} location /uwsgi_http/ { proxy_pass http://localhost/; } } }

如果upstream localhost 改为upstream aaa,浏览器访问: http://127.0.0.1/uwsgi_http/live/123.flv

srs接收到的是:http://aaa/live/123.flv

所以srs服务器要配置hosts文件,使aaa指向127.0.0.1

这里我直接命名为localhost,这样就可以偷懒啦。

还有负载均衡策略指定ip_hash,因为源站和边缘之间的视频可能不同步,这样可以提高用户体验。

但是如果srs集群的性能差异较大,还是用weight策略好一点。

15、flv.js优化--低延时(为了导播,直播不需要)

    <script>
if (flvjs.isSupported()) {
var videoElement = document.getElementById("myplayer");
var flvPlayer = flvjs.createPlayer({
type: 'flv',
isLive: true,
url: '{{ LIVE_URL }}{{ current_live.uuid }}.flv',
},
{
enableWorker: false,
enableStashBuffer: false,
stashInitialSize: 1,
lazyLoad: false,
lazyLoadMaxDuration: 1,
lazyLoadRecoverDuration: 1,
deferLoadAfterSourceOpen: false,
autoCleanupMaxBackwardDuration: 1,
autoCleanupMinBackwardDuration: 1,
statisticsInfoReportInterval: 1,
fixAudioTimestampGap: false, }); flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
}
</script>

srs低延时配置:

vhost __defaultVhost__ {
gop_cache off;
queue_length 10;
min_latency on;
mr {
enabled off;
}
mw_latency 100;
tcp_nodelay on;
}

video低延时:

videoElement.addEventListener('progress', function() {
var range = 0;
var bf = this.buffered;
var time = this.currentTime; while(!(bf.start(range) <= time && time <= bf.end(range))) {
range += 1;
}
this.currentTime = this.buffered.end(range) - 0.01;
});

设置video低延时会触发waiting事件,出现一个圆圈和降低屏幕亮度,有待处理...

这样大概可以把延时从2~3降到1秒左右。(环境不同可能有差别,在网线接交换机的情况下会比连WiFi要好)

SRS+flv.js打造兼容性较高的直播、点播平台

延时和流畅不可兼得,需求不同要设置不同参数。

16、音频不同步和卡顿的解决

参考:https://github.com/Bilibili/flv.js/issues/136

解决方法是 fixAudioTimestampGap: false,注意这个配置要在config的位置

17、添加HLS流

官方文档:https://github.com/ossrs/srs/wiki/v3_CN_SampleHLS

vhost __defaultVhost__ {
... hls {
enabled on;
hls_fragment 10;
hls_window 60;
hls_path ./objs/nginx/html;
hls_m3u8_file [app]/[stream].m3u8;
hls_ts_file [app]/[stream]-[seq].ts;
hls_dispose 10; }
}

存在的问题:同一地址第一次正常,后面推的都不能看,第一次生成ts切片正常,后面的ts切片会重复丢弃和生成。

参考: 转hls输出时出现的问题 #894:https://github.com/ossrs/srs/issues/894

在SrsHls::on_unpublish的时候设置SrsHls::aac_samples=0后正常。