安装可以参考网上的这里不一一介绍了
部分代码:(要完整代码请关注我并私信给我)
var fs = require('fs');
var http = require('http');
var WebSocket &#61; require(&#39;ws&#39;);if (process.argv.length < 3) {console.log(&#39;输入正确参数&#39;);process.exit();
}var stream_secret &#61; process.argv[2];//密码
var stream_port &#61; process.argv[3] || 8081;//ffpeng推送端口
var websocket_port &#61; process.argv[4] || 8082;//前端websocket端口 &#xff0c;比如&#xff1a;8082
var record_stream &#61; false;
var totalSize &#61; 0;
首先我们新建一个java项目&#xff0c;可以是普通的java&#xff0c;我这里新建的是springboot看起来比较高大上。
public class FfmpegTest {//自带map缓存&#xff0c;防止同一个设备机器多次转换导致视频丢帧public static Map<String , Integer> map &#61; new HashMap<>();//线程池&#xff0c;因为开启转换是持续的多线程开启保证主线程不被阻塞ExecutorService es3 &#61; Executors.newCachedThreadPool();&#64;RequestMapping(value &#61; "/hello/{code}",method &#61; RequestMethod.GET)public String test(&#64;PathVariable String code, HttpServletRequest request){//获取IP地址String ipAddress &#61; IpUtil.getIpAddr(request);System.out.println("客户端ip:"&#43;ipAddress);//转换rtsp流String ffmpeg &#61; ffmpeg(code);//获取pidString pid &#61; getPid(code);System.out.println(ffmpeg&#43;"进程号是:"&#43;pid);return ffmpeg&#43;"进程号是:"&#43;pid;}&#64;RequestMapping(value &#61; "/kill/{code}",method &#61; RequestMethod.GET)public String kill(&#64;PathVariable String code){String pid &#61; getPid(code);System.out.println(code&#43;"视频设备进程号是:"&#43;pid);String s &#61; killPid(pid , code);return code&#43;"视频设备进程号是:"&#43;pid&#43;" "&#43;s;}public String ffmpeg(String code) {String returnstr&#61;"";String s&#61;"";List<String> commend &#61; new ArrayList<String>();commend.add("ffmpeg");commend.add("-i");commend.add("rtsp://用户:摄像头密码&#64;192.168.1."&#43;code&#43;":554/h264/ch1/sub/av_stream");commend.add("-q");commend.add("0");commend.add("-f mpegts -codec:v mpeg1video -s 800x600");commend.add("http://192.168.1.142:8081/supersecret/live"&#43;code&#43;"");StringBuffer test&#61;new StringBuffer();for(int i&#61;0;i<commend.size();i&#43;&#43;)test.append(commend.get(i)&#43;" ");System.out.println(test);try {if (map.get(code)!&#61;null && map.get(code)>&#61;1){map.put(code,map.get(code)&#43;1);returnstr&#43;&#61;code&#43;"设备视频已经在转换列表里了";System.out.println("视频已经在转换列表里了");}else{//采用多线程处理防止主线程阻塞&#xff0c;一直等待startStream(test.toString(),code);map.put(code,1);System.out.println("视频转换成功:");returnstr&#43;&#61;code&#43;"设备视频转换成功";}} catch (Exception e) {e.printStackTrace();}return returnstr;}/*** 使用多线程执行linux的ffmpeg命令防止主线程阻塞* &#64;param test 命令* &#64;param code 对应的摄像投设备标志*/private void startStream(String test,String code) {//处理buffer的线程es3.submit(new Runnable() {&#64;Overridepublic void run() {String line &#61; null;try {Runtime rt &#61; Runtime.getRuntime();//执行linux命令Process proc &#61; rt.exec(test.toString());//添加缓存防止多次转换map.put(code,1);//启用多线程消费正常日志防止内存小导致线程阻塞clearStream(proc.getInputStream());//启用多线程消费错误日志防止内存小导致线程阻塞clearStream(proc.getErrorStream());} catch (IOException e) {e.printStackTrace();}}});}/*** 开启多线程消费日志&#xff0c;防止转换缓冲区内存溢出* &#64;param stream 输出流*/private void clearStream (InputStream stream) {//处理buffer的线程es3.submit(new Runnable() {&#64;Overridepublic void run() {String line &#61; null;try (BufferedReader in &#61; new BufferedReader(new InputStreamReader(stream));) {while ((line &#61; in.readLine()) !&#61; null) {System.out.println(line);}} catch (IOException e) {e.printStackTrace();}}});}/*** 根据pid关闭进程* &#64;param pid* &#64;param code* &#64;return*/public static String killPid(String pid , String code) {BufferedReader reader &#61; null;String pidStr&#61;"";try {// 显示所有进程Process process &#61; Runtime.getRuntime().exec("kill -9 " &#43; pid);pidStr&#43;&#61;"成功关闭进程&#xff1a;"&#43;pid;map.put(code,0);} catch (Exception e) {e.printStackTrace();} finally {if (reader !&#61; null) {try {reader.close();} catch (IOException e) {}}}return pidStr;}/***根据名称获取linux启动的进程pid* &#64;param pid 摄像头唯一标志开启进程时包含的标志* &#64;return*/public static String getPid(String pid) {BufferedReader reader &#61; null;try {// 显示所有进程Process process &#61; Runtime.getRuntime().exec("ps -ef");reader &#61; new BufferedReader(new InputStreamReader(process.getInputStream()));String line &#61; null;while ((line &#61; reader.readLine()) !&#61; null) {//根据设备标志获取需要的线程pidif (line.contains("rtsp://用户名:摄像头密码&#64;192.168.1."&#43;pid&#43;":554/h264/ch1/sub")) {//拿到第一个pidString[] strs &#61; line.split("\\s&#43;");return strs[1];}}} catch (Exception e) {e.printStackTrace();} finally {if (reader !&#61; null) {try {reader.close();} catch (IOException e) {}}}return null;}public static void main(String[] args) {}
}
<!DOCTYPE html>
<html>
<head><title>JSMpeg Stream Client</title><style type&#61;"text/css">html, body {text-align: center;}</style></head>
<body><canvas id&#61;"video-canvas1"></canvas><canvas id&#61;"video-canvas2"></canvas><canvas id&#61;"video-canvas3"></canvas><canvas id&#61;"video-canvas4"></canvas><canvas id&#61;"video-canvas5"></canvas><canvas id&#61;"video-canvas6"></canvas><canvas id&#61;"video-canvas7"></canvas><canvas id&#61;"video-canvas8"></canvas><canvas id&#61;"video-canvas9"></canvas><canvas id&#61;"canvas"></canvas><span id&#61;"jietu">截图</span><script type&#61;"text/Javascript" src&#61;"jsmpeg.min.js"></script><script type&#61;"text/Javascript" src&#61;"html2canvas.js"></script><script type&#61;"text/Javascript" src&#61;"canvas2Image.js"></script><script type&#61;"text/Javascript">let canvas1 &#61; document.getElementById(&#39;video-canvas1&#39;);let url1 &#61; &#39;ws://192.168.1.142:8082/live26&#39;;let player1 &#61; new JSMpeg.Player(url1, {canvas: canvas1});let canvas2 &#61; document.getElementById(&#39;video-canvas2&#39;);let url2 &#61; &#39;ws://192.168.1.142:8082/live24&#39;;let player2 &#61; new JSMpeg.Player(url2, {canvas: canvas2});let canvas3 &#61; document.getElementById(&#39;video-canvas3&#39;);let url3 &#61; &#39;ws://192.168.1.142:8082/live27&#39;;let player3 &#61; new JSMpeg.Player(url3, {canvas: canvas3});let canvas4 &#61; document.getElementById(&#39;video-canvas4&#39;);let url4 &#61; &#39;ws://192.168.1.142:8082/live33&#39;;let player4 &#61; new JSMpeg.Player(url4, {canvas: canvas4});let canvas5 &#61; document.getElementById(&#39;video-canvas5&#39;);let url5 &#61; &#39;ws://192.168.1.142:8082/live23&#39;;let player5 &#61; new JSMpeg.Player(url5, {canvas: canvas5});let canvas6 &#61; document.getElementById(&#39;video-canvas6&#39;);let url6 &#61; &#39;ws://192.168.1.142:8082/live30&#39;;let player6 &#61; new JSMpeg.Player(url6, {canvas: canvas6});let jietu &#61; document.getElementById("jietu");jietu.addEventListener("click", function(){/*var cntElem &#61; document.getElementById("video-canvas1");var shareContent &#61; cntElem;//需要截图的包裹的&#xff08;原生的&#xff09;DOM 对象var width &#61; shareContent.offsetWidth; //获取dom 宽度var height &#61; shareContent.offsetHeight; //获取dom 高度var canvas &#61; document.getElementById("canvas"); //创建一个canvas节点var scale &#61; 2; //定义任意放大倍数 支持小数canvas.width &#61; width * scale; //定义canvas 宽度 * 缩放canvas.height &#61; height * scale; //定义canvas高度 *缩放canvas.getContext("2d").scale(scale, scale); //获取context,设置scalevar opts &#61; {scale: scale, // 添加的scale 参数canvas: canvas, //自定义 canvas// logging: true, //日志开关&#xff0c;便于查看html2canvas的内部执行流程width: width, //dom 原始宽度height: height,useCORS: true // 【重要】开启跨域配置};html2canvas(shareContent, opts).then(function (canvas) {var context &#61; canvas.getContext(&#39;2d&#39;);// 【重要】关闭抗锯齿context.mozImageSmoothingEnabled &#61; false;context.webkitImageSmoothingEnabled &#61; false;context.msImageSmoothingEnabled &#61; false;context.imageSmoothingEnabled &#61; false;// 【重要】默认转化的格式为png,也可设置为其他格式var img &#61; Canvas2Image.saveAsPNG(canvas, canvas.width, canvas.height);document.body.appendChild(img);img.css &#61; {"width": canvas.width / 2 &#43; "px","height": canvas.height / 2 &#43; "px","position":"fixed","top":"0","left":"0","opacity":"1","z-index":222}});*/downloadFile(&#39;download&#39;, document.getElementById("video-canvas1").toDataURL());});function downloadFile(fileName, content) {let aLink &#61; document.createElement(&#39;a&#39;);let blob &#61; this.base64ToBlob(content); //new Blob([content]);let evt &#61; document.createEvent("HTMLEvents");evt.initEvent("click", true, true);//initEvent 不加后两个参数在FF下会报错 事件类型&#xff0c;是否冒泡&#xff0c;是否阻止浏览器的默认行为aLink.download &#61; fileName;aLink.href &#61; URL.createObjectURL(blob);// aLink.dispatchEvent(evt);aLink.click()}function base64ToBlob(code) {let parts &#61; code.split(&#39;;base64,&#39;);let contentType &#61; parts[0].split(&#39;:&#39;)[1];let raw &#61; window.atob(parts[1]);let rawLength &#61; raw.length;let uInt8Array &#61; new Uint8Array(rawLength);for (let i &#61; 0; i < rawLength; &#43;&#43;i) {uInt8Array[i] &#61; raw.charCodeAt(i);}return new Blob([uInt8Array], { type: contentType });}/*for (let i &#61; 1; i <&#61; 9; i&#43;&#43;){let canvas &#61; document.getElementById(&#39;video-canvas&#39; &#43; i);let url &#61; &#39;ws://127.0.0.1:8082/live&#39; &#43; i;let player &#61; new JSMpeg.Player(url, {canvas: canvas});}*/</script>
</body>
</html>