乐趣区

关于协议:认识流媒体协议从-RTSP-协议解析开始

RTSP 是 Internet 协定标准,是 TCP/IP 协定体系中的一个应用层协定级网络通信零碎。专为娱乐(如音频和视频)和通信零碎的应用,以管制流媒体服务器。该协定用于在端点之间建设和管制媒体会话。媒体服务器的客户端收回 VHS 款式的命令,例如:PLAY、PAUSE、SETUP、DESCRIBE、RECORD 等等。以促成对从服务器到客户端或从客户端到服务器的媒体流进行实时控制。

RTSP 传输过程

  • 当用户或应用程序尝试从近程源流式传输视频时,客户端设施会向服务器发送 RTSP 申请,以确定可用选项,例如 PLAY,PAUSE、SETUP。。。
  • 而后,服务器返回它能够通过 RTSP 承受的申请类型的列表。
  • 客户端晓得如何发出请求后,便将媒体形容申请发送到流服务器。
  • 服务器以媒体形容作为响应。
  • 客户端从那里发送设置申请,服务器以无关传输机制的信息作为响应。
  • 设置过程实现后,客户端将通过通知服务器应用设置申请中指定的传输机制发送位流(二进制序列)来启动流传输过程。

客户端 -> 服务器:DESCRIBE

服务器 -> 客户端: 200 OK (SDP)

客户端 -> 服务器:SETUP

服务器 -> 客户端: 200 OK

客户端 -> 服务器:PAUSE

协定的剖析和学习少不了抓包,截屏个 RTSP 协定抓包的图:

为什么 RTS 协定那么重要

  • RTSP 最后是一种容许用户间接从 Internet 播放音频和视频,而不用将媒体文件下载到其设施的办法。该协定已被利用于多种用处,包含互联网摄像机站点,在线教育和互联网播送。
  • RTSP 应用与根本 HTTP 雷同的概念,在很大水平上是为了兼容现有的 Web 根底构造。正因如此,HTTP 的扩大机制大都能够间接引入到 RTSP 中。
  • RTSP 协定还具备很大的灵活性。客户端能够申请他们要应用的性能,以找出媒体服务器是否反对它们。同样,领有媒体的任何人都能够从多个服务器传递媒体流。该协定还旨在适应媒体的将来倒退,以便媒体创建者能够在必要时批改协定。

RTSP 协定指令

只管 RTSP 在某些方面相似于 HTTP,但它定义了可用于管制多媒体播放的管制序列。只管 HTTP 是无状态的,但 RTSP 却具备状态。

在须要跟踪并发会话时应用标识符。像 HTTP 一样,RTSP 应用 TCP 来保护端到端连贯,端口号为 554。

只管大多数 RTSP 管制音讯是由客户端发送到服务器的,然而某些命令却是朝着另一个方向(即从服务器到客户端)传递的。

上面咱们来介绍根本的 RTSP 申请:

SETUP

SETUP 申请指定必须如何传输单个媒体流。必须在发送 PLAY 申请之前实现此操作。

该申请蕴含媒体流 URL 和传输说明符。

该说明符通常包含一个本地端口,用于接管 RTP 数据(音频或视频),另一个用于 RTCP 数据(元信息)。

服务器回答通常会确认选定的参数,并填写短少的局部,例如服务器的选定端口。必须先应用 SETUP 配置每个媒体流,而后能力发送聚合播放申请。

PLAY

PLAY 申请将导致播放一个或所有媒体流。能够通过发送多个 PLAY 申请来重叠播放申请。该 URL 能够是聚合 URL(以播放所有媒体流),也能够是单个媒体流 URL(仅播放该流)。

能够指定范畴。如果未指定范畴,则从头开始播放并播放到结尾,或者,如果流已暂停,则在暂停点复原播放。

PAUSE

PAUSE 申请会暂时中止一个或所有媒体流,因而稍后能够通过 PLAY 申请将其复原。该申请蕴含聚合或媒体流 URL。

PAUSE 申请上的 range 参数指定何时暂停。如果省略 range 参数,则暂停将立刻无限期地产生。

RECORD

此办法依据演示阐明开始记录一系列媒体数据。工夫戳反映开始工夫和完结工夫(UTC)。如果没有给出工夫范畴,请应用演示阐明中提供的开始工夫或完结工夫。

如果会话曾经开始,请立刻开始录制。服务器决定是否将记录的数据存储在申请 URl 或其余 URI 下。

如果服务器未应用申请 URI,则响应应为 201,并蕴含形容申请状态并援用新资源的实体和地位标头。

ANNOUNCE

当从客户端发送到服务器时,ANNOUNCE 将申请 URL 标识的演示或媒体对象的形容公布到服务器。ANNOUNCE 会实时更新会话形容。

如果将新的媒体流增加到演示文稿中(例如,在现场演示文稿中),则应再次发送整个演示文稿阐明,而不仅仅是其余组件,以便能够删除这些组件。

TEARDOWN

TEARDOWN 申请用于终止会话。它进行所有媒体流并开释服务器上所有与会话相干的数据。

GET_PARAMETER

GET_PARAMETER 申请检索 URI 中指定的示意模式或流的参数值。回答和响应的内容留给实现。

SET_PARAMETER

此办法要求为 URI 指定的示意或流设置参数值。

Wireshark RTSP 协定解析实现

对 RTSP 协定的应用有了一个大略的理解之后,咱们来解析实现一下 RTSP 协定。

#include <sys/stat.h>
#include <sys/types.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <net/ethernet.h>
#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/*RTSP 端口 */
#define RTSP_TCP_PORT_RANGE      554 

typedef enum {
    RTSP_REQUEST,
    RTSP_REPLY,
    RTSP_NOT_FIRST_LINE
} rtsp_type_t;

static const char *rtsp_methods[] = {
    "DESCRIBE",
    "ANNOUNCE",
    "GET_PARAMETER",
    "OPTIONS",
    "PAUSE",
    "PLAY",
    "RECORD",
    "REDIRECT",
    "SETUP",
    "SET_PARAMETER",
    "TEARDOWN"
};

/* 用于 RTSP 统计  */
struct rtsp_info_value_t {
  char  *request_method;
  unsigned long int  response_code;
};

/*
  假设一个字节数组(假设蕴含一个以空值结尾的字符串)作为参数,并返回字符串的长度 - 即该数组的大小,对于空终止符的值减去 1。*/
#define STRLEN_CONST(str)   (sizeof (str) - 1)

   
static const char rtsp_content_type[]      = "Content-Type:";
static const char rtsp_transport[]         = "Transport:";
static const char rtsp_sps_server_port[]   = "server_port=";
static const char rtsp_cps_server_port[]   = "client_port=";
static const char rtsp_sps_dest_addr[]     = "dest_addr=";
static const char rtsp_cps_src_addr[]      = "src_addr=";
static const char rtsp_rtp_udp_default[]   = "rtp/avp";
static const char rtsp_rtp_udp[]           = "rtp/avp/udp";
static const char rtsp_rtp_tcp[]           = "rtp/avp/tcp";
static const char rtsp_rdt_feature_level[] = "RDTFeatureLevel";
static const char rtsp_real_rdt[]          = "x-real-rdt/";
static const char rtsp_real_tng[]          = "x-pn-tng/"; /* synonym for x-real-rdt */
static const char rtsp_inter[]             = "interleaved=";
static const char rtsp_content_length[] = "Content-Length:";



static void rtsp_create_conversation(u_char *line_begin, size_t line_len,rtsp_type_t rtsp_type_packet)
{char    buf[256];
    char   *tmp;
    bool  rtp_udp_transport = false;
    bool  rtp_tcp_transport = false;
    bool  rdt_transport = false;
    //bool  is_video      = false; /* 是否须要显示视频  */
  unsigned int     c_data_port, c_mon_port;
    unsigned int     s_data_port, s_mon_port;
  unsigned int     ipv4_1, ipv4_2, ipv4_3, ipv4_4;

    if (rtsp_type_packet != RTSP_REPLY) {return;}


    /* 将行复制到 buf */
    if (line_len > sizeof(buf) - 1)
    {
        /* 防止溢出缓冲区。*/
        line_len = sizeof(buf) - 1;
    }

    memcpy(buf, line_begin, line_len);
    buf[line_len] = '\0';
  printf("%s\n",buf);
    /* Get past "Transport:" and spaces */
    tmp = buf + STRLEN_CONST(rtsp_transport);
  //printf("tmp %s\n",tmp);
    while (*tmp && isspace(*tmp))
        tmp++;

  if ((tmp = strstr(buf, rtsp_cps_src_addr))) 
  {tmp += strlen(rtsp_cps_src_addr);
    //printf("tmp ======  %s\n",tmp);
        if (sscanf(tmp, "\"%u.%u.%u.%u:%u\"", &ipv4_1, &ipv4_2, &ipv4_3, &ipv4_4, &c_data_port) == 5) 
    {
            char *tmp2;
            char *tmp3;
      //printf("ipv4_1 %d\n",ipv4_1);
      //printf("ipv4_2 %d\n",ipv4_2);
      //printf("ipv4_3 %d\n",ipv4_3);
      //printf("ipv4_4 %d\n",ipv4_4);
      printf("c_data_port %d\n",c_data_port);
            //Skip leading  
            tmp++;
            tmp2=strstr(tmp,":");
            tmp3=strndup(tmp,tmp2-tmp);      
      printf("src_addr  %s\n",tmp3);
                  
            free(tmp3);
        }
    }
  if ((tmp = strstr(buf, rtsp_sps_dest_addr))) 
  {tmp += strlen(rtsp_sps_dest_addr);
        if (sscanf(tmp, "\":%u\"", &s_data_port) == 1) 
    {
            /* :9 mean ignore */
            if (s_data_port == 9) {s_data_port = 0;}
      printf("s_data_port %d\n",s_data_port);
        }
  }
      
    if ((tmp = strstr(buf, rtsp_sps_server_port))) {tmp += strlen(rtsp_sps_server_port);
        if (sscanf(tmp, "%u", &s_mon_port) == 1) {printf("s_mon_port %d\n",s_mon_port);
        }
    }  
  
}


static bool is_rtsp_request_or_reply(unsigned char *line, int offset, rtsp_type_t *type)
{
    unsigned int   ii = 0;
  char *data = reinterpret_cast<char *>(line);
    int           tokenlen;
    char         response_chars[4];
  struct rtsp_info_value_t rtsp_info;
  char *token, *next_token;
    /* 这是 RTSP 的回复?*/
    if (strncasecmp("RTSP/", data, 5) == 0) {
        /*
         * Yes.
         */
        *type = RTSP_REPLY;
    
    /* 第一个标记是版本。*/
    offset += 9;
    
    memcpy(response_chars, data + offset, 3);
    response_chars[3] = '\0';
    rtsp_info.response_code = strtoul(response_chars, NULL, 10);
    //printf("rtsp_info.response_code %d\n",rtsp_info.response_code);
    
        return true;
    }

    /*
    这是 RTSP 申请吗?查看该行是否以 RTSP 申请办法之一结尾。*/
    for (ii = 0; ii < sizeof rtsp_methods / sizeof rtsp_methods[0]; ii++) {size_t len = strlen(rtsp_methods[ii]);
        if (strncasecmp(rtsp_methods[ii], data, len) == 0 &&(isspace(data[len])))
        {
            *type = RTSP_REQUEST;
            rtsp_info.request_method = strndupa(rtsp_methods[ii], len+1);
      //printf("request_method: %s\n",rtsp_info.request_method);

            return true;
        }
    }

    /* 既不是申请也不是回应 */
    *type = RTSP_NOT_FIRST_LINE;
    return false;
}


/* 浏览回复音讯的第一行  */
static void process_rtsp_reply(u_char *rtsp_data, int offset,rtsp_type_t rtsp_type_packet)
{char *lineend  = reinterpret_cast<char *>(rtsp_data + offset);
    char *status   = reinterpret_cast<char *>(rtsp_data);
    char *status_start;
    unsigned int         status_i;


    /* status code */

    /* Skip protocol/version */
    while (status < lineend && !isspace(*status))
        status++;
    /* Skip spaces */
    while (status < lineend && isspace(*status))
        status++;

    /* Actual code number now */
    status_start = status;
  //printf("status_start %s\n",status_start);
    status_i = 0;
    while (status < lineend && isdigit(*status))
        status_i = status_i * 10 + *status++ - '0';
  
  //printf("status_i %d\n",status_i);
  
  offset += strlen(lineend);
  rtsp_create_conversation(rtsp_data,offset,rtsp_type_packet);

}

static void process_rtsp_request(u_char *rtsp_data, int offset,rtsp_type_t rtsp_type_packet)
{char *lineend  = reinterpret_cast<char *>(rtsp_data + offset);
   // u_char *lineend  = rtsp_data + offset;
    unsigned int      ii = 0;
    char *url;
    char *url_start;
  char    buf[256];
  char   *tmp;
  int content_length = 0;
  char content_type[256];
    /* Request Methods */
    for (ii = 0; ii < sizeof rtsp_methods / sizeof rtsp_methods[0]; ii++) {size_t len = strlen(rtsp_methods[ii]);
        if (strncasecmp(rtsp_methods[ii], lineend, len) == 0 &&(isspace(lineend[len])))
            break;
    }


  //printf("process_rtsp_request 0x%.2X,0x%.2X,0x%.2X,0x%.2X\n",lineend[0],lineend[1],lineend[2],lineend[3]);  
    /* URL */
    url = lineend;

    /* Skip method name again */
    while (url < lineend && !isspace(*url))
        url++;
    /* Skip spaces */
    while (url < lineend && isspace(*url))
        url++;
    /* URL starts here */
    url_start = url;
  
    /* Scan to end of URL */
    while (url < lineend && !isspace(*url))
        url++;
  
  printf("%s\n",url_start);
  printf("111url %s\n",url);
  
  if ((tmp = strstr(url_start, rtsp_content_type))) 
  {tmp += strlen(rtsp_content_type);
        if (sscanf(tmp, "%s", content_type) == 1) 
    {//printf("content_type %s\n",content_type);
        }
    }  
  
  //Content-Length
  if ((tmp = strstr(url_start, rtsp_content_length))) 
  {tmp += strlen(rtsp_content_length);
        if (sscanf(tmp, "%u", &content_length) == 1) 
    {//printf("content_length %d\n",content_length);
        }
    }  
  
}



void dissect_rtsp(u_char *rtsp_data)
{
  int offset = 0;
  rtsp_type_t   rtsp_type_packet;
  bool      is_request_or_reply;
    u_char *linep, *lineend;
  u_char    c;
  //bool      is_header = false;
  is_request_or_reply = is_rtsp_request_or_reply(rtsp_data, offset, &rtsp_type_packet);
  
    if (is_request_or_reply)
    goto is_rtsp;


  

is_rtsp:

  
  switch(rtsp_type_packet)
  {
    case RTSP_REQUEST:

      process_rtsp_request(rtsp_data, offset,rtsp_type_packet);

      break;

    case RTSP_REPLY:

      process_rtsp_reply(rtsp_data, offset,rtsp_type_packet);
      
      break;

    case RTSP_NOT_FIRST_LINE:
      /* Drop through, it may well be a header line */
      break;
    default:
      break;
  }
  

}

static void dissect_rtsp_tcp(struct ip *pIp)
{
  int iHeadLen = pIp->ip_hl*4;
  int iPacketLen = ntohs(pIp->ip_len) - iHeadLen; 
  int offset = 0;
  int nFragSeq = 0;
  struct tcphdr *pTcpHdr = (struct tcphdr *)(((char  *)pIp) + iHeadLen);
  
  if (pIp->ip_p == IPPROTO_TCP && (ntohs(pTcpHdr->dest) == RTSP_TCP_PORT_RANGE) 
  || (ntohs(pTcpHdr->source) == RTSP_TCP_PORT_RANGE) )/* 仅解决 TCP 协定 */
  {  
    
      
    int iPayloadLen = iPacketLen - pTcpHdr->doff*4;
    //printf("TCP Payload Len %d\n",iPayloadLen);    
    u_char *RtspHdr = (u_char*)(pTcpHdr+1);
    if (RtspHdr == NULL)
      return;
    u_char *RtspData = RtspHdr + 12; /*skip OPtions */    
    //printf("NtpHdr 0x%.2X,0x%.2X,0x%.2X,0x%.2X\n",RtspData[0],RtspData[1],RtspData[2],RtspData[3]);    
    dissect_rtsp(RtspData);
  }  
}


编译运行

RTSP 是一种基于文本的协定,用回车换行 (\r\n) 作为每一行的结束符,其益处是,在应用过程中能够不便地减少自定义参数,也不便抓包剖析。

从音讯传送方向上来分,RTSP 的报文有两类:申请报文和响应报文。申请报文是指从客户端向服务器发送的申请,响应报文是指从服务器到客户端的回应。

总结

RTSP 对流媒体提供了诸如 PLAY,PAUSE、SETUP 等管制,但它自身并不传输数据,RTSP 的作用相当于流媒体服务器的近程管制。

服务器端能够自行抉择应用 TCP 或 UDP 来传送串流内容,它的语法和运作跟 HTTP 相似。更多解析请参考 RFC 官网文档,也是最权威的文档。

退出移动版