LibRTMP原始碼分析 1:解析URL

weixin_33670713發表於2016-03-26

本系列文件分析LibRTMP原始碼,所用原始碼位於GitHubLibrtmp for Android & iOS,應該不是最新的LibRTMP原始碼,對於學習是夠用的。

小廣告:歡迎加入iOS Android 直播、全景播放技術討論群:459486016

檔案:parseurl.c
函式呼叫關係:
RTMP_ParseURL
---- RTMP_ParsePlaypath

1、分析RTMP_ParseURL

函式定義:

int RTMP_ParseURL(const char *url,
                  int *protocol,
                  AVal *host,
                  unsigned int *port,
                  AVal *playpath,
                  AVal *app)

URL的組成格式為:[協議]://[主機名]:[埠]/檔案路徑?[鍵]:[值]
RTMP URL的格式為:

  • rtmp://host[:port]/app[/appinstance][/...]
  • application = app[/appinstance]

1.1、解析協議

1.1.1、原始碼

char *p, *end, *col, *ques, *slash;

RTMP_Log(RTMP_LOGDEBUG, "Parsing...");

*protocol = RTMP_PROTOCOL_RTMP;
*port = 0;
playpath->av_len = 0;
playpath->av_val = NULL;
app->av_len = 0;
app->av_val = NULL;

/* Old School Parsing */

/* look for usual :// pattern */
p = strstr(url, "://");
if (!p) {
    RTMP_Log(RTMP_LOGERROR, "RTMP URL: No :// in url!");
    return FALSE;
}
{
    int len = (int) (p - url);
    
    if (len == 4 && strncasecmp(url, "rtmp", 4) == 0)
        *protocol = RTMP_PROTOCOL_RTMP;
    else if (len == 5 && strncasecmp(url, "rtmpt", 5) == 0)
        *protocol = RTMP_PROTOCOL_RTMPT;
    else if (len == 5 && strncasecmp(url, "rtmps", 5) == 0)
        *protocol = RTMP_PROTOCOL_RTMPS;
    else if (len == 5 && strncasecmp(url, "rtmpe", 5) == 0)
        *protocol = RTMP_PROTOCOL_RTMPE;
    else if (len == 5 && strncasecmp(url, "rtmfp", 5) == 0)
        *protocol = RTMP_PROTOCOL_RTMFP;
    else if (len == 6 && strncasecmp(url, "rtmpte", 6) == 0)
        *protocol = RTMP_PROTOCOL_RTMPTE;
    else if (len == 6 && strncasecmp(url, "rtmpts", 6) == 0)
        *protocol = RTMP_PROTOCOL_RTMPTS;
    else {
        RTMP_Log(RTMP_LOGWARNING, "Unknown protocol!\n");
        goto parsehost;
    }
}

RTMP_Log(RTMP_LOGDEBUG, "Parsed protocol: %d", *protocol);

1.1.2、流程分析

  1. 找出://所在位置。找不到則報錯:RTMP URL: No :// in url!。
  2. 若找到,則p為:在URL字串中的位置。
  3. int len = (int) (p - url)得到://前面字串的長度,即協議的長度。
  4. 判斷協議。使用strncasecmp以忽略字串大小寫方式進行比較。
  5. 對於以://開頭的URL,預設為RTMP協議。

1.1.3、驗證

抽取如下程式碼並執行。

#define RTMP_FEATURE_HTTP   0x01
#define RTMP_FEATURE_ENC    0x02
#define RTMP_FEATURE_SSL    0x04
#define RTMP_FEATURE_MFP    0x08    /* not yet supported */
#define RTMP_FEATURE_WRITE  0x10    /* publish, not play */
#define RTMP_FEATURE_HTTP2  0x20    /* server-side rtmpt */

#define RTMP_PROTOCOL_UNDEFINED -1
#define RTMP_PROTOCOL_RTMP      0
#define RTMP_PROTOCOL_RTMPE     RTMP_FEATURE_ENC
#define RTMP_PROTOCOL_RTMPT     RTMP_FEATURE_HTTP
#define RTMP_PROTOCOL_RTMPS     RTMP_FEATURE_SSL
#define RTMP_PROTOCOL_RTMPTE    (RTMP_FEATURE_HTTP|RTMP_FEATURE_ENC)
#define RTMP_PROTOCOL_RTMPTS    (RTMP_FEATURE_HTTP|RTMP_FEATURE_SSL)
#define RTMP_PROTOCOL_RTMFP     RTMP_FEATURE_MFP

char *p;
int ptl, *protocol = &ptl;
char *url = "rtmp://live.hkstv.hk.lxdns.com/live/hks";
p = strstr(url, "://");
if (!p) {
    printf("RTMP URL: No :// in url!");
    return FALSE;
}
{
    int len = (int) (p - url);
    
    if (len == 4 && strncasecmp(url, "rtmp", 4) == 0)
        *protocol = RTMP_PROTOCOL_RTMP;
    else if (len == 5 && strncasecmp(url, "rtmpt", 5) == 0)
        *protocol = RTMP_PROTOCOL_RTMPT;
    else if (len == 5 && strncasecmp(url, "rtmps", 5) == 0)
        *protocol = RTMP_PROTOCOL_RTMPS;
    else if (len == 5 && strncasecmp(url, "rtmpe", 5) == 0)
        *protocol = RTMP_PROTOCOL_RTMPE;
    else if (len == 5 && strncasecmp(url, "rtmfp", 5) == 0)
        *protocol = RTMP_PROTOCOL_RTMFP;
    else if (len == 6 && strncasecmp(url, "rtmpte", 6) == 0)
        *protocol = RTMP_PROTOCOL_RTMPTE;
    else if (len == 6 && strncasecmp(url, "rtmpts", 6) == 0)
        *protocol = RTMP_PROTOCOL_RTMPTS;
    else {
        printf("Unknown protocol!\n");
    }
}

printf("Parsed protocol: %d", *protocol);

執行:

輸入:url 輸出:protocol
"rtmptslive.hkstv.hk.lxdns.com/live/hks" RTMP URL: No :// in url!
"rtmp://live.hkstv.hk.lxdns.com/live/hks" Parsed protocol: 0
"://live.hkstv.hk.lxdns.com/live/hks" Unknown protocol!
Parsed protocol: 0
"rtmpts://live.hkstv.hk.lxdns.com/live/hks" Parsed protocol: 5

協議解析結束,開始解析主機。

1.2、解析主機

1.2.1、原始碼

parsehost:
    /* let's get the hostname */
    p += 3;

    /* check for sudden death */
    if (*p == 0) {
        RTMP_Log(RTMP_LOGWARNING, "No hostname in URL!");
        return FALSE;
    }

    end = p + strlen(p);
    col = strchr(p, ':');
    ques = strchr(p, '?');
    slash = strchr(p, '/');

    {
        int hostlen;
        if (slash)
            hostlen = slash - p;
        else
            hostlen = end - p;
        if (col && col - p < hostlen)
            hostlen = col - p;

        if (hostlen < 256) {
            host->av_val = p;
            host->av_len = hostlen;
            RTMP_Log(RTMP_LOGDEBUG, "Parsed host    : %.*s", hostlen, host->av_val);
        } else {
            RTMP_Log(RTMP_LOGWARNING, "Hostname exceeds 255 characters!");
        }

        p += hostlen;
    }

1.2.2、流程分析

  1. 如果主機為空,則報錯No hostname in URL!。
  2. end = p + strlen(p);由p後移三個位置,跳過://且位於主機字串起始點。由strlen(p)計算出剩餘字串長度並加上起始位置,得到整個URL字串的終點位置。
  3. 跟緊主機字串結尾的/:字元不算在主機字串長度內。如果只有路徑,無埠,則主機字串的長度 = 路徑字串的起始位置 - 主機字串的起始位置。如果指定了埠,則主機字串的長度 = 路徑字串的起始位置 - 主機字串的起始位置。
  4. 由3得到的主機字串若超過255個字元,則警告Hostname exceeds 255 characters!。

1.2.3、驗證

抽取如下程式碼並執行。

    /* let's get the hostname */
    p += 3;
    
    /* check for sudden death */
    if (*p == 0) {
        printf("No hostname in URL!\n");
        return FALSE;
    }
    
    end = p + strlen(p);
    printf("url length = %lu, p = %p, p length = %lu, end = %p\n", strlen(url), p, strlen(p), end);
    col = strchr(p, ':');
    ques = strchr(p, '?');
    slash = strchr(p, '/');
    printf("port = %s\nques = %s\nslash = %s\n", col, ques, slash);

    {
        int hostlen;
        if (slash)
            hostlen = slash - p;
        else
            hostlen = end - p;
        if (col && col - p < hostlen)
            hostlen = col - p;
        
        if (hostlen < 256) {
            printf("Parsed host    : %.*s\n", hostlen, p);
        } else {
            printf("Hostname exceeds 255 characters!");
        }
        p += hostlen;
    }

執行:

輸入:URL 輸出:host
"rTmPe://live.hkstv.hk.lxdns.com/live/hks" live.hkstv.hk.lxdns.com
"rtmp://live.hkstv.hk.lxdns.com:2000/cardhouse/s3?e=1&starttime=000010" live.hkstv.hk.lxdns.com

其中,對於輸入URL
"rtmp://live.hkstv.hk.lxdns.com:2000/cardhouse/s3?e=1&starttime=000010"
printf("url length = %lu, p = %p, p length = %lu, end = %p\n", strlen(url), p, strlen(p), end);
輸出
url length = 68, p = 0x10d915e3a, p length = 61, end = 0x10d915e77
可驗證end為URL字串末尾。

port = :2000/cardhouse/s3?e=1&starttime=000010
ques = ?e=1&starttime=000010
slash = /cardhouse/s3?e=1&starttime=000010

strchr找出指定字元首次出現位置的指標。

主機解析結束,開始解析埠號、應用程式名或播放路徑。

1.3、解析埠號

1、原始碼

/* get the port number if available */
if (*p == ':') {
    unsigned int p2;
    p++;
    p2 = atoi(p);
    if (p2 > 65535) {
        RTMP_Log(RTMP_LOGWARNING, "Invalid port number!");
    } else {
        *port = p2;
    }
}

if (!slash) {
    RTMP_Log(RTMP_LOGWARNING, "No application or playpath in URL!");
    return TRUE;
}
p = slash + 1;

2、分析

如果p指向:字元,atoi持續讀取字元,直到非數字字元為止,並將已讀取的數值當成十進位制處理,如

int i;
char *buffer = "31273a";
i = atoi (buffer);
printf ("The value entered is %d. Its double is %d.\n",i,i*2);

輸出:The value entered is 31273. Its double is 62546.

3、驗證

unsigned int prt, *port = &prt;
/* get the port number if available */
if (*p == ':') {
    unsigned int p2;
    p++;
    p2 = atoi(p);
    if (p2 > 65535) {
        printf("Invalid port number!");
    } else {
        *port = p2;
        printf("port = %u", *port);
    }
} else {
    printf("port does not exists.\n");
}
輸入:URL 輸出:port
"rtmp://live.hkstv.hk.lxdns.com:2000/cardhouse/s3?e=1&starttime=000010" 2000
"rtmp://live.hkstv.hk.lxdns.com/cardhouse/s3?e=1&starttime=000010"

1.4、解析應用程式名或播放路徑

相關文章