直播协议选择与http-flv Demo 一、概述 面对网络带宽提升,直播成为不少网站、软件的必备功能。而流传输协议的选择将对直播效果产生巨大影响。 这里我们将对主流的几个协议进行简单的分析,并提供一个基础的应用实现。
二、推流方式 RMTP(Real Time Message Protocol)是目前主流的推流协议,他具有较低的时延,和高稳定性。 同时,有较多的相关实现,便于使用。具体内容将在后续分析。
三、拉流方式
HLS(Http Living Streaming) HLS是基于HTTP协议的,实现方式是,在服务器收到视频流的时候,根据一个分段时间将视频缓存,并封包成一个ts文件, 同时维护一个m3u8文件用于保存视频的分段信息。然后对于客户端而言,就是在根据m3u8文件 来播放一个个视频片段。而对于每次视频片段的请求,都需要因此TCP握手,这就使得分段的时间大小不仅影响了时延, 同时频繁的请求还会额外占用资源。 官方的建议分段是10s。而直播的时延必然不止分段大小,通常在20s以上。 但其分段的优势使得视频源(如清晰度)的切换相对平滑。而且也得到了较好的浏览器等的支持。
RTMP(Real Time Messaging Protocol) RTMP实际上是使用TCP作为传输协议,所以具有高可靠性,但与HLS不同的是,他使用的是长链接,因此只需要一次握手 即可重复传输数据。它将数据分为一个个的块,然后一个个发送。 由于是长连接,因此对服务器的资源消耗是比较大的,因此常是作为推流方式。 但其时延显著低于HLS协议,甚至能够达到1-3s。同时,由于不是http协议,可以防止http直接下载,具有较好的保密性 (技术门槛提高)。
RTSP(Real-Time Stream Protocol) RTSP类似于RTMP,但是更类似于FTP。它通过RTP传输数据,通过RTSP本身发送控制信息,而不像RTMP,直接将控制命令与数据一起封包。 如此方式使得RTSP在实现直播的时候比较复杂,并且与RTMP一样,都具有累积时延 但也具有实时性好,稳定性高的特点。
HTTP 最后是http协议的方式。我们通过将直播的流数据(通过RTMP推流)直接打包成flv文件,然后模拟HTTP的方式发送。 这种方式类似于HLS,但是其通过http的方式传输flv文件,因此在第一次握手后,客户端即可不断下载服务器的flv文件(直播流),服务器负担小。 但是与HLS相似,都具有保密性弱的劣势。 不过,它的时延可以达到与RTMP相似的程度。另外,原本需要使用Flash算是一个劣势, 但是,小破站NB,感想b站程序员开源的flv.js使得html5播放flv文件不再依赖flash。
四、直播Demo实现
这里通过obs+nginx+nginx-rtmp-module实现rtmp推流+http_flv拉流。
同时,依赖nginx-rtmp-module还实现了推流身份验证。
具体安装过程自行百度。
配置文件如下 Nginx配置1(rtmp推流配置)
script 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 # user nobody; worker_processes 1; events { worker_connections 1024; } rtmp_auto_push on; rtmp_auto_push_reconnect 1s; rtmp_socket_dir /tmp; rtmp { out_queue 4096; out_cork 8; max_streams 128; timeout 15s; drop_idle_publisher 15s; log_interval 5s; #interval used by log module to log in access.log, it is very useful for debug log_size 1m; #buffer size used by log module to log in access.log server { listen 1935; server_name localhost; #for suffix wildcard matching of virtual host name application http_flv { live on; on_publish http://xxx.com/auth; # 推拉开始身份验证地址 on_publish_done http://xxx.com/on_publish_done; # 推拉结束回调地址 gop_cache on; #open GOP cache for reducing the wating time for the first picture of video } } }
Nginx配置2(http拉流配置)
script 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; location / { root html; index index.html index.htm; } location /live { flv_live on; #open flv live streaming (subscribe) chunked_transfer_encoding on; #open 'Transfer-Encoding: chunked' response add_header 'Access-Control-Allow-Origin' '*'; #add additional HTTP header add_header 'Access-Control-Allow-Credentials' 'true'; #add additional HTTP header } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
obs推流地址配置
后端处理 由于nginx是通过post的方式请求的,所以通过该方法获取推流的相关信息。其中”推流地址”对应”swfurl”,”秘钥”对应”name” 这里我们将在播的主播存于Redis中,以加速查询在播用户。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @api.route('/auth' , methods=['POST' ] ) def auth (): data = request.form url = data.get('swfurl' ) pull_key = data.get('name' ) if url and pull_key: lst_query = dict (urllib.parse.parse_qsl(urllib.parse.urlparse(url).query)) push_key = lst_query.get('key' ) if push_key is not None : room = Room.query.filter (Room.push_key == push_key, Room.pull_key == pull_key).first() types = RoomType.query.filter (RoomType.id == room.type_id).first() if room and types: rd.sadd("liver" , set_room_key(room)) rd.sadd(types.mname, set_room_key(room)) return Response(response='success' , status=200 ) return Response(status=500 )
最后前端通过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 29 30 31 32 33 34 35 function LiveUrl ( ) { var params = {}; var roomname = GetRoomName(location.href); var roomurl = null ; $.ajax({ url : RequestUrl+roomname, type : "post" , data : params, async :false , dataType : "json" , }).done(function (ret ) { if (!ret['code' ]) { var room_data = ret['data' ]; roomurl = liveUrl+"&stream=" +room_data['url' ]; RoomSet(room_data); } else { alert(ret['msg' ]); } }); return roomurl } function IsLiveRound ( ) { if (!IsLive(GetRoomName(location.href))){ console .log(IsLive(GetRoomName(location.href))); $("#unlive" ).html("主播暂时不在哦" ); } else { $("#unlive" ).html("" ); } }
五、效果展示