• 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(四)用户接口层之处理SDP报文


    当RTSP客户端向RTSP服务端发送DESCRIBE命令时,服务端理应当回复一条SDP报文。

    该SDP报文中包含RTSP服务端的基本信息、所能提供的音视频媒体类型以及相应的负载能力,以下是一段SDP示例:

    RTSP/1.0 200 OK
    Server: VLC/2.1.6
    Date: Sun, 06 Dec 2015 11:51:38 GMT
    Content-Type: application/sdp
    Content-Base: rtsp://127.0.0.1:554/ansersion
    Content-Length: 572
    Cache-Control: no-cache
    Cseq: 2

    v=0
    o=- 15712671843665285047 15712671843665285047 IN IP4 localhost.localdomain
    s=Unnamed
    i=N/A
    c=IN IP4 0.0.0.0
    t=0 0
    a=tool:vlc 2.1.6
    a=recvonly
    a=type:broadcast
    a=charset:UTF-8
    a=control:rtsp://127.0.0.1:554/ansersion
    m=audio 0 RTP/AVP 14
    b=AS:128
    b=RR:0
    a=rtpmap:14 MPA/90000
    a=control:rtsp://127.0.0.1:554/ansersion/trackID=0
    m=video 0 RTP/AVP 96
    b=RR:0
    a=rtpmap:96 H264/90000
    a=fmtp:96 packetization-mode=1;profile-level-id=64000c;sprop-parameter-sets=Z2QADKzZQUH7ARAAAGewAA/DqPFCmWA=,aOvjyyLA;
    a=control:rtsp://127.0.0.1:554/ansersion/trackID=1

    对于myRTSPClient,所有的SDP报文均在“int RtspClient::ParseSDP(string SDP)”中被处理,并将这些信息记录进MediaSessionMap中,以便后续建立媒体流连接时使用。截至myRtspClient-1.2.3,该函数仅处理了关于SDP报文中关于音视频的部分信息(即以上示例中红色字体部分)。有关SDP的更多意义,可参见RFC4566

    一、int RtspClient::ParseSDP(string SDP)实现流程

    SDP报文有个特点,即每行均为“x=yyy”的形式,我们称x为Key, “yyy”为Value。Key为SDP中的关键字,有着特定意义(可参见RFC4566)。Value则为Key的具体参数。

    所以本函数的实现策略就是逐行提取报文中的Key和Value,并将其对应保存至MediaSessionMap中,逐行提取使用的是Regex.RegexLine正则表达式接口。有些Value比较复杂,需要进一步提取数据,则使用Regex.Regex正则表达式接口。

    本函数主要提取

    (1) m=***

    (2) a=***

    m指明了媒体流(media)的类型(audio/video)等参数;

    a指明了媒体流的属性(attribute),如编码格式(比如H264)等。

    解析完SDP后,再检查一下各媒体流基本信息是否有效(MediaInfoCheck)。

    代码如下

      1 int RtspClient::ParseSDP(string SDP)
      2 {
      3     MyRegex Regex;
      4     string Response("");
      5     int Result = 0; // don't have meaning yet
      6 
      7     if(SDP.length() != 0) Response.assign(SDP);
      8     else if(RtspResponse.length() != 0) Response.assign(SDPStr);
      9     else return Result;
     10 
     11     string Pattern = "([a-zA-Z])=(.*)";
     12     list<string> Group;
     13     bool CollectMediaInfo = false;
     14     string CurrentMediaSession("");
     15     while(Regex.RegexLine(&Response, &Pattern, &Group)) {
     16         string Key(""), Value("");
     17         if(!Group.empty()) {
     18             Group.pop_front(); // pop the line
     19             Group.pop_front(); // pop the matched part
     20             Key.assign(Group.front()); Group.pop_front();
     21             Value.assign(Group.front()); Group.pop_front();
     22             // SDPInfo->insert(pair<string, string>(Key, Value));
     23         }
     24         if(Key == "m") CollectMediaInfo = true;
     25         if(Key == "s") CollectMediaInfo = false;
     26         if(!CollectMediaInfo) continue;
     27 
     28         if(Key == "m") { 
     29             /* Pattern: (MediaType) +(Ports) +(Protocol) +(PayloadType)"
     30                Example: "(audio) (0) (RTP/AVP) (14)"
     31                */
     32             // string PatternTmp("([a-zA-Z]+) +.+ +(.+) +.*");
     33             string PatternTmp("([a-zA-Z]+) +([0-9/]+) +([A-Za-z/]+) +\b([0-9]+)\b");
     34             if(!Regex.Regex(Value.c_str(), PatternTmp.c_str(), &Group)) {
     35                 continue;
     36             }
     37             Group.pop_front();
     38             CurrentMediaSession.assign(Group.front());
     39             Group.pop_front();
     40             Group.pop_front(); // FIXME: Ports are ignored
     41             string Protocol(Group.front());
     42             Group.pop_front();
     43             int PayloadTypeTmp = -1;
     44             stringstream ssPayloadType;
     45             ssPayloadType << Group.front();
     46             ssPayloadType >> PayloadTypeTmp;
     47 
     48             MediaSession NewMediaSession;
     49             NewMediaSession.MediaType.assign(CurrentMediaSession);
     50             NewMediaSession.Protocol.assign(Protocol);
     51             NewMediaSession.PayloadType.push_back(PayloadTypeTmp);
     52             (*MediaSessionMap)[CurrentMediaSession] = NewMediaSession;
     53 
     54         }
     55         if("a" == Key) {
     56             string PatternRtpmap("rtpmap:.* +([0-9A-Za-z]+)/([0-9]+)");
     57             string PatternFmtp_H264("fmtp:.*sprop-parameter-sets=([A-Za-z0-9+/=]+),([A-Za-z0-9+/=]+)");
     58             string PatternFmtp_H265("fmtp:.*sprop-vps=([A-Za-z0-9+/=]+);.*sprop-sps=([A-Za-z0-9+/=]+);.*sprop-pps=([A-Za-z0-9+/=]+)");
     59             string PatternControl("control:(.+)");
     60             if(CurrentMediaSession.length() == 0) {
     61                 continue;
     62             }
     63             if(Regex.Regex(Value.c_str(), PatternRtpmap.c_str(), &Group)) {
     64                 Group.pop_front();
     65                 (*MediaSessionMap)[CurrentMediaSession].EncodeType = Group.front();;
     66                 Group.pop_front();
     67                 stringstream TimeRate;
     68                 TimeRate << Group.front();
     69                 TimeRate >> (*MediaSessionMap)[CurrentMediaSession].TimeRate;
     70             } else if(Regex.Regex(Value.c_str(), PatternControl.c_str(), &Group)) {
     71                 Group.pop_front();
     72                 string ControlURITmp("");
     73                 /* 'Value' could be  
     74                  * 1: "rtsp://127.0.0.1/ansersion/track=1"
     75                  * 2: "track=1"
     76                  * If is the '2', it should be prefixed with the URI. */
     77                 if(!Regex.Regex(Value.c_str(), "rtsp://")) {
     78                     ControlURITmp += RtspURI;
     79                     ControlURITmp += "/";
     80                 }
     81                 ControlURITmp += Group.front();
     82                 printf("Control: %s
    ", ControlURITmp.c_str());
     83                 (*MediaSessionMap)[CurrentMediaSession].ControlURI.assign(ControlURITmp);
     84             } else if(Regex.Regex(Value.c_str(), PatternFmtp_H264.c_str(), &Group)) {
     85                 Group.pop_front();
     86                 SPS.assign(Group.front());
     87                 Group.pop_front();
     88                 PPS.assign(Group.front());
     89 
     90                 if(Regex.Regex(Value.c_str(), "packetization-mode=([0-2])", &Group)) {
     91                     Group.pop_front();
     92                     stringstream PacketizationMode;
     93                     PacketizationMode << Group.front();
     94                     PacketizationMode >> (*MediaSessionMap)[CurrentMediaSession].Packetization;
     95                 }
     96             } else if(Regex.Regex(Value.c_str(), PatternFmtp_H265.c_str(), &Group)) {
     97                 Group.pop_front();
     98                 VPS.assign(Group.front());
     99                 Group.pop_front();
    100                 SPS.assign(Group.front());
    101                 Group.pop_front();
    102                 PPS.assign(Group.front());
    103             }
    104         }
    105     }
    106     
    107     for(map<string, MediaSession>::iterator it = MediaSessionMap->begin(); it != MediaSessionMap->end(); it++) {
    108         it->second.MediaInfoCheck();
    109     }
    110 
    111     return Result;
    112 }
    View Code

    二、提取媒体流信息

    while(Regex.RegexLine(&Response, &Pattern, &Group))

    其中Response为SDP字符串,Pattern为正则表达式模板"([a-zA-Z])=(.*)",Group为空的string的list。

    RegexLine会进行正则匹配Response的第1行,如果匹配成功,则将匹配内容放入Group这个list中,list的第1项是整行字符串,第2项是匹配上模板的字符串,第3项为Key,第4项为Value,然后将匹配指针指向下一行(下次匹配时就会匹配下一行),并且返回true。如果匹配失败,则返回false。

    if(!Group.empty()) {
    Group.pop_front(); // pop the line
    Group.pop_front(); // pop the matched part
    Key.assign(Group.front()); Group.pop_front();
    Value.assign(Group.front()); Group.pop_front();
    }
    if(Key == "m") CollectMediaInfo = true;

    保存Key和Value,如果Key为“m”,则将CollectMediaInfo标志置为true,表示可以提取媒体流信息,以及相关属性。

    if(Key == "m") {
    /* Pattern: (MediaType) +(Ports) +(Protocol) +(PayloadType)"
    Example: "(audio) (0) (RTP/AVP) (14)"
    */
    string PatternTmp("([a-zA-Z]+) +([0-9/]+) +([A-Za-z/]+) +\b([0-9]+)\b");
    if(!Regex.Regex(Value.c_str(), PatternTmp.c_str(), &Group)) {
    continue;
    }
    Group.pop_front();
    CurrentMediaSession.assign(Group.front());
    Group.pop_front();
    Group.pop_front(); // FIXME: Ports are ignored
    string Protocol(Group.front());
    Group.pop_front();
    int PayloadTypeTmp = -1;
    stringstream ssPayloadType;
    ssPayloadType << Group.front();
    ssPayloadType >> PayloadTypeTmp;

    MediaSession NewMediaSession;
    NewMediaSession.MediaType.assign(CurrentMediaSession);
    NewMediaSession.Protocol.assign(Protocol);
    NewMediaSession.PayloadType.push_back(PayloadTypeTmp);
    (*MediaSessionMap)[CurrentMediaSession] = NewMediaSession;

    }

    提取媒体流的类型、端口、协议类型和Payload,并添加进MediaSessionMap中,索引为媒体流类型(audio/video)

    注:截至myRtspClient-1.2.3,单个myRtspClient实例仅支持单一的audio和video媒体会话,即当RTSP服务器提供多种video(或audio)——比如不同码率——媒体流时,myRtspClient仅适用SDP中标识的最后一个。

    还有一些提取属性(a=***)的代码,形式与上述雷同,不再赘述。

    注:Regex.Regex不同于Regex.RegexLine,匹配成功后,string的list中的第1项为匹配上模板的字符串,第2项为第1个匹配上括号的字符串,以此类推。

     三、检查媒体信息

    for(map<string, MediaSession>::iterator it = MediaSessionMap->begin(); it != MediaSessionMap->end(); it++) {
    it->second.MediaInfoCheck();
    }

    检查媒体流信息,并打印错误。

    上一篇                    回目录                       下一篇

  • 相关阅读:
    【积累总结】JS基础日常总结
    【积累总结】CSS日常总结
    【学习】JavaScript单线程与异步
    【学习指南】淘宝首页性能优化实践
    【学习】JS中的跨域
    007_stdc_C语言基础
    006_stdc_C语言基础
    005_stdc_C语言基础
    004_stdc_C语言基础
    003_stdc_linux基本常用命令_C语言基础
  • 原文地址:https://www.cnblogs.com/ansersion/p/7418238.html
Copyright © 2020-2023  润新知