玩转直播:如何从 0 到 1 构建简单直播系统
作者:vivo 互联网服务器团队-Li Guolin
一、前言
二、推拉流模型
三、搭建步骤
搭建直播服务器 使用OBS进行推流 直播流如何观看 直播间消息的实现
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 服务监听地址
{ "status": 200, "data": "rfBd56ti2SMtYvSgD5xAV0YU99zampta7Z7S575KLkIZ9PYk"}
CBR(Constant Bitrate)恒定码率,一定时间范围内比特率基本保持恒定。使用该模式时,在视频动态画面较多的场景下,图像质量会变差,而在静态画面较多的场景下,图像质量又会变好。
VBR(Variable Bitrate)可变码率,其码率可以随着图像的复杂程度的不同而变化。使用该模式时,在图像内容比较简单的场景下,分配较少的码率,而在图像内容复杂的场景下,则分配较多的码率。这样既保证了质量,又兼顾到带宽限制,优先考虑到图像质量。
ABR(Average Bitrate)平均比特率,是VBR的一种插值参数。简单场景分配较低码率,复杂场景分配足够码率,这一点类似VBR。同时,一定时间内平均码率又接近设置的目标码率,这一点又类似CBR。可以认为ABR是CBR和VBR的折中方案。
CRF(Constant Rate Factor)恒定码率系数。CRF值可以理解为对视频的清晰度和流畅度期望的一个固定输出值,即无论是在复杂场景还是在简单场景下,都希望有一个稳定的主观视频质量。
I帧(intra coded picture):最完整的画面,自带全部信息,无需参考其他帧即可解码,每个GOP都是以I帧开始; P帧(predictive coded picture):帧间预测编码帧,需要参考前面的I帧或P帧,才能进行解码,压缩率较高; B帧(bipredictive coded picture):双向预测编码帧,以前帧后帧作为参考帧,压缩率最高。
RTMP
HLS
HTTP-FLV
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); }
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; } } }
@Override public RoomMessage queryRoomMessages(MessageMessageReq messageMessageReq) { RoomMessage result = new RoomMessage(); long timestamp = messageMessageReq.getTimestamp(); Set<Tuple> tuples = ; 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; }
四、小结
技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。