网络摄像头Rtsp直播方案(二)

本文深入探讨RTSP协议在网络摄像头直播中的应用,解释RTSP如何控制实时流媒体数据传输,涉及DESCRIBE、SETUP、PLAY和TEARDOWN等基本交互流程。同时,介绍了状态机实现及关键函数的作用,如RTSP_state_machine()和RtpCreate(),阐述了RTSP数据报文处理和SDP描述的构建。
摘要由CSDN通过智能技术生成

上一部分说了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()这个函数我们可以看出来首先是判断是不是一个新的套接字:

  1. 如果是一个新的套接字,给这个新的套接字建立一个新的连接,即加一个客户端。进到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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值