1、基本要求
根据《GB/T 28181 —2016》第9章关于实时视音频点播的描述,其内容如下:
- 实时视音频点播的SIP 消息应通过本域或其他域的SIP 服务器进行路由、 转发, 目标设备的实时视音频流宜通过本域内的媒体服务器进行转发。
- 实时视音频点播采用SIP 协议(IETF RFC3261) 中的Invite 方法实现会话连接, 采用 RTP/RTCP
协议(IETF RFC3550) 实现媒体传输。
2、基本流程
客户端主动发起的实时视音频点播流程见下图:
其中, 信令1、8、9、10、11、12 为SIP 服务器接收到客户端的呼叫请求后通过 B2BUA 代理方式建立媒体流接收者与媒体服务器之间的媒体流信令过程, 信令2-7 为SIP 服务器通过三方呼叫控制建立媒体服务器与媒体流发送者之间的媒体流信令过程, 信令13~16 为媒体流接收者断开与媒体服务器之间的媒体流信令过程, 信令17 ~20 为 SIP 服务器断开媒体服务器与媒体流发送者之间的媒体流信令过程。
命令流程描述如下:
- 媒体流接收者向SIP 服务器发送Invite 消息, 消息头域中携带 Subject 字段, 表明点播的视频源ID、 发送方媒体流序列号、 媒体流接收者ID、 接收端媒体流序列号等参数,SDP 消息体中s 字段为“Play”代表实时点播。
- SIP 服务器收到Invite 请求后, 通过三方呼叫控制建立媒体服务器和媒体流发送者之间的媒体连接。 向媒体服务器发送Invite 消息, 此消息不携带SDP 消息体。
- 媒体服务器收到SIP 服务器的Invite 请求后, 回复200 OK 响应, 携带SDP 消息体, 消息体中描述了媒体服务器接收媒体流的IP、 端口、 媒体格式等内容。
- SIP 服务器收到媒体服务器返回的200 OK 响应后, 向媒体流发送者发送Invite 请求, 请求中携带消息3 中媒体服务器回复的200 OK 响应消息体,s 字段为“Play”代表实时点播, 增加y 字段描述SSRC 值,f 字段描述媒体参数。
- 媒体流发送者收到SIP 服务器的Invite 请求后, 回复200 OK 响应, 携带SDP 消息体, 消息体中描述了媒体流发送者发送媒体流的IP、 端口、 媒体格式、SSRC 字段等内容。
- SIP 服务器收到媒体流发送者返回的200 OK 响应后, 向媒体服务器发送 ACK 请求, 请求中携带消息5 中媒体流发送者回复的200 OK 响应消息体, 完成与媒体服务器的Invite 会话建立过程。
- SIP 服务器收到媒体流发送者返回的200 OK 响应后, 向媒体流发送者发送 ACK 请求, 请求中不携带消息体, 完成与媒体流发送者的Invite 会话建立过程。
- 完成三方呼叫控制后,SIP 服务器通过B2BUA 代理方式建立媒体流接收者和媒体服务器之间的媒体连接。 在消息1 中增加SSRC 值, 转发给媒体服务器。
- 媒体服务器收到Invite 请求, 回复200 OK 响应, 携带SDP 消息体, 消息体中描述了媒体服务器发送媒体流的IP、 端口、 媒体格式、SSRC 值等内容。
- SIP 服务器将消息9 转发给媒体流接收者。
- 媒体流接收者收到200 OK 响应后, 回复 ACK 消息, 完成与SIP 服务器的Invite 会话建立过程。
- SIP 服务器将消息11 转发给媒体服务器, 完成与媒体服务器的Invite 会话建立过程。
- 媒体流接收者向SIP 服务器发送 BYE 消息, 断开消息1、10、11 建立的同媒体流接收者的Invite 会话。
- SIP 服务器收到 BYE 消息后回复200 OK 响应, 会话断开。
- SIP 服务器收到 BYE 消息后向媒体服务器发送 BYE 消息, 断开消息8、9、12 建立的同媒体服务器的Invite 会话。
- 媒体服务器收到 BYE 消息后回复200 OK 响应, 会话断开。
- SIP 服务器向媒体服务器发送 BYE 消息, 断开消息2、3、6 建立的同媒体服务器的Invite会话。
- 媒体服务器收到 BYE 消息后回复200 OK 响应, 会话断开。
- SIP 服务器向媒体流发送者发送 BYE 消息, 断开消息4、5、7 建立的同媒体流发送者的Invite 会话。
- 媒体流发送者收到 BYE 消息后回复200 OK 响应, 会话断开。
3、基于SIP库的点播开发
- sip信令处理:
int SipEventProcess(GB28181Param_t *pGB28181Param)
{int ret = 0;eXosip_event_t *sipEvent = NULL;sipEvent = eXosip_event_wait( 0, 100);if (!sipEvent){//GB_PrintError("sipEvent is null. \n");return -1;}eXosip_lock();eXosip_default_action(sipEvent);eXosip_automatic_refresh();eXosip_unlock();switch(sipEvent->type){case EXOSIP_CALL_INVITE:/* INVITE方法建立会话 */ret = SipInviteProcess(pGB28181Param, sipEvent);break;case EXOSIP_CALL_ACK:ret = SipStartStreamProcess(pGB28181Param, sipEvent);break;case EXOSIP_CALL_CLOSED:ret = SipStopStreamProcess(pGB28181Param, sipEvent);break;default:break;}eXosip_event_free(sipEvent);return ret;
}
- sip Invite处理
static int SipInviteProcess(GB28181Param_t *pGB28181Param, eXosip_event_t *sipEvent)
{int ret = -1;uint8_t iChn;char transMode[16] = "active";char *message = NULL;char sdpBody[2048] = {0,};osip_message_t *aswMsg = NULL;osip_message_t *cloneEvent = NULL;sdp_message_t *sdpMsg = NULL;if (!pGB28181Param){return -1;}if(MSG_IS_INVITE(sipEvent->request)){eXosip_lock();ret = eXosip_call_build_answer( sipEvent->tid, 200, &aswMsg);if(ret != OSIP_SUCCESS){eXosip_call_send_answer( sipEvent->tid, 603, NULL);eXosip_unlock();return -1;}eXosip_unlock();// 获取设备通道号if ((ret = osip_message_clone(sipEvent->request, &cloneEvent)) != OSIP_SUCCESS){return -1;}if ((ret = osip_to_to_str(cloneEvent->to, &message)) != OSIP_SUCCESS){return -1;}// 判断通道号是否有效iChn = GetChannelIdByMsg(message, pGB28181Param);if (!IS_ICHN_VALID(iChn)){return -1;}osip_message_free(cloneEvent);osip_free(message);sdpMsg = eXosip_get_remote_sdp(sipEvent->did); if(NULL == sdpMsg){return -1;}if (OSIP_SUCCESS == sdp_message_to_str(sdpMsg, &message)){char *str = NULL;char strTmp[16] = {0,};if ((str = strstr(message ,"a=setup:"))){strncpy(strTmp, str+strlen("a=setup:"), sizeof(strTmp));}if ((str = strstr(strTmp, "active"))){strncpy(transMode, "passive", strlen("passive"));}osip_free(message);}snprintf(sdpBody, sizeof(sdpBody),"v=0\r\n""o=%s 0 0 IN IP4 %s\r\n" /*会话源*//*用户名/会话ID/版本/网络类型/地址类型/地址*/"s=Play\r\n" /*会话名*/"c=IN IP4 %s\r\n" /*连接信息*//*网络类型/地址信息/多点会议的地址*/"t=0 0\n" /*时间*//*开始时间/结束时间*/"m=video %s RTP/AVP %d\r\n" /*媒体/端口/传送层协议/格式列表*/"a=setup:%s\r\n""a=sendonly\r\n" /*收发模式*/"a=rtpmap:%d %s/%d\r\n" /*净荷类型/编码名/时钟速率*/"a=username:%s\r\n""a=password:%s\r\n""a=filesize:0\r\n""y=%d\r\n",pGB28181Param->userParam.devSipID,pGB28181Param->userParam.devSipIP,pGB28181Param->userParam.devSipIP,pGB28181Param->userParam.devTcpActivePort[iChn],pGB28181Param->runParam.vedioPayload,transMode,pGB28181Param->runParam.vedioPayload,pGB28181Param->runParam.vedioPackType,pGB28181Param->runParam.vedioSample,pGB28181Param->userParam.devSipID,pGB28181Param->userParam.devSipPasswd,pGB28181Param->runParam.ssrc);eXosip_lock();osip_message_set_content_type(aswMsg, "application/sdp");osip_message_set_body(aswMsg, sdpBody, strlen(sdpBody));ret = eXosip_call_send_answer( sipEvent->tid, 200, aswMsg);eXosip_unlock();if (ret == OSIP_SUCCESS){ret = 0;}}return ret;
}
- 开始点播函数
static int SipStartStreamProcess(GB28181Param_t *pGB28181Param, eXosip_event_t *sipEvent)
{int ret = 0;uint8_t iChn = 0;uint8_t transMode = TCP_PASSIVE;char *message = NULL;char *mediaSeverIP = NULL;char *mediaSeverPort = NULL;sdp_message_t *sdpMsg = NULL;osip_message_t *cloneEvent = NULL;if (!pGB28181Param){return -1;}if ((ret = osip_message_clone(sipEvent->request, &cloneEvent)) != OSIP_SUCCESS){return -1;}if ((ret = osip_to_to_str(cloneEvent->to, &message)) != OSIP_SUCCESS){return -1;}/* 从SIP服务器发过来的INVITE请求的o字段或c字段或m字段中获取媒体服务器的IP地址与端口 */iChn = GetChannelIdByMsg(message, pGB28181Param);// 判断端口是否有效if (!IS_ICHN_VALID(iChn)){return -1;}osip_message_free(cloneEvent);osip_free(message);sdpMsg = eXosip_get_remote_sdp(sipEvent->did); if(NULL == sdpMsg){return -1;}mediaSeverIP = sdp_message_o_addr_get(sdpMsg); /*媒体服务器IP地址*/mediaSeverPort = sdp_message_m_port_get(sdpMsg, 0); /*媒体服务器IP端口*/if (OSIP_SUCCESS == sdp_message_to_str(sdpMsg, &message)){char *str = NULL;char strTmp[16] = {0,};if ((str = strstr(message ,"a=setup:"))){strncpy(strTmp, str+strlen("a=setup:"), sizeof(strTmp));}if ((str = strstr(strTmp, "active"))){transMode = TCP_ACTIVE;}osip_free(message);}ret = StartStreamByChannel(mediaSeverIP, mediaSeverPort, iChn, transMode, pGB28181Param);sdp_message_free(sdpMsg);return ret;
}
上面的这个函数会根据通道号,给流媒体服务器发送相关的码流数据。
参考资料:
《GBT 28181-2016 公共安全视频监控联网系统信息传输、交换、控制技术要求》
推荐阅读:
GB28181协议–设备注册和注销
网络流媒体–SDP会话描述协议(RFC-4566)