上一部分说了Socket通讯的一些东西,这部分就结合代码来说说RTSP交互的过程。
在放实现代码之前先说说概念上的东西吧,关于RTSP这个流媒体网络协议。RTSP作为一个应用层协议,提供了一个可供扩展的框架,使得流媒体的受控和点播变得可能,它主要用来控制具有实时特性的数据的发送,但其本身并不用于传送流媒体数据,而必须依赖下层传输协议(如RTP/RTCP)所提供的服务来完成流媒体数据的传送。RTSP负责定义具体的控制信息、操作方法、状态码,以及描述与RTP之间的交互操作。所以具体的码流数据其实是用RTP封装传输的,第三部分我会详细讲码流数据的处理和发送。
一次基本的RTSP交互过程如上图所示,C表示客户端即请求码流的用户,S为服务器即网络摄像机。
首先客户端连接到流媒体服务器并发送一个RTSP描述请求(DESCRIBE request),服务器通过一个SDP(Session DescriptionProtocol)描述来进行反馈(DESCRIBEresponse),反馈信息包括流数量、媒体类型等信息。客户端分析该SDP描述,并为会话中的每一个流发送一个RTSP连接建立请求(SETUPrequest),该命令会告诉服务器用于接收媒体数据的端口,服务器响应该请求(SETUP response)并建立连接之后,就开始传送媒体流(RTP包)到客户端。在播放过程中客户端还可以向服务器发送请求来控制快进、快退和暂停等。最后,客户端可发送一个终止请求(TEARDOWN request)来结束流媒体会话。
RTSP最基本的东西就是这些,其他复杂的东西我也不想说太多了,有兴趣的可以查查RFC2326(假装复杂的东西我懂似的),OK,讲代码。上一部分我放了我main函数写的东西,在tcp_listen()这个函数之后我建立了一个Socket连接,并把套接字传到了EventLoop()这个函数里面,上面说了RTSP并不负责传输具体视音频数据,这部分是由RTP传输的,所以在tcp_listen建立的套接字是用来做RTSP消息传输的这里的SOCKET是TCP,后面我还会再建立新的UDP SOCKET用以传输具体的视频数据,这个具体后面会说这里提一句。
static int s32ConCnt = 0;//已经连接的客户端数
int s32Fd = -1;
static RTSP_buffer *pRtspList=NULL;
RTSP_buffer *p=NULL;
unsigned int u32FdFound;
/*接收连接,创建一个新的socket*/
if (s32ConCnt!=-1)
{
s32Fd= tcp_accept(s32MainFd);
}
/*处理新创建的连接*/
if (s32Fd >= 0)
{
/*查找列表中是否存在此连接的socket*/
for (u32FdFound=0,p=pRtspList; p!=NULL; p=p->next)
{
if (p->fd == s32Fd)
{
printf("### exit socket Fd ###\n");
u32FdFound=1;
break;
}
}
if (!u32FdFound)
{
/*创建一个连接,增加一个客户端*/
if (s32ConCnt<MAX_CONNECTION)
{
++s32ConCnt;
AddClient(&pRtspList,s32Fd);
}
else
{
fprintf(stderr, "exceed the MAX client, ignore this connecting\n");
return;
}
num_conn++;
fprintf(stderr, "%s Connection reached: %d\n", __FUNCTION__, num_conn);
}
}
/*对已有的连接进行调度*/
ScheduleConnections(&pRtspList,&s32ConCnt);
上面是EventLoop()函数的源码,讲一下RTSP_buffer这个结构体,定义在下面。因为这个直播是一对多的场景,即一个摄像头,多个用户同时观看,所以注定连接数肯定是大于1的,那么,多一个连接,这个新的连接的套接字等信息肯定也不一样,所以将每一个连接的属性做一个统一的结构体,且设置为链表的结构便于处理。
typedef struct _RTSP_buffer {
int fd; /*socket文件描述符*/
unsigned int port;/*端口号*/
struct sockaddr stClientAddr;
char in_buffer[RTSP_BUFFERSIZE];/*接收缓冲区*/
unsigned int in_size;/*接收缓冲区的大小*/
char out_buffer[RTSP_BUFFERSIZE+MAX_DESCR_LENGTH];/*发送缓冲区*/
int out_size;/*发送缓冲区大小*/
unsigned int rtsp_cseq;/*序列号*/
char descr[MAX_DESCR_LENGTH];/*描述*/
RTSP_session *session_list;/*会话链表*/
struct _RTSP_buffer *next; /*指向下一个结构体,构成了链表结构*/
} RTSP_buffer;
OK,EventLoop()这个函数我们可以看出来首先是判断是不是一个新的套接字:
- 如果是一个新的套接字,给这个新的套接字建立一个新的连接,即加一个客户端。进到AddClient()函数
RTSP_buffer *pRtsp=NULL,*pRtspNew=NULL;
#ifdef RTSP_DEBUG
fprintf(stderr, "%s, %d\n", __FUNCTION__, __LINE__);
#endif
//在链表头部插入第一个元素
if (*ppRtspList==NULL)
{
/*分配空间*/
if ( !(*ppRtspList=(RTSP_buffer*)calloc(1,sizeof(RTSP_buffer)) ) )
{
fprintf(stderr,"alloc memory error %s,%i\n", __FILE__, __LINE__);
return;
}
pRtsp = *ppRtspList;
}
else
{
//向链表中插入新的元素
for (pRtsp=*ppRtspList; pRtsp!=NULL; pRtsp=pRtsp->next)
{
pRtspNew=pRtsp;
}
/*在链表尾部插入*/
if (pRtspNew!=NULL)
{
if ( !(pRtspNew->next=(RTSP_buffer *)calloc(1,sizeof(RTSP_buffer)) ) )
{
fprintf(stderr, "error calloc %s,%i\n", __FILE__, __LINE__);
return;
}
pRtsp=pRtspNew->next;
pRtsp->next=NULL;
}
}
//设置最大轮询id号
if(g_s32Maxfd < fd)
{
g_s32Maxfd = fd;
}
/*初始化新添加的客户端*/
RTSP_initserver(pRtsp,fd);
fprintf(stderr,"Incoming RTSP connection accepted on socket: %d\n",pRtsp->fd);
从上面的AddClient()函数我们可以看到其实就是给新的连接的链表分配空间,初始化套接字,缓冲区等信息。
2.如果不是一个新的套接字,即不是新的连接,进到ScheduleConnections()进行已有连接的交互操作
int res;
RTSP_buffer *pRtsp=*rtsp_list,*pRtspN=NULL;
RTP_session *r=NULL, *t=NULL;
while (pRtsp!=NULL)
{
if ((res = RtspServer(pRtsp))!=ERR_NOERROR)
{
if (res==ERR_CONNECTION_CLOSE || res==ERR_GENERIC)
{
/*连接已经关闭*/
if (res==ERR_CONNECTION_CLOSE)
fprintf(stderr,"fd:%d,RTSP connection closed by client.\n",pRtsp->fd);
else
fprintf(stderr,"fd:%d,RTSP connection closed by server.\n",pRtsp->fd);
/*客户端在发送TEARDOWN 之前就截断了连接,但是会话却没有被释放*/
if (pRtsp->session_list!=NULL)
{
r=pRtsp->session_list->rtp_session;
/*释放所有会话*/
while (r!=NULL)
{
t = r->next;
RtpDelete((unsigned int)(r->hndRtp));
schedule_remove(r->sched_id);
r=t;
}
/*释放链表头指针*/
free(pRtsp->session_list);
pRtsp->session_list=NULL;
g_s32DoPlay--;
if (g_s32DoPlay == 0)
{
printf("user abort! no user online now resetfifo\n");
ringreset();
/* 重新将所有可用的RTP端口号放入到port_pool[MAX_SESSION] 中 */
RTP_port_pool_init(RTP_DEFAULT_PORT);
}
fprintf(stderr,"WARNING! fd:%d RTSP connection truncated before ending operations.\n",pRtsp->fd);
}
// wait for
close(pRtsp->fd);
--*conn_count;
num_conn--;
/*释放rtsp缓冲区*/
if (pRtsp==*rtsp_list)
{
//链表第一个元素就出错,则pRtspN为空
printf("first error,pRtsp is null\n");
*rtsp_list=pRtsp->next;
free(pRtsp);
pRtsp=*rtsp_list;
}
else
{
//不是链表中的第一个,则把当前出错任务删除,并把next任务存放在pRtspN(上一个没有出错的任务)
//指向的next,和当前需要处理的pRtsp中.
printf("dell current fd:%d\n",pRtsp->fd);
pRtspN->next=pRtsp->next;
free(pRtsp);
pRtsp=pRtspN->next;
printf("current next fd:%d\n",pRtsp->fd);
}
/*适当情况下,释放调度器本身*/
if (pRtsp==NULL && *conn_count<0)
{
fprintf(stderr,"to stop cchedule_do thread\n");
stop_schedule=1;
}
}
else
{
printf("current fd:%d\n",pRtsp->fd);
pRtsp = pRtsp->next;
}
}
else
{
//没有出错
//上一个处理没有出错的list存放在pRtspN中,需要处理的任务放在pRtst中
pRtspN = pRtsp;
pRtsp = pRtsp->next;
}
}
上面的源码主要是循环处理所有RTSP_buffer链表中的RTSP报文,具体处理过程在RtspServer()函数中,若其中某个连接有问题就会从链表中清除此连接并释放相关的内存。我们来看看RtspServer():
fd_set rset,wset; /*读写I/O描述集*/
struct timeval t;
int size;
static char buffer[RTSP_BUFFERSIZE+1]; /* +1 to control the final '\0'*/
int n;
int res;
struct sockaddr ClientAddr;
if (rtsp == NULL)
{
return ERR_NOERROR;
}
/*变量初始化*/
FD_ZERO(&rset);
FD_ZERO(&wset);
t.tv_sec=0; /*select 时间间隔*/
t.tv_usec=100000;
FD_SET(rtsp->fd,&rset);
/*调用select等待对应描述符变化*/
if (select(g_s32Maxfd+1,&rset,0,0,&t)<0)
{
fprintf(stderr,"select error %s %d\n", __FILE__, __LINE__);
send_reply(500, NULL, rtsp);
return ERR_GENERIC; //errore interno al server
}
/*有可供读进的rtsp包*/
if (FD_ISSET(rtsp->fd,&rset))
{
memset(buffer,0,sizeof(buffer));
size=sizeof(buffer)-1; /*最后一位用于填充字符串结束标识*/
/*读入数据到缓冲区中*/
#ifdef RTSP_DEBUG
fprintf(stderr, "tcp_read, %d\n", __LINE__);
#endif
n= tcp_read(rtsp->fd, buffer, size, &ClientAddr);
if (n==0)
{
return ERR_CONNECTION_CLOSE;
}
if (n<0)
{
fprintf(stderr,"read() error %s %d\n", __FILE__, __LINE__);
send_reply(500, NULL, rtsp); //服务器内部错误消息
return ERR_GENERIC;
}
//检查读入的数据是否产生溢出
if (rtsp->in_size+n>RTSP_BUFFERSIZE)
{
fprintf(stderr,"RTSP buffer overflow (input RTSP message is most likely invalid).\n");
send_reply(500, NULL, rtsp);
return ERR_GENERIC;//数据溢出错误
}
#ifdef RTSP_DEBUG
fprintf(stderr,"INPUT_BUFFER was:%s\n", buffer);
#endif
/*填充数据*/
memcpy(&(rtsp->in_buffer[rtsp->in_size]),buffer,n);
rtsp->in_size+=n;
//清空buffer
memset(buffer, 0, n);
//添加客户端地址信息
memcpy( &rtsp->stClientAddr, &ClientAddr, sizeof(ClientAddr));
/*处理缓冲区的数据,进行rtsp处理*/
if ((res=RTSP_handler(rtsp))==ERR_GENERIC)
{
fprintf(stderr,"Invalid input message.\n");
return ERR_NOERROR;
}
}
/*有发送数据*/
if (rtsp->out_size>0)
{
//将数据发送出去
n= tcp_write(rtsp->fd,rtsp->out_buffer,rtsp->out_size);
fprintf(stderr,"S");
fflush(stderr);
if (n<0)
{
fprintf(stderr,"tcp_write error %s %i\n", __FILE__, __LINE__);
send_reply(500, NULL, rtsp);
return ERR_GENERIC; //errore interno al server
}
#ifdef RTSP_DEBUG
fprintf(stderr,"OUTPUT_BUFFER length %d\n%s\n", rtsp->out_size, rtsp->out_buffer);
#endif
//清空发送缓冲区
memset(rtsp->out_buffer, 0, rtsp->out_size);
rtsp->out_size = 0;
}
//如果需要RTCP在此出加入对RTCP数据的接收,并存放在缓存中。
//继而在schedule_do线程中对其处理。
//rtcp控制处理,检查读入RTCP数据报
return ERR_NOERROR;
很熟悉吧?是我们上部分说过的非阻塞的传输方式,通过Socket获取客户端发过来的rtsp信报,然后将rtsp信报传到RTSP_handler()函数进行处理
int s32Meth;
while(pRtspBuf->in_size)
{
s32Meth = RTSP_validate_method(pRtspBuf);
if (s32Meth < 0)
{
//错误的请求,请求的方法不存在
fprintf(stderr,"Bad Request %s,%d\n", __FILE__, __LINE__);
printf("bad request, requestion not exit %d",s32Meth);
send_reply(400, NULL, pRtspBuf);
}
else
{
//进入到状态机,处理接收的请求
RTSP_state_machine(pRtspBuf, s32Meth);
printf("exit Rtsp_state_machine\r\n");
}
//丢弃处理之后的消息
RTSP_discard_msg(pRtspBuf);
printf(" After RTSP_discard_msg\r\n");
}
return ERR_NOERROR;
RTSP_validate_method(pRtspBuf)这个函数是通过sscanf()来按格式读取rtsp数据报,这里简要分析一下这个函数的一些简单用法
- 常见用法。
charstr[512]={0};
sscanf(“123456”,"%s",str);
printf(“str=%s”,str);
2. 取指定长度的字符串。如在下例中,取最大长度为4字节的字符串。
sscanf(“123456”,"%4s",str);
printf(“str=%s”,str);
3. 取到指定字符为止的字符串。如在下例中,取遇到空格为止字符串。
sscanf(“123456abcdedf”,"%[^]",str);
printf(“str=%s”,str);
4. 取仅包含指定字符集的字符串。如在下例中,取仅包含1到9和小写字母的字符串。
sscanf(“123456abcdedfBCDEF”,"%[1-9a-z]",str);
printf(“str=%s”,str);
5. 取到指定字符集为止的字符串。如在下例中,取遇到大写字母为止的字符串。
sscanf(“123456abcdedfBCDEF”,"%[^A-Z]",str);
printf(“str=%s”,str);
RTSP_validate_method()通过sscanf()这个函数来读取客户端rtsp信报中当前的方法来设置状态并返回。
这里重点讲一下RTSP_state_machine()这个函数,前面获取的当前方法会传入这个函数,这函数其实就是一个状态机,来实现各个方法的回传的信报拼接并传回客户端。
/*除了播放过程中发送的最后一个数据流,
*所有的状态迁移都在这里被处理
* 状态迁移位于stream_event中
*/
char *s;
RTSP_session *pRtspSess;
long int session_id;
char trash[255];
char szDebug[255];
/*找到会话位置*/
if ((s = strstr(pRtspBuf->in_buffer, HDR_SESSION)) != NULL)
{
if (sscanf(s, "%254s %ld", trash, &session_id) != 2)
{
fprintf(stderr,"Invalid Session number %s,%i\n", __FILE__, __LINE__);
send_reply(454, 0, pRtspBuf); /* 没有此会话*/
return;
}
}
/*打开会话列表*/
pRtspSess = pRtspBuf->session_list;
if (pRtspSess == NULL)
{
return;
}
#ifdef RTSP_DEBUG
sprintf(szDebug,"state_machine:current state is ");
strcat(szDebug,((pRtspSess->cur_state==0)?"init state":((pRtspSess->cur_state==1)?"ready state":"play state")));
printf("%s\n", szDebug);
#endif
/*根据状态迁移规则,从当前状态开始迁移*/
switch (pRtspSess->cur_state)
{
case INIT_STATE: /*初始态*/
{
#ifdef RTSP_DEBUG
fprintf(stderr,"current method code is: %d \n",method);
#endif
switch (method)
{
case RTSP_ID_DESCRIBE: //状态不变
RTSP_describe(pRtspBuf);
//printf("3\r\n");
break;
case RTSP_ID_SETUP: //状态变为就绪态
//printf("4\r\n");
if (RTSP_setup(pRtspBuf) == ERR_NOERROR)
{
//printf("5\r\n");
pRtspSess->cur_state = READY_STATE;
fprintf(stderr,"TRANSFER TO READY STATE!\n");
}
break;
case RTSP_ID_TEARDOWN: //状态不变
RTSP_teardown(pRtspBuf);
break;
case RTSP_ID_OPTIONS:
if (RTSP_options(pRtspBuf) == ERR_NOERROR)
{
pRtspSess->cur_state = INIT_STATE; //状态不变
}
break;
case RTSP_ID_PLAY: //method not valid this state.
case RTSP_ID_PAUSE:
send_reply(455, 0, pRtspBuf);
break;
default:
send_reply(501, 0, pRtspBuf);
break;
}
break;
}
case READY_STATE:
{
#ifdef RTSP_DEBUG
fprintf(stderr,"current method code is:%d\n",method);
#endif
switch (method)
{
case RTSP_ID_PLAY: //状态迁移为播放态
if (RTSP_play(pRtspBuf) == ERR_NOERROR)
{
fprintf(stderr,"\tStart Playing!\n");
pRtspSess->cur_state = PLAY_STATE;
}
break;
case RTSP_ID_SETUP:
if (RTSP_setup(pRtspBuf) == ERR_NOERROR) //状态不变
{
pRtspSess->cur_state = READY_STATE;
}
break;
case RTSP_ID_TEARDOWN:
RTSP_teardown(pRtspBuf); //状态变为初始态 ?
break;
case RTSP_ID_OPTIONS:
if (RTSP_options(pRtspBuf) == ERR_NOERROR)
{
pRtspSess->cur_state = INIT_STATE; //状态不变
}
break;
case RTSP_ID_PAUSE: // method not valid this state.
send_reply(455, 0, pRtspBuf);
break;
case RTSP_ID_DESCRIBE:
RTSP_describe(pRtspBuf);
break;
default:
send_reply(501, 0, pRtspBuf);
break;
}
break;
}
case PLAY_STATE:
{
switch (method)
{
case RTSP_ID_PLAY:
// Feature not supported
fprintf(stderr,"UNSUPPORTED: Play while playing.\n");
send_reply(551, 0, pRtspBuf); // Option not supported
break;
/* //不支持暂停命令
case RTSP_ID_PAUSE: //状态变为就绪态
if (RTSP_pause(pRtspBuf) == ERR_NOERROR)
{
pRtspSess->cur_state = READY_STATE;
}
break;
*/
case RTSP_ID_TEARDOWN:
RTSP_teardown(pRtspBuf); //状态迁移为初始态
break;
case RTSP_ID_OPTIONS:
break;
case RTSP_ID_DESCRIBE:
RTSP_describe(pRtspBuf);
break;
case RTSP_ID_SETUP:
break;
}
break;
}/* PLAY state */
default:
{
/* invalid/unexpected current state. */
fprintf(stderr,"%s State error: unknown state=%d, method code=%d\n", __FUNCTION__, pRtspSess->cur_state, method);
}
break;
}/* end of current state switch */
#ifdef RTSP_DEBUG
printf("leaving rtsp_state_machine!\n");
#endif
以上就是处理rtsp交互的状态机,结合上面列出的RTSP交互过程,describe和setup两个方法将在初始态处理,当setup成功后,状态变为就绪态,这个状态下会给一些必要的属性重新赋值,这些属性是控制底层数据读取的标志,之后变成PLAY态,这个状态下会不断从底层取数据处理并封装发送到客户端,一旦连接中断,状态又会变回初始态。下面我讲一下各个方法的处理代码。
首先是Describe
char object[255], trash[255];
char *p;
unsigned short port;
char s8Url[255];
char s8Descr[MAX_DESCR_LENGTH];
char server[128];
char s8Str[128];
/*根据收到的请求请求消息,跳过方法名,分离出URL*/
if (!sscanf(pRtsp->in_buffer, " %*s %254s ", s8Url))//%*s表示第一个匹配到的%s被过滤掉
{
fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);
send_reply(400, 0, pRtsp); /* bad request */
printf("get URL error");
return ERR_NOERROR;
}
/*验证URL */
switch (ParseUrl(s8Url, server, &port, object, sizeof(object)))
{
case 1: /*请求错误*/
fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);
send_reply(400, 0, pRtsp);
printf("url request error");
return ERR_NOERROR;
break;
case -1: /*内部错误*/
fprintf(stderr,"url error while parsing !\n");
send_reply(500, 0, pRtsp);
printf("inner error");
return ERR_NOERROR;
break;
default:
break;
}
/*取得序列号,并且必须有这个选项*/
if ((p = strstr(pRtsp->in_buffer, HDR_CSEQ)) == NULL)
{
fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);
send_reply(400, 0, pRtsp); /* Bad Request */
printf("get serial num error");
return ERR_NOERROR;
}
else
{
if (sscanf(p, "%254s %d", trash, &(pRtsp->rtsp_cseq)) != 2)
{
fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);
send_reply(400, 0, pRtsp); /*请求错误*/
printf("get serial num 2 error");
return ERR_NOERROR;
}
}
//获取SDP内容
GetSdpDescr(pRtsp, s8Descr, s8Str);
//发送Describe响应
//printf("----------------1\r\n");
SendDescribeReply(pRtsp, object, s8Descr, s8Str);
//printf("2\r\n");
return ERR_NOERROR;
这里说一下一些常用的字段处理函数:
①strstr
strstr(str1,str2)
strstr()函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。
② strncmp
int strncmp ( const char * str1, const char * str2, size_t n );
str1, str2 为需要比较的两个字符串,n为要比较的字符的数目。若str1与str2的前n个字符相同,则返回0;若s1大于s2,则返回大于0的值;若s1 小于s2,则返回小于0的值。
③strchr
char *strchr(const char *s,char c)
查找字符串s中首次出现字符c的位置。
④strncpy
char*strncpy(char *dest, const char *src, int n)
把src所指向的字符串中以src地址开始的前n个字节复制到dest所指的数组中,并返回被复制后的dest
⑤strcpy
char *strcpy(char* dest, const char *src);
把从src地址开始且含有NULL结束符(’\0’)的字符串复制到以dest开始的地址空间,src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。
⑥strcat
char *strcat(char *dest, const char *src);
把src所指向的字符串(包括“\0”)复制到dest所指向的字符串后面(删除dest原来末尾的“\0”)。要保证dest足够长,以容纳被复制进来的*src。*src中原有的字符不变。返回指向dest的指针。
通过以上这些字段处理函数提取RTSP信报中想要的信息,并在GetSdpDescr(pRtsp, s8Descr, s8Str);这个函数中组建自己的SDP。这里贴一下组的SDP:
struct ifreq stIfr;//linux 网络接口用来配置ip地址,激活接口,配置MTU
char pSdpId[128];
//char rtp_port[5];
strcpy(stIfr.ifr_name, "br0");
if(ioctl(pRtsp->fd, SIOCGIFADDR, &stIfr) < 0)
{
//printf("Failed to get host eth0 ip\n");
strcpy(stIfr.ifr_name, "eth0");
if(ioctl(pRtsp->fd, SIOCGIFADDR, &stIfr) < 0)
{
printf("Failed to get host br0 or eth0 ip\n");
}
}
sock_ntop_host(&stIfr.ifr_addr, sizeof(struct sockaddr), s8Str, 128);
GetSdpId(pSdpId);
strcpy(pDescr, "v=0\r\n");
strcat(pDescr, "o=-");
strcat(pDescr, pSdpId);
strcat(pDescr," ");
strcat(pDescr, pSdpId);
strcat(pDescr," IN IP4 ");
strcat(pDescr, s8Str);
strcat(pDescr, "\r\n");
strcat(pDescr, "s=H.264 Video, streamed by the Test Media Server\r\n");//(session name)
strcat(pDescr, "i=test.h264\r\n");//session的信息
strcat(pDescr, "t=0 0\r\n");//时间信息,分别表示开始的时间和结束的时间,一般在流媒体的直播的时移中见的比较多
strcat(pDescr, "a=tool:Test Streaming Media v2018.11.30\r\n");//创建任务描述的工具的名称及版本号
strcat(pDescr, "a=type:broadcast\r\n");//会议类型
strcat(pDescr, "a=control:*\r\n");
strcat(pDescr, "m=video 0 RTP/AVP 96\r\n");
strcat(pDescr, "\r\n");
strcat(pDescr,"b=AS:500\r\n");
/**** Dynamically defined payload ****/
strcat(pDescr,"a=rtpmap:96");
strcat(pDescr," ");
strcat(pDescr,"H264/90000");
strcat(pDescr, "\r\n");
strcat(pDescr,"a=fmtp:96 packetization-mode=1;");
strcat(pDescr,"profile-level-id=");
strcat(pDescr,psp.base64profileid);
strcat(pDescr,";sprop-parameter-sets=");
strcat(pDescr,psp.base64sps);
strcat(pDescr,",");
strcat(pDescr,psp.base64pps);
strcat(pDescr, "\r\n");
strcat(pDescr,"a=control:track1");
strcat(pDescr, "\r\n");
printf("\n\n%s,%d===>psp.base64profileid=%s,psp.base64sps=%s,psp.base64pps=%s\n\n",__FUNCTION__,__LINE__,psp.base64profileid,psp.base64sps,psp.base64pps);
这篇博文写得挺全了我觉得,想了解多一点的可以在上面的网址看看。这里注意一下,sprop-parameter-sets=后面跟的是Base64编码后的SPS和PPS。组好SDP之后发送出去
char *pMsgBuf; /* 用于获取响应缓冲指针*/
int s32MbLen;
/* 分配空间,处理内部错误*/
s32MbLen = 2048;
pMsgBuf = (char *)malloc(s32MbLen);
if (!pMsgBuf)
{
fprintf(stderr,"send_describe_reply(): unable to allocate memory\n");
send_reply(500, 0, rtsp); /* internal server error */
if (pMsgBuf)
{
free(pMsgBuf);
}
return ERR_ALLOC;
}
/*构造describe消息串*/
sprintf(pMsgBuf, "%s %d %s"RTSP_EL"CSeq: %d"RTSP_EL"Server: %s/%s"RTSP_EL, RTSP_VER, 200, get_stat(200), rtsp->rtsp_cseq, PACKAGE, VERSION);
add_time_stamp(pMsgBuf, 0); /*添加时间戳*/
strcat(pMsgBuf, "Content-Type: application/sdp"RTSP_EL); /*实体头,表示实体类型*/
/*用于解析实体内相对url的 绝对url*/
sprintf(pMsgBuf + strlen(pMsgBuf), "Content-Base: rtsp://%s/%s/"RTSP_EL, s8Str, object);
sprintf(pMsgBuf + strlen(pMsgBuf), "Content-Length: %d"RTSP_EL, strlen(descr)); /*消息体的长度*/
strcat(pMsgBuf, RTSP_EL);
/*消息头结束*/
/*加上消息体*/
strcat(pMsgBuf, descr); /*describe消息*/
/*向缓冲区中填充数据*/
bwrite(pMsgBuf, (unsigned short) strlen(pMsgBuf), rtsp);
free(pMsgBuf);
return ERR_NOERROR;
Describe 之后是SetUp
char s8TranStr[128], *s8Str;
char *pStr;
RTP_transport Transport;
int s32SessionID=0;
RTP_session *rtp_s, *rtp_s_prec;
RTSP_session *rtsp_s;
if ((s8Str = strstr(pRtsp->in_buffer, HDR_TRANSPORT)) == NULL)
{
fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);
send_reply(406, 0, pRtsp); // Not Acceptable
printf("not acceptable");
return ERR_NOERROR;
}
//检查传输层子串是否正确
if (sscanf(s8Str, "%*10s %255s", s8TranStr) != 1)
{
fprintf(stderr,"SETUP request malformed: Transport string is empty\n");
send_reply(400, 0, pRtsp); // Bad Request
printf("check transport 400 bad request");
return ERR_NOERROR;
}
fprintf(stderr,"*** transport: %s ***\n", s8TranStr);
//如果需要增加一个会话
if ( !pRtsp->session_list )
{
pRtsp->session_list = (RTSP_session *) calloc(1, sizeof(RTSP_session));
}
rtsp_s = pRtsp->session_list;
//建立一个新会话,插入到链表中
if (pRtsp->session_list->rtp_session == NULL)
{
pRtsp->session_list->rtp_session = (RTP_session *) calloc(1, sizeof(RTP_session));
rtp_s = pRtsp->session_list->rtp_session;
}
else
{
for (rtp_s = rtsp_s->rtp_session; rtp_s != NULL; rtp_s = rtp_s->next)
{
rtp_s_prec = rtp_s;
}
rtp_s_prec->next = (RTP_session *) calloc(1, sizeof(RTP_session));
rtp_s = rtp_s_prec->next;
}
//起始状态为暂停
rtp_s->pause = 1;
rtp_s->hndRtp = NULL;
Transport.type = RTP_no_transport;
if((pStr = strstr(s8TranStr, RTSP_RTP_AVP)))
{
//Transport: RTP/AVP
pStr += strlen(RTSP_RTP_AVP);
if ( !*pStr || (*pStr == ';') || (*pStr == ' '))
{
//单播
if (strstr(s8TranStr, "unicast"))
{
//如果指定了客户端端口号,填充对应的两个端口号
if( (pStr = strstr(s8TranStr, "client_port")) )
{
pStr = strstr(s8TranStr, "=");
sscanf(pStr + 1, "%d", &(Transport.u.udp.cli_ports.RTP));
pStr = strstr(s8TranStr, "-");
sscanf(pStr + 1, "%d", &(Transport.u.udp.cli_ports.RTCP));
}
//服务器端口
if (RTP_get_port_pair(&Transport.u.udp.ser_ports) != ERR_NOERROR)
{
fprintf(stderr, "Error %s,%d\n", __FILE__, __LINE__);
send_reply(500, 0, pRtsp);/* Internal server error */
return ERR_GENERIC;
}
//建立RTP套接字
rtp_s->hndRtp = (struct _tagStRtpHandle*)RtpCreate((unsigned int)(((struct sockaddr_in *)(&pRtsp->stClientAddr))->sin_addr.s_addr), Transport.u.udp.cli_ports.RTP, _h264nalu);
printf("<><><><>Creat RTP<><><><>\n");
Transport.u.udp.is_multicast = 0;
}
else
{
printf("multicast not codeing\n");
//multicast 多播处理....
}
Transport.type = RTP_rtp_avp;
}
else if (!strncmp(s8TranStr, "/TCP", 4))
{
if( (pStr = strstr(s8TranStr, "interleaved")) )
{
pStr = strstr(s8TranStr, "=");
sscanf(pStr + 1, "%d", &(Transport.u.tcp.interleaved.RTP));
if ((pStr = strstr(pStr, "-")))
sscanf(pStr + 1, "%d", &(Transport.u.tcp.interleaved.RTCP));
else
Transport.u.tcp.interleaved.RTCP = Transport.u.tcp.interleaved.RTP + 1;
}
else
{
}
Transport.rtp_fd = pRtsp->fd;
// Transport.rtcp_fd_out = pRtsp->fd;
// Transport.rtcp_fd_in = -1;
}
}
printf("pstr=%s\n",pStr);
if (Transport.type == RTP_no_transport)
{
fprintf(stderr,"AAAAAAAAAAA Unsupported Transport,%s,%d\n", __FILE__, __LINE__);
send_reply(461, 0, pRtsp);// Bad Request
return ERR_NOERROR;
}
memcpy(&rtp_s->transport, &Transport, sizeof(Transport));
//如果有会话头,就有了一个控制集合
if ((pStr = strstr(pRtsp->in_buffer, HDR_SESSION)) != NULL)
{
if (sscanf(pStr, "%*s %d", &s32SessionID) != 1)
{
fprintf(stderr, "Error %s,%i\n", __FILE__, __LINE__);
send_reply(454, 0, pRtsp); // Session Not Found
return ERR_NOERROR;
}
}
else
{
//产生一个非0的随机的会话序号
struct timeval stNowTmp;
gettimeofday(&stNowTmp, 0);
srand((stNowTmp.tv_sec * 1000) + (stNowTmp.tv_usec / 1000));
s32SessionID = 1 + (int) (10.0 * rand() / (100000 + 1.0));
if (s32SessionID == 0)
{
s32SessionID++;
}
}
pRtsp->session_list->session_id = s32SessionID;
pRtsp->session_list->rtp_session->sched_id = schedule_add(rtp_s);
send_setup_reply(pRtsp, rtsp_s, rtp_s);
return ERR_NOERROR;
这里重点讲一下两地方吧,一个是RtpCreate()函数
HndRtp hRtp = NULL;
struct timeval stTimeval;
struct ifreq stIfr;
int s32Broadcast = 1;
struct sockaddr_in addr;
hRtp = (HndRtp)calloc(1, sizeof(StRtpObj));
if(NULL == hRtp)
{
printf("Failed to create RTP handle\n");
goto cleanup;
}
hRtp->s32Sock = -1;
if((hRtp->s32Sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
printf("Failed to create socket\n");
goto cleanup;
}
if(0xFF000000 == (u32IP & 0xFF000000))
{
if(-1 == setsockopt(hRtp->s32Sock, SOL_SOCKET, SO_BROADCAST, (char *)&s32Broadcast, sizeof(s32Broadcast)))
{
printf("Failed to set socket\n");
goto cleanup;
}
}
memset(&addr, 0, sizeof(addr));
while(1)
{
addr.sin_port = BigLittleSwap16(server_port);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
hRtp->stServAddr.sin_family = AF_INET;
hRtp->stServAddr.sin_port = BigLittleSwap16(s32Port);
hRtp->stServAddr.sin_addr.s_addr = u32IP;
bzero(&(hRtp->stServAddr.sin_zero), 8);
if (bind(hRtp->s32Sock, (struct sockaddr *)&addr, sizeof(addr)))
{
printf("can't bind !!!!!!!!!!!!!!!!!!!!!!!!!!!");
server_port++;
}
else
break;
}
//初始化序号
hRtp->u16SeqNum = 0;
//初始化时间戳
hRtp->u32TimeStampInc = 0;
hRtp->u32TimeStampCurr = 0;
//获取当前时间
if(gettimeofday(&stTimeval, NULL) == -1)
{
printf("Failed to get os time\n");
goto cleanup;
}
hRtp->u32PrevTime = stTimeval.tv_sec * 1000 + stTimeval.tv_usec / 1000;
hRtp->emPayload = emPayload;
//获取本机网络设备名
strcpy(stIfr.ifr_name, "br0");
if(ioctl(hRtp->s32Sock, SIOCGIFADDR, &stIfr) < 0)
{
//printf("Failed to get host ip\n");
strcpy(stIfr.ifr_name, "eth0");
if(ioctl(hRtp->s32Sock, SIOCGIFADDR, &stIfr) < 0)
{
printf("Failed to get host eth0 or wlan0 ip\n");
goto cleanup;
}
}
hRtp->u32SSrc = BigLittleSwap32(((struct sockaddr_in *)(&stIfr.ifr_addr))->sin_addr.s_addr);
local_ip = hRtp->u32SSrc;
//hRtp->u32SSrc = htonl(((struct sockaddr_in *)(&stIfr.ifr_addr))->sin_addr.s_addr);
printf("!!!!!!!!!!!!!!!!!!!!!!rtp create:addr:%x,port:%d,local%x\n",u32IP,s32Port,hRtp->u32SSrc);
printf("<><><><>success creat RTP<><><><>\n");
return (unsigned int)hRtp;
cleanup:
if(hRtp)
{
if(hRtp->s32Sock >= 0)
{
close(hRtp->s32Sock);
}
free(hRtp);
}
这个函数主要是创建了一个用于RTP传输的Socket,用来传输封装好的RTP数据包,这一部分我会在第三部分讲。另一个要注意的地方就是在
pRtsp->session_list->rtp_session->sched_id = schedule_add(rtp_s);
schedule_add(rtp_s)这个函数里面给每一个连接的关键参数置位,这些参数是用来控制取底层数据并保存在缓冲区的判断依据,还有就是设置了RtpSend()这个回调函数,这个回调函数会在Play阶段被调用,用来封装底层上来的码流数据并发送。
Play和Teardown比较简单,我这边就不放代码了,PLay主要还是设置属性,使进入读取底层数据并保存的判定为真,Teardown主要是释放一些内存,以及释放RTSP链表的一些操作。
总结一下吧,关于RTSP交互这一块,首先就是建立一个Socket用以接受发送RTSP的数据报文的,通过这个Socket进行我上面介绍过的服务器和客户端的交互,在Setup阶段创建新的Socket连接用来做具体的码流数据传输,Play阶段就是不断的取底层数据进行封装发送,TearDown阶段断开连接并释放相关的指针或链表。
关于具体的码流是怎么封装的我会在第三部分讲。