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,cruise
className: "",
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;
}
//#97994
if(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-repeat
scroll 0 center;
&:hover {
margin-right: 5px;
background: url("@{imgPath}/edit.png") no-repeat
scroll -22px center;
}
}
.del {
position: relative;
top: 3px;
height: 16px;
width: 14px;
background: url("@{imgPath}/del.png") no-repeat
scroll 0 center;
&:hover {
background: url("@{imgPath}/del.png") no-repeat
scroll -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 -21px
center;
}
}
.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-repeat
scroll 0 center;
&:hover {
background: url("@{imgPath}/up.png") no-repeat
scroll -14px center;
}
}
.down {
height: 16px;
width: 14px;
background: url("@{imgPath}/down.png") no-repeat
scroll 0 center;
&:hover {
background: url("@{imgPath}/down.png") no-repeat
scroll -14px center;
}
}
.del {
height: 16px;
width: 14px;
background: url("@{imgPath}/del.png") no-repeat
scroll 0 center;
&:hover {
background: url("@{imgPath}/del.png") no-repeat
scroll -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_api
import (
"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 uint64
var speedcmd uint64
if 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.HolderControlQuery
if channelNum != "" {
cn, e := strconv.Atoi(channelNum)
if e != nil {
logUtil.Log.Error("channelNum is error :" + e.Error())
} else {
device.ChannelNum = cn
}
}
device.Channel.ChannelID = channelid
device.Channel.Ip = ip
device.Channel.Port = port
device.Order = ordercmd
device.Num = numcmd
device.Speed = speedcmd
device.UserName = userName
device.UUID = uuid
device.ControlPriority = controlPriority
device.IsCross = "N"
device.Cmd = order
res, 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视频帧率
5.相关调用流程


389

被折叠的 条评论
为什么被折叠?



