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就会停止向服务器发注册消息。不过注册消息有效期过了以后会再次注册。注册有效期在配置页面默认设了3600

注册通过

 我们有个线程专门处理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流

invite请求流程

 // 向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

                                          国标PS流播放

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容