利用Docker挂载Nginx-rtmp(服务器直播流分发)+FFmpeg(推流)+Vue.js结合Video.js(播放器流播放)来实现实时网络直播

转载自 https://v3u.cn/book/zhibo.html

众所周知,在视频直播领域,有不同的商家提供各种的商业解决方案,其中比较靠谱的服务商有阿里云直播,腾讯云直播,以及又拍云和网易云的有偿直播服务,服务包括软硬件设备,摄像机,编码器,流媒体服务器等。但是其高昂的费用以及较高的准入门槛让许多个人和小型企业望而却步,本文要讲解的是如何使用nginx-rtmp搭建直播服务器,配合FFmpeg推流,在网页端vue.js作为载体利用video.js作为流播放器,打造一套可用的在线视频直播方案。

搭建直播服务器是一个漫长而复杂的过程,编译设置有点繁琐。好在docker上有大把别人编译设置好的rtmp环境,所以可以直接拿来用,docker的优越性由此可见一斑,这里用到的是alfg/nginx-rtmp库。

安装docker,可以在阿里云的镜像上下载比较快一点 http://mirrors.aliyun.com/docker-toolbox/windows/docker-toolbox/

目前稳定版是DockerToolbox-18.03.0-ce.exe 直接点击安装即可
安装过程有一点需要注意,必须要开启本机bios的cpu虚化技术

安装好docker后,下载nginx-rtmp镜像,并且运行服务映射端口到1945和8000

1
2
docker pull alfg/nginx-rtmp
docker run -it -p 1935:1935 -p 8000:80 --rm alfg/nginx-rtmp

访问宿主的8000端口显示nginx欢迎页面

然后利用FFmpeg进行推流操作,ffmpeg是什么请移步:Python3利用ffmpeg针对视频进行一些操作

输入命令,注意摄像头和麦必须和电脑的设备吻合,另外推流服务器也要推到刚刚部署好的nginx上面去

1
ffmpeg -f dshow -i video="VMware Virtual USB Video Device":audio="Microphone (High Definition Audio Device)" -tune:v zerolatency -f flv "rtmp://192.168.99.100:1935/stream/test"

推流成功后,我们就要在网站上观看现场直播了,这里前端服务我们使用vue.js来搭建,视频流播放器我们使用video.js

首先建立一个直播的脚手架项目,然后安装一下必要的直播库,最后启动项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#建立项目
vue init webpack-simple zhibo
cd zhibo
yarn install vue-router save
yarn install

#安装直播组件
yarn install video.js
yarn install aes-decrypter
yarn install m3u8-parser
yarn install mpd-parser
yarn install mux.js
yarn install url-toolkit
yarn install videojs-contrib-hls

#热启动项目
yarn run dev

新建一个video.vue,添加播放代码

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
<template>
<div>
<video id="my-video" class="video-js vjs-default-skin" controls preload="auto" >
<!-- 直播地址就是nginx映射后的播放地址,注意后缀为直播流的m3u8 -->
<source src="http://192.168.99.100:8000/live/test.m3u8" >
</video>
</div>
</template>

<script>
import videojs from 'video.js'
import 'videojs-contrib-hls'

export default {
data () {
return {}
},
mounted() {
videojs('my-video', {
bigPlayButton: true,
textTrackDisplay: false,
posterImage: true,
errorDisplay: false,
controlBar: true
}, function () {
this.play()
})
}
}
</script>

<style>
</style>

最后,在vue.js的路由里绑定video.vue组件,进入浏览器,测试直播播放

直播相关技术

转载自 https://v3u.cn/book/zhibo.html

直播原理

在视频直播领域,有不同的商家提供各种的商业解决方案,其中比较靠谱的服务商有阿里云直播,腾讯云直播,以及又拍云和网易云的有偿直播服务,服务包括软硬件设备,摄像机,编码器,流媒体服务器等

视频直播的流程可以分为如下几步:

采集 —>处理—>编码和封装—>推流到服务器—>服务器流分发—>播放器流播放

一般情况下视频采集处理后推流到流媒体服务器,第一部分功能完成。第二部分就是流媒体服务器,负责把从第一部分接收到的流进行处理并分发给观众。第三部分就是客户端播放,只需要拥有支持流传输协议的播放器即可

应用技术点:

nginx-rtmp搭建直播服务器,配合FFmpeg推流,在网页端vue.js作为载体利用video.js作为流播放器

具体搭建:<//live-build.html>

什么叫做“秒开”

秒开 即从视频播放开始到真正看到第一帧画面所消耗的时间要尽可能的短(几百毫秒时间),不能让观众有明显的等待时间

这种情况主要依靠第三方服务的优化以及播放器的配合,即依赖阿里云,腾讯云这种三方sdk的优化服务,最快可以实现 200ms 左右 的首屏打开速度,如果网络下行足够够好的话甚至可以秒开,如果是自研的推流服务和播放器则相对困难

视频卡顿怎么办

卡顿的原因无外乎三种情况:

1 帧率太低

主播端的pc或者手机性能较差,或者有很占 CPU 的后台程序在运行,可能导致视频的帧率太低。正常情况下每秒15FPS以上的视频流才能保证观看的流畅度,如果 FPS 低于10帧,可以判定为帧率太低,这会导致全部观众的观看体验都很卡顿

2 上传阻塞

主播的pc或者手机在推流时会源源不断地产生音视频数据,但如果客户端上传网速太小,那么产生的音视频数据都会被堆积在主播的客户端里传不出去,上传阻塞会导致全部观众的观看体验都很卡顿

3 下行不佳

就是观众的下载带宽跟不上或者网络很波动,比如直播流的码率是1Mbps的,也就是每秒钟有1M比特的数据流要下载下来,但如果观众端的带宽不够,就会导致观众端体验非常卡顿。 下行不佳只会影响当前网络环境下的观众。

如何降低延迟

按正常情况 RTMP 推流+FLV 播放的正常延迟在2秒 - 3秒左右,太长则是有问题的,如果您发现直播延迟时间特别长,可以按照如下思路来排查。

Step 1. 检查播放协议 不少客户播放协议采用 HLS(m3u8),并感觉延迟较大,这个是正常的。苹果主推的 HLS 是基于大颗粒的 TS 分片的流媒体协议,每个分片都有5s以上的时长,分片数量一般为3个 - 4个,所以总延迟在20s - 30s就不足为怪。 换用 FLV 作为播放协议即可解决这个问题,但是要注意,如果您要在手机浏览器上观看直播视频,只有 HLS(m3u8) 这一种播放协议可以选择,其它的直播协议在苹果的 Safari 浏览器上都是不支持的。

Step 2. 检查播放器设置

由于自研应用使用的开源播放器video.js,一些设置可以参考官方文档,如果希望更好的优化,还是建议使用成熟的付费第三方sdk,比如阿里云

Step 3. 后台不要打水印

众所周知,FFmpeg有推流打水印的功能,但是这种功能会严重影响直播视频的流畅度

Step 4. 检查 OBS 设置

有不少主播喜欢在pc上使用obs推流,播放端延迟比较大。建议调整配置对应的参数,并注意要把关键帧间隔设置为1或者2

为何推流不成功

1 直播服务器是否正常启动

检查nginx服务是否正常启动,端口是否正在监听

2 网路是否正常?

RTMP 推流所使用的默认端口号是1935 ,如果您测试时所在网络的防火墙不允许1935端口通行,就会遇到连不上与服务器的问题。此时您可以通过切换网络(比如 4G )来排查是不是这个原因导致的问题。

3 推流URL是否被占用?

一个推流 URL 同时只能有一个推流端,第二个尝试去推流的 Client 会被Nginx服务拒绝掉。

兼容苹果Mac os

我们知道mac端的Safari浏览器可以直接播放m3u8格式的视频流,所以建议为mac端定制直播页面,降低开发成本

vue-render如何渲染多个同名指令

本文介绍如何使用render渲染出多个同名指令(使用clipbard举例,其它同理)
本文测试代码

1
<button v-clipbard:copy="doCopy" v-clipbard:success="onSuccess">复制</button>

通常在vue中使用clipbard时会这么写绑定事件,这样多个同名的指令不同参数是可以正常使用的。
但在有时候会使用render来渲染这个dom,那怎么来实现呢,通过vue文档可以看出render的directives的属性是个数组,说明可以渲染多个指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
h(
'button', {
directives: [
{
name: 'clipbard',
arg: 'copy',
value: () => {
// doCopy
}
},
{
name: 'clipbard',
arg: 'success',
value: () => {
// onSuccess
}
}
]
}, '复制'
)

看上去好像没什么问题,但实际并没有生效。
通过阅读render的源码我们可以发现,render内部是通过每个对象中的name来区分不同指令,相同的name会被覆盖。那不同的arg就不能渲染了吗,并不是。在此处可以看出内部还读取了rawName。

给之前的指令添加一个唯一的rawName(name:arg)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
h(
'button', {
directives: [
{
name: 'clipbard',
rawName: "clipbard:copy", // new
arg: 'copy',
value: () => {
// doCopy
}
},
{
name: 'clipbard',
rawName: "clipbard:success", // new
arg: 'success',
value: () => {
// onSuccess
}
}
]
}, '复制'
)

测试一下两个方法都绑定成功!由此得出rawName才是自定义指令中的唯一key。

额外

为什么是name:arg呢,其实通过我们自己实现自定义指令,可以在binding对象中发现template中的写法对象是这样的

1
2
3
4
5
6
{
name: 'clipbard',
rawName: 'v-clipbard:copy',
arg: "copy",
...
}

而上述render的写法对象是这样的

1
2
3
4
5
{
name: "clipbard"
arg: "success",
...
}

template会自动生成rawName以v-name:arg格式,跟template中绑定的指令的写法相同,由此证实rawName确实是区分多指令的唯一key

为了与内部生成的rawName规范统一,我们也可以把rawName写成name:arg或v-name:arg的格式。

本文测试代码