nginx + nginx-rtmp-module搭建直播流伺服器實現推流實時直播功能

業務需求

最近公司在做養老相關的業務,現在需要子女從小程式端對家裡的老人通過家庭終端交互屏進行實時看護。

解決方案

第三方的一些現成的服務:騰訊音影片通話、直播功能; 阿里的音影片通訊;兩者都挺好的,但是需要收費因此放棄決定自己搭建一套直播流服務;

先看效果(自己伺服器配置低有延遲、放到公司伺服器上很流暢、清楚)

image

使用工具

Nginx、Nginx-Rtmp-Module

下載地址

Nginx://nginx.org/en/download.html
Nginx-Rtmp-Module://github.com/arut/nginx-rtmp-module

軟體安裝

1、下載Nginx
wget //nginx.org/download/nginx-1.21.6.tar.gz
2、將壓縮包移到需要的安裝目錄下
mv  nginx-1.21.6.tar.gz  /usr/local
3、下載Nginx-Rtmp-Module
git clone //github.com/arut/nginx-rtmp-module.git
4、將文件移到需要安裝目錄下
mv nginx-rtmp-module  /usr/local
5、進入目錄
cd /usr/local
6、解壓Nginx壓縮包
tar -zxvf nginx-1.21.6.tar.gz
7、進入Nginx目錄
cd nginx-1.21.6
8、配置
./configure --prefix=/usr/local/nginx --add-module=../nginx-rtmp-module --with-http_ssl_module
9、安裝
make && make install
10、配置 nginx.conf 文件(/usr/local/nginx/conf下)
#user  nobody;
# multiple workers works !
worker_processes  2;
 
#pid        logs/nginx.pid;
events {
    worker_connections  8192;
}
 
rtmp {
    server {
        listen 1935;
        chunk_size 4000;
        application live {
             live on;
 
             record all;
             record_path /tmp/av;
             record_max_size 1K;
 
             record_unique on;
             allow publish all;
             deny publish all;
 
             allow play all;
        }
    }
}
 
http {
    include       mime.types;
    default_type  application/octet-stream;
  
    sendfile        off;
 
    server_names_hash_bucket_size 128;
 
    client_body_timeout   10;
    client_header_timeout 10;
    keepalive_timeout     30;
    send_timeout          10;
    keepalive_requests    10;
    #gzip  on; 
    server {
        listen       8080;
        server_name  localhost;
 
 
        location /stat {
            rtmp_stat all;
            rtmp_stat_stylesheet stat.xsl;
        }
        location /stat.xsl {
            root nginx-rtmp-module/;
        }
        location /control {
            rtmp_control all;
        }
 
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

11、啟動Nginx

cd /usr/local/nginx/sbin
./nginx -t
./nginx -s reload

啟動時可能會包錯:nginx: [error] invalid PID number "" in "/usr/local/nginx/logs/nginx.pid"
解決:

/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf

12、測試(注意打開伺服器8080和1935埠安全組)
訪問伺服器外網 IP:8080
image

Java編碼實現直播推流、拉流

1、新建SpringBoot項目 pom.xml 引入所需的jar包
        <!--直播相關依賴-->
        <!--javacv-->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv-platform</artifactId>
            <version>1.5.5</version>
        </dependency>
        <!-- //mvnrepository.com/artifact/org.bytedeco.javacpp-presets/opencv-platform -->
        <!--opencv-->
        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>opencv-platform</artifactId>
            <version>4.0.1-1.4.4</version>
        </dependency>
2、新建直播推流實現類
package com.honyar.iot.vedio.pushandpullimpl;

import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.*;
import org.bytedeco.opencv.opencv_core.IplImage;
import javax.swing.*;
public class PushStream {

    /**
     * 直播推流實現
     */
    public void getRecordPush(String outputPath, int v_rs) throws Exception, org.bytedeco.javacv.FrameRecorder.Exception, InterruptedException {
            //創建採集器
            OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(0);  //本地攝影機默認為0
            //開啟採集器
            try {
                grabber.start();
            } catch (Exception e) {
                try {
                    grabber.restart();  //一次重啟嘗試
                } catch (Exception e2) {
                    throw e;
                }
            }
            OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();  //轉換器
            Frame grabframe = grabber.grab();  //獲取一幀
            IplImage grabbedImage = null;
            if (grabframe != null) {
                grabbedImage = converter.convert(grabframe); //將這一幀轉換為IplImage
            }
            //創建錄製器
            FrameRecorder recorder;
            recorder = FrameRecorder.createDefault(outputPath, 1280, 720);   //輸出路徑,畫面高,畫面寬
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);  //設置編碼格式
            recorder.setFormat("flv");
            recorder.setFrameRate(v_rs);
            recorder.setGopSize(v_rs);

            //開啟錄製器
            try {
                recorder.start();
            } catch (java.lang.Exception e) {
                try {
                    if (recorder != null) {  //嘗試重啟錄製器
                        recorder.stop();
                        recorder.start();
                    }
                } catch (java.lang.Exception e1) {
                    e.printStackTrace();
                }
            }

            //直播效果展示窗口
            CanvasFrame frame = new CanvasFrame("主播-菜雞-德華", CanvasFrame.getDefaultGamma() / grabber.getGamma());
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setAlwaysOnTop(true);

            //推流
            while (frame.isVisible() && (grabframe = grabber.grab()) != null) {
                frame.showImage(grabframe);   //展示直播效果
                grabbedImage = converter.convert(grabframe);
                Frame rotatedFrame = converter.convert(grabbedImage);

                if (rotatedFrame != null) {
                    recorder.record(rotatedFrame);
                }

                Thread.sleep(50);  //50毫秒/幀
            }
        }
    }
3、新建直播推流啟動類
package com.honyar.iot.vedio.start;

import com.honyar.iot.vedio.pushandpullimpl.PushStream;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;

/**
 * 直播推流--啟動
 */
@SpringBootApplication
@Configuration
public class PushApplication {
    public static void main(String[] args) throws Exception {
        //設置rtmp伺服器推流地址(寫你自己伺服器外網地址)
        String outputPath = "rtmp://xxx.xx.xxx.xx:1935/live/address";
        PushStream recordPush = new PushStream();
        recordPush.getRecordPush(outputPath, 25);
    }
}
4、新建直播拉流實現類
package com.honyar.iot.vedio.pushandpullimpl;

import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.*;
import org.bytedeco.opencv.opencv_core.IplImage;
import javax.swing.*;
public class PushStream {

    /**
     * 直播推流實現
     */
    public void getRecordPush(String outputPath, int v_rs) throws Exception, org.bytedeco.javacv.FrameRecorder.Exception, InterruptedException {
            //創建採集器
            OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(0);  //本地攝影機默認為0
            //開啟採集器
            try {
                grabber.start();
            } catch (Exception e) {
                try {
                    grabber.restart();  //一次重啟嘗試
                } catch (Exception e2) {
                    throw e;
                }
            }
            OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();  //轉換器
            Frame grabframe = grabber.grab();  //獲取一幀
            IplImage grabbedImage = null;
            if (grabframe != null) {
                grabbedImage = converter.convert(grabframe); //將這一幀轉換為IplImage
            }
            //創建錄製器
            FrameRecorder recorder;
            recorder = FrameRecorder.createDefault(outputPath, 1280, 720);   //輸出路徑,畫面高,畫面寬
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);  //設置編碼格式
            recorder.setFormat("flv");
            recorder.setFrameRate(v_rs);
            recorder.setGopSize(v_rs);

            //開啟錄製器
            try {
                recorder.start();
            } catch (java.lang.Exception e) {
                try {
                    if (recorder != null) {  //嘗試重啟錄製器
                        recorder.stop();
                        recorder.start();
                    }
                } catch (java.lang.Exception e1) {
                    e.printStackTrace();
                }
            }

            //直播效果展示窗口
            CanvasFrame frame = new CanvasFrame("主播-菜雞-德華", CanvasFrame.getDefaultGamma() / grabber.getGamma());
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setAlwaysOnTop(true);

            //推流
            while (frame.isVisible() && (grabframe = grabber.grab()) != null) {
                frame.showImage(grabframe);   //展示直播效果
                grabbedImage = converter.convert(grabframe);
                Frame rotatedFrame = converter.convert(grabbedImage);

                if (rotatedFrame != null) {
                    recorder.record(rotatedFrame);
                }

                Thread.sleep(50);  //50毫秒/幀
            }
        }
    }
5、新建直播拉流啟動類(可以多建幾個模擬多個客戶端)
package com.honyar.iot.vedio.start;

import com.honyar.iot.vedio.pushandpullimpl.PullStream;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;

/**
 * 直播拉流--啟動
 */
@SpringBootApplication
@Configuration
public class PullApplication1 {
    public static void main(String[] args) throws Exception {
        //rtmp伺服器拉流地址(自己伺服器外網地址)
        String inputPath = "rtmp://xxx.xx.xxx.xx/live/address";
        PullStream pullStream = new PullStream();
        pullStream.getPullStream(inputPath);
    }
}

6、測試
image