Browse Source

v1
Signed-off-by: caner <5658514@qq.com>

caner 3 years ago
parent
commit
8f9a3ab9d6
5 changed files with 209 additions and 68 deletions
  1. 3 12
      README.md
  2. 79 6
      index.js
  3. 56 49
      lib/Pwm.js
  4. 68 0
      lib/videoSource.js
  5. 3 1
      package.json

+ 3 - 12
README.md

@@ -2,23 +2,14 @@
 ```
 接收遥控信号,传输视频信息
 ```
-## TODO
-```
-1. 新版视频方案
-
-```
-
 ## 安装
 ```
-1. sudo npm install 
+1. apt install ffmpeg
+2. sudo npm install 
 ```
 
 ## 启动
 ```
 1. node index.js
 ```
-## 注意
-```
-1. 电调需自行找中位可参考:[sensor] (https://git.caner.top/Caner/sensor)
-2. 注意摇杆间的中位误差
-```
+

+ 79 - 6
index.js

@@ -1,7 +1,9 @@
 
 // wrt + socket + contrl
 const io = require("socket.io-client")
-const PWM = require('./Pwm')
+const RTCVideoSource = require('./lib/videoSource')
+//const PWM = require('./lib/Pwm')
+const { RTCPeerConnection, MediaStream } = require("wrtc")
 const HOST = 'ws://10.10.3.196:7896'
 class CarServer {
     constructor(HOST) {
@@ -15,23 +17,94 @@ class CarServer {
             }
         });
         this.socket.on('connect', () => {
-            console.log(`连接成功`)
+            this.connect()
         })
         this.socket.on('leaved', user => {
             console.log(`${user.name}离开${user.roomID}房间`)
+            // 用户离开移除RTC
+            this.destroyed()
         })
         this.socket.on('joined', user => {
             console.log(`${user.name}加入${user.roomID}房间`)
+            // 发送offer
+            this.createOffer()
         })
         this.socket.on('msg', data => {
             console.log('用户信息', data.type);
-            if (data.type === 'conctrl') {
-                PWM.changPWM(data.conctrl)
-            }
+            this.msg(data)
         })
         this.socket.on('connect_error', err => {
             console.log('连接错误', err)
+            this.destroyed()
+        })
+    }
+    // 连接
+    connect() {
+        console.log('连接成功,开始初始化webrtc')
+        // 初始化rtc
+        this.webRTC = new RTCPeerConnection()
+        // 初始化媒体源
+        this.mediaStream = new MediaStream()
+        this.videoSource = new RTCVideoSource()
+        // 加入媒体源
+        const videoTrack = this.videoSource.createTrack()
+        this.mediaStream.addTrack(videoTrack)
+        this.webRTC.addTrack(videoTrack, this.mediaStream)
+        // 开始媒体
+        this.videoSource.start()
+        // 监听ice
+        this.webRTC.onicecandidate = event => {
+            if (event.candidate) {
+                // 发送ICE
+                this.socket.emit('msg', {
+                    type: 'candidate',
+                    candidate: event.candidate
+                })
+            }
+        }
+        // 监听ice状态
+        this.webRTC.oniceconnectionstatechange = () => {
+            if (this.webRTC.iceConnectionState === 'failed' || this.webRTC.iceConnectionState === 'closed' || this.webRTC.iceConnectionState === "disconnected") {
+                console.log('ICE 连接失败')
+                this.destroyed()
+            }
+        }        
+    }
+    // 用户信息
+    msg(data){
+        if (data.type === 'answer') {
+            // 设置本地应答
+            this.webRTC.setRemoteDescription(data.answer)
+        } else if (data.type === 'candidate') {
+            // 本地设置ICE
+            this.webRTC.addIceCandidate(data.candidate)
+        } else if (data.type === 'conctrl') {
+            // 控制信息
+            //PWM.changPWM(data.conctrl)
+        } else if (data.type === 'startRTC') {
+            // 向其它房间人发送offer
+            this.createOffer()
+        }
+    }
+    // 创建offer
+    async createOffer() {
+        // 创建offer
+        const offer = await this.webRTC.createOffer()
+        this.webRTC.setLocalDescription(offer)
+        // 发送offer
+        this.socket.emit('msg', {
+            type: 'offer',
+            offer: offer
         })
-    }  
+    }
+
+    destroyed() {
+        if (this.webRTC) this.webRTC.close()
+        if (this.videoSource) this.videoSource.stop()
+        this.webRTC = null
+        this.mediaStream = null
+        this.videoSource = null     
+        process.exit(1) 
+    }    
 }
 new CarServer(HOST)

+ 56 - 49
Pwm.js → lib/Pwm.js

@@ -20,12 +20,12 @@ class ContrlService {
             s2: 3
         }
         // prio init
-        //RPIO.init({ mapping: "gpio" });
+        RPIO.init({ mapping: "gpio" });
         // 继电器解锁
-        //RPIO.open(6, RPIO.OUTPUT, RPIO.HIGH);
-        //RPIO.open(13, RPIO.OUTPUT, RPIO.HIGH);
+        RPIO.open(22, RPIO.OUTPUT, RPIO.HIGH);
+        RPIO.open(23, RPIO.OUTPUT, RPIO.HIGH);
         // 开灯
-        //RPIO.open(19, RPIO.OUTPUT, RPIO.HIGH);
+        RPIO.open(22, RPIO.OUTPUT, RPIO.HIGH);
         // init PWM
         this.PWM = new Pca9685Driver(this.pwmOption, er => {
             if (er) {
@@ -36,55 +36,45 @@ class ContrlService {
         this.Snum = 0
         // 解锁状态
         this.enable = false
-        // 摇杆解锁
-        this.contrlEnable = false
-        // 摇杆中立值110-120
-        this.centerNum = 120
     }
     // 设置pwm
-    async changPWM(params) {
+    async changPWM (params) {
         const { v0, v1, v2, v3 } = params
         if (typeof (v0) == 'number' || typeof (v3) == 'number') {
-            // 内八解锁
             if (this.enable) {
-                //中位值
-                if (!this.contrlEnable) {
-                    this.PWM.setPulseLength(this.MoAndSero.m1, this.Motor2pwm(Math.abs(this.centerNum)));
-                    this.PWM.setPulseLength(this.MoAndSero.m2, this.Motor2pwm(Math.abs(this.centerNum)));
-                }
-                //上推解锁
-                if (!this.contrlEnable && +v3 > 200) this.contrlEnable = true
-                // PWM操作
-                if (this.contrlEnable) {
-                    // 注意中位误差
-                    if (Math.abs(+v2 - this.centerNum) < 20) {
-                        this.PWM.setPulseLength(this.MoAndSero.m1, this.Motor2pwm(Math.abs(+v3 - 255)));
-                        this.PWM.setPulseLength(this.MoAndSero.m2, this.Motor2pwm(Math.abs(+v3 - 255)));
+                // 解锁之后需要2秒给电调中立值
+                if (this.Snum >= 50 && this.Snum <= 61) this.Snum++
+                if (this.Snum >= 60) {
+                   if (Math.abs(+v2 - 128) < 20) {
+                        console.log('前后', v3)
+                        this.PWM.setPulseLength(this.MoAndSero.m1, this.Motor2pwm(Math.abs(+v3 - 256)));
+                        this.PWM.setPulseLength(this.MoAndSero.m2, this.Motor2pwm(Math.abs(+v3 - 256)));
                     } else {
-                        // 分左右 注意左右误差
-                        if ((+v2 - this.centerNum) > 20) {
+                        // 分左右
+                        if ((+v2 - 128) > 20) {
                             // 右
-                            const a = this.centerNum - (+v2 - this.centerNum)
-                            const b = this.centerNum + (+v2 - this.centerNum)
+                            console.log('右', +v2);
+                            const a = 128 - (+v2 - 128)
+                            const b = 128 + (+v2 - 128)
                             this.PWM.setPulseLength(this.MoAndSero.m1, this.Motor2pwm(+a));
                             this.PWM.setPulseLength(this.MoAndSero.m2, this.Motor2pwm(+b));
-                        } else if (+v2 - this.centerNum < -20) {
+                        } else if (+v2 - 128 < -20) {
                             // 左
-                            const a = this.centerNum - (+v2 - this.centerNum)
-                            const b = this.centerNum + (+v2 - this.centerNum)
+                            console.log('左', +v2);
+                            const a = 128 - (+v2 - 128)
+                            const b = 128 + (+v2 - 128)
                             this.PWM.setPulseLength(this.MoAndSero.m1, this.Motor2pwm(+a));
                             this.PWM.setPulseLength(this.MoAndSero.m2, this.Motor2pwm(+b));
                         }
                     }
-
-                }
-                //云台
-                if (v0 || v1) {
-                    const a = +v1 > 200 ? 200 : +v1
-                    this.PWM.setPulseLength(this.MoAndSero.s1, this.Servo2pwm(+v0));
-                    this.PWM.setPulseLength(this.MoAndSero.s2, this.Servo2pwm(+a));
+                    //云台
+                    if (v0 || v1) {
+                        console.log('云台', v0, v1)
+                        const a = +v1 > 200 ? 200 : +v1
+                        this.PWM.setPulseLength(this.MoAndSero.s1, this.Servo2pwm(+v0));
+                        this.PWM.setPulseLength(this.MoAndSero.s2, this.Servo2pwm(+a));
+                    }
                 }
-                console.log('前后', v3, '左右', v2, '云台', v0, v1)
             } else {
                 this.unLOCK(v0, v1, v2, v3)
             }
@@ -101,8 +91,8 @@ class ContrlService {
      * @param {*} v2 
      * @param {*} v3 
      */
-    unLOCK(v0, v1, v2, v3) {
-        // 未解锁=>进行解锁:发送端的频率是0.05S,0.05*60 = 3S        
+    unLOCK (v0, v1, v2, v3) {
+        // 未解锁=>进行解锁:发送端的频率是0.04S,0.05*60 = 3S        
         // 内八 :v0<50,v1>200,v2>200,v3<50
         if (+v0 <= 60 && +v1 >= 190 && +v3 >= 190 && +v2 <= 60) {
             if (this.Snum >= 0 && this.Snum <= 51) this.Snum++
@@ -110,31 +100,48 @@ class ContrlService {
             this.Snum = 0
         }
         if (this.Snum == 50) {
-            // RPIO.open(19, RPIO.OUTPUT, RPIO.HIGH);
+            RPIO.open(23, RPIO.OUTPUT, RPIO.HIGH);
             this.enable = true
         }
         console.log('解锁中', v0, v1, v2, v3)
     }
     /**
-     * 电调换算比例:摇杆范围0-255,PWM范围500~1500~2500
+     * 电调换算比例:摇杆范围0-256,PWM范围500~1500~2500
      * @param {number} v 
      * @returns 
      */
-    Motor2pwm(v) {
-        if (v === 0) {
-            return parseInt(10 / (256 / 1000)) + 1000
-        } else if (v >= 200) {
-            return parseInt(200 / (256 / 1000)) + 1000
+    Motor2pwm (v) {
+        // 分为6挡位,中值128,步进256/6
+        const num = parseInt(256 / 6)
+        // 方向调换
+        const dirc = Math.abs(v - 256)
+        // 真实值
+        let value = 128
+        // 步进取中间值
+        if (dirc > 0 && dirc < num) {
+            value = num / 2
+        } else if (dirc > num && dirc <= num * 2) {
+            value = num + (num / 2)
+        } else if (dirc > num * 2 && dirc < 128) {
+            value = 128 - (num / 2)
+        } else if (dirc > 128 && dirc <= (128 + num)) {
+            value = 128 + (num / 2)
+        } else if (dirc > (128 + num) && dirc <= (128 + num * 2)) {
+            value = 128 + num + (num / 2)
+        } else if (dirc > (128 + num * 2) && dirc <= 256) {
+            value = 256 - (num / 2)
         } else {
-            return parseInt((v - 5) / (256 / 1000)) + 1000
+            value = 128
         }
+
+        return parseInt(value / (256 / 2000)) + 500
     }
     /**
      * 舵机换算比例:摇杆范围0-256,PWM范围1000~1500~2000
      * @param {number} v 
      * @returns 
      */
-    Servo2pwm(v) {
+    Servo2pwm (v) {
         return parseInt(v / (256 / 1000)) + 1000
     }
 }

+ 68 - 0
lib/videoSource.js

@@ -0,0 +1,68 @@
+const { RTCVideoSource } = require('wrtc').nonstandard;
+const ffmpeg = require('fluent-ffmpeg')
+
+class VideoSourceService extends RTCVideoSource {
+    constructor() {
+        super() //videosource int
+        this.command = null // 存储视频源
+        this.cache = Buffer.alloc(0)
+    }
+
+    // 创建通道
+    createTrack() {
+        const track = super.createTrack()
+        return track
+    }
+
+
+    // 获取视频源
+    async start() {
+        if (this.command !== null) this.stop()
+        const width = 400, height = 300
+        // libx264编码后花屏
+        this.command = ffmpeg('/dev/video0')
+            .size(`${width}x${height}`)
+            .outputOptions(['-pix_fmt yuv420p'])
+            .format("rawvideo")
+            .on('start', () => {
+                console.log('Video start !');
+            })
+            .on('error', (err) => {
+                console.log('Video processing An error occurred: ' + err.message);
+                // 退出进程
+                process.exit(1)
+            })
+        this.ffstream = this.command.pipe();
+        this.ffstream.on('data', (buffer) => {
+            this.cache = Buffer.concat([this.cache, buffer])
+        });
+
+        // 整理buffer
+        const frameSize = width * height * 1.5;
+        const processData = () => {
+            while (this.cache.length > frameSize) {
+                const buffer = this.cache.slice(0, frameSize)
+                this.cache = this.cache.slice(frameSize)
+                this.onFrame({
+                    width,
+                    height,
+                    data: new Uint8ClampedArray(buffer)
+                })
+            }
+            if (this.command !== null) {
+                setTimeout(() => processData())
+            }
+        }
+        processData()
+    }
+
+    // 停止获取
+    stop() {
+        console.log("Video source stop");
+        if (this.command != null) {
+            this.command.kill("SIGHUP")
+            this.command = null
+        }
+    }
+}
+module.exports = VideoSourceService

+ 3 - 1
package.json

@@ -12,6 +12,8 @@
     "socket.io-client": "^4.4.1",
     "i2c-bus": "^5.2.2",
     "pca9685": "^5.0.0",
-    "rpio": "^2.4.2"
+    "rpio": "^2.4.2",
+    "wrtc": "^0.4.7",
+    "fluent-ffmpeg": "^2.1.2"
   }
 }