2. Realización del protocolo RTSP

Escribir en frente


Esta serie solo busca la simplificación y tiene como objetivo aprender el proceso de implementación del protocolo RTSP, no perseguir complejo y perfecto, por lo que el servidor RTSP que se implementará aquí es simple, y solo puede haber un cliente a la vez.

Antes de escribir un servidor RTSP, debemos saber que el servidor RTSP más simple contiene dos partes, una parte es la interacción de RTSP y la otra parte es el envío de RTP. Este artículo primero implementa el proceso de interacción RTSP

Primero, crea un socket


¿Piensa en lo que sucedió después de ingresar rtsp: //127.0.0.1: 8554 en vlc?

En este caso, vlc es en realidad un cliente rtsp. Después de ingresar la url, vlc sabe que la IP de destino es 127.0.0.1 y el número de puerto de destino es 8854. En este momento, vlc iniciará una conexión tcp para conectarse al servidor. Después de que la conexión sea exitosa Comience a enviar solicitudes, el servidor responde

Entonces, tenemos que escribir un servidor rtsp, el primer paso debe ser crear un servidor tcp

Primero cree el socket tcp, enlace el puerto, escuche

//1.创建套接字
serverSockfd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(serverSockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));

//2.绑定地址和端口号  这个示例绑定的地址是INADDR_ANY,端口号为8554
bind(serverSockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)

//3.开始监听
listen(serverSockfd, 10);

El servidor RTSP utiliza RTP y RTCP para transmitir datos e información de audio y video, por lo que también necesitamos crear sockets UDP para RTP y RTCP y vincular puertos de números

//1.创建套接字
serverRtpSockfd = createUdpSocket();
serverRtcpSockfd = createUdpSocket();

//2.绑定端口号 当创建好套接字还有绑定号端口后,就可以接收客户端请求了
bindSocketAddr(serverRtpSockfd, "0.0.0.0", SERVER_RTP_PORT);
bindSocketAddr(serverRtcpSockfd, "0.0.0.0", SERVER_RTCP_PORT);

//3.开始accept等待客户端连接
clientfd = accept(serverSockfd, (struct sockaddr *)&addr, &len);

 

Segundo, analiza la solicitud

Cuando el cliente rtsp se conecta con éxito, comenzará a enviar solicitudes. El servidor debe recibir la solicitud del cliente y comenzar a analizar, y luego tomar las acciones correspondientes

Aquí estamos haciendo el más simple, en primer lugar la determinación 第一行de conseguir 方法, para OPTIONS, DESCRIBE, PLAY,TEARDOWN que acaba de resolver CSeq. Para SETUP, vamos a client_portanalizarlo

Entonces, el primer paso que tenemos que hacer es analizar la información en la solicitud

 

Después de analizar el comando de solicitud, el siguiente paso es tener una forma diferente de hacer diferentes respuestas, de la siguiente manera

/*
 * 作者:_JT_
 * 博客:https://blog.csdn.net/weixin_42462202
 */
if(!strcmp(method, "OPTIONS"))
{
	handleCmd_OPTIONS();
}
else if(!strcmp(method, "DESCRIBE"))
{
	handleCmd_DESCRIBE();
}
else if(!strcmp(method, "SETUP"))
{
	handleCmd_SETUP();
}
else if(!strcmp(method, "PLAY"))
{
	handleCmd_PLAY();
}
else if(!strcmp(method, "TEARDOWN"))
{
	handleCmd_TEARDOWN();
}

 

3. OPCIONES respuesta

OPTIONS es un método que el cliente solicita que use el servidor, y le responderemos con los métodos actualmente disponibles para nosotros.

sprintf(sBuf, "RTSP/1.0 200 OK\r\n"
				"CSeq: %d\r\n"
				"Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n"
				"\r\n",
				cseq);
send(clientSockfd, sBuf, strlen(sBuf));

4. DESCRIBA la respuesta

DESCRIBE es que el cliente solicita información de medios del servidor. Este es el servidor que necesita responder al archivo de descripción SDP. El medio en este ejemplo es H.264

  • generación de archivos sdp

sprintf(sdp, "v=0\r\n"
			"o=- 9%ld 1 IN IP4 %s\r\n"
			"t=0 0\r\n"
			"a=control:*\r\n"
			"m=video 0 RTP/AVP 96\r\n"
			"a=rtpmap:96 H264/90000\r\n"
			"a=control:track0\r\n",
			time(NULL), localIp);

Responder

sprintf(sBuf, "RTSP/1.0 200 OK\r\n"
		"CSeq: %d\r\n"
		"Content-Base: %s\r\n"
		"Content-type: application/sdp\r\n"
		"Content-length: %d\r\n\r\n"
		"%s",
		cseq,
		url,
		strlen(sdp),
		sdp);
		
send(clientSockfd, sBuf, strlen(sBuf));

V. RESPUESTA DE CONFIGURACIÓN

La CONFIGURACIÓN es que el cliente solicita establecer una conexión de sesión y envía el puerto RTP y el puerto RTCP del cliente, luego el servidor debe responder al puerto RTP y al puerto RTCP del servidor

Seis, respuesta PLAY

Durante PLAY, el cliente solicita la reproducción del servidor, y luego el servidor comienza a enviar paquetes RTP a través del socket udp creado durante el proceso de configuración después de responder a la solicitud

Siete, el código fuente


 
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>

#define SERVER_PORT     8554
#define SERVER_RTP_PORT  55532
#define SERVER_RTCP_PORT 55533
#define BUF_MAX_SIZE    (1024*1024)

static int createTcpSocket()
{
    int sockfd;
    int on = 1;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
        return -1;

    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));

    return sockfd;
}

static int createUdpSocket()
{
    int sockfd;
    int on = 1;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
        return -1;

    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));

    return sockfd;
}

static int bindSocketAddr(int sockfd, const char* ip, int port)
{
    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip);

    if(bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0)
        return -1;

    return 0;
}

static int acceptClient(int sockfd, char* ip, int* port)
{
    int clientfd;
    socklen_t len = 0;
    struct sockaddr_in addr;

    memset(&addr, 0, sizeof(addr));
    len = sizeof(addr);

    clientfd = accept(sockfd, (struct sockaddr *)&addr, &len);
    if(clientfd < 0)
        return -1;
    
    strcpy(ip, inet_ntoa(addr.sin_addr));
    *port = ntohs(addr.sin_port);

    return clientfd;
}

static char* getLineFromBuf(char* buf, char* line)
{
    while(*buf != '\n')
    {
        *line = *buf;
        line++;
        buf++;
    }

    *line = '\n';
    ++line;
    *line = '\0';

    ++buf;
    return buf; 
}

static int handleCmd_OPTIONS(char* result, int cseq)
{
    sprintf(result, "RTSP/1.0 200 OK\r\n"
                    "CSeq: %d\r\n"
                    "Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n"
                    "\r\n",
                    cseq);
                
    return 0;
}

static int handleCmd_DESCRIBE(char* result, int cseq, char* url)
{
    char sdp[500];
    char localIp[100];

    sscanf(url, "rtsp://%[^:]:", localIp);

    sprintf(sdp, "v=0\r\n"
                 "o=- 9%ld 1 IN IP4 %s\r\n"
                 "t=0 0\r\n"
                 "a=control:*\r\n"
                 "m=video 0 RTP/AVP 96\r\n"
                 "a=rtpmap:96 H264/90000\r\n"
                 "a=control:track0\r\n",
                 time(NULL), localIp);
    
    sprintf(result, "RTSP/1.0 200 OK\r\nCSeq: %d\r\n"
                    "Content-Base: %s\r\n"
                    "Content-type: application/sdp\r\n"
                    "Content-length: %d\r\n\r\n"
                    "%s",
                    cseq,
                    url,
                    strlen(sdp),
                    sdp);
    
    return 0;
}

static int handleCmd_SETUP(char* result, int cseq, int clientRtpPort)
{
    sprintf(result, "RTSP/1.0 200 OK\r\n"
                    "CSeq: %d\r\n"
                    "Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d\r\n"
                    "Session: 66334873\r\n"
                    "\r\n",
                    cseq,
                    clientRtpPort,
                    clientRtpPort+1,
                    SERVER_RTP_PORT,
                    SERVER_RTCP_PORT);
    
    return 0;
}

static int handleCmd_PLAY(char* result, int cseq)
{
    sprintf(result, "RTSP/1.0 200 OK\r\n"
                    "CSeq: %d\r\n"
                    "Range: npt=0.000-\r\n"
                    "Session: 66334873; timeout=60\r\n\r\n",
                    cseq);
    
    return 0;
}

static void doClient(int clientSockfd, const char* clientIP, int clientPort,
                        int serverRtpSockfd, int serverRtcpSockfd)
{
    char method[40];
    char url[100];
    char version[40];
    int cseq;
    int clientRtpPort, clientRtcpPort;
    char *bufPtr;
    char* rBuf = malloc(BUF_MAX_SIZE);
    char* sBuf = malloc(BUF_MAX_SIZE);
    char line[400];

    while(1)
    {
        int recvLen;

        recvLen = recv(clientSockfd, rBuf, BUF_MAX_SIZE, 0);
        if(recvLen <= 0)
            goto out;

        rBuf[recvLen] = '\0';
        printf("---------------C->S--------------\n");
        printf("%s", rBuf);

        /* 解析方法 */
        bufPtr = getLineFromBuf(rBuf, line);
        if(sscanf(line, "%s %s %s\r\n", method, url, version) != 3)
        {
            printf("parse err\n");
            goto out;
        }

        /* 解析序列号 */
        bufPtr = getLineFromBuf(bufPtr, line);
        if(sscanf(line, "CSeq: %d\r\n", &cseq) != 1)
        {
            printf("parse err\n");
            goto out;
        }

        /* 如果是SETUP,那么就再解析client_port */
        if(!strcmp(method, "SETUP"))
        {
            while(1)
            {
                bufPtr = getLineFromBuf(bufPtr, line);
                if(!strncmp(line, "Transport:", strlen("Transport:")))
                {
                    sscanf(line, "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n",
                                    &clientRtpPort, &clientRtcpPort);
                    break;
                }
            }
        }

        if(!strcmp(method, "OPTIONS"))
        {
            if(handleCmd_OPTIONS(sBuf, cseq))
            {
                printf("failed to handle options\n");
                goto out;
            }
        }
        else if(!strcmp(method, "DESCRIBE"))
        {
            if(handleCmd_DESCRIBE(sBuf, cseq, url))
            {
                printf("failed to handle describe\n");
                goto out;
            }
        }
        else if(!strcmp(method, "SETUP"))
        {
            if(handleCmd_SETUP(sBuf, cseq, clientRtpPort))
            {
                printf("failed to handle setup\n");
                goto out;
            }
        }
        else if(!strcmp(method, "PLAY"))
        {
            if(handleCmd_PLAY(sBuf, cseq))
            {
                printf("failed to handle play\n");
                goto out;
            }
        }
        else
        {
            goto out;
        }

        printf("---------------S->C--------------\n");
        printf("%s", sBuf);
        send(clientSockfd, sBuf, strlen(sBuf), 0);
    }
out:
    close(clientSockfd);
    free(rBuf);
    free(sBuf);
}

int main(int argc, char* argv[])
{
    int serverSockfd;
    int serverRtpSockfd, serverRtcpSockfd;
    int ret;

    serverSockfd = createTcpSocket();
    if(serverSockfd < 0)
    {
        printf("failed to create tcp socket\n");
        return -1;
    }

    ret = bindSocketAddr(serverSockfd, "0.0.0.0", SERVER_PORT);
    if(ret < 0)
    {
        printf("failed to bind addr\n");
        return -1;
    }

    ret = listen(serverSockfd, 10);
    if(ret < 0)
    {
        printf("failed to listen\n");
        return -1;
    }

    serverRtpSockfd = createUdpSocket();
    serverRtcpSockfd = createUdpSocket();
    if(serverRtpSockfd < 0 || serverRtcpSockfd < 0)
    {
        printf("failed to create udp socket\n");
        return -1;
    }

    if(bindSocketAddr(serverRtpSockfd, "0.0.0.0", SERVER_RTP_PORT) < 0 ||
        bindSocketAddr(serverRtcpSockfd, "0.0.0.0", SERVER_RTCP_PORT) < 0)
    {
        printf("failed to bind addr\n");
        return -1;
    }

    printf("rtsp://127.0.0.1:%d\n", SERVER_PORT);

    while(1)
    {
        int clientSockfd;
        char clientIp[40];
        int clientPort;

        clientSockfd = acceptClient(serverSockfd, clientIp, &clientPort);
        if(clientSockfd < 0)
        {
            printf("failed to accept client\n");
            return -1;
        }

        printf("accept client;client ip:%s,client port:%d\n", clientIp, clientPort);

        doClient(clientSockfd, clientIp, clientPort, serverRtpSockfd, serverRtcpSockfd);
    }

    return 0;
}

8. Prueba

Compile y ejecute el código fuente, abra vlc, ingrese rtsp: //127.0.0.1: 8554, haga clic en iniciar para reproducir, puede ver que la consola imprimirá el proceso interactivo, o use wireshak para capturar el paquete

Este artículo termina aquí, y se completa la parte interactiva del protocolo RTSP. Después de PLAY, el paquete RTP no se ha enviado, por lo que no puedo ver el video por el momento. Cómo enviar el paquete RTP, consulte el siguiente artículo
 

115 artículos originales publicados · Me gusta 29 · Visitantes 50,000+

Supongo que te gusta

Origin blog.csdn.net/huabiaochen/article/details/104528045
Recomendado
Clasificación