之前帮一个算法朋友弄的 ONVIF PTZ控制海康云台相机代码在无人车上的应用.
主要是鉴权没弄过,折腾onvif的人肯定会碰到,所以特别记录一下.
开始本来是在X64的WIN32或ubuntu工控机平台弄海康摄像头控制的,因为有SDK的直接支持.
结果无人车主控选的是NVIDIA XAIVER arm下控制云台,这就想起了用ONVIF了.
ONVIF无非就是用gsoap+rtsp来折腾的 就把gsoap库先下载和onvif官方下载对应的文件wsdl接口.如果是搞过webservice的活应该有所了解, gsoap 我不多说 百度一下应该好多讲的只管下载.
版本没有太大的要求, 下面文件有些我可能删除了. 我保留主要文件 .
onvif标准接口也是下载 一堆wsdl文件 PTZ在下面
主要作用类的封装:
class OnvifSDK
{
public:
std::string MediaAddr;
std::string PTZAddr;
public:
static struct soap* NewSoap(const char* ip);
OnvifSDK(struct soap* soap_, const char* username, const char* password);
virtual ~OnvifSDK();
bool GetDeviceInfo(char* url);
bool GetCapabilities(char* url);
bool GetProfiles();
std::string GetUri(enum tt__TransportProtocol transtype);
bool GetStstus(float* pvalue, float* tvalue, float* zvalue);
//连续控制
void ControlPTZ(int control, bool control_type, int speed);
//绝对控制
void SetPTZ(float pvalue, float tvalue, float zvalue);
//相对控制
void SetMoveStep(ptz_common commond, float move_step);
protected:
#ifdef WITH_OPENSSL
#else
void calc_nonce(struct soap* soap, char nonce[SOAP_WSSE_NONCELEN]);
struct _wsse__Security* soap_wsse_add_Security(struct soap* soap);
int soap_wsse_add_UsernameTokenText(struct soap* soap, const char* id, const char* username, const char* password);
int soap_wsse_add_UsernameTokenDigest(struct soap* soap, const char* id, const char* username, const char* password);
void calc_digest(struct soap* soap, const char* created,const char* nonce, int noncelen, const char* password, char hash[SOAP_SMD_SHA1_SIZE]);
void TokenTimestmap(struct soap* pSoap, time_t lifetime = TOKEN_TIMESTAMP);
int soap_wsse_add_Timestamp(struct soap* soap, const char* id, time_t lifetime);
#endif
private:
std::string UserName;
std::string PassWord;
std::string Band;
enum xsd__boolean TrueFlag;
struct tt__PTZSpeed PTZSpeed;
struct tt__Vector2D PTZVectorPT;
struct tt__Vector1D PTZVectorZ;
struct tt__PTZVector VectorABSPTZ;
struct soap* soap;
struct _tds__GetCapabilities capa_req;
struct _tds__GetCapabilitiesResponse capa_resp;
struct _trt__GetProfiles trt__GetProfiles;
struct _trt__GetProfilesResponse trt__GetProfilesResponse;
struct _trt__GetStreamUri trt__GetStreamUri;
struct _trt__GetStreamUriResponse trt__GetStreamUriResponse;
struct _tptz__GetStatus tptz__GetStatus;
struct _tptz__GetStatusResponse tptz__GetStatusResponse;
struct _tptz__ContinuousMove tptz__ContinuousMove;
struct _tptz__ContinuousMoveResponse tptz__ContinuousMoveResponse;
struct _tptz__Stop tptz__Stop;
struct _tptz__StopResponse tptz__StopResponse;
struct _tds__GetDeviceInformation tds__GetDeviceInformation;
struct _tds__GetDeviceInformationResponse tds__GetDeviceInformationResponse;
struct _tptz__AbsoluteMove tptz__AbsoluteMove;
struct _tptz__AbsoluteMoveResponse tptz__AbsoluteMoveResponse;
struct _tptz__RelativeMove tptz__RelativeMove;
struct _tptz__RelativeMoveResponse tptz__RelativeMoveResponse;
};
实现部分(ptz控制方法):
bool OnvifSDK::GetDeviceInfo(char* url)
{
soap_wsse_add_UsernameTokenDigest(soap, "", UserName.c_str(), PassWord.c_str());
int ret = soap_call___tds__GetDeviceInformation(soap, url, NULL, &tds__GetDeviceInformation, &tds__GetDeviceInformationResponse);
if (ret != SOAP_OK)
return false;
if (soap->error)
return false;
else
{
Band = std::string(tds__GetDeviceInformationResponse.Manufacturer);
return true;
}
}
bool OnvifSDK::GetCapabilities(char* url)
{
capa_req.__sizeCategory = 1;
capa_req.Category = (enum tt__CapabilityCategory*)malloc(sizeof(enum tt__CapabilityCategory));
enum tt__CapabilityCategory temp_category = tt__CapabilityCategory__All;
capa_req.Category = &temp_category;
capa_resp.Capabilities = (struct tt__Capabilities*)malloc(sizeof(struct tt__Capabilities)) ;
soap_wsse_add_UsernameTokenDigest(soap, "", UserName.c_str(), PassWord.c_str());
int result = soap_call___tds__GetCapabilities(soap, url, NULL, &capa_req, &capa_resp);
if (soap->error)
{
return false;
}
else
{
if (capa_resp.Capabilities == NULL)
{
printf(" GetCapabilities failed! result=%d \n", result);
return false;
}
else
{
printf(" Media->XAddr=%s \n", capa_resp.Capabilities->Media->XAddr);
printf(" Media->PTZ=%s \n", capa_resp.Capabilities->PTZ->XAddr);
return true;
}
}
}
bool OnvifSDK::GetProfiles()
{
soap_wsse_add_UsernameTokenDigest(soap, "", UserName.c_str(), PassWord.c_str());
if (soap_call___trt__GetProfiles(soap, capa_resp.Capabilities->Media->XAddr, NULL, &trt__GetProfiles, &trt__GetProfilesResponse) == -1)
{
return false;
}
else
{
if (trt__GetProfilesResponse.Profiles != NULL)
{
if(trt__GetProfilesResponse.Profiles->Name!=NULL)
{
printf("Profiles Name:%s \n", trt__GetProfilesResponse.Profiles->Name);
}
if (trt__GetProfilesResponse.Profiles->token != NULL)
{
trt__GetStreamUri.ProfileToken = trt__GetProfilesResponse.Profiles->token;
tptz__GetStatus.ProfileToken = trt__GetProfilesResponse.Profiles->token;
tptz__ContinuousMove.ProfileToken = trt__GetProfilesResponse.Profiles->token;
tptz__Stop.ProfileToken = trt__GetProfilesResponse.Profiles->token;
tptz__AbsoluteMove.ProfileToken = trt__GetProfilesResponse.Profiles->token;
tptz__RelativeMove.ProfileToken = trt__GetProfilesResponse.Profiles->token;
return true;
}
else
return false;
}
else
return false;
}
}
std::string OnvifSDK::GetUri(enum tt__TransportProtocol transtype)
{
trt__GetStreamUri.StreamSetup = (struct tt__StreamSetup*)soap_malloc(soap, sizeof(struct tt__StreamSetup));
trt__GetStreamUri.StreamSetup->Stream = tt__StreamType__RTP_Unicast;//stream type
trt__GetStreamUri.StreamSetup->Transport = (struct tt__Transport*)soap_malloc(soap, sizeof(struct tt__Transport));
trt__GetStreamUri.StreamSetup->Transport->Protocol = transtype;
trt__GetStreamUri.StreamSetup->Transport->Tunnel = 0;
soap_wsse_add_UsernameTokenDigest(soap, "", UserName.c_str(), PassWord.c_str());
soap_call___trt__GetStreamUri(soap, capa_resp.Capabilities->Media->XAddr, NULL, &trt__GetStreamUri, &trt__GetStreamUriResponse);
if (soap->error)
return "";
else
return std::string(trt__GetStreamUriResponse.MediaUri->Uri);
}
bool OnvifSDK::GetStstus(float* pvalue, float* tvalue, float* zvalue)
{
soap_wsse_add_UsernameTokenDigest(soap, "", UserName.c_str(), PassWord.c_str());
soap_call___tptz__GetStatus(soap
, capa_resp.Capabilities->PTZ->XAddr
, NULL
, &tptz__GetStatus
, &tptz__GetStatusResponse);
if (soap->error)
return false;
else
{
*pvalue = tptz__GetStatusResponse.PTZStatus->Position->PanTilt->x;
*tvalue = tptz__GetStatusResponse.PTZStatus->Position->PanTilt->y;
*zvalue = tptz__GetStatusResponse.PTZStatus->Position->Zoom->x;
return true;
}
}
void OnvifSDK::ControlPTZ(int control, bool control_type, int speed)
{
memset(&tptz__ContinuousMove, 0x0, sizeof(tptz__ContinuousMove));
memset(&tptz__ContinuousMoveResponse, 0x0, sizeof(tptz__ContinuousMoveResponse));
tptz__ContinuousMove.ProfileToken = trt__GetProfilesResponse.Profiles->token;
tptz__ContinuousMove.Velocity = (struct tt__PTZSpeed*)soap_malloc(soap, sizeof(tt__PTZSpeed));
memset(tptz__ContinuousMove.Velocity, 0x0, sizeof(tt__PTZSpeed));
tptz__ContinuousMove.Velocity->PanTilt = (struct tt__Vector2D*)soap_malloc(soap, sizeof(tt__Vector2D));
memset(tptz__ContinuousMove.Velocity->PanTilt, 0x0, sizeof(tt__Vector2D));
tptz__ContinuousMove.Velocity->Zoom = (struct tt__Vector1D*)soap_malloc(soap, sizeof(tt__Vector1D));
memset(tptz__ContinuousMove.Velocity->Zoom, 0x0, sizeof(tt__Vector1D));
tptz__ContinuousMove.Velocity->PanTilt->x = 0;
tptz__ContinuousMove.Velocity->PanTilt->y = 0;
tptz__ContinuousMove.Velocity->Zoom->x = 0;
soap_wsse_add_UsernameTokenDigest(soap, "", UserName.c_str(), PassWord.c_str());
if (control_type)
{
switch (control)
{
case 0:
tptz__ContinuousMove.Velocity->PanTilt->y = (float)speed / 10;
break;
case 1:
tptz__ContinuousMove.Velocity->PanTilt->y = (float)speed / 10 * -1.0;
break;
case 2:
tptz__ContinuousMove.Velocity->PanTilt->x = (float)speed / 10 * -1.0;
break;
case 3:
tptz__ContinuousMove.Velocity->PanTilt->x = (float)speed / 10;
break;
case 4:
tptz__ContinuousMove.Velocity->PanTilt->y = (float)speed / 10;
tptz__ContinuousMove.Velocity->PanTilt->x = (float)speed / 10 * -1.0;
break;
case 5:
tptz__ContinuousMove.Velocity->PanTilt->y = (float)speed / 10;
tptz__ContinuousMove.Velocity->PanTilt->x = (float)speed / 10;
break;
case 6:
tptz__ContinuousMove.Velocity->PanTilt->y = (float)speed / 10 * -1.0;
tptz__ContinuousMove.Velocity->PanTilt->x = (float)speed / 10 * -1.0;
break;
case 7:
tptz__ContinuousMove.Velocity->PanTilt->y = (float)speed / 10 * -1.0;
tptz__ContinuousMove.Velocity->PanTilt->x = (float)speed / 10;
break;
case 8:
tptz__ContinuousMove.Velocity->Zoom->x = (float)speed / 10;
break;
case 9:
tptz__ContinuousMove.Velocity->Zoom->x = (float)speed / 10 * -1.0;
break;
default:
return;
}
int ret = soap_call___tptz__ContinuousMove(soap, capa_resp.Capabilities->PTZ->XAddr, NULL, &tptz__ContinuousMove, &tptz__ContinuousMoveResponse);
if (SOAP_OK == ret)
{
printf("RelativeMove----OK\n");
}
}
else
{
TrueFlag = xsd__boolean__true_;
tptz__Stop.PanTilt = &TrueFlag;
tptz__Stop.Zoom = &TrueFlag;
soap_call___tptz__Stop(soap, capa_resp.Capabilities->PTZ->XAddr, NULL, &tptz__Stop, &tptz__StopResponse);
}
}
void OnvifSDK::SetPTZ(float pvalue, float tvalue, float zvalue)
{
tptz__AbsoluteMove.Position->PanTilt->x = pvalue;
tptz__AbsoluteMove.Position->PanTilt->y = tvalue;
tptz__AbsoluteMove.Position->Zoom->x = zvalue;
soap_wsse_add_UsernameTokenDigest(soap, "", UserName.c_str(), PassWord.c_str());
int ret = soap_call___tptz__AbsoluteMove(soap, capa_resp.Capabilities->PTZ->XAddr, NULL, &tptz__AbsoluteMove, &tptz__AbsoluteMoveResponse);
}
void OnvifSDK::SetMoveStep(ptz_common commond, float move_step)
{
memset(&tptz__RelativeMove, 0x0, sizeof(tptz__RelativeMove));
memset(&tptz__RelativeMoveResponse, 0x0, sizeof(tptz__RelativeMoveResponse));
// printf("__sizeProfiles: %d\n", reponse_pro.__sizeProfiles);
// printf("token: %s\n", reponse_pro.Profiles->token);
tptz__RelativeMove.ProfileToken = trt__GetProfilesResponse.Profiles->token;
tptz__RelativeMove.Translation = (struct tt__PTZVector*)soap_malloc(soap, sizeof(tt__PTZVector));
memset(tptz__RelativeMove.Translation, 0x0, sizeof(tt__PTZVector));
tptz__RelativeMove.Translation->PanTilt = (struct tt__Vector2D*)soap_malloc(soap, sizeof(tt__Vector2D));
memset(tptz__RelativeMove.Translation->PanTilt, 0x0, sizeof(tt__Vector2D));
tptz__RelativeMove.Translation->Zoom = (struct tt__Vector1D*)soap_malloc(soap, sizeof(tt__Vector1D));
memset(tptz__RelativeMove.Translation->Zoom, 0x0, sizeof(tt__Vector1D));
if (move_step >= 1)
move_step = 0.99999;
switch (commond)
{
case PTZ_LEFT:
tptz__RelativeMove.Translation->PanTilt->x = move_step;
break;
case PTZ_RIGHT:
tptz__RelativeMove.Translation->PanTilt->x = -move_step;
break;
case PTZ_UP:
tptz__RelativeMove.Translation->PanTilt->y = -move_step;
break;
case PTZ_DOWN:
tptz__RelativeMove.Translation->PanTilt->y = move_step;
break;
case PTZ_LEFTUP:
tptz__RelativeMove.Translation->PanTilt->x = move_step;
tptz__RelativeMove.Translation->PanTilt->y = -move_step;
break;
case PTZ_LEFTDOWN:
tptz__RelativeMove.Translation->PanTilt->x = move_step;
tptz__RelativeMove.Translation->PanTilt->y = move_step;
break;
case PTZ_RIGHTUP:
tptz__RelativeMove.Translation->PanTilt->x = -move_step;
tptz__RelativeMove.Translation->PanTilt->y = -move_step;
break;
case PTZ_RIGHTDOWN:
tptz__RelativeMove.Translation->PanTilt->x = -move_step;
tptz__RelativeMove.Translation->PanTilt->y = move_step;
break;
case PTZ_ZOOMIN:
tptz__RelativeMove.Translation->Zoom->x = move_step;
break;
case PTZ_ZOOMOUT:
tptz__RelativeMove.Translation->Zoom->x = -move_step;
break;
default:
soap_destroy(soap);
soap_end(soap);
soap_free(soap);
return ;
break;
}
soap_wsse_add_UsernameTokenDigest(soap, "", UserName.c_str(), PassWord.c_str());
int result = soap_call___tptz__RelativeMove(soap, capa_resp.Capabilities->PTZ->XAddr, NULL, &tptz__RelativeMove, &tptz__RelativeMoveResponse);
if (SOAP_OK == result)
printf("RelativeMove----OK\n");
else
{
printf("RelativeMove----faild\n");
//printf("soap error: %d--%d, %s, %s\n", __LINE__, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
}
}
鉴权是要用到openssl库的,不过嵌入式下集成一个大库你可能不太乐意,也可以用下面sha1文件中的实现 是等效的啊! sha1.c代码见工程
#include "soapH.h"
#include "soapStub.h"
#include "PTZBinding.nsmap"
#ifdef WITH_OPENSSL
#include "dom.c"
#include "mecevp.c"
#include "smdevp.c"
#include "threads.c"
#include "wsaapi.c"
#include "wsseapi.c"
#else
#include "sha1.c"
#endif
实现中鉴权部分涉及使用:
#ifdef WITH_OPENSSL
#else
void OnvifSDK::calc_nonce(struct soap* soap, char nonce[SOAP_WSSE_NONCELEN])
{
int i;
time_t r = time(NULL);
memcpy(nonce, &r, 4);
for (i = 4; i < SOAP_WSSE_NONCELEN; i += 4)
{
r = soap_random;
memcpy(nonce + i, &r, 4);
}
}
_wsse__Security* OnvifSDK::soap_wsse_add_Security(struct soap* soap)
{
/* if we don't have a SOAP Header, create one */
//?? soap_header(soap); 此处无法编译 到源码去拷贝
if (soap->header == NULL)
{
if ((soap->header = (struct SOAP_ENV__Header*)soap_malloc(soap, sizeof(struct SOAP_ENV__Header))))
soap_default_SOAP_ENV__Header(soap, soap->header);
}
/* if we don't have a wsse:Security element in the SOAP Header, create one */
if (!soap->header->wsse__Security)
{
soap->header->wsse__Security = (_wsse__Security*)soap_malloc(soap, sizeof(_wsse__Security));
soap_default__wsse__Security(soap, soap->header->wsse__Security);
}
return soap->header->wsse__Security;
}
int OnvifSDK::soap_wsse_add_UsernameTokenText(struct soap* soap, const char* id,const char* username, const char* password)
{
_wsse__Security* security = soap_wsse_add_Security(soap);
/* allocate a UsernameToken if we don't have one already */
if (!security->UsernameToken)
security->UsernameToken = (_wsse__UsernameToken*)soap_malloc(soap, sizeof(_wsse__UsernameToken));
soap_default__wsse__UsernameToken(soap, security->UsernameToken);
/* populate the UsernameToken */
security->UsernameToken->wsu__Id = soap_strdup(soap, id);
security->UsernameToken->Username = soap_strdup(soap, username);
/* allocate and populate the Password */
if (password)
{
security->UsernameToken->Password = (_wsse__Password*)soap_malloc(soap, sizeof(_wsse__Password));
soap_default__wsse__Password(soap, security->UsernameToken->Password);
security->UsernameToken->Password->Type = (char*)wsse_PasswordTextURI;
security->UsernameToken->Password->__item = soap_strdup(soap, password);
}
return SOAP_OK;
}
int OnvifSDK::soap_wsse_add_UsernameTokenDigest(struct soap* soap, const char* id,const char* username, const char* password)
{
_wsse__Security* security = soap_wsse_add_Security(soap);
time_t now = time(NULL);
const char* created = soap_dateTime2s(soap, now);
char HA[SOAP_SMD_SHA1_SIZE], HABase64[29];
char nonce[SOAP_WSSE_NONCELEN], *nonceBase64;
/* generate a nonce */
calc_nonce(soap, nonce);
nonceBase64 = soap_s2base64(soap, (unsigned char*)nonce, NULL, SOAP_WSSE_NONCELEN);
/* The specs are not clear: compute digest over binary nonce or base64 nonce? */
/* compute SHA1(created, nonce, password) */
/* // boost计算结果不对,应该是我的使用方法不对
unsigned int Digest[5] = {0};
boost::uuids::detail::sha1 sha;
sha.process_bytes(nonce, strlen(nonce));
sha.process_bytes(created, strlen(created));
sha.process_bytes(password, strlen(password));
sha.get_digest(Digest);
for (int n=0,i=0; n> 24) & 0xFF;
HA[n++] = (Digest[i] >> 16) & 0xFF;
HA[n++] = (Digest[i] >> 8) & 0xFF;
HA[n++] = Digest[i] & 0xFF;
i++;
}*/
calc_digest(soap, created, nonce, SOAP_WSSE_NONCELEN, password, HA);
// calc_digest(soap, created, nonce, SOAP_WSSE_NONCELEN, password, HA);
/*
calc_digest(soap, created, nonceBase64, strlen(nonceBase64), password, HA);
*/
soap_s2base64(soap, (unsigned char*)HA, HABase64, SOAP_SMD_SHA1_SIZE);
/* populate the UsernameToken with digest */
soap_wsse_add_UsernameTokenText(soap, id, username, HABase64);
/* populate the remainder of the password, nonce, and created */
security->UsernameToken->Password->Type = (char*)wsse_PasswordDigestURI;
security->UsernameToken->Nonce = (struct wsse__EncodedString*)soap_malloc(soap, sizeof(struct wsse__EncodedString));
if (!security->UsernameToken->Nonce)
return soap->error = SOAP_EOM;
soap_default_wsse__EncodedString(soap, security->UsernameToken->Nonce);
security->UsernameToken->Nonce->__item = nonceBase64;
security->UsernameToken->Nonce->EncodingType = (char*)wsse_Base64BinaryURI;
security->UsernameToken->wsu__Created = soap_strdup(soap, created);
return SOAP_OK;
}
void OnvifSDK::calc_digest(struct soap* soap, const char* created, const char* nonce, int noncelen, const char* password, char hash[SOAP_SMD_SHA1_SIZE])
{
SHA1Context sha;
SHA1Reset(&sha);
SHA1Input(&sha, (unsigned char*)nonce, noncelen);
SHA1Input(&sha, (unsigned char*)created, strlen(created));
SHA1Input(&sha, (unsigned char*)password, strlen(password));
if (!SHA1Result(&sha))
fprintf(stderr, "ERROR-- could not compute message digest\n");
else
{
int j = 0;
for (int i = 0; i < 5; i++)
{
hash[j++] = sha.Message_Digest[i] >> 24;
hash[j++] = sha.Message_Digest[i] >> 16;
hash[j++] = sha.Message_Digest[i] >> 8;
hash[j++] = sha.Message_Digest[i] >> 0;
}
}
}
void OnvifSDK::TokenTimestmap(struct soap* pSoap, time_t lifetime /*= TOKEN_TIMESTAMP*/)
{
soap_wsse_add_Timestamp(pSoap, "Time", lifetime);
}
int OnvifSDK::soap_wsse_add_Timestamp(struct soap* soap, const char* id, time_t lifetime)
{
_wsse__Security* security = soap_wsse_add_Security(soap);
time_t now = time(NULL);
char* created = soap_strdup(soap, soap_dateTime2s(soap, now));
char* expired = lifetime ? soap_strdup(soap, soap_dateTime2s(soap, now + lifetime)) : NULL;
/* allocate a Timestamp if we don't have one already */
if (!security->wsu__Timestamp)
security->wsu__Timestamp = (_wsu__Timestamp*)soap_malloc(soap, sizeof(_wsu__Timestamp));
soap_default__wsu__Timestamp(soap, security->wsu__Timestamp);
/* populate the wsu:Timestamp element */
security->wsu__Timestamp->wsu__Id = soap_strdup(soap, id);
security->wsu__Timestamp->Created = created;
security->wsu__Timestamp->Expires = expired;
return SOAP_OK;
}
#endif
完整工程下载