乐趣区

关于服务器:玩转直播系列之从-0-到-1-构建简单直播系统1

一、前言

随着 5G 时代的到来,音视频行业也可能迎来一个行业的春天,直播则是新视频行业始终以来的一个重要的产品状态,从最后的秀场直播,游戏直播,到往年因为疫情,目前比拟火的在线教育直播,带货直播等,各类新的直播模式则是越来越多的展现在公众背后。

作为技术开发的咱们,明天咱们一起简略的理解一下,如何疾速搭建一套最简略的直播零碎,简略地理解一下支流直播的架构模型。

二、推拉流模型

首先咱们先看一张残缺的直播推拉流的模型图,咱们能够很分明地看到直播宏观上的架构模型图。

2.1 直播三个次要模块

推流模块

推流模块次要分为音视频数据的采集,如果是秀场类直播,能够做美颜滤镜相干性能,用来晋升直播的画面品质和用户体验,最初通过编码压缩,升高音视频数据的体积,最初通过流媒体传输协定将数据依照固定格局传递到 RTMP 服务器,这样整个推流端的工作就实现了。

RTMP 服务端模块

传统意义上的 RTMP 服务器其实可能就只有转码的性能,将推流端传递过去的数据,转成 flv 等网络格局的数据文件,不便播放端的观看,不过目前云商都提供了一整套的解决方案,例如清晰度转码,内容健康检查,直播封面的生成,数据统计,录制回放等性能,这也是在 RTMP 服务器的根底上,进行的业务封装,这样能力提供一整套的解决方案。

播放端模块

播放端的逻辑就绝对比较简单,简而言之就是获取拉流地址,进行音视频的播放,不过在理论开发的过程中,播放端的业务工作量和技术优化点都是最多的,如上图所示的首屏秒开,解码优化,切换直播间等性能,都是须要破费大量的精力,依据业务一直地去演进优化的。

三、搭建步骤

本入门直播简略教程次要分为如下几个模块:

搭建直播服务器;

应用 OBS 进行推流;

直播流如何观看;

直播间音讯的实现。

3.1 搭建直播服务器

直播服务器实时地将推流端上传的视频流进行解析和编解码,以用于反对 rtmp、hls 或 httpflv 等直播协定的观看端进行观看。

以后市面上有很多开源的直播服务器解决方案,如 livego、srs 和 nginx-rtmp,亦或者是目前比拟支流的云解决方案,目前阿里云,七牛云,腾讯云等都提供了规范的成熟的解决方案,本篇文章旨在疾速地搭建一个简略的直播,所以咱们能够采纳 livego 这个凋谢源代码的形式去搭建推拉流服务器,livego 应用纯 go 语言编写,性能高且跨平台,装置和应用非常简单,反对罕用的传输协定、文件格式和编码格局,或者装置上文所示,间接在云商开播直播服务。

装置 livego 次要有三种形式:1)间接下载二进制可运行文件;2)从 Docker 启动;3)从源码编译。

docker run -p 1935:1935 -p 7001:7001 -p 7002:7002 -p 8090:8090 -d gwuhaolin/livego

其中,各个端口的含意如下:

8090:HTTP 治理拜访监听地址

1935:RTMP 服务监听地址

7001:HTTP-FLV 服务监听地址

7002:HLS 服务监听地址

3.2 应用 OBS 推流

OBS(Open Broadcaster Software)是一款开源收费的提供视频录制和直播性能的软件,去 OBS 官网下载对应平台的软件进行装置即可。

要想推流,首先要解决的是“推什么”的问题,也就是要明确流的起源。关上 OBS,点击新建“起源”按钮,如下图中第 1 步所示,能够看到 OBS 反对的起源比拟丰盛,有媒体源、显示器采集、浏览器和窗口采集等等。此处用现有的 mp4 文件来进行循环推流,因而起源抉择“媒体源”,名称用默认的就行,点击“确定”后,设置要播放的视频文件,而后点击“确定”即可。

而后,要解决的就是“往哪推”的问题,也就是须要有一个可用的推流地址才行。

后面咱们曾经搭建好了 livego 直播服务器,它提供了一个默认推流地址:rtmp://localhost:1935/live,一个规范的 RTMP 服务器的推流 URL 相似这种格局:rtmp://domain/AppName/StreamName,然而要想应用该推流地址,须要有受权的 channelkey 才行。

通过拜访 http://localhost:8090/control/get?room=movie 就能够获取用于推流的 channelkey,如下所示,其中 data 字段就是此次获取到的 channelkey。

{
    "status": 200,
    "data": "rfBd56ti2SMtYvSgD5xAV0YU99zampta7Z7S575KLkIZ9PYk"
}

到当初,推流地址和 channelkey 都有了,只须要在 OBS 外面进行相干设置就能够进行推流。首先点击“控件”的“设置”按钮,进入设置面板。

而后,抉择“推流”选项。服务抉择“自定义”,服务器设置为:rtmp://localhost:1935/live,串流密钥设置为后面获取到的 channelkey:rfBd56ti2SMtYvSgD5xAV0YU99zampta7Z7S575KLkIZ9PYk。设置好后,点击“控件”的“开始推流”按钮,就能够进行推流了。

个别状况下,默认的输入配置就足以应酬大多数场景了,然而要想取得更适宜本人想要的的直播成果的话,能够在“输入”选项里设置“高级”输入模式,对此无需要的话能够间接跳过本局部。如下图所示,在高级输入设置界面,能够对串流、录像、音频和回放缓存进行配置,其中,最重要的就是对串流的设置。编码器软件能够抉择 x264 和 QuickSync H.264,应用弱小的 x264 就能够。“从新缩放输入”能够设置输入的分辨率,默认应用原视频的分辨率。

比特率(码率)的含意是视频通过压缩编码后每秒的数据量的大小,单位是 Kbps,此处 K=1000。该值越大,每秒推送的视频数据流就越大,视频品质也越高,然而占用的带宽也更多,能够依据须要进行调整,个别秀场直播罕用 2000~2500Kbps 就可,游戏直播可能对码率的要求比拟高一点,能够做对应的调整。

直播推流时,能够应用多种码率管制形式,次要有 CBR、ABR、VBR 和 CRF。

CBR(Constant Bitrate)恒定码率,肯定工夫范畴内比特率根本放弃恒定。应用该模式时,在视频动静画面较多的场景下,图像品质会变差,而在动态画面较多的场景下,图像品质又会变好。

VBR(Variable Bitrate)可变码率,其码率能够随着图像的复杂程度的不同而变动。应用该模式时,在图像内容比较简单的场景下,调配较少的码率,而在图像内容简单的场景下,则调配较多的码率。这样既保证了品质,又兼顾到带宽限度,优先思考到图像品质。

ABR(Average Bitrate)均匀比特率,是 VBR 的一种插值参数。简略场景调配较低码率,简单场景调配足够码率,这一点相似 VBR。同时,肯定工夫内平均码率又靠近设置的指标码率,这一点又相似 CBR。能够认为 ABR 是 CBR 和 VBR 的折中计划。

CRF(Constant Rate Factor)恒定码率系数。CRF 值能够了解为对视频的清晰度和晦涩度冀望的一个固定输入值,即无论是在简单场景还是在简略场景下,都心愿有一个稳固的主观视频品质。

关键帧距离(Group of Pictures,GOP)指的是一组由一个 I 帧、多个 P 帧和 B 帧组成的一个帧序列。一帧就是视频中的一个画面,其中:

I 帧(intra coded picture):最残缺的画面,自带全副信息,无需参考其余帧即可解码,每个 GOP 都是以 I 帧开始;

P 帧(predictive coded picture):帧间预测编码帧,须要参考后面的 I 帧或 P 帧,能力进行解码,压缩率较高;

B 帧(bipredictive coded picture):双向预测编码帧,以前帧后帧作为参考帧,压缩率最高。

对于一般视频,加大 GOP 长度有利于减小视频体积,然而在直播场景下,GOP 过大会导致客户端的首屏播放工夫变长。GOP 越小图片品质越高,倡议设为 2 秒,最长不要超过 4 秒。

3.3 直播流观看

咱们刚刚曾经搭建实现了 RTMP 服务器,并且应用目前比拟成熟,性能比拟丰盛的推流工具 OBS 进行推流,接下来咱们就要解决如何在用户终端进行观看了的问题。

FLV(Flash Video)是一种网络视频格式,是一种流媒体格式,目前支流的一些直播网络应用的流媒体格式比拟多的都是 flv,它可能不须要装置任何插件即可进行播放。

3.3.1 小试牛刀:应用 VLC 工具观看

VLC 是一款音视频播放器,能够播放本地媒体,也能够播放网络上的媒体,到官网 https://www.videolan.org/index.zh.html 下载对应的安装包装置即可。

点击“媒体”tab 下的“关上网络串流”选项,而后网络地址设置为:rtmp://localhost:1935/live/movie,点击“确定”后就能够看到 OBS 推流的视频啦。

应用 VLC 次要是不便开发同学进行观看测试,例如观看卡顿的问题,分辨率查看,时延问题的定位,VLC 算是一个比拟业余的工具,可能不便咱们去定位问题和解决问题的

3.3.2 应用 flv.js 进行浏览器端的观看

flv.js 是指标最为风行的 html5 的纯的 javascript,也是目前国内比拟支流的浏览器终端播放 flv 格局的解决方案,本大节咱们就应用 flv.js 进行简略的播放,关上如下的网址:http://bilibili.github.io/flv.js/demo/。


能够看到如图所示的,将如下 streamURL 的输入框输出 http://127.0.0.1:7001/live/movie.flv 后,点击 switch to MediaDataSource 后 Load 即可播放如下的画面。

3.3.3 直播协定的简略介绍

到目前为止,咱们曾经胜利的搭建了 RTMP 小框架,理解了整个推拉流的残缺过程,接下来咱们就须要对与 RTMP 协定几个强相干的直播网络传输协定有一个入门的理解。

国内常见的直播协定有几个:

RTMP

HLS

HTTP-FLV

HLS全称是 HTTP Live Streaming。这是 Apple 提出的直播流协定。目前,IOS 和 高版本 Android 都反对 HLS,HLS 次要的两块内容是 .m3u8 文件和 .ts 播放文件。接管服务器会将接管到的视频流进行缓存,而后缓存到肯定水平后,会将这些视频流进行编码格式化,同时会生成一份 .m3u8 文件和其它很多的 .ts 文件,HLS 的长处是跨平台性比拟好,HTML5 能够间接关上播放,挪动端兼容性良好,毛病也是比拟显著,就是时延比拟高,如果有些直播,例如互动性不高的直播,能够应用该协定,HLS 网络传输格局是非常适合用于点播的场景。

RTMP全称 Real Time Messaging Protocol,即实时音讯传送协定,对于开发者来说,咱们先明确 RTMP 是应用层协定,底层是应用的 TCP 传输协定,这边咱们晓得 RTMP 是音视频相干畛域的协定,所以这块应用 TCP 作为次要的传输层协定也给后续 RTMP 对于网络的各种各样的演进,留下了很多的空间,在直播行业,特地是在推流端,RTMP 协定是货真价实的霸主,基本上所有支流的直播网站都是反对 rtmp 协定进行推流的,对于 RTMP 的具体协定细节,后续文章有具体的剖析。

FLV(Flash Video)是 Adobe 公司推出的另一种视频格式,是一种在网络上传输的流媒体数据存储容器格局。其格局绝对简略轻量,不须要很大的媒体头部信息。整个 FLV 由 The FLV Header, The FLV Body 以及其它 Tag 组成。因而加载速度极快。采纳 FLV 格局封装的文件后缀为 .flv。

流媒体协定 RTMP, HTTP-FLV, HLS 简略比照:

3.3.4 直播中的音讯

在秀场直播零碎中,如果说音视频性能的实现,是给直播打扮上了富丽的新装表面的话,那么直播零碎中音讯零碎的实现,则是整个直播富丽新装下的灵魂,如何搭建高可用的直播间音讯零碎,也是每一个直播零碎必须要解决的问题。

在设计秀场直播的音讯零碎之前,咱们须要简略地梳理一下直播间的音讯类型。

告诉类音讯 例如送礼、弹幕、进场、榜单变动、等级变动等等音讯。他们的特色是告诉用户直播间的事件,营造直播间气氛,晋升用户观看直播的体验。

性能类音讯 例如踢人、反垃圾审核、红包、PK 音讯等等。这类音讯的特色是辅助直播业务发展,在流程上串联开播端、观看端、服务端三个角色。

咱们能够从业务角度中,剖析出直播间的各类音讯尽管因为业务状态各式各样,最终出现的模式也是多彩壮丽,然而咱们能够从各类的音讯展示模式能够剖析出,音讯从开发的角度,有如下几个个性,咱们依照音讯是否可抛弃,和实时性划分,咱们能够把所有的业务音讯归为如下几类:


在直播零碎中,秀场直播,带货直播的直播间音讯信令通信是比拟偏多的,次要是因为业务性质所决定的,秀场直播和带货直播这两类直播的互动性绝对比拟强,玩法也比拟多样,依照咱们上图的分类,每一个业务的音讯的可抛弃性和实时性要求都不一样,所以在开发音讯零碎的时候,也须要对音讯进行优先级排序,对音讯散发的实时性也要有业务性能考量。

刚刚针对直播间音讯实时性和不可抛弃性这两个属性做了业务上相干的论述,不过对于直播音讯而言,第一因素是稳定性,音讯如何精确稳固地散发到指定的直播间,也是咱们须要思考的问题之一,直播音讯的散发实现,从总体上说能够分为两种实现形式,第一是依附直播间的实时通信(Instant Messaging),也就是咱们常说的 IM 音讯零碎,第二个是依附 http 短轮询,例如客户端每隔 1 秒来申请一次服务器,服务器返回这一秒内产生的增量音讯信息,客户端获取到这些增量信息,再依据具体的音讯业务类型,再进行绝对业务的页面 UI 渲染,这样就能够了,从技术上说,一个是“推”模型,一个是“拉”模型,明天咱们因为搭建一个简略的直播间音讯零碎,咱们先用一个简略的 ” 拉 ” 模型进行简略的实现。

根本实现思路:客户端每隔一个极短的工夫,例如 1 秒亦或者更短的工夫,依据直播间的 id 来调用服务端的接口,轮询该直播间产生的音讯,服务端这边咱们应用 redis 的 SortedSet 的数据结构来存储音讯,其中 key 是直播间的房间 id,score 是服务器接管到该音讯事件生成的工夫戳,value 能够简略地间接存储该音讯序列化后的字符串,这样能够依照工夫程序地去存储音讯,并且配置过期音讯的删除逻辑,整个音讯的存储就能够简略地搭建起来。

音讯存储用 java 的伪代码所示:

 long time = new Date().getTime();
 
try {
     // redis 中插入音讯数据
     jedisTemplate.zadd(V_UNIQUE_ROOM_ID, time, JSON.toJSONString(roomMessage));
 
     // 依照概率性的去删除 redis 中过期的音讯数据
     if (probability()) {deleteOverTimeCache(V_UNIQUE_ROOM_ID);
        }
     } catch (Exception e) {log.error("message save error", e);
 }

能够看到音讯存储,如果应用 redis 的 sortedSet 进行存储还是比拟不便的,接下来咱们须要解决就是 redis 中过期音讯的删除,因为有效的过期音讯是没有价值的(所有的音讯能够做长久化存储),redis 中如果繁多的 key 存储的音讯过多,也会导致音讯的慢查,和内存的使用量一直增大,这是咱们不想看到的,这边因为是示例代码,所以简略地解决一下删除逻辑。

    private void deleteOverTimeCache(String roomId) {Long totalCount = jedisTemplate.zcard(roomId);
 
        log.info("deleteOldTimeCache size is {}", totalCount);
 
        if (totalCount < 600) {return;}
 
        // 倒序删除过期数据
        Set<Tuple> tuples = jedisTemplate.zrangeWithScores(roomId, -601, -1);
 
        if (CollectionUtils.isNotEmpty(tuples)) {for (Tuple tuple : tuples) {
                // 这是第一个 -600 条的那个 score
                double score = tuple.getScore();
                jedisTemplate.zremrangeByScore(roomId, 0d, score);
                break;
            }
        }
    }

下面的伪代码 probability()首先先做一个概率性的判断,例如咱们做百分之一的随机判断,判断该次申请是否要进行音讯的删除(请留神咱们删除的逻辑是放在插入的逻辑之中的。如果每一次插入都须要判断是否要删除过期数据,会影响插入的性能)。如果通过概率性判断后,咱们就优先判断某一个直播间的音讯个数,如果音讯个数还是比拟少的话,则退出删除逻辑,如果超过音讯阀值,则依照工夫倒序删除曾经过期的音讯。

说完了 http 短轮询音讯的存储后,咱们最初再简略地说一下客户端音讯查问实现逻辑。客户端通过直播间 id 和工夫戳两个字段来申请服务端以查问直播间音讯,其中 ” 工夫戳 ” 是每一次服务端返回的,这个工夫戳是渐进式的,当下一次客户端来申请服务端的数据的时候,都会带来上次服务端返回的工夫戳,伪代码如下:

   @Override
 public RoomMessage queryRoomMessages(MessageMessageReq messageMessageReq) {RoomMessage result = new RoomMessage();
 
        long timestamp = messageMessageReq.getTimestamp();
 
        Set<Tuple> tuples = null;
        if (timestamp == 0) {
            // 如果传递是 0,阐明这个客户端终端是第一次来轮询,咱们只有返回一个最近最新的音讯返回即可
            tuples = jedisTemplate.zrevrangeWithScores(UNIQUE_ROOM_ID, 0, 0);
        } else
            // 加上一毫秒,返回后续的音讯,每次返回 5 个,避免客户端因为低端手机起因,过多的音讯渲染不进去
            tuples = jedisTemplate.zrangeByScoreWithScores(UNIQUE_ROOM_ID, timestamp + 1, System.currentTimeMillis(), 0, 5);
        }
 
        List<EachRoomMessage> eachRoomMessages = new ArrayList<>();
        long lastTimestamp = 0L;
 
        if (!CollectionUtils.isEmpty(tuples)) {for (Tuple tuple : tuples) {
                // 最初一次循环后,会把最初一条音讯产生的工夫戳,返回给客户端,这样下次客户端就能够拿着这个工夫戳来进行查问
                lastTimestamp = new Double(tuple.getScore()).longValue();
                eachRoomMessages.add(JSON.parseObject(tuple.getElement(), EachRoomMessage.class));
            }
        }
 
        result.setTimestamp(lastTimestamp);
        result.setEachRoomMessages(eachRoomMessages);
        return result;
    }

上述三段比拟残缺地代码次要陈说了一个依赖 http 短轮询这种形式疾速实现的直播间的能力,这种形式是比拟毛糙的,不过却是一个很好的实现思路,目前咱们线上局部业务也是依据这个轮询的思维进行局部模块的实现。

这样实现的思路也有一个小坑,如果有采纳该思路去实现的,能够尝试去躲避。如果 Android 客户端断网的状况下,轮询的线程是不会进行的,例如是早晨 8 点整断网的,8 点 01 分复原网络的,当网络复原的时候,第一次轮询就会导致服务端返回大量的音讯,这边是须要进行解决的,否则会返回过多的音讯,服务端也会呈现慢查,客户端因为渲染过期的音讯也会呈现局部音讯展现区间呈现闪跳。例如公屏区可能会 ” 发疯 ” 般的呈现各类音讯,这些能够通过客户端和服务端的单方约定进行躲避,例如客户端当呈现网络问题的时候,在超过 5 秒以上,能够把工夫戳置为 0,要求服务端返回最新的直播间音讯即可,两头失落掉的音讯,能够在业务返回内的进行抛弃。

四、小结

本文次要是想让大家对直播有一个初步的理解,理解直播根本的概念模型,一些根底的概念,后续咱们会深刻直播具体的模块的学习,进一步去理解直播的原理,也可能帮忙咱们更好的做好直播的业务。

作者:vivo 互联网服务器团队 -Li Guolin

退出移动版