海康sip服务器是什么意思_海康摄像头入门

海康RTSP取流URL格式

一、预览取流

设备预览取流的RTSP URL有新老版本,2012年之前的设备(比如V2.0版本的Netra设备)支持老的取流格式,之后的设备新老取流格式都支持。

老版本

URL规定:

rtsp://username: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://username: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://username: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的格式要符合ISO 8601。具体格式是:

YYYYMMDD”T”HHmmSS.fraction”Z” ,Y是年,M是月,D是日,T是时间分格符,H是小时,M是分,S是秒,Z是可选的、表示Zulu (GMT) 时间。

VLC播放示例:

媒体--》打开网络串流--》网络:

rtsp://username:password@192.168.1.17:554/MPEG-4/ch1/main/av_stream

Linux下编译eXosip2库以及测试

原文作者:这个名字不知道有没有人用啊

原文链接:https://blog.csdn.net/weixin_43272766/article/details/89899257

环境:

Ubuntu18.04 + libosip2-5.1.0 + libexosip2-5.1.0 + c-ares-1.15.0

下载

https://c-ares.haxx.se/      好像不使用也可以

http://ftp.twaren.net/Unix/NonGNU//osip/

http://ftp.yzu.edu.tw/nongnu/exosip/

依次解压编译(注意顺序,exosip要在最后编译)

tarxvf 对应压缩包名

cd 解压出来的文件夹

./configuremake

sudo make install

测试

#include #include#include#include

using namespacestd;intmain()

{

eXosip_t*sip =eXosip_malloc();if(eXosip_init(sip) ==OSIP_SUCCESS)

{

cout<< "eXosip init ok" <

}else{

cout<< "exosip init fail" <

}int ret = eXosip_listen_addr(sip, IPPROTO_UDP, NULL, 0, AF_INET, 0);if(ret ==OSIP_SUCCESS)

{

cout<< "exosiop listen addr success" <

}elsecout<< "listen addr fail, ret:" << ret <

eXosip_quit(sip);

cout<< "test" <

}

编译运行

g++ test.cpp -losip2 -leXosip2

./a.out

uac.cpp

#include #include#include#include#include#include#include

int main(int argc,char *argv[])

{

struct eXosip_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;int flag1=1;intiReturnCode;char identity[30]="sip:140@127.0.0.1"; //UAC1,端口是15060

char registar[30]="sip:133@127.0.0.1:15061"; //UAS,端口是15061

char source_call[30]="sip:140@127.0.0.1";char dest_call[30]="sip:133@127.0.0.1:15061";//identify和register这一组地址是和source和destination地址相同的//在这个例子中,uac和uas通信,则source就是自己的地址,而目的地址就是uac1的地址

charcommand;char tmp[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't initialize eXosip!\n");

return-1;

}else{

printf("eXosip_init successfully!\n");

}//绑定uac自己的端口15060,并进行端口监听

iReturnCode = eXosip_listen_addr(excontext, IPPROTO_UDP, NULL, 15060, AF_INET, 0);if(iReturnCode!=0)

{

eXosip_quit(excontext);

fprintf(stderr,"Couldn't initialize transport layer!\n");

return-1;

}while(true)

{//输入命令

std::cout << "Please input the command:" <<:endl>

std::cin>>command;

switch(command)

{case 'r':

std::cout<< "This modal is not completed!" <<:endl>

break;case 'i'://INVITE,发起呼叫请求

i=eXosip_call_build_initial_invite(excontext,&invite,dest_call,source_call,NULL,"This is a call for conversation");if(i!=0)

{

std::cout<< "Initial INVITE failed!" <<:endl>

break;

}//符合SDP格式,其中属性a是自定义格式,也就是说可以存放自己的信息,//但是只能有两列,比如帐户信息//但是经过测试,格式vot必不可少,原因未知,估计是协议栈在传输时需要检查的

snprintf(tmp,4096,"v=0\r\n"

"o=anonymous 0 0 IN IP4 0.0.0.0\r\n"

"t=1 10\r\n"

"a=username:rainfish\r\n"

"a=password:123\r\n");

osip_message_set_body(invite,tmp,strlen(tmp));

osip_message_set_content_type(invite,"application/sdp");

eXosip_lock(excontext);

i=eXosip_call_send_initial_invite(excontext,invite); //invite SIP INVITE message to send

eXosip_unlock(excontext);//发送了INVITE消息,等待应答

flag1=1;while(flag1)

{

je=eXosip_event_wait(excontext,0,200); //Wait for an eXosip event//(超时时间秒,超时时间毫秒)

if(je==NULL)

{

printf("No response or the time is over!\n");

break;

}

switch(je->type) //可能会到来的事件类型

{case EXOSIP_CALL_INVITE: //收到一个INVITE请求

printf("a new invite received!\n");

break;case EXOSIP_CALL_PROCEEDING: //收到100 trying消息,表示请求正在处理中

printf("proceeding!\n");

break;case EXOSIP_CALL_RINGING: //收到180 Ringing应答,表示接收到INVITE请求的UA

printf("ringing!\n");

printf("call_id is %d,dialog_id is %d \n",je->cid,je->did);

break;case EXOSIP_CALL_ANSWERED: //收到200 OK,表示请求已经被成功接受,用户应答

printf("ok!connected!\n");

call_id=je->cid;

dialog_id=je->did;

printf("call_id is %d,dialog_id is %d \n",je->cid,je->did);//回送ack应答消息

eXosip_call_build_ack(excontext,je->did,&ack);

eXosip_call_send_ack(excontext,je->did,ack);

flag1=0; //推出While循环

break;case EXOSIP_CALL_CLOSED: //a BYE was received for this call

printf("the other sid closed!\n");

break;case EXOSIP_CALL_ACK: //ACK received for 200ok to INVITE

printf("ACK received!\n");

break;

default://收到其他应答

printf("other response!\n");

break;

}

eXosip_event_free(je);//Free ressource in an eXosip event

}

break;case 'h': //挂断

printf("Holded!\n");

eXosip_lock(excontext);

eXosip_call_terminate(excontext,call_id,dialog_id);

eXosip_unlock(excontext);

break;case 'c':

printf("This modal is not commpleted!\n");

break;case 's': //传输INFO方法

eXosip_call_build_info(excontext,dialog_id,&info);

snprintf(tmp,4096,"\nThis is a sip message(Method:INFO)");

osip_message_set_body(info,tmp,strlen(tmp));//格式可以任意设定,text/plain代表文本信息;

osip_message_set_content_type(info,"text/plain");

eXosip_call_send_request(excontext,dialog_id,info);

break;case 'm'://传输MESSAGE方法,也就是即时消息,和INFO方法相比,我认为主要区别是://MESSAGE不用建立连接,直接传输信息,而INFO消息必须在建立INVITE的基础上传输

printf("the method : MESSAGE\n");

eXosip_message_build_request(excontext,&message,"MESSAGE",dest_call,source_call,NULL);//内容,方法, to ,from ,route

snprintf(tmp,4096,"This is a sip message(Method:MESSAGE)");

osip_message_set_body(message,tmp,strlen(tmp));//假设格式是xml

osip_message_set_content_type(message,"text/xml");

eXosip_message_send_request(excontext,message);

break;case 'q':

eXosip_quit(excontext);

printf("Exit the setup!\n");

flag=0;

break;

}

}

return(0);

}

View Code

uas.cpp

# include # include# include# include# include#include#include

//# include

int main (int argc, char *argv[])

{

struct eXosip_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;int id;char sour_call[30] = "sip:140@127.0.0.1";char dest_call[30] = "sip:133@127.0.0.1:15060";//client ip

charcommand;char tmp[4096];char localip[128];int pos = 0;//初始化sip

excontext =eXosip_malloc();

iReturnCode=eXosip_init(excontext);if (iReturnCode != 0)

{

printf ("Can't initialize eXosip!\n");

return-1;

}else{

printf ("eXosip_init successfully!\n");

}

iReturnCode= eXosip_listen_addr(excontext,IPPROTO_UDP, NULL, 15061, AF_INET, 0);if (iReturnCode != 0)

{

eXosip_quit (excontext);

fprintf (stderr,"eXosip_listen_addr error!\nCouldn't initialize transport layer!\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 ("the cid is %s, did is %s/n", je->did, je->cid);

switch (je->type)

{case EXOSIP_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 ("I get the msg is: %s\n", body->body);//printf ("the cid is %s, did is %s/n", je->did, je->cid);

}//按照规则,需要回复OK信息

eXosip_message_build_answer (excontext,je->tid, 200,&answer);

eXosip_message_send_answer (excontext,je->tid, 200,answer);

}

break;caseEXOSIP_CALL_INVITE://得到接收到消息的具体信息

printf ("Received a INVITE msg from %s:%s, UserName is %s, password is %s\n",je->request->req_uri->host,

je->request->req_uri->port, je->request->req_uri->username, 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_send_answer (excontext,je->tid, 180, NULL);

i= eXosip_call_build_answer (excontext,je->tid, 200, &answer);if (i != 0)

{

printf ("This request msg is invalid!Cann't response!\n");

eXosip_call_send_answer (excontext,je->tid, 400, NULL);

}else{

snprintf (tmp,4096,"v=0\r\n"

"o=anonymous 0 0 IN IP4 0.0.0.0\r\n"

"t=1 10\r\n"

"a=username:rainfish\r\n"

"a=password:123\r\n");//设置回复的SDP消息体,下一步计划分析消息体//没有分析消息体,直接回复原来的消息,这一块做的不好。

osip_message_set_body (answer, tmp, strlen(tmp));

osip_message_set_content_type (answer,"application/sdp");

eXosip_call_send_answer (excontext,je->tid, 200, answer);

printf ("send 200 over!\n");

}

eXosip_unlock (excontext);//显示出在sdp消息体中的attribute 的内容,里面计划存放我们的信息

printf ("the INFO is :\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 : %s\n", at->a_att_field, at->a_att_value);//这里解释了为什么在SDP消息体中属性a里面存放必须是两列

pos++;

}

break;caseEXOSIP_CALL_ACK:

printf ("ACK recieved!\n");//printf ("the cid is %s, did is %s/n", je->did, je->cid);

break;caseEXOSIP_CALL_CLOSED:

printf ("the remote hold the session!\n");//eXosip_call_build_ack(dialog_id, &ack);//eXosip_call_send_ack(dialog_id, ack);

i = eXosip_call_build_answer (excontext,je->tid, 200, &answer);if (i != 0)

{

printf ("This request msg is invalid!Cann't response!\n");

eXosip_call_send_answer (excontext,je->tid, 400, NULL);

}else{

eXosip_call_send_answer (excontext,je->tid, 200, answer);

printf ("bye send 200 over!\n");

}

break;case EXOSIP_CALL_MESSAGE_NEW://至于该类型和EXOSIP_MESSAGE_NEW的区别,源代码这么解释的

/*// request related events within calls (except INVITE)

EXOSIP_CALL_MESSAGE_NEW, < announce new incoming request.

// response received for request outside calls

EXOSIP_MESSAGE_NEW, < announce new incoming request.

我也不是很明白,理解是:EXOSIP_CALL_MESSAGE_NEW是一个呼叫中的新的消息到来,比如ring trying都算,所以在接受到后必须判断

该消息类型,EXOSIP_MESSAGE_NEW而是表示不是呼叫内的消息到来。

该解释有不妥地方,仅供参考。*/printf("EXOSIP_CALL_MESSAGE_NEW\n");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_send_answer (excontext,je->tid, 200, answer);

}

eXosip_unlock (excontext);

{

osip_body_t*body;

osip_message_get_body (je->request, 0, &body);

printf ("the body is %s\n", body->body);

}

}

break;

default:

printf ("Could not parse the msg!\n");

}

}

}

View Code

编译并运行

g++ uac.cpp -o uac -losip2 -leXosip2 -lpthread -losipparser2

g++ uas.cpp -o uas -losip2 -leXosip2 -lpthread -losipparser2

./uas

./uac

exosip对接海康摄像头

#include #include#include#include#include#include#include

static void RegisterSuccess(struct eXosip_t * peCtx,eXosip_event_t *je)

{int iReturnCode = 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_send_answer (peCtx,je->tid,200,pSRegister);

eXosip_unlock(peCtx);//osip_message_free(pSRegister);

}

}void RegisterFailed(struct eXosip_t * peCtx,eXosip_event_t *je) {int iReturnCode = 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_send_answer (peCtx,je->tid,401,pSRegister);

eXosip_unlock(peCtx);

}

}int main(int argc,char *argv[])

{struct eXosip_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;int flag1=1;intiReturnCode;intregisterOk;char *p;char identity[30]="sip:140@127.0.0.1"; //UAC1,端口是15060

char registar[30]="sip:133@127.0.0.1:15061"; //UAS,端口是15061

char source_call[30]="sip:140@127.0.0.1";char dest_call[30]="sip:133@127.0.0.1:15061";//identify和register这一组地址是和source和destination地址相同的//在这个例子中,uac和uas通信,则source就是自己的地址,而目的地址就是uac1的地址

charcommand;char tmp[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't initialize eXosip!\n");return -1;

}else{

printf("eXosip_init successfully!\n");

}//绑定uac自己的端口15060,并进行端口监听

iReturnCode = eXosip_listen_addr(excontext, IPPROTO_UDP, NULL, 5060, AF_INET, 0);if(iReturnCode!=0)

{

eXosip_quit(excontext);

fprintf(stderr,"Couldn't initialize transport layer!\n");return -1;

}while(true)

{

eXosip_event_t*je =NULL;

je= eXosip_event_wait (excontext, 0, 4);if (je ==NULL) {

std::cout<< "event is null" <<:endl>

osip_usleep(100000*50);continue;

}switch (je->type) {caseEXOSIP_MESSAGE_NEW:

{//printf("new msg method:%s\n", je->request->sip_method);

if(MSG_IS_REGISTER(je->request)) {

std::cout<< "msg body:" <<:endl>

registerOk= 1;

}else if(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<< "msg body:" <<:endl>

std::cout<< body->body <<:endl>

}else{

std::cout<< "msg body:" <<:endl>

std::cout<< body->body <<:endl>

}

}else{

std::cout<< "get body failed" <<:endl>

}

}else if(strncmp(je->request->sip_method, "BYE", 4) != 0){

std::cout<< "unsupport new msg method :" << je->request->sip_method <<:endl>

}

RegisterSuccess(excontext, je);

}break;caseEXOSIP_MESSAGE_ANSWERED:

{

printf("answered method:%s\n", je->request->sip_method);

RegisterSuccess(excontext, je);

}break;caseEXOSIP_CALL_ANSWERED:

{

osip_message_t*ack=NULL;

call_id= je->cid;

dialog_id= je->did;

printf("call answered method:%s, call_id:%d, dialog_id:%d\n", je->request->sip_method, call_id, dialog_id);

eXosip_call_build_ack(excontext, je->did, &ack);

eXosip_lock(excontext);

eXosip_call_send_ack(excontext, je->did, ack);

eXosip_unlock(excontext);

}break;caseEXOSIP_CALL_PROCEEDING:

{

printf("recv EXOSIP_CALL_PROCEEDING\n");

RegisterSuccess(excontext, je);

}break;caseEXOSIP_CALL_REQUESTFAILURE:

{

printf("recv EXOSIP_CALL_REQUESTFAILURE\n");

RegisterSuccess(excontext, je);

}break;caseEXOSIP_CALL_MESSAGE_ANSWERED:

{

printf("recv EXOSIP_CALL_MESSAGE_ANSWERED\n");

RegisterSuccess(excontext, je);

}break;caseEXOSIP_CALL_RELEASED:

{

printf("recv EXOSIP_CALL_RELEASED\n");

RegisterSuccess(excontext, je);

}break;caseEXOSIP_CALL_CLOSED:

{

printf("recv EXOSIP_CALL_CLOSED\n");

RegisterSuccess(excontext, je);

}break;caseEXOSIP_CALL_MESSAGE_NEW:

{

printf("recv EXOSIP_CALL_MESSAGE_NEW\n");

RegisterSuccess(excontext, je);

}break;default:

{

printf("##test,%s:%d, unsupport type:%d\n", __FILE__, __LINE__, je->type);

RegisterSuccess(excontext, je);

}break;

}

eXosip_event_free(je);

}return(0);

}

View Code

海康--》sip服务器发送注册包

sip服务器--》 海康发送200包

sip服务器--》 海康摄像头发送message保活包

问题汇总:

./a.out: error while loading shared libraries: libeXosip2.so.12: cannot open shared object file: No such file or directory

解决办法:

sudo vim /etc/ld.so.conf

末尾添加 /usr/local/lib

保存退出,执行sudo ldconfig

参考文档:

exosip官网: http://savannah.nongnu.org/projects/exosip/

oSIP官网:http://savannah.gnu.org/projects/osip/

开发手册:https://wenku.baidu.com/view/88cb5112cc7931b765ce15b0.html?sxts=1570885286497

eXosip开发手册 :https://blog.csdn.net/mantis_1984/article/details/52948216

https://www.cnblogs.com/swing07/p/10862480.html

https://blog.csdn.net/zzqgtt/article/details/87179815

https://gitee.com/leixiaohua1020/simplest_librtmp_example

https://github.com/ossrs/librtmp

https://github.com/logisticpeach/librtp

https://www.jianshu.com/p/cc7df89d98f4

https://blog.csdn.net/wwyyxx26/article/details/15224879

https://www.bbsmax.com/A/RnJWZqYEzq/

https://www.cnblogs.com/codenow/p/4871704.html

从海康7816的ps流里获取数据h264数据

github: 作为上级域,可以对接海康、大华、宇视等gb28181平台,获取ps流,转换为标准h.264裸流

http://www.voidcn.com/article/p-cimuzoim-bab.html

https://blog.csdn.net/mo4776/article/details/78239344

https://blog.csdn.net/wh8_2011/article/details/48415105

python-librtmp

https://www.jb51.net/article/165927.htm

librtmp使用的是0.3.0,使用树莓派noir官方摄像头适配的。

目的是能使用Python进行rtmp推流,方便在h264帧里加入弹幕等操作。通过wireshark抓ffmpeg的包一点点改动,最终可以在red5和斗鱼上推流了。

#-- coding: utf-8 --#http://blog.csdn.net/luhanglei

importpicameraimporttimeimporttracebackimportctypesfrom librtmp import *

globalmeta_packetglobalstart_timeclass Writer(): #camera可以通过一个类文件的对象来输出,实现write方法即可

conn = None #rtmp连接

sps = None #记录sps帧,发过以后就不需要再发了(抓包看到ffmpeg是这样的)

pps = None #同上

sps_len = 0 #同上

pps_len = 0 #同上

time_stamp=0def __init__(self, conn):

self.conn=conndefwrite(self, data):try:#寻找h264帧间隔符

indexs =[]

index=0

data_len=len(data)while index < data_len - 3:if ord(data[index]) == 0x00 and ord(data[index + 1]) == 0x00 andord(

data[index+ 2]) == 0x00 and ord(data[index + 3]) == 0x01:

indexs.append(index)

index= index + 3index= index + 1

#寻找h264帧间隔符 完成

#通过间隔符个数确定类型,树莓派摄像头的第一帧是sps+pps同时发的

if len(indexs) == 1: #非sps pps帧

buf = data[4: len(data)] #裁掉原来的头(00 00 00 01),把帧内容拿出来

buf_len =len(buf)

type= ord(buf[0]) & 0x1f

if type == 0x05: #关键帧,根据wire shark抓包结果,需要拼装sps pps 帧内容 三部分,长度都用4个字节表示

body0 = 0x17data_body_array=[bytes(bytearray(

[body0,0x01, 0x00, 0x00, 0x00, (self.sps_len >> 24) & 0xff, (self.sps_len >> 16) & 0xff,

(self.sps_len>> 8) & 0xff,

self.sps_len& 0xff])), self.sps,

bytes(bytearray(

[(self.pps_len>> 24) & 0xff, (self.pps_len >> 16) & 0xff, (self.pps_len >> 8) & 0xff,

self.pps_len& 0xff])),

self.pps,

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,此后发真时间戳

if self.time_stamp !=0:

time_stamp= int((time.time() - start_time) * 1000)

packet_body= RTMPPacket(type=PACKET_TYPE_VIDEO, format=PACKET_SIZE_LARGE, channel=0x06,

timestamp=time_stamp, body=mbody)

packet_body.packet.m_nInfoField2= 1

else: #非关键帧

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 (self.time_stamp == 0):

self.time_stamp = int((time.time() - start_time) * 1000)

packet_body= RTMPPacket(type=PACKET_TYPE_VIDEO, format=PACKET_SIZE_MEDIUM, channel=0x06,

timestamp=self.time_stamp, body=mbody)

self.conn.send_packet(packet_body)elif len(indexs) == 2: #sps pps帧

if self.sps is notNone: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)

self.sps=sps

self.sps_len=sps_len

self.pps=pps

self.pps_len=pps_len

data_body_array.append(sps[1:4])

data_body_array.append(bytes(bytearray([0xff, 0xe1, (sps_len >> 8) & 0xff, sps_len & 0xff])))

data_body_array.append(sps)

data_body_array.append(bytes(bytearray([0x01, (pps_len >> 8) & 0xff, pps_len & 0xff])))

data_body_array.append(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_packet.packet.m_nInfoField2= 1self.conn.send_packet(meta_packet, queue=True)

self.conn.send_packet(body_packet, queue=True)exceptException, e:

traceback.print_exc()defflush(self):pass

def get_property_string(string): #返回两字节string长度及string

length =len(string)return ''.join([chr((length >> 8) & 0xff), chr(length & 0xff), string])def get_meta_string(string): #按照meta packet要求格式返回bytes,带02前缀

return ''.join([chr(0x02), get_property_string(string)])defget_meta_double(db):

nums= [0x00]

fp=ctypes.pointer(ctypes.c_double(db))

cp=ctypes.cast(fp, ctypes.POINTER(ctypes.c_longlong))for i in range(7, -1, -1):

nums.append((cp.contents.value>> (i * 8)) & 0xff)return ''.join(bytes(bytearray(nums)))defget_meta_boolean(isTrue):

nums= [0x01]if(isTrue):

nums.append(0x01)else:

nums.append(0x00)return ''.join(bytes(bytearray(nums)))

conn=RTMP('rtmp://192.168.199.154/oflaDemo/test', #推流地址

live=True)

librtmp.RTMP_EnableWrite(conn.rtmp)

conn.connect()

start_time=time.time()#拼装视频格式的数据包

meta_body_array = [get_meta_string('@setDataFrame'), get_meta_string('onMetaData'),

bytes(bytearray([0x08, 0x00, 0x00, 0x00, 0x06])), #两个字符串和ECMA array头,共计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)print meta_body.encode('hex')

meta_packet= RTMPPacket(type=PACKET_TYPE_INFO, format=PACKET_SIZE_LARGE, channel=0x04,

timestamp=0, body=meta_body)

meta_packet.packet.m_nInfoField2= 1 #修改stream id

stream = conn.create_stream(writeable=True)

with picamera.PiCamera() as camera:

camera.start_preview()

time.sleep(2)

camera.start_recording(Writer(conn), format='h264', resize=(640, 480), intra_period=25,

quality=25) #开始录制,数据输出到Writer的对象里

while True:#永远不停止

time.sleep(60)

camera.stop_recording()

camera.stop_preview()

srs-librtmp

https://blog.csdn.net/ai2000ai/article/details/78329039

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如何做server,实在不胜其骚扰,所以单列一章。

server的特点是会有多个客户端连接,至少有两个:一个推流连接,一个播放连接。所以server有两种策略:

每个连接一个线程或进程:像apache。这样可以用同步socket来收发数据(同步简单)。坏处就是没法支持很高并发,1000个已经到顶了,得开1000个线程/进程啊。

使用单进程,但是用异步socket:像nginx这样。好处就是能支持很高并发。坏处就是异步socket麻烦。

rtmpdump提供的librtmp,当然是基于同步socket的。所以使用librtmp做server,只能采取第一种方法,即用多线程处理多个连接。多线程多麻烦啊!要锁,同步,而且还支持不了多少个。

librtmp的定位就是客户端程序,偏偏要超越它的定位去使用,这种大约只有中国人才能这样“无所畏惧”。

嵌入式设备上做rtmp server,当然可以用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就可以完成到服务器的测速。参考:Bandwidth Test

SRS提供了日志接口,可以获取服务器端的信息,譬如版本,对应的session id。参考:Tracable log

SRS可以直接导出一个srs-librtmp的project,编译成.h和.a使用。或者导出为.h和.cpp,一个大文件。参考:export srs librtmp

一句话,srs为何提供客户端开发库?因为rtmp客户端开发不方便,不直观,不简洁。

Export Srs Librtmp

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/research/librtmp/srs_play rtmp://ossrs.net/live/livestream

SRS将srs-librtmp导出为独立可以make的项目,生成.a静态库和.h头文件,以及生成了srs-librtmp的所有实例。

还可以直接导出为一个文件,提供了简单的使用实例,其他实例参考research的其他例子:

dir=/home/winlin/srs-librtmp &&

rm -rf $dir &&

./configure --export-librtmp-single=$dir &&

cd $dir && gcc example.c srs_librtmp.cpp -g -O0 -lstdc++ -o example &&

strip example && ./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中编译,参考:export srs librtmp

使用了一些linux的头文件,需要做一些portal。

注意:srs-librtmp客户端推流和抓流,不需要ssl库。代码都是c++/stl,网络部分用的是同步socket。

数据格式

srs-librtmp提供了一系列接口函数,就数据按照一定格式发送到服务器,或者从服务器读取音视频数据。

数据接口包括:

读取数据包:int srs_read_packet(int* type, u_int32_t* timestamp, char** data, int* size)

发送数据包:int srs_write_packet(int type, u_int32_t timestamp, char* data, int size)

接口接受的的数据(char* data),音视频数据,格式为flv的Video/Audio数据。参考srs的doc目录的规范文件video_file_format_spec_v10_1.pdf

音频数据格式参考:E.4.2.1 AUDIODATA,p76,譬如,aac编码的音频数据。

视频数据格式参考:E.4.3.1 VIDEODATA,p78,譬如,h.264编码的视频数据。

脚本数据格式参考:E.4.4.1 SCRIPTDATA,p80,譬如,onMetadata,流的信息(宽高,码率,分辨率等)

数据类型(int type)定义如下(E.4.1 FLV Tag,page 75):

音频:8 = audio,宏定义:SRS_RTMP_TYPE_AUDIO

视频:9 = video,宏定义:SRS_RTMP_TYPE_VIDEO

脚本数据:18 = script data,宏定义:SRS_RTMP_TYPE_SCRIPT

其他的数据,譬如时间戳,都是通过参数接受和发送。

另外,文档其他重要信息:

flv文件头格式:E.2 The FLV header,p74。

flv文件主体格式:E.3 The FLV File Body,p74。

tag头格式:E.4.1 FLV Tag,p75。

使用flv格式的原因:

flv的格式足够简单。

ffmpeg也是用的这种格式

收到流后加上flv tag header,就可以直接保存为flv文件

从flv文件解封装数据后,只要将tag的内容给接口就可以,flv的tag头很简单。

Publish H.264 Raw Data

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] 00 00 01, where N>=0.

加了一个直接发送h264裸码流的接口:

/**

* write h.264 raw frame over RTMP to rtmp server.

* @param frames the input h264 raw data, encoded h.264 I/P/B frames data.

* frames can be one or more than one frame,

* each frame prefixed h.264 annexb header, by N[00] 00 00 01, where N>=0,

* for instance, frame = header(00 00 00 01) + payload(67 42 80 29 95 A0 14 01 6E 40)

* about annexb, @see H.264-AVC-ISO_IEC_14496-10.pdf, page 211.

* @paam frames_size the size of h264 raw data.

* assert frames_size > 0, at least has 1 bytes header.

* @param dts the dts of h.264 raw data.

* @param pts the pts of h.264 raw data.

*

* @remark, user should free the frames.

* @remark, the tbn of dts/pts is 1/1000 for RTMP, that is, in ms.

* @remark, cts = pts - dts

*

* @return 0, success; otherswise, failed.

*/

extern int srs_h264_write_raw_frames(srs_rtmp_t rtmp,

char* frames, int frames_size, u_int32_t dts, u_int32_t pts

);

里面的数据是:

// SPS

000000016742802995A014016E40

// PPS

0000000168CE3880

// IFrame

0000000165B8041014C038008B0D0D3A071.....

// PFrame

0000000141E02041F8CDDC562BBDEFAD2F.....

调用时,可以SPS和PPS一起发,帧一次发一个:

// SPS+PPS

srs_h264_write_raw_frame('000000016742802995A014016E400000000168CE3880', size, dts, pts)

// IFrame

srs_h264_write_raw_frame('0000000165B8041014C038008B0D0D3A071......', size, dts, pts)

// PFrame

srs_h264_write_raw_frame('0000000141E02041F8CDDC562BBDEFAD2F......', size, dts, pts)

调用时,可以一次发一次frame也行:

// SPS

srs_h264_write_raw_frame('000000016742802995A014016E4', size, dts, pts)

// PPS

srs_h264_write_raw_frame('00000000168CE3880', size, dts, pts)

// IFrame

srs_h264_write_raw_frame('0000000165B8041014C038008B0D0D3A071......', size, dts, pts)

// PFrame

srs_h264_write_raw_frame('0000000141E02041F8CDDC562BBDEFAD2F......', size, dts, pts)

Publish Audio Raw Stream

srs-librtmp提供了api可以将音频裸码流发布到SRS,支持AAC ADTS格式。

API定义如下:

/**

* write an audio raw frame to srs.

* not similar to h.264 video, the audio never aggregated, always

* encoded one frame by one, so this api is used to write a frame.

*

* @param sound_format Format of SoundData. The following values are defined:

* 0 = Linear PCM, platform endian

* 1 = ADPCM

* 2 = MP3

* 3 = Linear PCM, little endian

* 4 = Nellymoser 16 kHz mono

* 5 = Nellymoser 8 kHz mono

* 6 = Nellymoser

* 7 = G.711 A-law logarithmic PCM

* 8 = G.711 mu-law logarithmic PCM

* 9 = reserved

* 10 = AAC

* 11 = Speex

* 14 = MP3 8 kHz

* 15 = Device-specific sound

* Formats 7, 8, 14, and 15 are reserved.

* AAC is supported in Flash Player 9,0,115,0 and higher.

* Speex is supported in Flash Player 10 and higher.

* @param sound_rate Sampling rate. The following values are defined:

* 0 = 5.5 kHz

* 1 = 11 kHz

* 2 = 22 kHz

* 3 = 44 kHz

* @param sound_size Size of each audio sample. This parameter only pertains to

* uncompressed formats. Compressed formats always decode

* to 16 bits internally.

* 0 = 8-bit samples

* 1 = 16-bit samples

* @param sound_type Mono or stereo sound

* 0 = Mono sound

* 1 = Stereo sound

* @param timestamp The timestamp of audio.

*

* @example /trunk/research/librtmp/srs_aac_raw_publish.c

* @example /trunk/research/librtmp/srs_audio_raw_publish.c

*

* @remark for aac, the frame must be in ADTS format.

* @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 75, 1.A.2.2 ADTS

* @remark for aac, only support profile 1-4, AAC main/LC/SSR/LTP,

* @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 23, 1.5.1.1 Audio object type

*

* @see https://github.com/ossrs/srs/issues/212

* @see E.4.2.1 AUDIODATA of video_file_format_spec_v10_1.pdf

*

* @return 0, success; otherswise, failed.

*/

extern int srs_audio_write_raw_frame(srs_rtmp_t rtmp,

char sound_format, char sound_rate, char sound_size, char sound_type,

char* frame, int frame_size, u_int32_t timestamp

);

/**

* whether aac raw data is in adts format,

* which bytes sequence matches '1111 1111 1111'B, that is 0xFFF.

* @param aac_raw_data the input aac raw data, a encoded aac frame data.

* @param ac_raw_size the size of aac raw data.

*

* @reamrk used to check whether current frame is in adts format.

* @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 75, 1.A.2.2 ADTS

* @example /trunk/research/librtmp/srs_aac_raw_publish.c

*

* @return 0 false; otherwise, true.

*/

extern srs_bool srs_aac_is_adts(char* aac_raw_data, int ac_raw_size);

/**

* parse the adts header to get the frame size,

* which bytes sequence matches '1111 1111 1111'B, that is 0xFFF.

* @param aac_raw_data the input aac raw data, a encoded aac frame data.

* @param ac_raw_size the size of aac raw data.

*

* @return failed when <=0 failed; otherwise, ok.

*/

extern int srs_aac_adts_frame_size(char* aac_raw_data, int ac_raw_size);

调用实例参考#212,以及srs_audio_raw_publish.c和srs_aac_raw_publish.c,参考examples.

srs-librtmp Examples

SRS提供了实例sample,也会在编译srs-librtmp时自动编译:

research/librtmp/srs_play.c:播放RTMP流实例。

research/librtmp/srs_publish.c:推送RTMP流实例。

research/librtmp/srs_ingest_flv.c:读取本地FLV文件并推送RTMP流实例。

research/librtmp/srs_ingest_mp4.c:读取本地MP4文件并推送RTMP流实例。

research/librtmp/srs_ingest_rtmp.c:读取RTMP流并推送RTMP流实例。

research/librtmp/srs_bandwidth_check.c:带宽测试工具。

research/librtmp/srs_flv_injecter.c:点播FLV关键帧注入文件。

research/librtmp/srs_flv_parser.c:FLV文件查看工具。

research/librtmp/srs_detect_rtmp.c:RTMP流检测工具。

research/librtmp/srs_h264_raw_publish.c:H.264裸码流发布到SRS实例。

research/librtmp/srs_audio_raw_publish.c: Audio裸码流发布到SRS实例。

research/librtmp/srs_aac_raw_publish.c: Audio AAC ADTS裸码流发布到SRS实例。

research/librtmp/srs_rtmp_dump.c: 将RTMP流录制成flv文件实例。

./objs/srs_ingest_hls: 将HLS流采集成RTMP推送给SRS。

运行实例

启动SRS:

make && ./objs/srs -c srs.conf

推流实例:

make && ./objs/research/librtmp/objs/srs_publish rtmp://127.0.0.1:1935/live/livestream

备注:推流实例发送的视频数据不是真正的视频数据,实际使用时,譬如从摄像头取出h.264裸码流,需要封装成接口要求的数据,然后调用接口发送出去。或者直接发送h264裸码流。

播放实例:

make && ./objs/research/librtmp/objs/srs_play rtmp://ossrs.net/live/livestreamsuck rtmp stream like rtmpdump

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值