SRS+flv.js打造兼容性较高的直播、点播平台
*************************************************************************************************************************************************
注意:强烈建议使用srs3.0,srs2.0存在的问题(回调,跨域)等问题不需要修改源码,而且可以修改生成mp4视频。
回调:
1 2 3 4 5 6 7 8 9 10 11 12 | # 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、上图,看看效果:#
样式什么的没添加,官方的demon 直接copy过来。
4、flv.js下载,构建:#
Github:https://github.com/Bilibili/flv.js
解压后进入mater:
构建:
1 2 3 | npm install npm install - g gulp gulp release |
在dist下生成了我们需要的js
flv.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <!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,内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | # 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:
1 | . / objs / srs - c conf / my.http.flv.live.conf |
接下来就是推流了。
假定你安装了ffmpeg。
1 | 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的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | # 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 也是可以访问,但是回调时报错:
1 2 3 4 5 6 7 | [ 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # 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
找到报错的位置:
1 2 3 4 5 6 7 | / / 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;
然后重新编译安装。
还可以打包传服务器上用。
1 | . / scripts / package.sh - - x86 - x64 |
当然这只是权宜之计,因为我不需要判断用户的权限来滤用户,所以不用控制response返回值,希望日后搞明白说明原因导致。
如果有读者知道原因,还请告知,谢谢。
7、视频编辑#
剪切:
1 | ffmpeg - ss 0 : 1 : 30 - t 0 : 0 : 20 - i input .avi - vcodec copy - acodec copy output.avi |
-ss 开始时间,-t 持续时间
提取图片:
1 | ffmpeg –i test.avi –r 1 –f image2 image - % 3d .jpeg |
封装:
1 | ffmpeg –i video_file –i audio_file –vcodec copy –acodec copy output_file |
flv快速添加关键帧(为了拖动播放):
1 | yamdi - i tmp.flv - o 51e714ded33a11e7889a365426bed70e .flv |
8、压力测试#
1 | ~ / Downloads / flazr - 0.7 - RC2 # ./client.sh rtmp://192.168.2.134:1935/live/a54b2dceda5911e7a5b1365426bed70e -load 200 |
查看srs服务器的网卡信息:
1 | ethtool eth0 |
查看 srs服务器的流量:
1 | 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、转机#
测试几天发现只有录播设备播放文件通道的时候声音会卡顿,直接将其他电脑的音视频接入没有卡顿,但是随着播放时间加长,会出现声音延迟的现象。
11、环境和部署脚本:#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ├── 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | #!/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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #!/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:
1 2 3 4 5 6 7 8 9 | #!/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配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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优化--低延时(为了导播,直播不需要)#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <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低延时配置:
1 2 3 4 5 6 7 8 9 10 | vhost __defaultVhost__ { gop_cache off; queue_length 10; min_latency on ; mr { enabled off; } mw_latency 100; tcp_nodelay on ; } |
video低延时:
1 2 3 4 5 6 7 8 9 10 | 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要好)
延时和流畅不可兼得,需求不同要设置不同参数。
16、音频不同步和卡顿的解决#
参考:https://github.com/Bilibili/flv.js/issues/136
解决方法是 fixAudioTimestampGap: false,注意这个配置要在config的位置
17、添加HLS流#
官方文档:https://github.com/ossrs/srs/wiki/v3_CN_SampleHLS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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后正常。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(三):用.NET IoT库
· 【非技术】说说2024年我都干了些啥