上一部分说了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 (rts