首先墨迹一下前提:
公司接了个电商的项目,然后甲方要求有类似于淘宝的直播的功能。重点是!!!甲方爸爸不愿意除阿里云直播,腾讯云直播的钱。所以问题就来了,这个直播的实现要免费。
然后这个项目是java+vue实现的。前后端分离的,至于用不用分布式啥的暂时先没定下来,不过因为确定有直播模块,所以这块是先测试实现了的。这个帖子就是对实现直播这一需求的整理的记录。
互联网直播服务管理规定
2016年4月13日,百度,新浪,搜狐等20余家直播平台共同发布《北京网络直播行业自律公约》,承诺网络直播房间必须标识水印;内容存储时间不少于15天备查;所有主播必须实名认证;对于播出涉政、涉枪、涉毒、涉暴、涉黄内容的主播,情节严重的将列入黑名单;审核人员对平台上的直播内容进行24小时实时监管。
咳咳,想来想去我决定把这个放在最开始。上面的规定其实不算多:
- 实名认证是能直播的用户要经过实名认证,代码里就可以搞定。
- 直播间房间要水印其实这个可以和点赞等做一个挂在视频前面的“画布”,所以我们这个项目最终决定前端搞定。
- 至于内容存储时间不小于15天,因为我做了保存直播的功能,虽然还没做定时清理,不过技术上不是难点,所以也算是完成了。
- 最后这个涉黄,涉政,涉爆啥的人工审核我也没办法了,只能到时候甲方将项目上线自己去找人看着了。
如上,反正这个直播功能是理清楚了,大概做出来也是合法的了。接下来开始说技术吧(因为我也是第一次做直播,所以可能走了一些冤路,不过我觉得稍微有用的都会写出来,毕竟前车之鉴)
直播技术的实现
其实这一块没想的那么难,但是也没想的那么简单。我一点点分析。
流
首先,要理解直播是什么:
- 图片可以有base64编码,这一串码就是代表这个图片。
- 视频我们可以理解成是一张张图片的编码的和,展现出来的动图也不过是一张张图片连续播放的结果(我这么说并不准确,我只是为了方便理解)
- 因为直播是一个个图片传输的,在你刚开播的一秒是没有下一秒的图片的,所以这个视频的编码是要一直传输的,而这种一直传输编码的情况,我们用一个专业术语表示:流。
我不知道别的语言怎么理解,但是java中有字节流,字符流等等,所以这里的流一般是音频+视频。所以具体是什么不深究,只要知道这里的流是用来传输视频的就行。
简单直播流程
然后这里有一张图很好的说明了直播的流程:
如图,直播的流程其实主要就这么多(也可以在中间有别的操作,我说的是最简单的操作),又因为我们决定直播的形式是服务器作为中间站。所以大概流程是直播用户推流到服务器,然后有想要看直播的去服务器拉流。接下来一点点说:
-
采集。其实这个很好理解,打开语音,摄像头,采集数据。这里也说了,需要终端音视频引擎解决。(终端是指用户直播的设备。比如手机,pc等)
-
前处理。这个是已经试过的,比如我们现在手机拍照片自带美颜,你以为是你摄像头给你美颜的么?不是的,是摄像头拍出来的是正常照片,手机拍照后对这个照片进行了处理。而这里的前处理也是类似的道理,前端调用麦克风摄像头,拍/录视频的同时对这个视频进行一些加工(我们已经测试完了,反正是可以美颜,磨皮等。刚刚顺口问了下我们前端,说是用的h5 plus中一些自带的api,我是前端小白,不是很理解,反正是能实现就对了。)
-
编码。这个其实就类似于图片的base 64 一样,视频流传输之前肯定是要经过编码的,应该可能也有很多种设置,反正我们调试用的H264。(关于这个编码我在网上看了下,好像直接影响编码速度解码速度什么的,也就是影响延迟,卡顿,清晰度等。不过我们现在真的是只要能实现就行了,所以暂定h264.没做什么多的尝试)
-
推流。这个其实刚刚我解释了流是什么,推流应该不难理解,就是把自己这个视频直播的编码以流的形式传给谁。
-
拉流。和推流对应,就是去有流的地方,把这个直播流拉倒本地。这两个行为其实很容易理解:我把这篇图文上传到简书,也就是推到简书。你们来简书看这篇图文,也就是从简书拉到你的显示器。只不过因为直播推来的都是流而已。
-
解码。这个其实就不用说了吧,你当时传输的时候是按照一定编码编译的,肯定想要复原要按照这个编码方式解码。
-
渲染。属于后期显示问题了,就是这个视频已经传到你观看直播的画面,不同的手机会有不同的显示,同一个视频有的手机看颜色发紫(因为我手机防辐射蓝光膜),有的手机看颜色艳丽,有的手机看颜色昏黄,这个就是渲染吧。
如上,其实一个简单的直播就是这么个流程。
然后注意一下,我这里说的只是一个最简单的实现直播的流程。复杂的比如多人连麦啊,甚至多人视频直播啊这种,推流是差不多的,但是拉流要众多流合在一起展示,这个属于复杂操作了,我也没做,有兴趣的可以自己做。
本地搭建直播平台
其实刚刚我也说了,我们打算是流程是直播用户把自己的直播流推给服务器,然后想要看直播的用户来服务器拉取对应的流。所以重点就是怎么把一台电脑作为服务器。
其实现在已经有现成的框架了,再次感谢共享技术的大大。然后我一点点说:
其实本地作为服务器是多多种方式的,我也是在网上看到的教程(最后会附上写这个文章参考的连接)。我这里只写我自己用的方法啦。
我这里用到的比较简单,是ffmpeg+nginx实现的。
其实主要就三步:
- 下载nginx并启动nginx(以后的所有推拉工作都要在nginx启动的情况下实现);
- 下载ffmpeg并设置环境变量(说实话我不知道如果没有这个会怎么样,不过三台电脑我都按照要求配置了。这个应该是推流的时候用的,不过demo中是本地推才用到这个,我感觉我和前端联调的时候好像是没用到这个啊)
- 自推自拉做个demo
然后感谢之前提供网盘压缩包的大佬。我这里借花献佛贴在这里:
链接: https: //pan.baidu.com/s/1lN1ps0ZhCb-1A56ycNR88g
密码: 2t88
点进这个链接可以下载需要的ffmpeg和nginx的压缩包(我在网址的https:后面加了个空格,复制粘贴的时候注意删除啊,不然链接格式发不出来)。
然后打开压缩包里面两个小压缩包,一个视频。视频是用来做实验的,用不用都行。
我解压到d盘demo文件夹中,并把ffmpeg和nginx都解压到当前文件夹了。
接下来按照步骤:
- 启动nginx:
cmd打开控制台,进入到nginx-1.7.11.3-Gryphon目录,然后启动。
比如我的是放在d盘demo下,然后命令就是:
第一步,跳到d盘:
d:
第二步,cd到nginx-1.7.11.3-Gryphon目录下:
cd demo/nginx-1.7.11.3-Gryphon
第三步,启动nginx:
nginx.exe -c conf\nginx-win-rtmp.conf
然后正常启动是不会显示内容的,但是一直在运行,这个控制台页面不要关,就这么放着。
然后可以访问ip:80直接访问,查看自己nginx是否正常启动了
-
下载ffmpeg并设置环境变量
第一步:设置环境变量(步骤就不重复了,这个比较初级了。)
我直接贴我的截图了: -
其实这个时候前台推来的流我就已经可以正常接收并且被同局域网的设备拉取了,不过如果自己做没有这个前端配合,或者说想确定一下是不是前面都做对了,可以自己模拟一下推流,再自己模拟拉流。
因为我这块完全按教程做的,好像模拟拉流只能用VLC播放器,反正我是只用了这个测试。然后这里就用到了之前下载顺便带的测试视频了,当然你电脑里有别的视频也可以用,新打开一个控制台,然后下面这串命令:
ffmpeg -re -i d:/demo/orange.mp4 -vcodec libx264 -acodec aac -f flv rtmp://ip:1935/live/home
重点来了!!首先,之前我看教程,人家是直接-i orange.mp4,我复制粘贴运行一直报找不到文件,后来我改成绝对路径就ok了。 其次这个ip+端口ip写你自己的ip就行,端口1935的设置是在nginx的配置文件中配置的,是可以改的。要酌情使用
然后如上命令中,大概意思是把orange.mp4这个视频以流的形式用h264编码,flv的格式 以rtmp的形式推给ip指向的电脑的/live/home这(自我理解,错了勿喷,欢迎指点)。
同理如果拉流也要来这个地方拉。所以可以直接复制这个路径,直接在vlc上ctrl+v可以贴网址,把这个网址贴上就能看到你之前推的视频了。
如果你能看到你传的视频,说明你成功了!直播平台已经搭建完了!
视频传输和直播的区别
可能看到这有人不懂了,这不就是视频传输么?凭什么叫直播啊?这不是忽悠人么?其实不是的,这个在细节上可以看到。如果是视频传输,那么接收的人只能从视频的一开始看,而直播的区别是传输到哪里就应该从哪里看(不要和我抬杠延迟)、还有就是应该是直播一结束立刻观看直播的也会结束。
因为上面的demo我们用的视频,所以这点可能让很多人误会,不过好一点的是这个视频一分多钟,而推流的过程不是一秒钟一秒钟推的,所以速度比较快,好几秒好几秒推的,但是我们可以稍微晚几秒拉流,明显可以看到每次显示的视频开始的内容是不一样的。
同样,如果到这还不能理解或者有疑问,我建议大家不要用传视频的方式证明平台搭建成功。可以直接调用本地视频音频,然后推流到本地,再用vlc拉流,这样明显看到开始直播则能拉倒,关闭直播则立刻拉不到。
做法我觉得比较麻烦,但是不难,最后我列出来的技术贴上会有教程。大家可以去看看。
保存直播视频
最开始就说了,保存直播视频15天是一个规范。然后15天其实可以多种方式实现,重点是保存。
因为我推拉流都没经过代码,我还以为保存会很复杂, 但是也比想的简单的多。我就不多说心路历程了,直接上结果(我要重复一下,实现的方式可能是多种多样的,我只不过选了一种实现了,不代表性能是最好的或者技术最新的):
- 导包(因为用到了javaCV的FFmpegFrameGrabber帧捕捉器捕捉流的音频帧和视频帧,所以要导相应的包)
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.4.3</version>
</dependency>
- 复制粘贴代码
这里因为有些不好解释,中间遇到问题FFmpegFrameGrabber类也不说清楚,还是比较坑的,我是调了很多细微的东西才实现的,我先贴出来,咱们再一点点说需要注意什么。
package io.renren.modules.other.tool;
import java.io.File;
import java.io.IOException;
import org.bytedeco.javacpp.avcodec;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.FrameRecorder;
public class RecordVideoThread extends Thread {
public String streamURL;// 流地址 网上有自行百度
public String filePath;// 文件路径
public Integer id;// 案件id
public void setStreamURL(String streamURL) {
this.streamURL = streamURL;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
@Override
public void run() {
System.out.println(streamURL);
// 获取视频源
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(streamURL);
FFmpegFrameRecorder recorder = null;
try {
grabber.start();
Frame frame = grabber.grabFrame();
if (frame != null) {
File outFile = new File(filePath);
if (!outFile.isFile()) {
try {
outFile.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 流媒体输出地址,分辨率(长,高),是否录制音频(0:不录制/1:录制)
recorder = new FFmpegFrameRecorder(filePath, 1080, 1440, 1);
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);// 直播流格式
recorder.setFormat("flv");// 录制的视频格式
recorder.setFrameRate(25);// 帧数
//百度翻译的比特率,默认400000,但是我400000贼模糊,调成800000比较合适
recorder.setVideoBitrate(800000);
recorder.start();
while ((frame != null)) {
recorder.record(frame);// 录制
frame