ONVIF協議網絡攝像機(IPC)客戶端程序開發(13):圖像抓拍

版權聲明:本文爲博主原創文章,如要轉載請標明出處。 https://blog.csdn.net/benkaoya/article/details/72487077

1 專欄導讀

本專欄第一篇文章「專欄開篇」列出了專欄的完整目錄,按目錄順序閱讀,有助於你的理解,專欄前面文章講過的知識點(或代碼段),後面文章不會贅述。爲了節省篇幅,突出重點,在文章中展示的示例代碼僅僅是關鍵代碼,你可以在「專欄開篇」中獲取完整代碼。

如有錯誤,歡迎你的留言糾正!讓我們共同成長!你的「點贊」「打賞」是對我最大的支持和鼓勵!

2 原理簡介

IPC圖像抓拍有兩種方法:

  1. 對RTSP視頻流進行視頻截圖;
  2. 使用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 編碼流程

  1. 通過「設備發現」,得到 「設備服務地址」。

  2. 使用「設備服務地址」調用GetCapabilities接口,得到「媒體服務地址」。

  3. 使用「媒體服務地址」調用GetProfiles接口,得到主次碼流的「媒體配置信息」,其中包含ProfileToken。

  4. 使用ProfileToken 調用GetSnapshotUri接口,得到主次碼圖像抓拍的URI地址。

  5. 根據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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章