GB/T28181平台C++实现学习笔记5: 使用eXosip获取国标摄像头的媒体流
文章目录
获取国标媒体流的流程
GB/T 28181-2016 中有两种方式,分别是客户端主动发起和第三方呼叫控制,但是作为SIP服务器,和摄像头交互的流程其实是一样的。
开启媒体流
1. SIP服务器 ---> Invite(sdp) ---> 媒体流发送者
2. 媒体流发送者 <--- 200(sdp) <--- SIP服务器
3. SIP服务器 ---> Ack ---> 媒体流发送者
4. 媒体流发送者 <--- 数据流(rtp) <--- SIP服务器
停止媒体流
1. SIP服务器 ---> Bye ---> 媒体流发送者
2. 媒体流发送者 <--- 200 <--- SIP服务器
开启获取国标流任务线程
同样使用任务池扫描任务,自动获取任务。
void TaskMgr::start()
{
stl::ThreadTask recvTask = std::bind(&TaskMgr::DoScanTasks, this);
stl::ThreadPool::instance()->pushTask(recvTask);
}
int TaskMgr::DoScanTasks()
{
static int dump = 0;
if (dump++ < 10)
{
return 0;
}
dump = 0;
// stl::debug("DoScanTasks");
std::map<int, DBMgr::TaskStatus> tasks = DBMgr::instance()->getTaskList();
std::map<int, DBMgr::TaskStatus>::iterator it;
for (it = tasks.begin(); it != tasks.end(); it++)
{
if (it->second == DBMgr::TaskStatus::NEW)
{
int deviceId = it->first;
stl::debug("Start Task[%d]", deviceId);
DBMgr::instance()->setTask(deviceId, DBMgr::TaskStatus::WORK);
auto deviceInfo = DBMgr::instance()->getDevice(deviceId);
send_invite_sdp(g_excontext, deviceInfo);
}
}
}
发送Invite
int TaskMgr::send_invite_sdp(eXosip_t *context_eXosip, DBMgr::Device deviceInfo)
{
std::string channel_code = "41010200001310000001";// TODO 通过查询获取<CmdType>Catalog</CmdType>
int ret = 0;
osip_message_t *invitesip = NULL;
// std::string from = g_sip_from_ipport;
std::string from = g_sip_from_domain;
std::string to = "sip:" + channel_code + "@" + deviceInfo.host + ":" + std::to_string(deviceInfo.port);
eXosip_lock(context_eXosip);
ret = eXosip_call_build_initial_invite(context_eXosip, &invitesip, to.c_str(), from.c_str(), NULL, "");
if (ret < 0)
{
stl::error("(%t)%D [GB28181ServerTask] send_invite_sdp eXosip_call_build_initial_invite fail,from:%s,to:%s", from.c_str(), to.c_str());
return -1;
}
ret = eXosip_call_send_initial_invite(context_eXosip, invitesip);
if (ret < 0)
{
stl::error("(%t)%D [GB28181ServerTask] send_invite eXosip_call_send_initial_invite fail,from:%s,to:%s", from.c_str(), to.c_str());
return -1;
}
{
std::string sdpcontent = SDPHandler::generateSDP(g_local_sip_cfg_svr_id, g_local_sip_cfg_svr_ip, 13398, "0102000001");
osip_message_set_body(invitesip, sdpcontent.c_str(), sdpcontent.length());
osip_message_set_content_type(invitesip, "application/sdp");
}
ret = eXosip_call_send_initial_invite(context_eXosip, invitesip);
eXosip_unlock(context_eXosip);
if (ret < 0)
{
stl::error("(%t)%D [GB28181ServerTask] send_invite_sdp eXosip_call_send_initial_invite fail,from:%s,to:%s", from.c_str(), to.c_str());
return -1;
}
stl::info("(%t)%D [GB28181ServerTask] send_invite_sdp from:%s,to:%s\n", from.c_str(), to.c_str());
return ret;
}
SDP的生成
关于SDP的生成,本来是想使用osip库来生成,但是接口不满足我的需求,暂时懒得改源码,没有搞定,直接自己手工拼的字符串。
std::string SDPHandler::generateSDP(std::string username,std::string rtpIp,uint16_t rtpPort,std::string ssrc)
{
return "v=0\r\no="+username+" 0 0 IN IP4 "+rtpIp+"\r\ns=Play\r\nc=IN IP4 "+rtpIp+"\r\nt=0 0\r\nm=video "+std::to_string(rtpPort)+" RTP/AVP 96 97 98\r\na=recvonly\r\na=rtpmap:96 PS/90000\r\na=rtpmap:97 MPEG4/90000\r\na=rtpmap:98 H264/90000\r\ny="+ssrc+"\r\n";
}
不过还是粘出来我使用osip库的代码,有高手请指点(微信号:yjkhtddx),测试时信息写死的,没有使用变量。
int SDPHandler::AssembleSdp(std::string &sdp)
{
// int SDPHandler::AssembleSdp(std::string &sdp, const sdp_description_t &sdp_desc)
sdp_message_t *osip_sdp;
sdp_message_init(&osip_sdp);
sdp_message_v_version_set(osip_sdp, osip_strdup("0"));
osip_sdp->o_username = osip_strdup("34020000002000000001");
osip_sdp->o_sess_id = osip_strdup("0");
osip_sdp->o_sess_version = osip_strdup("0");
osip_sdp->o_nettype = osip_strdup("IN");
osip_sdp->o_addrtype = osip_strdup("IP4");
osip_sdp->o_addr = osip_strdup("10.0.0.42");
sdp_message_c_connection_add(osip_sdp, -1, osip_strdup("IN"), osip_strdup("IP4"), osip_strdup("10.0.0.42"), nullptr, nullptr);
sdp_message_s_name_set(osip_sdp, osip_strdup("Play"));
sdp_message_t_time_descr_add(osip_sdp, osip_strdup("0"), osip_strdup("0"));
sdp_message_m_media_add(osip_sdp, osip_strdup("video"), osip_strdup("30016"), osip_strdup("96 97 98"), osip_strdup("RTP/AVP"));
sdp_message_a_attribute_add(osip_sdp, -1, osip_strdup("recvonly"), nullptr);
sdp_message_a_attribute_add(osip_sdp, -1, osip_strdup("rtpmap"), osip_strdup("96 PS/90000"));
sdp_message_a_attribute_add(osip_sdp, -1, osip_strdup("rtpmap"), osip_strdup("97 MPEG4/90000"));
sdp_message_a_attribute_add(osip_sdp, -1, osip_strdup("rtpmap"), osip_strdup("98 H264/90000"));
char *sdpPtr = nullptr;
int ret = sdp_message_to_str(osip_sdp, &sdpPtr);
if (sdpPtr)
{
sdp = std::string(sdpPtr);
osip_free(sdpPtr);
}
stl::debug("[%d]>>>\n%s\n<<<", ret, sdp.c_str());
sdp_message_free(osip_sdp);
stl::debug(">>>忒不好用了<<<");
return 0;
}
发送ACK
当国标摄像头返回200时,会触发EXOSIP_CALL_ANSWERED事件,事件处理直接返回ACK就好
int HandlerCall::HandleResponseSuccess(const sip_event_sptr &e)
{
stl::debug("CALL RES status_code:%d",e->exevent->response->status_code);
if(e->exevent->response->status_code != 200) return 0;
osip_message_t *ack = NULL;
eXosip_lock(e->excontext);
eXosip_call_build_ack(e->excontext,e->exevent->tid,&ack);
eXosip_call_send_ack(e->excontext,e->exevent->tid,ack);
eXosip_unlock(e->excontext);
stl::log("send CALL ACK");
return 0;
}
测试
使用命令监听rtp的端口
nc -lu 30016
满屏乱码就对了,下篇使用jrtplib接收数据。
微信号:yjkhtddx