业务需求
最近公司在做养老相关的业务,现在需要子女从小程序端对家里的老人通过家庭终端交互屏进行实时看护。
解决方案
第三方的一些现成的服务:腾讯音视频通话、直播功能; 阿里的音视频通信;两者都挺好的,但是需要收费因此放弃决定自己搭建一套直播流服务;
先看效果(自己服务器配置低有延迟、放到公司服务器上很流畅、清楚)
使用工具
Nginx、Nginx-Rtmp-Module
下载地址
Nginx:https://nginx.org/en/download.html
Nginx-Rtmp-Module:https://github.com/arut/nginx-rtmp-module
软件安装
1、下载Nginx
wget https://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 https://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
Java编码实现直播推流、拉流
1、新建SpringBoot项目 pom.xml 引入所需的jar包
<!--直播相关依赖--> <!--javacv--> <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv-platform</artifactId> <version>1.5.5</version> </dependency> <!-- https://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、测试