上一部分说了Socket通讯的一些东西,这部分就结合代码来说说RTSP交互的过程。
在放实现代码之前先说说概念上的东西吧,关于RTSP这个流媒体网络协议。RTSP作为一个应用层协议,提供了一个可供扩展的框架,使得流媒体的受控和点播变得可能,它主要用来控制具有实时特性的数据的发送,但其本身并不用于传送流媒体数据,而必须依赖下层传输协议(如RTP/RTCP)所提供的服务来完成流媒体数据的传送。RTSP负责定义具体的控制信息、操作方法、状态码,以及描述与RTP之间的交互操作。所以具体的码流数据其实是用RTP封装传输的,第三部分我会详细讲码流数据的处理和发送。

rtsp协议获取监控视频流 rtsp协议的摄像头_数据


一次基本的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 (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数据报,这里简要分析一下这个函数的一些简单用法

  1. 常见用法。

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阶段断开连接并释放相关的指针或链表。

关于具体的码流是怎么封装的我会在第三部分讲。