RTMP協議播放流程的實現及抓包分析

RTMP服務器搭建可參考:Nginx與Nginx-rtmp-module搭建RTMP視頻直播和點播服務器
     實時流協議(Real-TimeMessaging Protocol,RTMP)是用於互聯網上傳輸視音頻數據的網絡協議。本API提供了支持RTMP, RTMPT,RTMPE, RTMP RTMPS以及以上幾種協議的變種(RTMPTE, RTMPTS)協議所需的大部分客戶端功能以及少量的服務器功能。RTMP是目前各種網絡直播應用最核心的傳輸協議,也是互動直播採用最廣泛的協議。
     RTMP協議規定,播放一個流媒體有兩個前提步驟:第一步,建立一個網絡連接(NetConnection);第二步,建立一個網絡流(NetStream)。其中,網絡連接代表服務器端應用程序和客戶端之間基礎的連通關係。網絡流代表了發送多媒體數據的通道。服務器和客戶端之間只能建立一個網絡連接,但是基於該連接可以創建很多網絡流。播放一個RTMP協議的流媒體需要經過以下幾個步驟:握手,建立連接,建立流,播放。RTMP連接都是以握手作爲開始的。建立連接階段用於建立客戶端與服務器之間的“網絡連接”;建立流階段用於建立客戶端與服務器之間的“網絡流”;播放階段用於傳輸視音頻數據。
一 RTMP保存爲FLV
使用librtmp接收RTMP流的函數執行流程圖如下圖所示。

InitSockets():初始化Socket
RTMP_Alloc():爲結構體“RTMP”分配內存。
RTMP_Init():初始化結構體“RTMP”中的成員變量。
RTMP_SetupURL():設置輸入的RTMP連接的URL。
RTMP_Connect():建立RTMP連接,創建一個RTMP協議規範中的NetConnection。
RTMP_ConnectStream():創建一個RTMP協議規範中的NetStream。
RTMP_Read():從服務器讀取數據。
RTMP_Close():關閉RTMP連接。
RTMP_Free():釋放結構體“RTMP”。
CleanupSockets():關閉Socket。
源代碼:
[cpp] view plain copy
  1. #include <stdio.h>    
  2. #include "librtmp/rtmp_sys.h"    
  3. #include "librtmp/log.h"    
  4.     
  5. int InitSockets()    
  6. {    
  7.     WORD version;    
  8.     WSADATA wsaData;    
  9.     version = MAKEWORD(1, 1);    
  10.     return (WSAStartup(version, &wsaData) == 0);    
  11. }    
  12.     
  13. void CleanupSockets()    
  14. {    
  15.     WSACleanup();    
  16. }    
  17.     
  18. int main(int argc, char* argv[])    
  19. {    
  20.     InitSockets();    
  21.         
  22.     double duration=-1;    
  23.     int nRead;    
  24.     //is live stream ?    
  25.     bool bLiveStream=true;                  
  26.         
  27.         
  28.     int bufsize=1024*1024*10;               
  29.     char *buf=(char*)malloc(bufsize);    
  30.     memset(buf,0,bufsize);    
  31.     long countbufsize=0;    
  32.         
  33.     FILE *fp=fopen("receive.flv","wb");    
  34.     if (!fp){    
  35.         RTMP_LogPrintf("Open File Error.\n");    
  36.         CleanupSockets();    
  37.         return -1;    
  38.     }    
  39.         
  40.     /* set log level */    
  41.     //RTMP_LogLevel loglvl=RTMP_LOGDEBUG;    
  42.     //RTMP_LogSetLevel(loglvl);    
  43.     
  44.     RTMP *rtmp=RTMP_Alloc();    
  45.     RTMP_Init(rtmp);    
  46.     //set connection timeout,default 30s    
  47.     rtmp->Link.timeout=10;       
  48.     // HKS's live URL    
  49.     if(!RTMP_SetupURL(rtmp,"rtmp://live.hkstv.hk.lxdns.com/live/hks"))    
  50.     {    
  51.         RTMP_Log(RTMP_LOGERROR,"SetupURL Err\n");    
  52.         RTMP_Free(rtmp);    
  53.         CleanupSockets();    
  54.         return -1;    
  55.     }    
  56.     if (bLiveStream){    
  57.         rtmp->Link.lFlags|=RTMP_LF_LIVE;    
  58.     }    
  59.         
  60.     //1hour    
  61.     RTMP_SetBufferMS(rtmp, 3600*1000);          
  62.         
  63.     if(!RTMP_Connect(rtmp,NULL)){    
  64.         RTMP_Log(RTMP_LOGERROR,"Connect Err\n");    
  65.         RTMP_Free(rtmp);    
  66.         CleanupSockets();    
  67.         return -1;    
  68.     }    
  69.     
  70.     if(!RTMP_ConnectStream(rtmp,0)){    
  71.         RTMP_Log(RTMP_LOGERROR,"ConnectStream Err\n");    
  72.         RTMP_Close(rtmp);    
  73.         RTMP_Free(rtmp);    
  74.         CleanupSockets();    
  75.         return -1;    
  76.     }    
  77.     
  78.     while(nRead=RTMP_Read(rtmp,buf,bufsize)){    
  79.         fwrite(buf,1,nRead,fp);    
  80.     
  81.         countbufsize+=nRead;    
  82.         RTMP_LogPrintf("Receive: %5dByte, Total: %5.2fkB\n",nRead,countbufsize*1.0/1024);    
  83.     }    
  84.     
  85.     if(fp)    
  86.         fclose(fp);    
  87.     
  88.     if(buf){    
  89.         free(buf);    
  90.     }    
  91.     
  92.     if(rtmp){    
  93.         RTMP_Close(rtmp);    
  94.         RTMP_Free(rtmp);    
  95.         CleanupSockets();    
  96.         rtmp=NULL;    
  97.     }       
  98.     return 0;    
  99. }    
二 RTMP協議播放流程抓包分析
1  Wireshark抓RTMP包
用wireshark抓取RTMP包,打開如下:

2 握手(Handshake)
一個RTMP連接以握手開始,我們先看下圖:

首先我們要明確的是客戶端IP是192.168.1.50(我的電腦),192.168.1.123是RTMP服務器。
劇本應該是這樣子的:
1.RTMP協議是TCP協議的上層協議,所以必須要先建立TCP連接,所以就看到了1-4這幾個TCP三次握手的包。
2.客戶端向服務器發送C0塊(chunks),表示要和服務器握手,C0中包含版本號。
3.服務器收到C0後,檢查C0中的版本是否支持,如果支持發送S0作爲響應,否則應該終止連接。
4.客戶端和服務器都分別等待C1和S1,等待版本確認。
5.客戶端收到S1後發送C2,服務器收到C1後發送S2(確認發送,測試握手完成。)
然而,協議的實際執行卻不是按照劇本來的(如果按劇本來,延遲就要大大增大了),實際執行是這樣的:
1.RTMP協議是TCP協議的上層協議,所以必須要先建立TCP連接,所以就看到了1-4這幾個TCP三次握手的包。
2.客戶端發送的是C0+C1塊,直接告訴服務器我發的版本我自己確認了。
3.服務器更狠,一個大嘴巴子就抽回來了(發送S0+S1+S2)。
4.客戶端收到後,發送C2,握手完成!
附上RTMP協議中的流程圖:

3 建立一個網絡連接(NetConnection)
提示:網絡連接代表服務器應用程序和客戶端之間基礎的連通關係
我們接着看抓到的包:

RTMP握手完成後,要建立網絡連接。大家都知道一個普通的標準的rtmp流是什麼樣子的?rtmp://IP:PORT/APP/Stream 是不是這樣?
實際劇本是這樣子滴:
1.客戶端在發送C2的時候,順帶還發了一個請求連接的命令,要求與服務器應用建立網絡連接,這就是RTMP URL中的的Application。soga,是不是恍然大悟?
2.服務器在收到客戶端發送的連接請求後發送如下信息:

主要是告訴客戶端確認窗口大小,設置節點帶寬,然後服務器把“連接”連接到指定的應用並返回結果,“網絡連接成功”。並且返回流開始的的消息(Stream Begin 0)。
3.客戶端在收到服務器發來的消息後,返回確認窗口大小,此時網絡連接創建完成。
協議流程圖:

4 建立一個網絡流(NetStream)
提示:網絡流代表了發送多媒體數據的通道。服務器和客戶端之間只能建立一個網絡連接,且多個網絡流可以複用這一個網絡連接。
接着看抓包:

現在地洞挖好了,就差鋪鐵軌了!
1.客戶端向服務器發送請求創建流(createStream)。
2.服務器收到請求後向客戶端發送_result(),對創建流的消息進行響應。此時NetStream創建完成。
協議流程圖:

5 播放
提示:主要功能:傳輸音視頻數據
看抓包:

萬事具備,只欠東風了。
1.客戶端向服務器發送播放命令,請求播放stream,並設置Buffer Length 1,3000ms。
2.服務器收到請求後,向客戶端發送設置塊大小的協議消息,並且還附加了一堆其他的消息一起發送:

包括 Stream Begin(告知客戶端流ID爲0)、NetStream.Play.Start( 告知客戶端播放成功)等。
3. 服務器向客戶端發送推流通知,並附帶元數據信息(分辨率、幀率、音頻採樣率、音頻碼率等等)和視頻、音頻數據。此時客戶端就可以開始正常播放rtmp流了。 協議流程圖:

Reference:
發佈了205 篇原創文章 · 獲贊 150 · 訪問量 216萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章