国标云台控制状态
1.基本概念
国标联网系统的信息传输、交换、控制方面的都是通过SIP服务器负责通讯得,SIP负责信令流逐级转发。其中最重要的一部分就是和摄像机进行信令交互。
像安全注册、实时视音频点播、历史视音频的回放等应用的会话控制采用IETFRFC3261规定的Register、Invite等请求和响应方法实现,历史视音频回放控制采用SIP扩展协议IETFRFC2976 规定的INFO方法实现,前端设备控制、信息査询、报警事件通知和分发等应用的会话控制采用SIP 扩展协议IETFRFC3428规定的Message方法实现,SIP 消息应支持基于UDP和TCP的传输互联的系统平台及设备不应向对方的SIP端口发送应用无关消息,避免应用无关消息占用系统平台及设备的SIP消息处理资源。本标准基于IETFRFC3261等基础性协议,进行监控联网各项业务功能的规定,本标准中各项功能如有特殊规定应遵循本标准,否则应遵循IETFRFC3261等引用协议。
1.1前端设备控制协议格式
字节 | 字节1 | 字节2 | 字节3 | 字节4 | 字节5 | 字节6 | 字节7 | 字节8 |
含义 | A5H | 组合码1 | 地址 | 指令 | 数据1 | 数据2 | 组合码2 | 校验码 |
各字节定义如下:
- 字节1:令的首字节为 A5H.
- 字节2:组合码高位是版本信息,低4位是校验位。本标准的版本号是1.0,版本信息为 0H。校验位=(享节1的高4位十字节1的低4位十字节2的高4位)%16
- 字节3:地址的低8亿
- 字节 4:指令码。
- 字节5、6:数据1和数据2。
- 字节7:组合码2,高4位是数据3,低4位是地址的高4位;在后续叙述中,没有特别指明的高4位,表示该4位与所指定的功能无关。
- 字节8:校验码,为前面的第1~7字节的算术和的低8位,即算术和对256取模后的结果字节8=(字节1十字节2字节3十字节4十字节5+字节6字节7)%256。地址范围000H~FFFH(即0~4095),其中000H地址作为广播地址。注:前端设备控制中,不使用字节3和字节7的低4位地址码,使用前端设备控制消息体中的(DeviceID)统一编码标识控制的前端设备。
1.2ptz指令
PTZ指令见表 A.4,其中Bit5和 Bit4不应同时为1,Bit3和Bit2不应同时为1;Bitl和 Bit0 不应同时为1。镜头变倍指令、云台上下指令、云台左右指令三者可以组合。
1.3 FI指令
FI指令见表A.6,其中 Bit3和Bit2不应同时为1,Bit1和Bit0不应同时为1;光圈控制和聚焦控制的指令可以组合。
2.前端代码实现
vue template
<template><div id="ptz-control" v-if="showDialog" v-on:mouseover="drag('ptz-control','wonderMain',true)" ref="videoCloudCheck" :class="[className]"><div class="video-cloud-check-content" ><div class="win-dialog-title"><span class="ptztab" :class="{'active': currentTab=='ptz'}" @click="changeTab('ptz')">云台控制</span><span v-show="isShowPresetAndCruise"><span class="vline"></span><span class="ptztab" :class="{'active': currentTab=='preset'}" @click="changeTab('preset')">预置位</span><span class="vline"></span><span class="ptztab" :class="{'active': currentTab=='cruise'}" @click="changeTab('cruise')">巡航</span></span><span class="close iconfont i-f_a_close" @click="close"></span></div><div class="win-dialog-neck"></div><!--云台控制开始--><div class="view ptz" v-if="currentTab=='ptz'"><div class="dir-control"><span class="cmd vertical dir right" @mousedown="ptzCtrlMouseDown(0)" @mouseup="ptzCtrlMouseUp(0)" v-btnPer="'CAMERA_ptzCtrl'"></span><span class="cmd dir rightup" @mousedown="ptzCtrlMouseDown(1)" @mouseup="ptzCtrlMouseUp(1)" v-btnPer="'CAMERA_ptzCtrl'"></span><span class="cmd dir up" @mousedown="ptzCtrlMouseDown(2)" @mouseup="ptzCtrlMouseUp(2)" v-btnPer="'CAMERA_ptzCtrl'"></span><span class="cmd dir leftup" @mousedown="ptzCtrlMouseDown(3)" @mouseup="ptzCtrlMouseUp(3)" v-btnPer="'CAMERA_ptzCtrl'"></span><span class="cmd vertical dir left" @mousedown="ptzCtrlMouseDown(4)" @mouseup="ptzCtrlMouseUp(4)" v-btnPer="'CAMERA_ptzCtrl'"></span><span class="cmd dir leftdown" @mousedown="ptzCtrlMouseDown(5)" @mouseup="ptzCtrlMouseUp(5)" v-btnPer="'CAMERA_ptzCtrl'"></span><span class="cmd dir down" @mousedown="ptzCtrlMouseDown(6)" @mouseup="ptzCtrlMouseUp(6)" v-btnPer="'CAMERA_ptzCtrl'"></span><span class="cmd dir rightdown" @mousedown="ptzCtrlMouseDown(7)" @mouseup="ptzCtrlMouseUp(7)" v-btnPer="'CAMERA_ptzCtrl'"></span><span class="cmd zoom up adjust" title="焦距增大" @mousedown="ptzCtrlMouseDown(11, 1)" @mouseup="ptzCtrlMouseUp(11, 1)" v-btnPer="'CAMERA_ptzCtrl'"></span><span class="cmd zoom down adjust" title="焦距减小" @mousedown="ptzCtrlMouseDown(11, -1)" @mouseup="ptzCtrlMouseUp(11, -1)" v-btnPer="'CAMERA_ptzCtrl'"></span><span class="cmd focus up adjust" title="焦点增大" @mousedown="ptzCtrlMouseDown(12, 1)" @mouseup="ptzCtrlMouseUp(12, 1)" v-btnPer="'CAMERA_ptzCtrl'"></span><span class="cmd focus down adjust" title="焦点减小" @mousedown="ptzCtrlMouseDown(12, -1)" @mouseup="ptzCtrlMouseUp(12, -1)" v-btnPer="'CAMERA_ptzCtrl'"></span><span class="cmd iris up adjust" title="光圈增大" @mousedown="ptzCtrlMouseDown(10, 1)" @mouseup="ptzCtrlMouseUp(10, 1)" v-btnPer="'CAMERA_ptzCtrl'"></span><span class="cmd iris down adjust" title="光圈减小" @mousedown="ptzCtrlMouseDown(10, -1)" @mouseup="ptzCtrlMouseUp(10, -1)" v-btnPer="'CAMERA_ptzCtrl'"></span></div><div class="ptz-speed"><div class="control-speed"><span class="item">控制速度:</span><div class="speedSlider"><xui-slider v-model="speed" :options="sliderOptions" @change="changeHandle" v-btnPer="'CAMERA_ptzCtrl'"></xui-slider></div></div><div class="equipment"><span class="choose"><xui-select v-model="equip" :disabled="!isOn" :options="equipOptions" @change="changeSelect" v-btnPer="'CAMERA_ptzCtrl'"></xui-select></span><span class="switch"><label>开关:</label><xui-switch v-model="isOn" class="switch-input" size="normal" @change="changeSwitch" v-btnPer="'CAMERA_ptzCtrl'"></xui-switch></span></div></div></div><!--云台控制结束--><!--预置位开始--><div class="view preset" v-if="currentTab=='preset'"><div style="display:none;"><xui-button color="primary" size="mini" @click="addPreset">新增</xui-button></div><div class="head"><span class="preset-index">编号</span><span class="preset-img">图片</span><span class="preset-name"> 标题</span><span class="preset-do">操作</span></div><xui-scroll class="preset-content"><ul class="preset-list"><li v-for="(item,index) in presetsList" :key="index"><span class="preset-index num">{{index+1}}</span><span class='preset-img'><img :src="getImgUrl(item.imageUrl)" @dblclick="zooImg(item.imageUrl, item.name)"/></span><span class='preset-name' :title="item.name">{{item.name}}</span><span class='preset-do'><span class='preset-use' @click="presetUse(item)"></span><span class='preset-delete' @click="presetDelete(item)"></span></span></li></ul></xui-scroll><div class="hline"></div></div><!--预置位结束--><!--巡航计划开始--><div class="view cruise" v-if="currentTab=='cruise'"><div class="auto-content" v-if="isShowCruiseLists"><div class="auto-title" v-if="cruisesList.length >0"><span class="l-index">编号</span><span class="l-name">名称</span><span class="l-controller">操作</span></div><xui-scroll class="show-cruise-list" v-if="cruisesList.length >0"><ul><li class="auto-list common-list" v-for="(item,index) in cruisesList" :key="index"><span class="l-index">{{(index+1)}}</span><span class="l-name too-long" :title="item.name">{{item.name}}</span><span class="l-controller"><xui-switch v-model="item.status" :ctx="item" size="mini" @change="switchCruise(item)"></xui-switch><i class="common-icon edit" @click="editCruise(item)"></i><i class="common-icon del" @click="delCruise(item)"></i></span></li></ul></xui-scroll><span v-else class="no-data">暂无巡航数据</span></div><xui-button color="primary" size="mini" v-if="isShowCruiseLists" @click="showAddCruise">新增巡航</xui-button><div class="add-content add-auto" v-if="isShowAddCruise"><div class="n-title clearfix"><span class="add-new" title="新增自动巡航预置位" @click="addCruiseList"></span><span class="go-back" @click="goBack">返回</span></div><div class="n-content"><div class="auto-title common-title"><span class="l-index">预置位标题</span><span class="l-name">停留时间(秒)</span><span class="l-controller">操作</span></div><xui-scroll class="auto-ul"><ul><li class="auto-list" v-for="(item,index) in mockList" :key="index"><!-- 选择已有预置位 --><div class="pre-name"><xui-select v-model="item.presetId" :options="selectOptions" class="preset-select"></xui-select></div><div class='pre-time'><xui-input v-model="item.internalTime" @blur="validate('internalTime', item.internalTime)" :options="inputOptions"></xui-input></div><div class='pre-controller'><i class="common-icon up" title="上移" v-if="index !== 0" @click="moveUp(index,item)"></i><i class="common-icon down" title="下移" v-if="index !== (mockList.length-1)" @click="moveDown(index,item)"></i><i class="common-icon del" title="删除" @click="deleteLi(index)"></i></div></li></ul></xui-scroll><div class="new-cruise-info"><div class="title-name"><span class="cruise-title">名 称:</span><xui-input v-model="cruiseName" :options="cruiseNameOptions" class="cruiseName"></xui-input></div><div class="auto-point"><span class="after-down">回位点:</span><xui-select v-model="homingPresetId" :options="selectOptions" @change="changePreset" class="preset-select"></xui-select></div><div class="save-or-cancel"><xui-button color="primary" size="mini" style="margin-right: 10px" @click="saveCruise">保存</xui-button><xui-button color="ghost" size="mini" @click="cancelCruise">取消</xui-button></div></div></div></div></div><!--巡航计划结束--></div><iframe scroll="none" src="about:blank" class="video-cloud-check-iframe"></iframe></div>
</template>
javascript
export default {props: {options: {}},mixins: [DialogPollify],data() {return {statusCache:[],showDialog: false,currentTab: "ptz", //可选值ptz,preset,cruiseclassName: "",sliderOptions: {min: 1,max: 15,step: 1,sliderWidth: 220,range: false},speed: 10,equipOptions: {multiple: false,clearable: false,filter: false,placeholder: "请选择",pickerClass: 'equipment-select',data: [{text: "雨刷",value: 17},{text: "灯光",value: 18}]},equip: 17,isOn: true,cameraInfo: null,_index: 0,H5Player: null,presetsList: [],cruisesList: [],isShowCruiseLists: true,isShowAddCruise: false,selectOptions: {placeholder: "选择预置位",multiple: false,clearable: true,filter: false,data: [],pickerClass: 'position-select'},inputOptions: {type: "text",clearable: false},cruiseNameOptions: {type: "text",placeholder: "请输入巡航名称",clearable: true},homingPresetId: null,mockList: [{"presetId": "","internalTime": 5}],cruiseName: "",cruiseId: "",isShowPresetAndCruise: true,playerInstance: {}};},computed: {},methods: {/*** 确定当前摄像机通道和播放窗口*/getPlayWindow(){return{index:this._index,channelNo:this._channel.channelNo}},getImgUrl: function (src) {// return "/vdtimg/" + src;return Toolkits.getUrl(src, 0, false, true, true);},zooImg(url, title) {window.VueExpress.$structImage.show({image: url,name: title//imgBase64: pic});},show(options, index, className, isShowPresetAndCruise) {if (!options || !options.cameraList) {return;}this.showDialog = true;this.playerInstance = options;this.cameraInfo = options.cameraList[index].deviceInfo;this.speed = options.cameraList[index].windowPtzSpeed || 10;//存储控制通道this._channel = Toolkits.getPtzChannel(this.cameraInfo.channelList);this._index = index;//云台状态记录this.statusCache[index]={};this.statusCache[index].status="open";this.H5Player = options.h5PlayerC;//样式this.className = className || "";if (typeof isShowPresetAndCruise === "boolean") {this.isShowPresetAndCruise = isShowPresetAndCruise;}this.currentTab = "ptz";var CAMERA_ptzCtrl = $permission.getPermissionMap()["CAMERA_ptzCtrl"];if (!CAMERA_ptzCtrl) {//不具有云台控制权限console.log("不具有云台控制权限");}},close() {this.showDialog = false;this.className = "";//云台状态记录this.statusCache[this._index].status="close";this.destoryInstance();},//弹出框拖拽事件drag: function (imgId, parentId, isLimitedDrag) {Toolkits.drag(imgId, parentId, isLimitedDrag);},changeTab: function (tab) {this.currentTab = tab;//如果是预置位面板,则需要加载预置位列表if (tab === "preset") {this.showPreset();}//如果是计划巡航面板,则需要加载计划巡航列表if (tab === "cruise") {this.isShowCruiseLists = true;this.isShowAddCruise = false;this.$nextTick(() => {this.showCruise();});}},changeHandle: function (v) {this.speed = v;this.playerInstance.cameraList[this._index].speed = v;this.playerInstance.setPtzSpeed(this._index,v)},changeSwitch: function (v) {this.isOn = v;},changeSelect: function (v) {// if(!this.isOn) return;var self = this;if (!self.cameraInfo || self.cameraInfo.channelList.length == 0) {return;}//组装参数并发送请求var data = {channelNo: this._channel.channelNo,cmd: v,param: this.isOn ? 1 : 0,cameraId: this._channel.cameraId};STORE.setPTZDir(data);},ptzCtrlMouseDown: function (cmd, param) {// if(!this.isOn) return;var self = this;//收集参数if (typeof (param) !== "number") {param = this.speed;}if (!self.cameraInfo || self.cameraInfo.channelList.length == 0) {return;}var data = {channelNo: this._channel.channelNo,cmd: cmd,param: param,cameraId: this._channel.cameraId};STORE.setPTZDir(data);},ptzCtrlMouseUp: function (cmd) {var self = this;if (!self.cameraInfo || self.cameraInfo.channelList.length == 0) {return;}//发送停止命令setTimeout(function () {STORE.setPTZDir({channelNo: self._channel.channelNo,cmd: cmd,param: 0,cameraId: self._channel.cameraId});}, 200);},showPreset: function () {var self = this;if (!self.cameraInfo || self.cameraInfo.channelList.length == 0) {return;}STORE.getPresets(this._channel.channelNo).then((res) => {self.presetsList = res;});},presetUse: function (item) {STORE.callPreset(item.id).then((res) => {if (res) {notify.success("调用预置位成功");} else {notify.error("调用预置位失败");}});},presetDelete: function (item) {// this.$confirm("确定要删除该预置位吗?", "删除提示", {// confirmButtonText: "确定",// cancelButtonText: "取消",// type: "warning"// }).then(() => {// STORE.hasCruiseByPresetId(item.id).then((res) => {// if (res) {// notify.error("该预置位存在于巡航中,请先删除巡航再删除此预置位!");// } else {// STORE.deletePreset(item.id).then((res) => {// if (res) {// notify.success("删除预置位成功!");// this.showPreset();// } else {// notify.error("删除预置位失败!");// }// });// }// });// })$confirm("确定要删除该预置位吗?").then( () => {STORE.hasCruiseByPresetId(item.id).then((res) => {if (res) {notify.error("该预置位存在于巡航中,请先删除巡航再删除此预置位!");} else {STORE.deletePreset(item.id).then((res) => {if (res) {notify.success("删除预置位成功!");this.videoPresetRefresh()this.showPreset();} else {notify.error("删除预置位失败!");}});}});})},addPreset: function () {var self = this;var imgStr = self.H5Player.capturePicture(self._index, "png", 300, 0);var num = Math.floor(Math.random() * 10 + 1);if (self.cameraInfo.channelList.length == 0) {return;}var param = {"cameraId": self.cameraInfo.id,"channelNo": this._channel.channelNo,"imageStream": imgStr,"isRestoration": 0,"name": "我是预置位" + num,"server": {"apsType": 0,"id": this._channel.id,"ip": this._channel.ip,"password": this._channel.password,"port": this._channel.port,"userName": this._channel.userName},"stopTime": 5}STORE.createPreset(param).then((res) => {if (res) {this.showPreset();}});},showCruise: function () {if (!this.cameraInfo || this.cameraInfo.channelList.length == 0) {return;}STORE.getCruiseByCameraId(this.cameraInfo.id).then((res) => {if (res.length > 0) {this.cruisesList = res.map(item=>{item.status=!!item.status;return item});} else {this.cruisesList = [];}});},showAddCruise: function () {var self = this;if (!self.cameraInfo || self.cameraInfo.channelList.length == 0) {return;}self.cruiseId = "";//首先查询预置位列表,填充selectOptions.data数据STORE.getPresets(this._channel.channelNo).then((res) => {self.presetsList = res;self.selectOptions.data = self.presetsList.map((item) => {return {text: item.name,value: item.id}});//切换新增面板self.isShowAddCruise = true;self.isShowCruiseLists = false;//清空数据self.mockList = [];self.homingPresetId = null;self.cruiseName = "";});},addCruiseList: function () {var data = {"presetId": null,"internalTime": 5};this.mockList.push(data);},changePreset: function (v) {this.homingPresetId = v;},saveCruise: function () {var self = this;if (!self.cameraInfo || self.cameraInfo.channelList.length == 0) {return;}let validate = true;this.mockList.map((item, pos) => {item.sortNo = pos;if (!this.validate("internalTime", item.internalTime)) validate = false;});if (!validate) return;if (!self.cruiseName) {notify.warn("请输入巡航名称");return;}if (self.cruiseName.length > 20) {notify.warn("巡航名称不能超过20个字符");return;}var param = {"cameraId": self.cameraInfo.id,"channelNo": this._channel.channelNo,"cprList": self.mockList,"homingPresetId": self.homingPresetId, //回归点的预置位id"id": self.cruiseId ? self.cruiseId : "","name": self.cruiseName,"status": self.cruiseId?+self.status:0};console.log("param", param);//编辑if (self.cruiseId) {STORE.updateCruise(param).then((res) => {if (res) {notify.success("编辑自动巡航成功!");self.cruiseId = "";//切换新增面板self.isShowAddCruise = false;self.isShowCruiseLists = true;self.showCruise();} else {notify.warn("编辑自动巡航失败!");}});} else {STORE.addCruise(param).then((res) => {if (res) {notify.success("新增自动巡航成功!");self.cruiseId = "";//切换新增面板self.isShowAddCruise = false;self.isShowCruiseLists = true;self.showCruise();} else {notify.warn("新增自动巡航失败!");}});}},cancelCruise: function () {//切换新增面板this.isShowAddCruise = false;this.isShowCruiseLists = true;},moveUp: function (index, item) {var pre = this.mockList[index - 1];this.mockList.splice(index - 1, 2, item, pre);},moveDown: function (index, item) {var next = this.mockList[index + 1];this.mockList.splice(index, 2, next, item);},deleteLi: function (index) {this.mockList.splice(index, 1);},validate: function (type, val) {if (type === "internalTime" && !$regexp.isPositiveIntegers(val)) {$tip.warn("停留时间须为正整数");return false;}return true;},goBack: function () {//切换新增面板this.isShowAddCruise = false;this.isShowCruiseLists = true;},editCruise: function (item) {var self = this;if (!self.cameraInfo || self.cameraInfo.channelList.length == 0) {return;}//#97994if(item.status){ notify.warn("正在巡航,请先关闭巡航,然后编辑");return}//debugger//首先查询预置位列表,填充selectOptions.data数据if (self.selectOptions.data.length === 0) {STORE.getPresets(this._channel.channelNo).then((res) => {self.presetsList = res;self.selectOptions.data = self.presetsList.map((item) => {return {text: item.name,value: item.id}});});}self.cruiseName = item.name;self.homingPresetId = item.homingPresetId;self.mockList = IX.clone(item.cprList);self.cruiseId = item.id;self.status = item.status;//切换新增面板self.isShowAddCruise = true;self.isShowCruiseLists = false;},delCruise: function (item) {//没有在巡航if (!item.status) {$confirm("确定要删除该巡航吗?").then(() => {STORE.deleteCruise(item.id).then((res) => {notify.success("删除巡航成功");this.showCruise();});})} else {notify.warn("正在巡航中,清先关闭巡航,然后删除");}},switchCruise: function (item) {var param = {cruiseId: item.id,status: +item.status};STORE.switchOnOffCruise(param).then((res) => {//this.playerInstance.setWindowPtz(this._index, true); //云台notify.success("修改巡航状态成功");// 判断有没有开启的任务,如果有就锁定,没有就不锁let hasTask = this.cruisesList.some(i => i.status);if (hasTask) {STORE.videoCloudLock({cameraId: this.cameraInfo.id}).then(res => {this.H5Player.setWindowPtz(this._index, true); //<E4><BA><91><E5><8F><B0>this.H5Player.showOrHideToolbar(this._index, true, '29');this.H5Player.showOrHideToolbar(this._index, false, '28');})} else {STORE.videoCloudUnlock({cameraId: this.cameraInfo.id}).then(res => {this.H5Player.setWindowPtz(this._index, true); //<E4><BA><91><E5><8F><B0>this.H5Player.showOrHideToolbar(this._index, true, '28');this.H5Player.showOrHideToolbar(this._index, false, '29');})}})}}
};
css
<style lang="less">
@imgPath: "/viua/assets/player";
.position-select{z-index: 1000;
}
.equipment-select{z-index: 1000;
}
#ptz-control.left-bottom {bottom: 0px;left: 0px;
}
#ptz-control.right-bottom {bottom: 0px;right: 0px;
}
#ptz-control.left-top {top: 0px;left: 0px;
}
#ptz-control.right-top {top: 0px;right: 0px;
}
#ptz-control {position: fixed;z-index: 1000;width: 282px;height: 330px;border: solid 1px var(--border-color-gray);background-color: var(--bg-fill-normal);border-radius: 10px;cursor: move;bottom: 25%;left: 50%;.video-cloud-check-content, .video-cloud-check-iframe {width: 100%;height: 100%;position: absolute;}.video-cloud-check-content {z-index: 1;}.video-cloud-check-iframe {z-index: 0;}.win-dialog-title {font-family: "Microsoft YaHei" !important;height: 43px;line-height: 43px;padding-left: 10px;padding-top: 0px;padding-right: 3px;font-size: 13px;.close {float: right;margin-right: 10px;cursor: pointer;}.ptztab {display: inline-block;text-align: center;padding-left: 10px;padding-right: 10px;*display: inline;zoom: 1;*width: 60px;}& .ptztab:hover {border-bottom: solid 2px var(--prod-normal-color);color: var(--prod-normal-color);cursor: default;}& .ptztab.active {border-bottom: solid 2px var(--prod-normal-color);color: var(--prod-normal-color);}.vline {width: 2px;height: 18px;border-left: solid 1px var(--border-color-gray);border-right: solid 1px var(--border-color-gray);}}.win-dialog-neck {font-size: 0;height: 1px;line-height: 1px;border-top: solid 1px var(--border-color-gray);border-bottom: solid 1px var(--border-color-gray);}.ptz {.dir-control {display: block;width: auto;height: 134px;background: url("@{imgPath}/ptz-disable.png") no-repeat 0 0;margin-top: 30px;margin-left: 20px;position: relative;border: solid 0 red;*width: 240px;span {display: inline-block;background: url("@{imgPath}/ptz-normal.png") no-repeat 0 0;position: absolute;width: 15px;height: 15px;border: solid 0 red;cursor: pointer;&:hover {background: url("@{imgPath}/ptz-hover.png") no-repeat 0 0;}&:active {background: url("@{imgPath}/ptz-active.png") no-repeat 0 0;}&.dir.up {left: 59px;top: 21px;background-position: -59px -21px;}&.dir.leftup {left: 35px;top: 36px;background-position: -35px -36px;}&.dir.left {left: 23px;top: 61px;background-position: -23px -61px;}&.dir.leftdown {left: 35px;top: 87px;background-position: -35px -87px;}&.dir.down {left: 60px;top: 101px;background-position: -60px -101px;}&.dir.rightdown {left: 86px;top: 87px;background-position: -86px -87px;}&.dir.right {left: 101px;top: 61px;background-position: -101px -61px;}&.dir.rightup {left: 86px;top: 36px;background-position: -86px -36px;}&.zoom.up.adjust {left: 152px;top: 45px;background-position: -152px -45px;}&.zoom.down.adjust {left: 152px;top: 76px;background-position: -152px -76px;}&.focus.up.adjust {left: 183px;top: 44px;background-position: -183px -44px;}&.focus.down.adjust {left: 183px;top: 75px;background-position: -183px -75px;}&.iris.up.adjust {left: 214px;top: 44px;background-position: -214px -44px;}&.iris.down.adjust {left: 214px;top: 75px;background-position: -214px -75px;}}}.ptz-speed {margin-left: 25px;margin-top: 10px;.control-speed {color: var(--part-font-color);font-size: 12px;}}.equipment {margin-top: 10px;.choose {display: inline-block;width: 100px;color: var(--part-font-color);font-size: 12px;}.switch {display: inline-block;width: 130px;height: 18px;text-align: center;margin-left: 20px;position: relative;top: -10px;label {color: var(--part-font-color);}.switch-input {position: relative;top: -2px;left: -5px;background: var(--switch-normal-track-bg-color);&:checked {background: var(--switch-normal-track-bg-color);}}}}}.preset {.head {height: 32px;line-height: 32px;text-align: center;*width: 260px;*padding-left: 0;*text-align: left;*font-size: 0;span {display: inline-block;color: var(--part-font-color);text-align: center;*display: inline;zoom: 1;*font-size: 12px;&.preset-index {width: 40px;}&.preset-img {width: 64px;}&.preset-name {width: 90px;text-align: left;*text-align: center;}&.preset-do {width: 50px;text-align: left;*text-align: right;padding-left: 0;}}}.preset-content {height: 235px;border-top: solid 1px var(--border-color-gray);ul {margin: 10px 0 0 0;padding: 0;border: solid 0 red;li {height: 60px;line-height: 60px;border-bottom: solid 1px var(--border-color-gray);white-space: nowrap;span {vertical-align: middle;display: inline-block;border: solid 0 red;text-align: center;&.preset-index {width: 40px;color: var(--part-font-color);}&.preset-img {width: 64px;height: 50px;position: relative;top: 5px;}&.preset-img img {vertical-align: middle;padding: 0;margin: -27px 0 0 0;width: 64px;height: 50px;}&.preset-name {width: 80px;max-width: 80px;margin: 0px 5px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}&.preset-do {width: 50px;}&.preset-use {width: 18px;height: 18px;background: url("@{imgPath}/preset-use.png")no-repeat 0 0;cursor: pointer;}&.preset-use:hover {background-position: -22px 0;}&.preset-delete {width: 18px;height: 18px;background: url("@{imgPath}/preset-delete.png")no-repeat 0 0;cursor: pointer;}&.preset-delete:hover {background-position: -22px 0;}}}}}}.cruise {height: calc(~"100% - 45px");text-align: center;.auto-content {height: calc(~"100% - 30px");.auto-title {display: inline-block;width: 90%;height: 30px;line-height: 30px;white-space: nowrap;border-bottom: 1px solid var(--border-color-gray);color: var(--part-font-color);span {display: inline-block;white-space: nowrap;}.l-index {width: 25px;}.l-name {width: 150px;}.l-controller {width: 85px;}}.show-cruise-list {height: 220px;width: 90%;margin: auto;.auto-list {line-height: 30px;border-bottom: 1px solid var(--border-color-gray);table-layout: fixed;display: table;span {display: table-cell;}.l-index {width: 25px;}.l-name {width: 130px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;display: inline-block;vertical-align: middle;}.l-controller {width: 85px;.common-icon {cursor: pointer;display: inline-block;}.edit {position: relative;top: 3px;height: 16px;width: 18px;margin-left: 5px;margin-right: 5px;background: url("@{imgPath}/edit.png") no-repeatscroll 0 center;&:hover {margin-right: 5px;background: url("@{imgPath}/edit.png") no-repeatscroll -22px center;}}.del {position: relative;top: 3px;height: 16px;width: 14px;background: url("@{imgPath}/del.png") no-repeatscroll 0 center;&:hover {background: url("@{imgPath}/del.png") no-repeatscroll -22px center;}}}}}.no-data {display: inline-block;margin-top: 115px;color: var(--part-font-color);}}.add-content {height: 100%;.n-title {height: 30px;line-height: 30px;border-bottom: 1px solid var(--border-color-gray);.add-new {cursor: pointer;background: url("@{imgPath}/add.png") no-repeat 0 0;display: inline-block;height: 16px;width: 16px;margin-left: 205px;margin-top: 7px;&:hover {background: url("@{imgPath}/add.png") no-repeat scroll -21pxcenter;}}.go-back {cursor: pointer;color:var(--prod-normal-color);font-weight: bold;background: url("@{imgPath}/back.png") no-repeat 0px 10px;padding-left: 10px;margin-left: 10px;float: left;}}.n-content {border-top: 1px solid var(--border-color-gray);.auto-title {display: inline-block;height: 30px;line-height: 30px;white-space: nowrap;border-bottom: 1px solid var(--border-color-gray);width: 93%;margin: 0 10px;span {display: inline-block;white-space: nowrap;text-align: center;}.l-index {width: 40%;}.l-name {width: 40%;}.l-controller {width: 20%;}}.auto-ul {height: 105px;.auto-list {display: flex;align-items: center;justify-content: space-around;border-bottom: 1px solid var(--border-color-gray);padding: 2px 0;}.pre-name {display: inline-block;flex: 2;text-align: center;.preset-select {width: 100px;max-width: 100px;background-color: var(--bg-fill-normal);}.xui-select-inputwrap {line-height: normal;}.xui-single-text {position: relative;top: 4px;max-width: 64px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}.select-trigger-icon {top: 4px;}}.pre-time {display: inline-block;flex: 1;.xui-input-style {width: 40px;}}.pre-controller {display: inline-block;flex: 1;.common-icon {cursor: pointer;display: inline-block;margin-left: 4px;}.up {height: 16px;width: 14px;background: url("@{imgPath}/up.png") no-repeatscroll 0 center;&:hover {background: url("@{imgPath}/up.png") no-repeatscroll -14px center;}}.down {height: 16px;width: 14px;background: url("@{imgPath}/down.png") no-repeatscroll 0 center;&:hover {background: url("@{imgPath}/down.png") no-repeatscroll -14px center;}}.del {height: 16px;width: 14px;background: url("@{imgPath}/del.png") no-repeatscroll 0 center;&:hover {background: url("@{imgPath}/del.png") no-repeatscroll -22px center;}}}}.new-cruise-info {padding-top: 10px;border-top: 1px solid var(--border-color-gray);.title-name, .auto-point {padding-right: 10px;display: flex;align-items: center;span{flex: 1;}div{flex: 3;}}.title-name {.cruise-title {&:before{content: "*";color: rgb(255, 0, 0);padding: 0px 2px;}}}.auto-point {margin-top: 5px;.preset-select {background-color: var(--bg-fill-normal);text-align: left;}}.save-or-cancel {position:absolute;bottom:5px;width: 100%;display: flex;justify-content: center;}.xui-select-inputwrap{line-height: normal;display: inline-flex;align-items: center;}.select-trigger-icon{top: 5px;}.xui-single-text {max-width: 165px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}}}}}
}
</style>
sip信令交互
package http_apiimport ("github.com/gin-gonic/gin""sipserver-rebuild/pkg/constant""sipserver-rebuild/pkg/constant/model""sipserver-rebuild/pkg/logUtil""sipserver-rebuild/pkg/sipapi""strconv"
)// @Summary 设备保活心跳设置
// @Description 用来获取下级设备或下级平台的设备目录列表,会包含地理位置等。
// @Tags records
// @Accept x-www-form-urlencoded
// @Produce json
// @Param id path string true "通道id"
// @Param ip query string true "通道ip"
// @Param port query string true "通道port"
// @Success 0 {object} sipapi.Catalogs
// @Failure 1000 {object} string
// @Failure 1001 {object} string
// @Failure 1002 {object} string
// @Failure 1003 {object} string
// @Router /channels/{id}/records [get]
func HolderControl(c *gin.Context) {logUtil.Log.Info("")channelid := c.Query("gbid")ip := c.Query("ip")port := c.Query("port")channelNum := c.Query("channelNum")order := c.Query("order")num := c.Query("num")speed := c.Query("speed")userName := c.Query("userId")controlPriority := c.Query("controlPriority")uuid := c.Query("uuid") // 同一会话日志唯一标识traceId := c.Query("traceId")var numcmd uint64var speedcmd uint64if order == "" {constant.JsonResponse(c, constant.OrderNil, "order不能为空")return}ordercmd, err := strconv.ParseUint(order, 16, 8)if err != nil || ordercmd < 0 {constant.JsonResponse(c, constant.OrderParamFail, "order错误")return}if userName == "" {constant.JsonResponse(c, constant.OrderUserNil, "用户名不能为空")return}if controlPriority == "" {constant.JsonResponse(c, constant.OrderControlPriorityNil, "控制权限不能为空")return}speedcmd, err = strconv.ParseUint(speed, 16, 8)if speedcmd < 0 {constant.JsonResponse(c, constant.OrderSpeedNil, "巡航速度错误")return}numcmd, err = strconv.ParseUint(num, 10, 8)if numcmd < 0 {constant.JsonResponse(c, constant.OrderNumNil, "巡航速度错误")return}var device model.HolderControlQueryif channelNum != "" {cn, e := strconv.Atoi(channelNum)if e != nil {logUtil.Log.Error("channelNum is error :" + e.Error())} else {device.ChannelNum = cn}}device.Channel.ChannelID = channeliddevice.Channel.Ip = ipdevice.Channel.Port = portdevice.Order = ordercmddevice.Num = numcmddevice.Speed = speedcmddevice.UserName = userNamedevice.UUID = uuiddevice.ControlPriority = controlPrioritydevice.IsCross = "N"device.Cmd = orderres, err := sipapi.SipHolderControl(&device, traceId)if err != nil {constant.JsonResponse(c, constant.HoldControlERR, err)return}if res.Code == 1005 {constant.JsonResponse(c, constant.HoldControlERR, res)return}constant.JsonResponse(c, res.Code, res)
}
4.要求信令网络传视质量
联网系统IP网络的传输质量(如传输时延、包丢失率、包误差率、虚假包率等)应符合如下要求
a)网络时延上限值为400ms;
b)时延抖动上限值为50ms;
丢包率上限值为1x10-3;。)
d)包误差率上限值为1x10-
5.6视频帧率