1 專欄導讀
本專欄第一篇文章「專欄開篇」列出了專欄的完整目錄,按目錄順序閱讀,有助於你的理解,專欄前面文章講過的知識點(或代碼段),後面文章不會贅述。爲了節省篇幅,突出重點,在文章中展示的示例代碼僅僅是關鍵代碼,你可以在「專欄開篇」中獲取完整代碼。
如有錯誤,歡迎你的留言糾正!讓我們共同成長!你的「點贊」或「打賞」是對我最大的支持和鼓勵!
2 原理簡介
IPC圖像抓拍有兩種方法:
- 對RTSP視頻流進行視頻截圖;
- 使用HTTP的GET方式獲取圖片。
第一種方法我還沒試過,沒有發言權,以下介紹第二種方法。
ONVIF協議除了提供RTSP的URL外,其實也給出了抓拍的URL,使用Media模塊的GetSnapshotUri接口可獲取圖像抓拍的URL。
比如,我從IPC獲得的抓拍URL爲:http://100.100.100.160/onvifsnapshot/media_service/snapshot?channel=1&subtype=0
。
那如何通過這個地址獲得圖片呢?其實在media.wsdl中,該接口的函數功能說明中已經描述的很清楚了:「The URI can be used for acquiring a JPEG image through a HTTP GET operation」,也就是通過HTTP的GET方式獲得JPEG圖片。
在瀏覽器上輸入抓拍的URL,在瀏覽器中就會顯示出圖片,刷新,圖片會變化,對於需要驗證的IPC,會要求我們輸入用戶名密碼進行HTTP用戶認證。
3 編碼流程
通過「設備發現」,得到 「設備服務地址」。
使用「設備服務地址」調用GetCapabilities接口,得到「媒體服務地址」。
使用「媒體服務地址」調用GetProfiles接口,得到主次碼流的「媒體配置信息」,其中包含ProfileToken。
使用ProfileToken 調用GetSnapshotUri接口,得到主次碼圖像抓拍的URI地址。
根據URI地址,使用HTTP的GET方式獲取圖片。
4 示例代碼
Windows的MFC裏有CInternetSession,CHttpConnection,CHttpFile這些類提供通過HTTP獲得圖像數據。
Linux可以使用很多開源方案,甚至可以直接使用shell命令wget來下載圖像即可,簡單高效。比如:
wget -O out.jpeg 'http://100.100.100.5:80/capture/webCapture.jpg?channel=1&FTpsend=0&checkinfo=0'
如果需要帶認證信息,可以使用:
wget -O out.jpeg 'http://username:[email protected]:80/capture/webCapture.jpg?channel=1&FTpsend=0&checkinfo=0'
以下的示例代碼就是使用wget實現的圖像抓拍功能。
/************************************************************************
**函數:make_uri_withauth
**功能:構造帶有認證信息的URI地址
**參數:
[in] src_uri - 未帶認證信息的URI地址
[in] username - 用戶名
[in] password - 密碼
[out] dest_uri - 返回的帶認證信息的URI地址
[in] size_dest_uri - dest_uri緩存大小
**返回:
0成功,非0失敗
**備註:
1). 例子:
無認證信息的uri:rtsp://100.100.100.140:554/av0_0
帶認證信息的uri:rtsp://username:[email protected]:554/av0_0
************************************************************************/
static int make_uri_withauth(char *src_uri, char *username, char *password, char *dest_uri, unsigned int size_dest_uri)
{
int result = 0;
unsigned int needBufSize = 0;
SOAP_ASSERT(NULL != src_uri);
SOAP_ASSERT(NULL != username);
SOAP_ASSERT(NULL != password);
SOAP_ASSERT(NULL != dest_uri);
memset(dest_uri, 0x00, size_dest_uri);
needBufSize = strlen(src_uri) + strlen(username) + strlen(password) + 3; // 檢查緩存是否足夠,包括‘:’和‘@’和字符串結束符
if (size_dest_uri < needBufSize) {
SOAP_DBGERR("dest uri buf size is not enough.\n");
result = -1;
goto EXIT;
}
if (0 == strlen(username) && 0 == strlen(password)) { // 生成新的uri地址
strcpy(dest_uri, src_uri);
} else {
char *p = strstr(src_uri, "//");
if (NULL == p) {
SOAP_DBGERR("can't found '//', src uri is: %s.\n", src_uri);
result = -1;
goto EXIT;
}
p += 2;
memcpy(dest_uri, src_uri, p - src_uri);
sprintf(dest_uri + strlen(dest_uri), "%s:%s@", username, password);
strcat(dest_uri, p);
}
EXIT:
return result;
}
/************************************************************************
**函數:ONVIF_GetSnapshotUri
**功能:獲取設備圖像抓拍地址(HTTP)
**參數:
[in] MediaXAddr - 媒體服務地址
[in] ProfileToken - the media profile token
[out] uri - 返回的地址
[in] sizeuri - 地址緩存大小
**返回:
0表明成功,非0表明失敗
**備註:
1). 並非所有的ProfileToken都支持圖像抓拍地址。舉例:XXX品牌的IPC有如下三個配置profile0/profile1/TestMediaProfile,其中TestMediaProfile返回的圖像抓拍地址就是空指針。
************************************************************************/
int ONVIF_GetSnapshotUri(const char *MediaXAddr, char *ProfileToken, char *uri, unsigned int sizeuri)
{
int result = 0;
struct soap *soap = NULL;
struct _trt__GetSnapshotUri req;
struct _trt__GetSnapshotUriResponse rep;
SOAP_ASSERT(NULL != MediaXAddr);
SOAP_ASSERT(NULL != uri);
memset(uri, 0x00, sizeuri);
SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
memset(&req, 0x00, sizeof(req));
memset(&rep, 0x00, sizeof(rep));
req.ProfileToken = ProfileToken;
result = soap_call___trt__GetSnapshotUri(soap, MediaXAddr, NULL, &req, &rep);
SOAP_CHECK_ERROR(result, soap, "GetSnapshotUri");
dump_trt__GetSnapshotUriResponse(&rep);
result = -1;
if (NULL != rep.MediaUri) {
if (NULL != rep.MediaUri->Uri) {
if (sizeuri > strlen(rep.MediaUri->Uri)) {
strcpy(uri, rep.MediaUri->Uri);
result = 0;
} else {
SOAP_DBGERR("Not enough cache!\n");
}
}
}
EXIT:
if (NULL != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
void cb_discovery(char *DeviceXAddr)
{
int stmno = 0; // 碼流序號,0爲主碼流,1爲輔碼流
int profile_cnt = 0; // 設備配置文件個數
struct tagProfile *profiles = NULL; // 設備配置文件列表
struct tagCapabilities capa; // 設備能力信息
char cmd[256];
char uri[ONVIF_ADDRESS_SIZE] = {0}; // 不帶認證信息的URI地址
char uri_auth[ONVIF_ADDRESS_SIZE + 50] = {0}; // 帶有認證信息的URI地址
ONVIF_GetCapabilities(DeviceXAddr, &capa); // 獲取設備能力信息(獲取媒體服務地址)
profile_cnt = ONVIF_GetProfiles(capa.MediaXAddr, &profiles); // 獲取媒體配置信息(主/輔碼流配置信息)
if (profile_cnt > stmno) {
ONVIF_GetSnapshotUri(capa.MediaXAddr, profiles[stmno].token, uri, sizeof(uri)); // 獲取圖像抓拍URI
make_uri_withauth(uri, USERNAME, PASSWORD, uri_auth, sizeof(uri_auth)); // 生成帶認證信息的URI(有的IPC要求認證)
sprintf(cmd, "wget -O out.jpeg '%s'", uri_auth); // 使用wget下載圖片
system(cmd);
}
if (NULL != profiles) {
free(profiles);
profiles = NULL;
}
}
int main(int argc, char **argv)
{
ONVIF_DetectDevice(cb_discovery);
return 0;
}