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

引言

现在很多项目都有视频实时播放的功能需求,例如监控,直播等,原始的摄像头采集的视频流协议一般都是 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:

解压安装包:

tar zxvf yasm-1.3.0.tar.gz

切换路径:

 cd yasm-1.3.0

执行配置:

 ./configure

编译:

make

安装:

make install

安装FFmpeg:

解压安装包:

tar xvf ffmpeg-4.1.tar.xz

切换路径:

cd ffmpeg-4.1

执行配置:

 ./configure

编译:

make

安装:

make install

测试FFmpeg:

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

root@mach9:~# ffmpeg -version

ffmpeg version 3.1 Copyright (c) 2000-2016 the FFmpeg developers

built with gcc 4.8 (Ubuntu 4.8.4-2ubuntu1~14.04.3)

configuration: --prefix=/usr/local/ffmpeg --enable-decoder=h264

libavutil      55. 27.100 / 55. 27.100

libavcodec     57. 48.101 / 57. 48.101

libavformat    57. 40.101 / 57. 40.101

libavdevice    57.  0.101 / 57.  0.101

libavfilter     6. 46.102 /  6. 46.102

libswscale      4.  1.100 /  4.  1.100

libswresample   2.  1.100 /  2.  1.100

nginx安装nginx-rtmp-module模块

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

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

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

nginx -V

nginx version: nginx/1.18.0

built by gcc 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3) 

built with OpenSSL 1.0.1f 6 Jan 2014

TLS SNI support enabled

configure arguments: --prefix=/usr/local/nginx/

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

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

unzip nginx-rtmp-module-master.zip

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

./configure --prefix=/usr/local/nginx/(之前拷贝的参数) --add-module=/nginx-rtmp-module-master(填写实际的解压位置)

编译:

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进行访问。

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

rtmp{

    server{

        listen 1935;

        application live{

          live on;

          record off;

        }

        application hls{

          live on;

          hls on;

          hls_path /server/hls;

          #hls_cleanup off;

          hls_fragment 8s;

        }

    }

}

重载nginx

nginx -s reload

FFmpeg转流推流

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

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模块下加入以下配置:

server {

        listen 8080;

        location /hls {

           #server hls fragments

           types{

             application/vnd.apple.mpegurl m3u8;

             video/mp2t ts;

           }

           alias /server/hls;

           expires -1;

           #跨域一定要放开

           add_header Access-Control-Allow-Origin *;

           add_header Access-Control-Allow-Headers X-Requested-With;

           add_header Access-Control-Allow-Methods GET,POST,OPTIONS;

        }

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

VLC下载):

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

打开网络串流

打开成功:

成功

代码实现自动转流

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

    public static List<Process> processes = new CopyOnWriteArrayList<>();

    private final String hlsPath = "/server/hls/";

   /**

     * 避免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!!");
        }
    }

    @SneakyThrows

    @Override

    public static Map<String, Object> getM3u8(String rtspUrl) {

        String uuid = UUID.randomUUID().toString().replaceAll("-","");

        log.warn("===" + deviceNo + ":start change to m3u8...====");

        //转rtmp的shell 在hls目录下会生成m3u8文件

        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 + "\"";

        log.warn("======the shell is " + shell);

        String[] cmd = new String[]{"sh", "-c", shell};

        Process process = Runtime.getRuntime().exec(cmd);

        //放入map中

        processes.add(process);

        log.warn("====change to m3u8 has run...");

        File file = new File(hlsPath + "mystream_" + uuid + ".m3u8");

        //循环查找m3u8文件

        for (int i = 0; i < 600; i++) {

            Thread.sleep(100);

            if (file.exists()) {

                log.warn("the m3u8 file has been found");

                break;

            }

        }

        //前台拼接前面的ip:port或域名即可

        map.put("m3u8Url", "/hls/mystream_" + uuid + ".m3u8");

        return map;

    }

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

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

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

<!DOCTYPE html>

<html lang="zh-CN">

<head>

    <meta charset="UTF-8">

    <title>前端播放m3u8格式视频</title>

    <link href="https://vjs.zencdn.net/7.4.1/video-js.css" rel="stylesheet">

    <script src='https://vjs.zencdn.net/7.4.1/video.js'></script>

    <!-- videojs-contrib-hls 用于在电脑端播放 如果只需手机播放可以不引入 -->

    <script src="https://cdn.bootcdn.net/ajax/libs/videojs-contrib-hls/5.15.0/videojs-contrib-hls.min.js"></script>

</head>

<body>

<style>

    .video-js .vjs-tech {position: relative !important;}

</style>

<div>

    <video id="myVideo" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto" data-setup='{}' style='width: 60%;height: auto'>

        <source id="source" src="你的m3u8地址" type="application/x-mpegURL"></source>

    </video>

</div>

<div class="qiehuan" style="width:100px;height: 100px;background: red;margin:0 auto;line-height: 100px;color:#fff;text-align: center">切换视频</div>

</body>

<script>

    // videojs 简单使用

    var myVideo = videojs('myVideo', {

        bigPlayButton: true,

        textTrackDisplay: false,

        posterImage: false,

        errorDisplay: false,

    })

    myVideo.play()// 视频播放

    myVideo.pause() // 视频暂停

    var changeVideo = function (vdoSrc) {

        if (/\.m3u8$/.test(vdoSrc)) { //判断视频源是否是m3u8的格式

            myVideo.src({

                src: vdoSrc,

                type: 'application/x-mpegURL' //在重新添加视频源的时候需要给新的type的值

            })

        } else {

            myVideo.src(vdoSrc)

        }

        myVideo.load();

        myVideo.play();

    }

    //测试地址

    var src = 'http://1252093142.vod2.myqcloud.com/4704461fvodcq1252093142/f865d8a05285890787810776469/playlist.f3.m3u8';

    document.querySelector('.qiehuan').addEventListener('click', function () {

        changeVideo(src);

    })

</script>

</html>

效果:

浏览器效果

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

正文完