多数安卓手机摄像头是支持调焦距的, GB28181标准定义了云台操作, 只要安卓端实现云台XML指令解析, 实时视频回看时远程控制安卓摄像头焦距调整是一个很实用的功能。
GB28181云台控制属于设备控制的一部分,使用SIP Message方法实现,走无应答命令流程。和云台相关的还有预置位查询,要走有应答命令流程。
云台控制和其他设备控制的XML定义差不多, 下面是一个具体的实例:
<?xml version="1.0" encoding="GB2312"?>
<Control>
<CmdType>DeviceControl</CmdType>
<SN>32211</SN>
<DeviceID>31010000011380000013</DeviceID>
<PTZCmd>A50F0100000000B5</PTZCmd>
</Control>
XML中最关键的是PTZCmd元素, PTZCmd在GB28181-2016中有详细定义,总共8个字节,定义格式如下:
字节 | 字节1 | 字节2 | 字节3 | 字节4 | 字节5 | 字节6 | 字节7 | 字节8 |
含义 | A5H | 组合码1 | 地址 | 指令 | 数据1 | 数据2 | 组合码2 | 校验码 |
8个字节中,最重要的字节4(指令码), 定义了云台各种操作类型。字节5, 6, 7主要用来传递指令相关参数。
字节1是首字节,固定为A5H。
针对PTZ, 字节4的第7和第6位都是0, 第5和第4位用来镜头调焦距, 第3和第2位是云台垂直方向控制, 第1和第0位是云台水平方向控制。解析时需要注意的是镜头焦距调整指令, 云台垂直上下指令、云台水平左右指令三者可以组合, 可能一条控制消息要执行三种类型的云台控制, 实际实现中要处理好。
实际云台操作中,会先发一个云台控制SIP Message, 紧接着再发一个云台停止指令SIP Message,云台停止指令中字节4的每一位都为0。
更详细定义请参考28181-2016文档A.3,指令解析实现还是比较容易的。
预置位查询属于网络设备信息查询的一部分,也使用SIP Message方法实现, 和云台控制不同的是,需要有响应消息, 下面给出一个查询和响应的具体XML实例:
<?xml version="1.0" encoding="GB2312"?>
<Query>
<CmdType>PresetQuery</CmdType>
<SN>7733</SN>
<DeviceID>31010000011380000021</DeviceID>
</Query>
<?xml version="1.0" encoding="GB2312"?>
<Response>
<CmdType>PresetQuery</CmdType>
<SN>7733</SN>
<DeviceID>31010000011380000021</DeviceID>
<PresetList Num="2">
<Item>
<PresetID>1</PresetID>
<PresetName>安卓预置位1</PresetName>
</Item>
<Item>
<PresetID>2</PresetID>
<PresetName>安卓预置位2</PresetName>
</Item>
</PresetList>
</Response>
定义接口和Demo代码:
// Github: https://github.com/daniulive/SmarterStreaming
// Copyright (C) 1130758427@qq.com
package com.gb.ntsignalling;
public interface GBSIPAgentDeviceControlListener {
/*
* 收到远程启动控制命令
*/
void ntsOnDeviceControlTeleBootCommand(String deviceId, String teleBootValue);
/*
* 云台控制
*/
void ntsOnDeviceControlPTZCmd(String deviceId, String typeValue);
}
package com.gb.ntsignalling;
public interface GBSIPAgentQueryCommandListener {
/*
* 设备预置位查询
*/
void ntsOnDevicePresetQueryCommand(String fromUserName, String fromUserNameAtDomain, String sn, String deviceId);
}
package com.gb.ntsignalling;
public interface GBSIPAgent {
void addDeviceControlListener(GBSIPAgentDeviceControlListener deviceControlListener);
void addQueryCommandListener(GBSIPAgentQueryCommandListener queryCommandListener);
/*
预置位查询应答
*/
boolean respondDevicePresetQueryCommand(String fromUserName, String fromUserNameAtDomain, String sn, String deviceId, java.util.List<PresetItem> presetList);
}
public class MyAndroidG8181DemoImpl implements GBSIPAgentDeviceControlListener,
GBSIPAgentQueryCommandListener {
@Override
public void ntsOnDeviceControlPTZCmd(String deviceId, String typeValue) {
handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "ntsOnDeviceControlPTZCmd device_id:" + device_id_ + " PTZType:" + ptz_type_);
if (null == ptz_type_)
return;
ptz_type_ = ptz_type_.trim();
if (ptz_type_.length() != 16)
return;
int instruction = hexStringToInt(ptz_type_.substring(6, 8));
int combination_code2 = hexStringToInt(ptz_type_.substring(12, 14));
if ((instruction & 0xc0) == 0 ) {
if ((instruction & 0x20) != 0) {
// Zoom Out
int zoom_speed = (combination_code2 >> 4) & 0xf;
Log.i(TAG, "ntsOnDeviceControlPTZCmd device_id:" + device_id_ + " PTZType:" + ptz_type_ + " Zoom Out, speed:" +zoom_speed);
cameraZoom(true, zoom_speed);
}
else if ((instruction & 0x10) != 0) {
// Zoom In
int zoom_speed = (combination_code2 >> 4) & 0xf;
Log.i(TAG, "ntsOnDeviceControlPTZCmd device_id:" + device_id_ + " PTZType:" + ptz_type_ + " Zoom In, speed:" +zoom_speed);
cameraZoom(false, zoom_speed);
}
}
}
int hexStringToInt(String hex) {
if (null == hex)
return 0;
try
{
int v = Integer.parseInt(hex, 16);
return v;
}catch (Exception e) {
return 0;
}
}
private String device_id_;
private String ptz_type_;
public Runnable set(String device_id, String ptz_type) {
this.device_id_ = device_id;
this.ptz_type_ = ptz_type;
return this;
}
}.set(deviceId, typeValue),0);
}
@Override
public void ntsOnDevicePresetQueryCommand(String fromUserName, String fromUserNameAtDomain, String sn, String deviceId) {
handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "ntsOnDevicePresetQueryCommand from_user_name:" + from_user_name_ + ", sn:" + sn_ + ", device_id:" + device_id_);
List<com.gb.ntsignalling.PresetItem> preset_list = new LinkedList<>();
preset_list.add(new com.gb.ntsignalling.PresetItem("1", "安卓预置位1"));
preset_list.add(new com.gb.ntsignalling.PresetItem("2", "安卓预置位2"));
if (gb28181_agent_ != null )
gb28181_agent_.respondDevicePresetQueryCommand(this.from_user_name_, this.from_user_name_at_domain_, this.sn_, this.device_id_, preset_list);
}
private String from_user_name_;
private String from_user_name_at_domain_;
private String sn_;
private String device_id_;
public Runnable set(String from_user_name, String from_user_name_at_domain,String sn, String device_id) {
this.from_user_name_ = from_user_name;
this.from_user_name_at_domain_ = from_user_name_at_domain;
this.sn_ = sn;
this.device_id_ = device_id;
return this;
}
}.set(fromUserName, fromUserNameAtDomain, sn, deviceId),0);
}
}
上面的代码我只实现了对焦距控制的指令解析,如果想处理其他云台指令,解析代码类似,不难实现,测试安卓机前后摄像头都支持焦距调节,实际测下来,效果还可以。更多问题可以联系qq: 1130758427