最近公司在做养老相关的业务,现在需要子女从小程序端对家里的老人通过家庭终端交互屏进行实时看护。
第三方的一些现成的服务:腾讯音视频通话、直播功能; 阿里的音视频通信;两者都挺好的,但是需要收费因此放弃决定自己搭建一套直播流服务;
Nginx、Nginx-Rtmp-Module
Nginx:https://nginx.org/en/download.html
Nginx-Rtmp-Module:https://github.com/arut/nginx-rtmp-module
wget https://nginx.org/download/nginx-1.21.6.tar.gz
mv nginx-1.21.6.tar.gz /usr/local
git clone https://github.com/arut/nginx-rtmp-module.git
mv nginx-rtmp-module /usr/local
cd /usr/local
tar -zxvf nginx-1.21.6.tar.gz
cd nginx-1.21.6
./configure --prefix=/usr/local/nginx --add-module=../nginx-rtmp-module --with-http_ssl_module
make && make install
#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
<!--直播相关依赖--> <!--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>
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毫秒/帧 } } }
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); } }
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毫秒/帧 } } }
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、测试