Nginx+FFmpeg打造自己的视频直播服务

2021-11-24 14:19:39 浏览数 (1)

引言

现在很多项目都有视频实时播放的功能需求,例如监控,直播等,原始的摄像头采集的视频流协议一般都是 rtsp 协议,在旧版的浏览器中使用

FLASH 可以支撑其进行播放,但是现在各大主流浏览器都关闭了对 FLASH 的支持,这就需要我们把 rtsp 转为浏览器支持的

http

,业务体量很大的公司一般会把这种事情委托给专业的第三方公司去做,但很多公司在这方面没有那么大的业务量,往往只是播放一下监控录像之类的需求,则是搭建了自己的流媒体服务器来应对,现在比较主流的方式是使用

FFmpeg 进行转流,再使用 Nginx 进行转发,下面我们一起来看一下吧!(所需安装包请查看文末获取)

安装yasm和FFmpeg

安装 FFmpeg 还是比较简单的,但在安装之前,需要先安装一下 yasm ,否则执行./configure时,报 **yasm/nasm

not found or too old. Use --disable-yasm for a crippledbuild** 错误。

yasm是汇编编译器,ffmpeg为了提高效率使用了汇编指令,如MMX和SSE等。所以系统中未安装yasm时,就会报上面错误。

安装yasm:

解压安装包:

代码语言:txt复制
tar zxvf yasm-1.3.0.tar.gz

切换路径:

代码语言:txt复制
 cd yasm-1.3.0

执行配置:

代码语言:txt复制
 ./configure

编译:

代码语言:txt复制
make

安装:

代码语言:txt复制
make install

安装FFmpeg:

解压安装包:

代码语言:txt复制
tar xvf ffmpeg-4.1.tar.xz

切换路径:

代码语言:txt复制
cd ffmpeg-4.1

执行配置:

代码语言:txt复制
 ./configure

编译:

代码语言:txt复制
make

安装:

代码语言:txt复制
make install

测试FFmpeg:

输入 ffmpeg -version 命令,如下,安装成功!

代码语言:txt复制
root@mach9:~# ffmpeg -version
代码语言:txt复制
ffmpeg version 3.1 Copyright (c) 2000-2016 the FFmpeg developers
代码语言:txt复制
built with gcc 4.8 (Ubuntu 4.8.4-2ubuntu1~14.04.3)
代码语言:txt复制
configuration: --prefix=/usr/local/ffmpeg --enable-decoder=h264
代码语言:txt复制
libavutil      55. 27.100 / 55. 27.100
代码语言:txt复制
libavcodec     57. 48.101 / 57. 48.101
代码语言:txt复制
libavformat    57. 40.101 / 57. 40.101
代码语言:txt复制
libavdevice    57.  0.101 / 57.  0.101
代码语言:txt复制
libavfilter     6. 46.102 /  6. 46.102
代码语言:txt复制
libswscale      4.  1.100 /  4.  1.100
代码语言:txt复制
libswresample   2.  1.100 /  2.  1.100

nginx安装nginx-rtmp-module模块

nginx的安装方式大同小异,相信大家已经非常熟悉了,不多赘述,这里主要介绍一下如何在已安装的nginx上添加nginx-rtmp-

module模块,因为想要通过nginx转发视频流需要这一个组件,相关依赖包请看文末。

查看原有nginx的配置参数并拷贝出来 (V大写),如下,configure arguments:后面就是我们所需要的。

代码语言:txt复制
nginx -V
代码语言:txt复制
nginx version: nginx/1.18.0
代码语言:txt复制
built by gcc 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3) 
代码语言:txt复制
built with OpenSSL 1.0.1f 6 Jan 2014
代码语言:txt复制
TLS SNI support enabled
代码语言:txt复制
configure arguments: --prefix=/usr/local/nginx/

下载跟原有版本一样的nginx安装包,这里以nginx-1.18.0为例(如果之前的安装包没删,可以直接用之前的)

解压nginx-rtmp-module-master.zip(文末获取安装包)

代码语言:txt复制
unzip nginx-rtmp-module-master.zip

解压nginx安装包,cd到解压目录下,然后执行配置:

代码语言:txt复制
./configure --prefix=/usr/local/nginx/(之前拷贝的参数) --add-module=/nginx-rtmp-module-master(填写实际的解压位置)

编译:

代码语言:txt复制
make

编译完成后,我们需要在我们 /nginx-1.18.0/objs/ 目录下。找到刚刚编译好的 nginx 文件(

没有扩展名),然后将nginx文件复制到我们之前安装的 /usr/local/nginx/sbin/ 目录(以实际目录为准),替换旧的 nginx

文件,替换之前记得备份。

接下来我们执行nginx -V,可以发现已经有了nginx-rtmp-module模块,至此,nginx安装nginx-rtmp-module模块成功!

修改nginx配置

nginx的rtmp-module模块可以帮助我们接收ffmpeg推送的流媒体文件,使用http进行访问。

在文件里加入下面内容(加在最外层,属于独立模块):

代码语言:txt复制
rtmp{
代码语言:txt复制
    server{
代码语言:txt复制
        listen 1935;
代码语言:txt复制
        application live{
代码语言:txt复制
          live on;
代码语言:txt复制
          record off;
代码语言:txt复制
        }
代码语言:txt复制
        application hls{
代码语言:txt复制
          live on;
代码语言:txt复制
          hls on;
代码语言:txt复制
          hls_path /server/hls;
代码语言:txt复制
          #hls_cleanup off;
代码语言:txt复制
          hls_fragment 8s;
代码语言:txt复制
        }
代码语言:txt复制
    }
代码语言:txt复制
}

重载nginx

代码语言:txt复制
nginx -s reload

FFmpeg转流推流

nginx配置完毕,接下来我们测试ffmpeg的转流和向nginx推流,执行以下命令:

代码语言:txt复制
ffmpeg -rtsp_transport tcp -i "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov" -vcodec copy -acodec copy -f flv -an -b 1024k -y "rtmp://127.0.0.1:1935/hls/mystream"

rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov是我找的公网rtsp测试地址,执行完以上命令之后如下图,则表示转流成功:

转流截图

转流成功后在我们之前配置的nginx

rtmp模块的接收路径下(/server/hls)会生成m3u8索引文件,m3u8其实就是ts文件的索引,ffmpeg会把一个直播源的数据分割成很多个ts文件,访问m3u8可以获取ts文件的播放顺序,逐个播放,ts文件达到一定数量会自动删除前面无用的ts,并且如果ffmpeg停止转流,文件夹底下的文件也会自动清除,nginx的rtmp模块帮我们做了这一点来防止内存溢出的问题,生成的文件如下:

m3u8

为了可以直接用http访问m3u8文件,我们在nginx的http模块下加入以下配置:

代码语言:txt复制
server {
代码语言:txt复制
        listen 8080;
代码语言:txt复制
        location /hls {
代码语言:txt复制
           #server hls fragments
代码语言:txt复制
           types{
代码语言:txt复制
             application/vnd.apple.mpegurl m3u8;
代码语言:txt复制
             video/mp2t ts;
代码语言:txt复制
           }
代码语言:txt复制
           alias /server/hls;
代码语言:txt复制
           expires -1;
代码语言:txt复制
           #跨域一定要放开
代码语言:txt复制
           add_header Access-Control-Allow-Origin *;
代码语言:txt复制
           add_header Access-Control-Allow-Headers X-Requested-With;
代码语言:txt复制
           add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
代码语言:txt复制
        }

使用VLC软件测试(下载地址

VLC下载):

打开网络串流(填写自己服务器的地址):

打开网络串流

打开成功:

成功

代码实现自动转流

在前面我们利用ffmpeg的转流命令成功把rtsp视频流转化为了http流地址,但在实际的程序应用中不可能手动去做这些事情,所以我们利用java实现一个自动转流方法,调用该方法返回转流后的m3u8地址供前台访问,核心代码如下:

代码语言:txt复制
    public static List<Process> processes = new CopyOnWriteArrayList<>();
代码语言:txt复制
    private final String hlsPath = "/server/hls/";
代码语言:txt复制
   /**
代码语言:txt复制
     * 避免process过多导致服务器卡死
     * (正常操作应该是返回前台一个唯一标识,当前台关闭直播流的时候关闭对应的进程,这里我们简单处理)
     */
    @Scheduled(cron = "10 * * * * ?")
    public void removeProcess() {
        try {
            //如果进程大于50个,去除第一个进程
            if (processes.size() > 50) {
                processes.get(0).destroy();
                processes.remove(0);
                log.warn("more than 50,remove the first success!!");
                removeProcess();
            }
        } catch (Exception e) {
            log.error("destroy process error!!");
        }
    }
代码语言:txt复制
    @SneakyThrows
代码语言:txt复制
    @Override
代码语言:txt复制
    public static Map<String, Object> getM3u8(String rtspUrl) {
代码语言:txt复制
        String uuid = UUID.randomUUID().toString().replaceAll("-","");
代码语言:txt复制
        log.warn("==="   deviceNo   ":start change to m3u8...====");
代码语言:txt复制
        //转rtmp的shell 在hls目录下会生成m3u8文件
代码语言:txt复制
        String shell = "ffmpeg -rtsp_transport tcp -i ""   rtspUrl   "" -vcodec copy -acodec copy -f flv -an -b 1024k -y "rtmp://127.0.0.1:1935/hls/mystream_"   uuid   """;
代码语言:txt复制
        log.warn("======the shell is "   shell);
代码语言:txt复制
        String[] cmd = new String[]{"sh", "-c", shell};
代码语言:txt复制
        Process process = Runtime.getRuntime().exec(cmd);
代码语言:txt复制
        //放入map中
代码语言:txt复制
        processes.add(process);
代码语言:txt复制
        log.warn("====change to m3u8 has run...");
代码语言:txt复制
        File file = new File(hlsPath   "mystream_"   uuid   ".m3u8");
代码语言:txt复制
        //循环查找m3u8文件
代码语言:txt复制
        for (int i = 0; i < 600; i  ) {
代码语言:txt复制
            Thread.sleep(100);
代码语言:txt复制
            if (file.exists()) {
代码语言:txt复制
                log.warn("the m3u8 file has been found");
代码语言:txt复制
                break;
代码语言:txt复制
            }
代码语言:txt复制
        }
代码语言:txt复制
        //前台拼接前面的ip:port或域名即可
代码语言:txt复制
        map.put("m3u8Url", "/hls/mystream_"   uuid   ".m3u8");
代码语言:txt复制
        return map;
代码语言:txt复制
    }

利用上面的代码我们可以封装一个http服务来实现访问接口自动转流,这样才算一个完成的流媒体服务!

前台利用video.js播放视频流

在前台我们可以利用video.js来对m3u8索引文件进行播放,使用方式也十分简单,代码如下:

代码语言:txt复制
<!DOCTYPE html>
代码语言:txt复制
<html lang="zh-CN">
代码语言:txt复制
<head>
代码语言:txt复制
    <meta charset="UTF-8">
代码语言:txt复制
    <title>前端播放m3u8格式视频</title>
代码语言:txt复制
    <link href="https://vjs.zencdn.net/7.4.1/video-js.css" rel="stylesheet">
代码语言:txt复制
    <script src='https://vjs.zencdn.net/7.4.1/video.js'></script>
代码语言:txt复制
    <!-- videojs-contrib-hls 用于在电脑端播放 如果只需手机播放可以不引入 -->
代码语言:txt复制
    <script src="https://cdn.bootcdn.net/ajax/libs/videojs-contrib-hls/5.15.0/videojs-contrib-hls.min.js"></script>
代码语言:txt复制
</head>
代码语言:txt复制
<body>
代码语言:txt复制
<style>
代码语言:txt复制
    .video-js .vjs-tech {position: relative !important;}
代码语言:txt复制
</style>
代码语言:txt复制
<div>
代码语言:txt复制
    <video id="myVideo" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto" data-setup='{}' style='width: 60%;height: auto'>
代码语言:txt复制
        <source id="source" src="你的m3u8地址" type="application/x-mpegURL"></source>
代码语言:txt复制
    </video>
代码语言:txt复制
</div>
代码语言:txt复制
<div class="qiehuan" style="width:100px;height: 100px;background: red;margin:0 auto;line-height: 100px;color:#fff;text-align: center">切换视频</div>
代码语言:txt复制
</body>
代码语言:txt复制
<script>
代码语言:txt复制
    // videojs 简单使用
代码语言:txt复制
    var myVideo = videojs('myVideo', {
代码语言:txt复制
        bigPlayButton: true,
代码语言:txt复制
        textTrackDisplay: false,
代码语言:txt复制
        posterImage: false,
代码语言:txt复制
        errorDisplay: false,
代码语言:txt复制
    })
代码语言:txt复制
    myVideo.play()// 视频播放
代码语言:txt复制
    myVideo.pause() // 视频暂停
代码语言:txt复制
    var changeVideo = function (vdoSrc) {
代码语言:txt复制
        if (/.m3u8$/.test(vdoSrc)) { //判断视频源是否是m3u8的格式
代码语言:txt复制
            myVideo.src({
代码语言:txt复制
                src: vdoSrc,
代码语言:txt复制
                type: 'application/x-mpegURL' //在重新添加视频源的时候需要给新的type的值
代码语言:txt复制
            })
代码语言:txt复制
        } else {
代码语言:txt复制
            myVideo.src(vdoSrc)
代码语言:txt复制
        }
代码语言:txt复制
        myVideo.load();
代码语言:txt复制
        myVideo.play();
代码语言:txt复制
    }
代码语言:txt复制
    //测试地址
代码语言:txt复制
    var src = 'http://1252093142.vod2.myzijiebao.com/4704461fvodcq1252093142/f865d8a05285890787810776469/playlist.f3.m3u8';
代码语言:txt复制
    document.querySelector('.qiehuan').addEventListener('click', function () {
代码语言:txt复制
        changeVideo(src);
代码语言:txt复制
    })
代码语言:txt复制
</script>
代码语言:txt复制
</html>

效果:

浏览器效果

至此,实现完整的视频直播服务成功!

0 人点赞