海康sip服务器是什么意思_海康摄像头⼊门
海康RTSP取流URL格式
⼀、预览取流
设备预览取流的RTSPURL有新⽼版本,2012年之前的设备(⽐如V2.0版本的Netra设备)⽀持⽼的取流格式,之后的设备新⽼取流格式都
⽀持。
⽼版本
URL规定:
rtsp://urname:password@//ch/
注:VLC可以⽀持解析URL⾥的⽤户名密码,实际发给设备的RTSP请求不⽀持带⽤户名密码。
举例说明:
DS-9016HF-ST的IP通道01主码流:rtsp://admin:12345@172.6.22.106:554/h264/ch33/main/av_stream
DS-9016HF-ST的模拟通道01⼦码流:rtsp://admin:12345@172.6.22.106:554/h264/ch1/sub/av_stream
DS-9016HF-ST的零通道主码流(零通道⽆⼦码流):rtsp://admin:12345@172.6.22.106:554/h264/ch0/main/av_stream
DS-2DF7274-A的第三码流:rtsp://admin:12345@172.6.10.11:554/h264/ch1/stream3/av_stream
URL规定:
rtsp://urname:password@
:/Streaming/Channels/(?parm1=value1&parm2-=value2…)
注:VLC可以⽀持解析URL⾥的⽤户名密码,实际发给设备的RTSP请求不⽀持带⽤户名密码。
详细描述:
举例说明:
DS-9632N-ST的IP通道01主码流:rtsp://admin:12345@172.6.22.234:554/Streaming/Channels/101?
transportmode=unicast
DS-9016HF-ST的IP通道01主码流:rtsp://admin:12345@172.6.22.106:554/Streaming/Channels/1701?
transportmode=unicast
DS-9016HF-ST的模拟通道01⼦码流:rtsp://admin:12345@172.6.22.106:554/Streaming/Channels/102?
transportmode=unicast
(单播):rtsp://admin:12345@172.6.22.106:554/Streaming/Channels/102?transportmode=multicast
(多播):rtsp://admin:12345@172.6.22.106:554/Streaming/Channels/102(?后⾯可省略,默认单播)
DS-9016HF-ST的零通道主码流(零通道⽆⼦码流):rtsp://admin:12345@172.6.22.106:554/Streaming/Channels/001
DS-2DF7274-A的第三码流:rtsp://admin:12345@172.6.10.11:554/Streaming/Channels/103
注:前⾯⽼URL,NVR(>=64路的除外)的IP通道从33开始;新URL,通道号全部按顺序从1开始。
URL规定:
rtsp://urname:password@
:/Streaming/tracks/(?parm1=value1&parm2-=value2…)
注:VLC可以⽀持解析URL⾥的⽤户名密码,实际发给设备的RTSP请求不⽀持带⽤户名密码。
举例说明:
DS-9016HF-ST的模拟通道01:
rtsp://admin:12345@172.6.22.106:554/Streaming/tracks/101?starttime=20120802t063812z&endtime=20120802t064816z
DS-9016HF-ST的IP通道01:rtsp://admin:12345@172.6.22.106:554/Streaming/tracks/1701?
starttime=20131013t093812z&endtime=20131013t104816z
表⽰以单播形式回放指定设备的通道中的录像⽂件,时间范围是starttime到endtime,
其中starttime和endtime的格式要符合ISO8601。具体格式是:
YYYYMMDD”T”on”Z”,Y是年,M是⽉,D是⽇,T是时间分格符,H是⼩时,M是分,S是秒,Z是可选的、表⽰
Zulu(GMT)时间。
VLC播放⽰例:
媒体--》打开⽹络串流--》⽹络:
rtsp://urname:password@192.168.1.17:554/MPEG-4/ch1/main/av_stream
Linux下编译eXosip2库以及测试
原⽂作者:这个名字不知道有没有⼈⽤啊
环境:
Ubuntu18.04+libosip2-5.1.0+libexosip2-5.1.0+c-ares-1.15.0
下载
依次解压编译(注意顺序,exosip要在最后编译)
tarxvf对应压缩包名
cd解压出来的⽂件夹
./configuremake
sudomakeinstall
测试
#include#include#include#include
usingnamespacestd;intmain()
{
eXosip_t*sip=eXosip_malloc();if(eXosip_init(sip)==OSIP_SUCCESS)
{
cout<<"eXosipinitok"<
}el{
cout<<"exosipinitfail"<
}intret=eXosip_listen_addr(sip,IPPROTO_UDP,NULL,0,AF_INET,0);if(ret==OSIP_SUCCESS)
{
cout<<"exosioplistenaddrsuccess"<
}elcout<<"listenaddrfail,ret:"<
eXosip_quit(sip);
cout<<"test"<
}
编译运⾏
g++-losip2-leXosip2
./
#include#include#include#include#include#include#include
intmain(intargc,char*argv[])
{
structeXosip_t*excontext;
eXosip_event_t*je;
osip_message_t*reg=NULL;
osip_message_t*invite=NULL;
osip_message_t*ack=NULL;
osip_message_t*info=NULL;
osip_message_t*message=NULL;intcall_id,dialog_id;inti,flag;intflag1=1;intiReturnCode;char
identity[30]="sip:140@127.0.0.1";//UAC1,端⼝是15060
charregistar[30]="sip:133@127.0.0.1:15061";//UAS,端⼝是15061
charsource_call[30]="sip:140@127.0.0.1";chardest_call[30]="sip:133@127.0.0.1:15061";//identify和register这⼀组地址是
和source和destination地址相同的//在这个例⼦中,uac和uas通信,则source就是⾃⼰的地址,⽽⽬的地址就是uac1的地址
charcommand;chartmp[4096];
std::cout<<"r向服务器注册"<<:endl>
std::cout<<"c取消注册"<<:endl>
std::cout<<"i发起呼叫请求"<<:endl>
std::cout<<"h挂断"<<:endl>
std::cout<<"q推出程序"<<:endl>
std::cout<<"s执⾏⽅法INFO"<<:endl>
std::cout<<"m执⾏⽅法MESSAGE"<<:endl>
excontext=eXosip_malloc();
iReturnCode=eXosip_init(excontext);if(iReturnCode!=0)
{
printf("Can'tinitializeeXosip!n");
return-1;
}el{
printf("eXosip_initsuccessfully!n");
}//绑定uac⾃⼰的端⼝15060,并进⾏端⼝监听
iReturnCode=eXosip_listen_addr(excontext,IPPROTO_UDP,NULL,15060,AF_INET,0);if(iReturnCode!=0)
{
eXosip_quit(excontext);
fprintf(stderr,"Couldn'tinitializetransportlayer!n");
return-1;
}while(true)
{//输⼊命令
std::cout<<"Pleainputthecommand:"<<:endl>
std::cin>>command;
switch(command)
{ca'r':
std::cout<<"Thismodalisnotcompleted!"<<:endl>
break;ca'i'://INVITE,发起呼叫请求
i=eXosip_call_build_initial_invite(excontext,&invite,dest_call,source_call,NULL,"Thisisacallforconversation");if(i!=0)
{
std::cout<<"InitialINVITEfailed!"<<:endl>
break;
}//符合SDP格式,其中属性a是⾃定义格式,也就是说可以存放⾃⼰的信息,//但是只能有两列,⽐如帐户信息//但是经过测试,格式vot
必不可少,原因未知,估计是协议栈在传输时需要检查的
snprintf(tmp,4096,"v=0rn"
"o=anonymous00INIP40.0.0.0rn"
"t=110rn"
"a=urname:rainfishrn"
"a=password:123rn");
osip_message_t_body(invite,tmp,strlen(tmp));
osip_message_t_content_type(invite,"application/sdp");
eXosip_lock(excontext);
i=eXosip_call_nd_initial_invite(excontext,invite);//inviteSIPINVITEmessagetond
eXosip_unlock(excontext);//发送了INVITE消息,等待应答
flag1=1;while(flag1)
{
je=eXosip_event_wait(excontext,0,200);//WaitforaneXosipevent//(超时时间秒,超时时间毫秒)
if(je==NULL)
{
printf("Noresponorthetimeisover!n");
break;
}
switch(je->type)//可能会到来的事件类型
{caEXOSIP_CALL_INVITE://收到⼀个INVITE请求
printf("anewinvitereceived!n");
break;caEXOSIP_CALL_PROCEEDING://收到100trying消息,表⽰请求正在处理中
printf("proceeding!n");
break;caEXOSIP_CALL_RINGING://收到180Ringing应答,表⽰接收到INVITE请求的UA
printf("ringing!n");
printf("call_idis%d,dialog_idis%dn",je->cid,je->did);
break;caEXOSIP_CALL_ANSWERED://收到200OK,表⽰请求已经被成功接受,⽤户应答
printf("ok!connected!n");
call_id=je->cid;
dialog_id=je->did;
printf("call_idis%d,dialog_idis%dn",je->cid,je->did);//回送ack应答消息
eXosip_call_build_ack(excontext,je->did,&ack);
eXosip_call_nd_ack(excontext,je->did,ack);
flag1=0;//推出While循环
break;caEXOSIP_CALL_CLOSED://aBYEwasreceivedforthiscall
printf("theothersidclod!n");
break;caEXOSIP_CALL_ACK://ACKreceivedfor200oktoINVITE
printf("ACKreceived!n");
break;
default://收到其他应答
printf("otherrespon!n");
break;
}
eXosip_event_free(je);//FreeressourceinaneXosipevent
}
break;ca'h'://挂断
printf("Holded!n");
eXosip_lock(excontext);
eXosip_call_terminate(excontext,call_id,dialog_id);
eXosip_unlock(excontext);
break;ca'c':
printf("Thismodalisnotcommpleted!n");
break;ca's'://传输INFO⽅法
eXosip_call_build_info(excontext,dialog_id,&info);
snprintf(tmp,4096,"nThisisasipmessage(Method:INFO)");
osip_message_t_body(info,tmp,strlen(tmp));//格式可以任意设定,text/plain代表⽂本信息;
osip_message_t_content_type(info,"text/plain");
eXosip_call_nd_request(excontext,dialog_id,info);
break;ca'm'://传输MESSAGE⽅法,也就是即时消息,和INFO⽅法相⽐,我认为主要区别是://MESSAGE不⽤建⽴连接,直接传输
信息,⽽INFO消息必须在建⽴INVITE的基础上传输
printf("themethod:MESSAGEn");
eXosip_message_build_request(excontext,&message,"MESSAGE",dest_call,source_call,NULL);//内容,⽅法,to,from
,route
snprintf(tmp,4096,"Thisisasipmessage(Method:MESSAGE)");
osip_message_t_body(message,tmp,strlen(tmp));//假设格式是xml
osip_message_t_content_type(message,"text/xml");
eXosip_message_nd_request(excontext,message);
break;ca'q':
eXosip_quit(excontext);
printf("Exitthetup!n");
flag=0;
break;
}
}
return(0);
}
ViewCode
#include#include#include#include#include#include#include
//#include
intmain(intargc,char*argv[])
{
structeXosip_t*excontext;
eXosip_event_t*je=NULL;
osip_message_t*ack=NULL;
osip_message_t*invite=NULL;
osip_message_t*answer=NULL;
sdp_message_t*remote_sdp=NULL;intcall_id,dialog_id;inti,j,iReturnCode;intid;charsour_call[30]=
"sip:140@127.0.0.1";chardest_call[30]="sip:133@127.0.0.1:15060";//clientip
charcommand;chartmp[4096];charlocalip[128];intpos=0;//初始化sip
excontext=eXosip_malloc();
iReturnCode=eXosip_init(excontext);if(iReturnCode!=0)
{
printf("Can'tinitializeeXosip!n");
return-1;
}el{
printf("eXosip_initsuccessfully!n");
}
iReturnCode=eXosip_listen_addr(excontext,IPPROTO_UDP,NULL,15061,AF_INET,0);if(iReturnCode!=0)
{
eXosip_quit(excontext);
fprintf(stderr,"eXosip_listen_addrerror!nCouldn'tinitializetransportlayer!n");
}for(;;)
{//侦听是否有消息到来
je=eXosip_event_wait(excontext,0,50);//协议栈带有此语句,具体作⽤未知
eXosip_lock(excontext);
eXosip_default_action(excontext,je);//eXosip_automatic_refresh(excontext);
eXosip_unlock(excontext);if(je==NULL)//没有接收到消息
continue;//printf("thecidis%s,didis%s/n",je->did,je->cid);
switch(je->type)
{caEXOSIP_MESSAGE_NEW://新的消息到来
printf("EXOSIP_MESSAGE_NEW!n");if(MSG_IS_MESSAGE(je->request))//如果接受到的消息类型是MESSAGE
{
{
osip_body_t*body;
osip_message_get_body(je->request,0,&body);
printf("Igetthemsgis:%sn",body->body);//printf("thecidis%s,didis%s/n",je->did,je->cid);
}//按照规则,需要回复OK信息
eXosip_message_build_answer(excontext,je->tid,200,&answer);
eXosip_message_nd_answer(excontext,je->tid,200,answer);
}
break;caEXOSIP_CALL_INVITE://得到接收到消息的具体信息
printf("ReceivedaINVITEmsgfrom%s:%s,UrNameis%s,passwordis%sn",je->request->req_uri->host,
je->request->req_uri->port,je->request->req_uri->urname,je->request->req_uri->password);//得到消息体,认为该消息就是SDP
格式.
remote_sdp=eXosip_get_remote_sdp(excontext,je->did);
call_id=je->cid;
dialog_id=je->did;
eXosip_lock(excontext);
eXosip_call_nd_answer(excontext,je->tid,180,NULL);
i=eXosip_call_build_answer(excontext,je->tid,200,&answer);if(i!=0)
{
printf("Thisrequestmsgisinvalid!Cann'trespon!n");
eXosip_call_nd_answer(excontext,je->tid,400,NULL);
}el{
snprintf(tmp,4096,"v=0rn"
"o=anonymous00INIP40.0.0.0rn"
"t=110rn"
"a=urname:rainfishrn"
"a=password:123rn");//设置回复的SDP消息体,下⼀步计划分析消息体//没有分析消息体,直接回复原来的消息,这⼀块做的不好。
osip_message_t_body(answer,tmp,strlen(tmp));
osip_message_t_content_type(answer,"application/sdp");
eXosip_call_nd_answer(excontext,je->tid,200,answer);
printf("nd200over!n");
}
eXosip_unlock(excontext);//显⽰出在sdp消息体中的attribute的内容,⾥⾯计划存放我们的信息
printf("theINFOis:n");while(!osip_list_eol(&(remote_sdp->a_attributes),pos))
{
sdp_attribute_t*at;
at=(sdp_attribute_t*)osip_list_get(&remote_sdp->a_attributes,pos);
printf("%s:%sn",at->a_att_field,at->a_att_value);//这⾥解释了为什么在SDP消息体中属性a⾥⾯存放必须是两列
pos++;
}
break;caEXOSIP_CALL_ACK:
printf("ACKrecieved!n");//printf("thecidis%s,didis%s/n",je->did,je->cid);
break;caEXOSIP_CALL_CLOSED:
printf("theremoteholdthession!n");//eXosip_call_build_ack(dialog_id,&ack);//eXosip_call_nd_ack(dialog_id,ack);
i=eXosip_call_build_answer(excontext,je->tid,200,&answer);if(i!=0)
{
printf("Thisrequestmsgisinvalid!Cann'trespon!n");
eXosip_call_nd_answer(excontext,je->tid,400,NULL);
}el{
eXosip_call_nd_answer(excontext,je->tid,200,answer);
printf("byend200over!n");
}
break;caEXOSIP_CALL_MESSAGE_NEW://⾄于该类型和EXOSIP_MESSAGE_NEW的区别,源代码这么解释的
/*//requestrelatedeventswithincalls(exceptINVITE)
EXOSIP_CALL_MESSAGE_NEW,
//responreceivedforrequestoutsidecalls
EXOSIP_MESSAGE_NEW,
我也不是很明⽩,理解是:EXOSIP_CALL_MESSAGE_NEW是⼀个呼叫中的新的消息到来,⽐如ringtrying都算,所以在接受到后必须
判断
该消息类型,EXOSIP_MESSAGE_NEW⽽是表⽰不是呼叫内的消息到来。
该解释有不妥地⽅,仅供参考。*/printf("EXOSIP_CALL_MESSAGE_NEWn");if(MSG_IS_INFO(je->request))//如果传输的是INFO
⽅法
{
eXosip_lock(excontext);
i=eXosip_call_build_answer(excontext,je->tid,200,&answer);if(i==0)
{
eXosip_call_nd_answer(excontext,je->tid,200,answer);
}
eXosip_unlock(excontext);
{
osip_body_t*body;
osip_message_get_body(je->request,0,&body);
printf("thebodyis%sn",body->body);
}
}
break;
default:
printf("Couldnotparthemsg!n");
}
}
}
ViewCode
编译并运⾏
g++-ouac-losip2-leXosip2-lpthread-losipparr2
g++-ouas-losip2-leXosip2-lpthread-losipparr2
./uas
./uac
exosip对接海康摄像头
#include#include#include#include#include#include#include
staticvoidRegisterSuccess(structeXosip_t*peCtx,eXosip_event_t*je)
{intiReturnCode=0;
osip_message_t*pSRegister=NULL;
iReturnCode=eXosip_message_build_answer(peCtx,je->tid,200,&pSRegister);if(iReturnCode==0&&pSRegister!=NULL
)
{
eXosip_lock(peCtx);
eXosip_message_nd_answer(peCtx,je->tid,200,pSRegister);
eXosip_unlock(peCtx);//osip_message_free(pSRegister);
}
}voidRegisterFailed(structeXosip_t*peCtx,eXosip_event_t*je){intiReturnCode=0;
osip_message_t*pSRegister=NULL;
iReturnCode=eXosip_message_build_answer(peCtx,je->tid,401,&pSRegister);if(iReturnCode==0&&pSRegister!=NULL
)
{
eXosip_lock(peCtx);
eXosip_message_nd_answer(peCtx,je->tid,401,pSRegister);
eXosip_unlock(peCtx);
}
}intmain(intargc,char*argv[])
{structeXosip_t*excontext;
eXosip_event_t*je;
osip_message_t*reg=NULL;
osip_message_t*invite=NULL;
osip_message_t*ack=NULL;
osip_message_t*info=NULL;
osip_message_t*message=NULL;intcall_id,dialog_id;inti,flag;intflag1=1;intiReturnCode;intregisterOk;char*p;char
identity[30]="sip:140@127.0.0.1";//UAC1,端⼝是15060
charregistar[30]="sip:133@127.0.0.1:15061";//UAS,端⼝是15061
charsource_call[30]="sip:140@127.0.0.1";chardest_call[30]="sip:133@127.0.0.1:15061";//identify和register这⼀组地址是
和source和destination地址相同的//在这个例⼦中,uac和uas通信,则source就是⾃⼰的地址,⽽⽬的地址就是uac1的地址
charcommand;chartmp[4096];
std::cout<<"r向服务器注册"<<:endl>
std::cout<<"c取消注册"<<:endl>
std::cout<<"i发起呼叫请求"<<:endl>
std::cout<<"h挂断"<<:endl>
std::cout<<"q推出程序"<<:endl>
std::cout<<"s执⾏⽅法INFO"<<:endl>
std::cout<<"m执⾏⽅法MESSAGE"<<:endl>
excontext=eXosip_malloc();
iReturnCode=eXosip_init(excontext);if(iReturnCode!=0)
{
printf("Can'tinitializeeXosip!n");return-1;
}el{
printf("eXosip_initsuccessfully!n");
}//绑定uac⾃⼰的端⼝15060,并进⾏端⼝监听
iReturnCode=eXosip_listen_addr(excontext,IPPROTO_UDP,NULL,5060,AF_INET,0);if(iReturnCode!=0)
{
eXosip_quit(excontext);
fprintf(stderr,"Couldn'tinitializetransportlayer!n");return-1;
}while(true)
{
eXosip_event_t*je=NULL;
je=eXosip_event_wait(excontext,0,4);if(je==NULL){
std::cout<<"eventisnull"<<:endl>
osip_usleep(100000*50);continue;
}switch(je->type){caEXOSIP_MESSAGE_NEW:
{//printf("newmsgmethod:%sn",je->request->sip_method);
if(MSG_IS_REGISTER(je->request)){
std::cout<<"msgbody:"<<:endl>
registerOk=1;
}elif(MSG_IS_MESSAGE(je->request)){
osip_body_t*body=NULL;
osip_message_get_body(je->request,0,&body);if(body!=NULL){
p=strstr(body->body,"Keepalive");if(p!=NULL){
registerOk=1;
std::cout<<"msgbody:"<<:endl>
std::cout<
}el{
std::cout<<"msgbody:"<<:endl>
std::cout<
}
}el{
std::cout<<"getbodyfailed"<<:endl>
}
}elif(strncmp(je->request->sip_method,"BYE",4)!=0){
std::cout<<"unsupportnewmsgmethod:"<
}
RegisterSuccess(excontext,je);
}break;caEXOSIP_MESSAGE_ANSWERED:
{
printf("answeredmethod:%sn",je->request->sip_method);
RegisterSuccess(excontext,je);
}break;caEXOSIP_CALL_ANSWERED:
{
osip_message_t*ack=NULL;
call_id=je->cid;
dialog_id=je->did;
printf("callansweredmethod:%s,call_id:%d,dialog_id:%dn",je->request->sip_method,call_id,dialog_id);
eXosip_call_build_ack(excontext,je->did,&ack);
eXosip_lock(excontext);
eXosip_call_nd_ack(excontext,je->did,ack);
eXosip_unlock(excontext);
}break;caEXOSIP_CALL_PROCEEDING:
{
printf("recvEXOSIP_CALL_PROCEEDINGn");
RegisterSuccess(excontext,je);
}break;caEXOSIP_CALL_REQUESTFAILURE:
{
printf("recvEXOSIP_CALL_REQUESTFAILUREn");
RegisterSuccess(excontext,je);
}break;caEXOSIP_CALL_MESSAGE_ANSWERED:
{
printf("recvEXOSIP_CALL_MESSAGE_ANSWEREDn");
RegisterSuccess(excontext,je);
}break;caEXOSIP_CALL_RELEASED:
{
printf("recvEXOSIP_CALL_RELEASEDn");
RegisterSuccess(excontext,je);
}break;caEXOSIP_CALL_CLOSED:
{
printf("recvEXOSIP_CALL_CLOSEDn");
RegisterSuccess(excontext,je);
}break;caEXOSIP_CALL_MESSAGE_NEW:
{
printf("recvEXOSIP_CALL_MESSAGE_NEWn");
RegisterSuccess(excontext,je);
}break;default:
{
printf("##test,%s:%d,unsupporttype:%dn",__FILE__,__LINE__,je->type);
RegisterSuccess(excontext,je);
}break;
}
eXosip_event_free(je);
}return(0);
}
ViewCode
海康--》sip服务器发送注册包
sip服务器--》海康发送200包
sip服务器--》海康摄像头发送message保活包
问题汇总:
./:errorwhileloadingsharedlibraries:.12:cannotopensharedobjectfile:Nosuchfileordirectory
解决办法:
sudovim/etc/
末尾添加/usr/local/lib
保存退出,执⾏sudoldconfig
参考⽂档:
从海康7816的ps流⾥获取数据h264数据
github:作为上级域,可以对接海康、⼤华、宇视等gb28181平台,获取ps流,转换为标准h.264裸流
python-librtmp
librtmp使⽤的是0.3.0,使⽤树莓派noir官⽅摄像头适配的。
⽬的是能使⽤Python进⾏rtmp推流,⽅便在h264帧⾥加⼊弹幕等操作。通过wireshark抓ffmpeg的包⼀点点改动,最终可以在red5和⽃
鱼上推流了。
importpicameraimporttimeimporttracebackimportctypesfromlibrtmpimport*
globalmeta_packetglobalstart_timeclassWriter():#camera可以通过⼀个类⽂件的对象来输出,实现write⽅法即可
conn=None#rtmp连接
sps=None#记录sps帧,发过以后就不需要再发了(抓包看到ffmpeg是这样的)
pps=None#同上
sps_len=0#同上
pps_len=0#同上
time_stamp=0def__init__(lf,conn):
=conndefwrite(lf,data):try:#寻找h264帧间隔符
indexs=[]
index=0
data_len=len(data)whileindex
data[index+2])==0x00andord(data[index+3])==0x01:
(index)
index=index+3index=index+1
#寻找h264帧间隔符完成
#通过间隔符个数确定类型,树莓派摄像头的第⼀帧是sps+pps同时发的
iflen(indexs)==1:#⾮spspps帧
buf=data[4:len(data)]#裁掉原来的头(00000001),把帧内容拿出来
buf_len=len(buf)
type=ord(buf[0])&0x1f
iftype==0x05:#关键帧,根据wireshark抓包结果,需要拼装spspps帧内容三部分,长度都⽤4个字节表⽰
body0=0x17data_body_array=[bytes(bytearray(
[body0,0x01,0x00,0x00,0x00,(_len>>24)&0xff,(_len>>16)&0xff,
(_len>>8)&0xff,
_len&0xff])),,
bytes(bytearray(
[(_len>>24)&0xff,(_len>>16)&0xff,(_len>>8)&0xff,
_len&0xff])),
,
bytes(bytearray(
[(buf_len>>24)&0xff,(buf_len>>16)&0xff,(buf_len>>8)&0xff,(buf_len)&0xff])),
buf
]
mbody=''.join(data_body_array)
time_stamp=0#第⼀次发出的时候,发时间戳0,此后发真时间戳
_stamp!=0:
time_stamp=int((()-start_time)*1000)
packet_body=RTMPPacket(type=PACKET_TYPE_VIDEO,format=PACKET_SIZE_LARGE,channel=0x06,
timestamp=time_stamp,body=mbody)
packet_.m_nInfoField2=1
el:#⾮关键帧
body0=0x27data_body_array=[bytes(bytearray(
[body0,0x01,0x00,0x00,0x00,(buf_len>>24)&0xff,(buf_len>>16)&0xff,
(buf_len>>8)&0xff,
(buf_len)&0xff])),buf]
mbody=''.join(data_body_array)#if(_stamp==0):
_stamp=int((()-start_time)*1000)
packet_body=RTMPPacket(type=PACKET_TYPE_VIDEO,format=PACKET_SIZE_MEDIUM,channel=0x06,
timestamp=_stamp,body=mbody)
_packet(packet_body)eliflen(indexs)==2:#spspps帧
otNone:returndata_body_array=[bytes(bytearray([0x17,0x00,0x00,0x00,0x00,0x01]))]
sps=data[indexs[0]+4:indexs[1]]
sps_len=len(sps)
pps=data[indexs[1]+4:len(data)]
pps_len=len(pps)
=sps
_len=sps_len
=pps
_len=pps_len
data_body_(sps[1:4])
data_body_(bytes(bytearray([0xff,0xe1,(sps_len>>8)&0xff,sps_len&0xff])))
data_body_(sps)
data_body_(bytes(bytearray([0x01,(pps_len>>8)&0xff,pps_len&0xff])))
data_body_(pps)
data_body=''.join(data_body_array)
body_packet=RTMPPacket(type=PACKET_TYPE_VIDEO,format=PACKET_SIZE_LARGE,channel=0x06,
timestamp=0,body=data_body)
body_.m_nInfoField2=_packet(meta_packet,queue=True)
_packet(body_packet,queue=True)exceptException,e:
_exc()defflush(lf):pass
defget_property_string(string):#返回两字节string长度及string
length=len(string)return''.join([chr((length>>8)&0xff),chr(length&0xff),string])defget_meta_string(string):#按照meta
packet要求格式返回bytes,带02前缀
return''.join([chr(0x02),get_property_string(string)])defget_meta_double(db):
nums=[0x00]
fp=r(ctypes.c_double(db))
cp=(fp,R(ctypes.c_longlong))foriinrange(7,-1,-1):
((>>(i*8))&0xff)return''.join(bytes(bytearray(nums)))defget_meta_boolean(isTrue):
nums=[0x01]if(isTrue):
(0x01)el:
(0x00)return''.join(bytes(bytearray(nums)))
conn=RTMP('rtmp://192.168.199.154/oflaDemo/test',#推流地址
live=True)
_EnableWrite()
t()
start_time=()#拼装视频格式的数据包
meta_body_array=[get_meta_string('@tDataFrame'),get_meta_string('onMetaData'),
bytes(bytearray([0x08,0x00,0x00,0x00,0x06])),#两个字符串和ECMAarray头,共计6个元素,注释掉了⾳频相关数据
get_property_string('width'),get_meta_double(640.0),
get_property_string('height'),get_meta_double(480.0),
get_property_string('videodatarate'),get_meta_double(0.0),
get_property_string('framerate'),get_meta_double(25.0),
get_property_string('videocodecid'),get_meta_double(7.0),#get_property_string('audiodatarate'),get_meta_double(125.0),
#get_property_string('audiosamplerate'),get_meta_double(44100.0),
#get_property_string('audiosamplesize'),get_meta_double(16.0),
#get_property_string('stereo'),get_meta_boolean(True),
#get_property_string('audiocodecid'),get_meta_double(10.0),
get_property_string('encoder'),get_meta_string('Lavf57.56.101'),
bytes(bytearray([0x00,0x00,0x09]))
]
meta_body=''.join(meta_body_array)printmeta_('hex')
meta_packet=RTMPPacket(type=PACKET_TYPE_INFO,format=PACKET_SIZE_LARGE,channel=0x04,
timestamp=0,body=meta_body)
meta_.m_nInfoField2=1#修改streamid
stream=_stream(writeable=True)
ra()ascamera:
_preview()
(2)
_recording(Writer(conn),format='h264',resize=(640,480),intra_period=25,
quality=25)#开始录制,数据输出到Writer的对象⾥
whileTrue:#永远不停⽌
(60)
_recording()
_preview()
srs-librtmp
SRS提供的librtmp
应⽤场景
librtmp的主要应⽤场景包括:
播放RTMP流:譬如rtmpdump,将服务器的流读取后保存为flv⽂件。
推流:提供推流到RTMP服务器。
基于同步阻塞socket,客户端⽤可以了。
arm:编译出来给arm-linux⽤,譬如某些设备上,采集后推送到RTMP服务器。
不⽀持直接发布h.264裸码流,⽽srs-librtmp⽀持,参考:publish-h264-raw-data
备注:关于链接ssl,握⼿协议,简单握⼿和复杂握⼿,参考RTMP握⼿协议
备注:ARM上使⽤srs-librtmp需要交叉编译,参考srs-arm,即使⽤交叉编译环境编译srs-librtmp(可以不依赖于其他库,ssl/st都不需
要)
librtmp做Server
群⾥有很多⼈问,librtmp如何做rver,实在不胜其骚扰,所以单列⼀章。
rver的特点是会有多个客户端连接,⾄少有两个:⼀个推流连接,⼀个播放连接。所以rver有两种策略:
每个连接⼀个线程或进程:像apache。这样可以⽤同步socket来收发数据(同步简单)。坏处就是没法⽀持很⾼并发,1000个已经到顶了,
得开1000个线程/进程啊。
使⽤单进程,但是⽤异步socket:像nginx这样。好处就是能⽀持很⾼并发。坏处就是异步socket⿇烦。
rtmpdump提供的librtmp,当然是基于同步socket的。所以使⽤librtmp做rver,只能采取第⼀种⽅法,即⽤多线程处理多个连接。多
线程多⿇烦啊!要锁,同步,⽽且还⽀持不了多少个。
librtmp的定位就是客户端程序,偏偏要超越它的定位去使⽤,这种⼤约只有中国⼈才能这样“⽆所畏惧”。
嵌⼊式设备上做rtmprver,当然可以⽤srs/crtmpd/nginx-rtmp,轮也轮不到librtmp。
SRS为何提供librtmp
srs提供的客户端srs-librtmp的定位和librtmp不⼀样,主要是:
librtmp的代码确实很烂,⽏庸置疑,典型的代码堆积。
librtmp接⼝定义不良好,这个对⽐srs就可以看出,使⽤起来得看实现代码。
没有实例:接⼝的使⽤最好提供实例,srs提供了publish/play/rtmpdump实例。
最⼩依赖关系:srs调整了模块化,只取出了core/kernel/rtmp三个模块,其他代码没有编译到srs-librtmp中,避免了冗余。
最少依赖库:srs-librtmp只依赖c/c++标准库(若需要复杂握⼿需要依赖openssl,srs也编译出来了,只需要加⼊链接即可)。
不依赖st:srs-librtmp使⽤同步阻塞socket,没有使⽤st(st主要是服务器处理并发需要)。
SRS提供了测速函数,直接调⽤srs-librtmp就可以完成到服务器的测速。参考:BandwidthTest
SRS提供了⽇志接⼝,可以获取服务器端的信息,譬如版本,对应的ssionid。参考:Tracablelog
SRS可以直接导出⼀个srs-librtmp的project,编译成.h和.a使⽤。或者导出为.h和.cpp,⼀个⼤⽂件。参考:exportsrslibrtmp
⼀句话,srs为何提供客户端开发库?因为rtmp客户端开发不⽅便,不直观,不简洁。
ExportSrsLibrtmp
SRS在2.0提供了导出srs-librtmp的编译选项,可以将srs-librtmp单独导出为project,单独编译⽣成.h和.a,⽅便在linux和windows平台
编译。
使⽤⽅法,导出为project,可以make成.h和.a:
dir=/home/winlin/srs-librtmp&&
rm-rf$dir&&
./configure--export-librtmp-project=$dir&&
cd$dir&&make&&
./objs/rearch/librtmp/srs_playrtmp:///live/livestream
SRS将srs-librtmp导出为独⽴可以make的项⽬,⽣成.a静态库和.h头⽂件,以及⽣成了srs-librtmp的所有实例。
还可以直接导出为⼀个⽂件,提供了简单的使⽤实例,其他实例参考rearch的其他例⼦:
dir=/home/winlin/srs-librtmp&&
rm-rf$dir&&
./configure--export-librtmp-single=$dir&&
cd$dir&&_-g-O0-lstdc++-oexample&&
stripexample&&./example
备注:导出⽬录⽀持相对⽬录和绝对⽬录。
编译srs-librtmp
编译SRS时,会⾃动编译srs-librtmp,譬如:
./configure--with-librtmp--without-ssl
编译会⽣成srs-librtmp和对应的实例。
备注:⽀持librtmp只需要打开--with-librtmp,但推荐打开--without-ssl,不依赖于ssl,对于⼀般客户端(不需要模拟flash)⾜够了。这样
srs-librtmp不依赖于任何其他库,在x86/x64/arm等平台都可以编译和运⾏
备注:就算打开了--with-ssl,srslibrtmp也只提供simple_handshake函数,不提供complex_handshake函数。所以推荐关闭ssl,不依
赖于ssl,没有实际的⽤处。
SRS编译成功后,⽤户就可以使⽤这些库开发
Windows下编译srs-librtmp
srs-librtmp可以只依赖于c++和socket,可以在windows下编译。
先使⽤SRS导出srs-librtmp,然后在vs中编译,参考:exportsrslibrtmp
使⽤了⼀些linux的头⽂件,需要做⼀些portal。
注意:srs-librtmp客户端推流和抓流,不需要ssl库。代码都是c++/stl,⽹络部分⽤的是同步socket。
数据格式
srs-librtmp提供了⼀系列接⼝函数,就数据按照⼀定格式发送到服务器,或者从服务器读取⾳视频数据。
数据接⼝包括:
读取数据包:intsrs_read_packet(int*type,u_int32_t*timestamp,char**data,int*size)
发送数据包:intsrs_write_packet(inttype,u_int32_ttimestamp,char*data,intsize)
接⼝接受的的数据(char*data),⾳视频数据,格式为flv的Video/Audio数据。参考srs的doc⽬录的规范⽂件
video_file_format_spec_v10_
⾳频数据格式参考:E.4.2.1AUDIODATA,p76,譬如,aac编码的⾳频数据。
视频数据格式参考:E.4.3.1VIDEODATA,p78,譬如,h.264编码的视频数据。
脚本数据格式参考:E.4.4.1SCRIPTDATA,p80,譬如,onMetadata,流的信息(宽⾼,码率,分辨率等)
数据类型(inttype)定义如下(E.4.1FLVTag,page75):
⾳频:8=audio,宏定义:SRS_RTMP_TYPE_AUDIO
视频:9=video,宏定义:SRS_RTMP_TYPE_VIDEO
脚本数据:18=scriptdata,宏定义:SRS_RTMP_TYPE_SCRIPT
其他的数据,譬如时间戳,都是通过参数接受和发送。
另外,⽂档其他重要信息:
flv⽂件头格式:E.2TheFLVheader,p74。
flv⽂件主体格式:E.3TheFLVFileBody,p74。
tag头格式:E.4.1FLVTag,p75。
使⽤flv格式的原因:
flv的格式⾜够简单。
ffmpeg也是⽤的这种格式
收到流后加上flvtagheader,就可以直接保存为flv⽂件
从flv⽂件解封装数据后,只要将tag的内容给接⼝就可以,flv的tag头很简单。
PublishH.264RawData
SRS-librtmp⽀持发布h.264裸码流,直接调⽤api即可将数据发送给SRS。
总结起来就是说,H264的裸码流(帧)转换RTMP时:
dts和pts是不在h264流中的,外部给出。
SPS和PPS在RTMP⼀个包⾥⾯发出去。
RTMP包=5字节RTMP包头+H264头+H264数据,具体参考:SrsAvcAacCodec::video_avc_demux
直接提供接⼝,发送h264数据,其中包含annexb的头:N[00]000001,whereN>=0.
加了⼀个直接发送h264裸码流的接⼝:
/**
*writeh.264rawframeoverRTMPtortmprver.
*@paramframestheinputh264rawdata,encodedh.264I/P/Bframesdata.
*framescanbeoneormorethanoneframe,
*eachframeprefixedh.264annexbheader,byN[00]000001,whereN>=0,
*forinstance,frame=header(00000001)+payload(6742802995A014016E40)
*aboutannexb,@eH.264-AVC-ISO_IEC_,page211.
*@paamframes_sizethesizeofh264rawdata.
*asrtframes_size>0,atleasthas1bytesheader.
*@paramdtsthedtsofh.264rawdata.
*@paramptstheptsofh.264rawdata.
*
*@remark,urshouldfreetheframes.
*@remark,thetbnofdts/ptsis1/1000forRTMP,thatis,inms.
*@remark,cts=pts-dts
*
*@return0,success;otherswi,failed.
*/
externintsrs_h264_write_raw_frames(srs_rtmp_trtmp,
char*frames,intframes_size,u_int32_tdts,u_int32_tpts
);
⾥⾯的数据是:
//SPS
802995A014016E40
//PPS
CE3880
//IFrame
B8041014C038008B0D0D3A071.....
//PFrame
E02041F8CDDC562BBDEFAD2F.....
调⽤时,可以SPS和PPS⼀起发,帧⼀次发⼀个:
//SPS+PPS
srs_h264_write_raw_frame('802995A014016E4CE3880',size,dts,pts)
//IFrame
srs_h264_write_raw_frame('B8041014C038008B0D0D3A071......',size,dts,pts)
//PFrame
srs_h264_write_raw_frame('E02041F8CDDC562BBDEFAD2F......',size,dts,pts)
调⽤时,可以⼀次发⼀次frame也⾏:
//SPS
srs_h264_write_raw_frame('802995A014016E4',size,dts,pts)
//PPS
srs_h264_write_raw_frame('CE3880',size,dts,pts)
//IFrame
srs_h264_write_raw_frame('B8041014C038008B0D0D3A071......',size,dts,pts)
//PFrame
srs_h264_write_raw_frame('E02041F8CDDC562BBDEFAD2F......',size,dts,pts)
PublishAudioRawStream
srs-librtmp提供了api可以将⾳频裸码流发布到SRS,⽀持AACADTS格式。
API定义如下:
/**
*writeanaudiorawframetosrs.
*notsimilartoh.264video,theaudioneveraggregated,always
*encodedoneframebyone,sothisapiisudtowriteaframe.
*
*@paramsound_lowingvaluesaredefined:
*0=LinearPCM,platformendian
*1=ADPCM
*2=MP3
*3=LinearPCM,littleendian
*4=Nellymor16kHzmono
*5=Nellymor8kHzmono
*6=Nellymor
*7=G.711A-lawlogarithmicPCM
*8=G.711mu-lawlogarithmicPCM
*9=rerved
*10=AAC
*11=Speex
*14=MP38kHz
*15=Device-specificsound
*Formats7,8,14,and15arererved.
*AACissupportedinFlashPlayer9,0,115,0andhigher.
*SpeexissupportedinFlashPlayer10andhigher.
*@paramsound_lowingvaluesaredefined:
*0=5.5kHz
*1=11kHz
*2=22kHz
*3=44kHz
*@paramsound_rameteronlypertainsto
*sdformatsalwaysdecode
*to16bitsinternally.
*0=8-bitsamples
*1=16-bitsamples
*@paramsound_typeMonoorstereosound
*0=Monosound
*1=Stereosound
*@paramtimestampThetimestampofaudio.
*
*@example/trunk/rearch/librtmp/srs_aac_raw_publish.c
*@example/trunk/rearch/librtmp/srs_audio_raw_publish.c
*
*@remarkforaac,theframemustbeinADTSformat.
*@eaac-mp4a-format-ISO_IEC_14496-3+,page75,1.A.2.2ADTS
*@remarkforaac,onlysupportprofile1-4,AACmain/LC/SSR/LTP,
*@eaac-mp4a-format-ISO_IEC_14496-3+,page23,1.5.1.1Audioobjecttype
*
*@eE.4.2.1AUDIODATAofvideo_file_format_spec_v10_
*
*@return0,success;otherswi,failed.
*/
externintsrs_audio_write_raw_frame(srs_rtmp_trtmp,
charsound_format,charsound_rate,charsound_size,charsound_type,
char*frame,intframe_size,u_int32_ttimestamp
);
/**
*whetheraacrawdataisinadtsformat,
*whichbytesquencematches'1'B,thatis0xFFF.
*@paramaac_raw_datatheinputaacrawdata,aencodedaacframedata.
*@paramac_raw_sizethesizeofaacrawdata.
*
*@reamrkudtocheckwhethercurrentframeisinadtsformat.
*@eaac-mp4a-format-ISO_IEC_14496-3+,page75,1.A.2.2ADTS
*@example/trunk/rearch/librtmp/srs_aac_raw_publish.c
*
*@return0fal;otherwi,true.
*/
externsrs_boolsrs_aac_is_adts(char*aac_raw_data,intac_raw_size);
/**
*partheadtsheadertogettheframesize,
*whichbytesquencematches'1'B,thatis0xFFF.
*@paramaac_raw_datatheinputaacrawdata,aencodedaacframedata.
*@paramac_raw_sizethesizeofaacrawdata.
*
*@returnfailedwhen<=0failed;otherwi,ok.
*/
externintsrs_aac_adts_frame_size(char*aac_raw_data,intac_raw_size);
调⽤实例参考#212,以及srs_audio_raw_publish.c和srs_aac_raw_publish.c,参考examples.
srs-librtmpExamples
SRS提供了实例sample,也会在编译srs-librtmp时⾃动编译:
rearch/librtmp/srs_play.c:播放RTMP流实例。
rearch/librtmp/srs_publish.c:推送RTMP流实例。
rearch/librtmp/srs_ingest_flv.c:读取本地FLV⽂件并推送RTMP流实例。
rearch/librtmp/srs_ingest_mp4.c:读取本地MP4⽂件并推送RTMP流实例。
rearch/librtmp/srs_ingest_rtmp.c:读取RTMP流并推送RTMP流实例。
rearch/librtmp/srs_bandwidth_check.c:带宽测试⼯具。
rearch/librtmp/srs_flv_injecter.c:点播FLV关键帧注⼊⽂件。
rearch/librtmp/srs_flv_parr.c:FLV⽂件查看⼯具。
rearch/librtmp/srs_detect_rtmp.c:RTMP流检测⼯具。
rearch/librtmp/srs_h264_raw_publish.c:H.264裸码流发布到SRS实例。
rearch/librtmp/srs_audio_raw_publish.c:Audio裸码流发布到SRS实例。
rearch/librtmp/srs_aac_raw_publish.c:AudioAACADTS裸码流发布到SRS实例。
rearch/librtmp/srs_rtmp_dump.c:将RTMP流录制成flv⽂件实例。
./objs/srs_ingest_hls:将HLS流采集成RTMP推送给SRS。
运⾏实例
启动SRS:
make&&./objs/
推流实例:
make&&./objs/rearch/librtmp/objs/srs_publishrtmp://127.0.0.1:1935/live/livestream
备注:推流实例发送的视频数据不是真正的视频数据,实际使⽤时,譬如从摄像头取出h.264裸码流,需要封装成接⼝要求的数据,然后调
⽤接⼝发送出去。或者直接发送h264裸码流。
播放实例:
make&&./objs/rearch/librtmp/objs/srs_playrtmp:///live/livestreamsuckrtmpstreamlikertmpdump
本文发布于:2023-02-04 19:53:50,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/fan/88/188928.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |