如何接入IPC的GB28181平臺

通常工業級的IPC一般支持onvif,GB28181以及各廠傢俬有協議。上篇文章我們講解如何通過onvif協議對接IPC,本文接下來介紹如何接入通過國內最主流的GB28181協議對接IPC。對於GB28181協議內容細節不多介紹,他是國家公安部定義的安防設備互通的協議,細節詳見《GBT28181-2016 公共安全視頻監控聯網系統信息傳輸、交換、控制技術要求.pdf》。目前城市街道,公共場所,社區等各個安防設備基本都是通過GB28181在協議互通。如IPC,NVR,媒體網關等。本文以大華IPC爲例子,直接上代碼,演示如何通過GB28181協議將視頻流拉下來。
一.配置IPC


 IPC配置如上所述,主要關注SIP服務器相關參數,也就是你的代碼將來部署的參數。在這種場景下,IPC扮演UAC(客戶端代理)角色,你的代碼扮演是UAS(服務器端代理)角色。
 SIP服務器編碼:44011300002000000001 該編碼是服務器根據公安部GBT28181編碼標準自定義的 編碼規則由中心編碼(8位) 、行業編碼(2位) 、類型編碼(3位)和序號(7 位)四個碼段共20位十進制數字字符構成,即系統編碼 =中心編碼 + 行業編碼 + 類型編碼 + 序號。 詳見《GBT28181-2016 公共安全視頻監控聯網系統信息傳輸、交換、控制技術要求.pdf》附錄D統一編碼規則。44011300002000000001 其中44011300爲廣東省廣州市番禺區,00爲社會治安路面接入,200爲SIP 服務器。0000001 爲序列號。

 SIP服務器IP:即UAS的IP地址
 設備編碼:即IPC的編碼。該編碼也是根據GB28181編碼的,其中132代表IPC,其他與服務器編碼意義雷同
 本地SIP 端口:默認採用5060
 SIP域:即SIP服務器編碼的前10bit。
  註冊密碼:無或者任意,因爲服務器目前還沒有做密碼認證。如果需要可以根據《GBT28181-2016 公共安全視頻監控聯網系統    信息傳輸、交換、控制技術要求.pdf》附錄H的數字摘要信令認證過程和方法加以認證。
  通道編碼:跟設備編號一致即可
  其他選項暫時默認即可
二 接入方案
 因爲GB28181信令是基於SIP協議的一個應用,本文采用eXosip開源方案作爲GB28181的協議棧完成接入。
 1.配置IPC後,IPC就會不斷向服務器UAS發註冊信息。


 2.完成註冊後,ICP就會停止向服務器發註冊消息。不過註冊消息有效期過了以後會再次註冊。註冊有效期在配置頁面默認設了3600s.


 我們有個線程專門處理SIP消息。處理註冊消息接口爲IPCRegister
 void DealSipThread()
{
    while (1)
    {
        eXosip_event_t* je = NULL;

        je = eXosip_event_wait(m_context, 0, 200);   // 等待一個eXosip事件,超時時間秒數使用第一個參數,微秒使用第二個參數
        eXosip_lock(m_context);
        eXosip_default_action(m_context, je);
        eXosip_automatic_action(m_context);
        eXosip_unlock(m_context);
        if (je)
        {
            switch (je->type)
            {
                case EXOSIP_MESSAGE_NEW:
                {
                        if (MSG_IS_REGISTER(je->request))
                        {
                            // ipc註冊消息
                            printf("regist is comming\n");
                            IPCRegister(je);
                        }
                        else if (MSG_IS_MESSAGE(je->request))
                        {
                            // ipc心跳消息
                            osip_message_t* answer = NULL;
                            eXosip_lock(m_context);
                            eXosip_message_build_answer(m_context, je->tid, 200, &answer);
                            eXosip_message_send_answer(m_context, je->tid, 200, answer);   // 回覆200 OK
                            printf("[INTest] get ipc heart beat [did %d] [cid %d] [tid %d]\n", je->did, je->cid, je->tid);
                            eXosip_unlock(m_context);
                        }
                        break;
                }
            case EXOSIP_CALL_ANSWERED:
            {
                    // ipc收到invite之後會返回200 OK,並給IPC返回一個ACK
                    osip_message_t* ack = NULL;
                    eXosip_call_build_ack(m_context, je->did, &ack);
                    eXosip_call_send_ack(m_context, je->did, ack);

                    osip_via_t* via = NULL;
                    osip_message_get_via(je->request, 0, &via);

                    printf("[INTest] recv 200 OK after invite, send ack [ipcIp %s] [ipcId %s]\n", via->host, je->request->from->url->username);

                    // 記錄此次會話的dialog id、call id、transction id
                    int ipcCnt;
                    for (ipcCnt = 0; ipcCnt < INTEST_MAX_IPC_NUM; ipcCnt++)
                    {
                        if (!strcmp(m_ipcInfo[ipcCnt].ipcId, je->request->req_uri->username))
                        {
                            m_ipcInfo[ipcCnt].invdid = je->did;
                            m_ipcInfo[ipcCnt].invcid = je->cid;
                            m_ipcInfo[ipcCnt].invtid = je->tid;
                            printf("[INTest] recv 200 OK after invite, save info [did %d] [cid %d] [tid %d]\n",
                                m_ipcInfo[ipcCnt].invdid, m_ipcInfo[ipcCnt].invcid, m_ipcInfo[ipcCnt].invtid);
                        }
                    }
                    if (ipcCnt == INTEST_MAX_IPC_NUM)
                    {
                        printf("[INTest] recv 200 OK after invite, save ipc info failed [ipcId %s]\n", je->request->from->url->username);
                    }
                    break;
            }
                
        }
    }
            
  }
}

// ipc註冊
void IPCRegister(eXosip_event_t* je)
{
    osip_authorization_t* auth = NULL;
    osip_message_get_authorization(je->request, 0, &auth);

    // 攝像機第一次發來未鑑權註冊信息,返回401
    if (NULL == auth)
    {
        AnswerRegister(je, 401);
    }
    else
    {
        // 攝像機第二次發來鑑權註冊信息,返回200 OK,暫時沒有做認證,有需求可以做認證開發
        AnswerRegister(je, 200);

        // 獲取鑑權攝像機的信息
        osip_via_t* via = NULL;
        osip_message_get_via(je->request, 0, &via);
        if (via)
        {
            // 記錄註冊上來的攝像機終端設備,併發送catalog請求設備信息
            eXosip_lock(m_context);

            // 先判斷該ipc是否已註冊
            int ipcCnt;
            for (ipcCnt = 0; ipcCnt < m_ipcCurrCount; ipcCnt++)
            {
                if (!strcmp(m_ipcInfo[ipcCnt].ipcId, je->request->from->url->username))
                    break;
            }

            if (ipcCnt == m_ipcCurrCount)
            {
                memcpy(m_ipcInfo[m_ipcCurrCount].ipcId, je->request->from->url->username, strlen(je->request->from->url->username) + 1);
                memcpy(m_ipcInfo[m_ipcCurrCount].ipcIp, via->host, strlen(via->host) + 1);
                m_ipcInfo[m_ipcCurrCount].bWork = true;

                printf("[INTest] ipc register [sn %d] [id %s] [ip %s]\n", m_ipcCurrCount, m_ipcInfo[m_ipcCurrCount].ipcId, m_ipcInfo[m_ipcCurrCount].ipcIp);
                m_ipcCurrCount++;

                //GetIPCInfo(via->host, je->request->from->url->username);
            }

            eXosip_unlock(m_context);
        }
    }
}
 3.完成IPC註冊後,我們向IPC發一個invite消息。invite指定媒體接收端口(6000)和IP地址(即媒體服務器地址),IPC給server回覆了200OK,server再向IPC回覆一個ACK,完成3次握手後,IPC就向server發RTP流


 // 向ipc發送invite
int SendInviteToIPC(int ipcSn)
{
    osip_message_t* invite = NULL;
    char cmd[4096] = { 0 };
    char ipcCall[128] = { 0 };
    int iRet;
    eXosip_lock(m_context);
    sprintf(ipcCall, "sip:%s@%s:%d", m_ipcInfo[ipcSn].ipcId, m_ipcInfo[ipcSn].ipcIp, _sip_ipc_port_);   // 構建ipc基本信息,通道編號,設備編號要一致,否則返回404
    printf("[INTest] create ipcCall:  %s", ipcCall);
    iRet = eXosip_call_build_initial_invite(m_context, &invite, ipcCall, m_serverCall, NULL, "This is a call for camera conversation");
    if (iRet)
    {
        printf("[INTest] eXosip_call_build_initial_invite failed [error %d]\n", iRet);
        eXosip_unlock(m_context);
        return iRet;
    }
    // 構建向ipc申請發流信息
    sprintf(cmd,
        "v=0\r\n"
        "o=%s 0 0 IN IP4 %s\r\n"
        "s=Play\r\n"
        "c=IN IP4 %s\r\n"
        "t=0 0\r\n"
        "m=video %d RTP/AVP 96 98 97\r\n"
        "a=recvonly\r\n"
        "a=rtpmap:96 PS/90000\r\n"
        "a=rtpmap:98 H264/90000\r\n"
        "a=rtpmap:97 MPEG4/90000\r\n"
        "m=audio %d RTP/AVP 97\r\n"
        "a=rtpmap:97 mpeg4-generic/44100/2\r\n"
        "a=control:trackID=1\r\n"
        "a=mpeg4-esid:3\r\n"
        "a=fmtp:97 streamtype=5;profile-level-id=15;mode=AAC-hbr;config=1210;SizeLength=13;IndexLength=3;IndexDeltaLength=3;Profile=1;\r\n"
        "y=0100000001\r\n",
        m_realm,
        GetLocalIp(),
        GetLocalIp(),
        m_ipcInfo[ipcSn].recvPort,
        m_ipcInfo[ipcSn].recvPort
        );
    osip_message_set_body(invite, cmd, strlen(cmd));
    osip_message_set_content_type(invite, "application/sdp");
    eXosip_call_send_initial_invite(m_context, invite);
    eXosip_unlock(m_context);
    printf("[INTest] send invite to IPC %s succeed [recvPort %d]\n", m_ipcInfo[ipcSn].ipcId, m_ipcInfo[ipcSn].recvPort);
    return 0;
}

 3.國標RTP流是PS流。我們開始用PS播放器來接收。PS播放器這個時候就在信令調試過程就派上用場了。PS播放器詳見:https://blog.csdn.net/fengliang191/article/details/105102495

 更多更詳細資源請關注公衆號:AV_Chat
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章