RTP的實現

linux 下基於jrtplib庫的實時傳送實現
一、RTP 是進行實時流媒體傳輸的標準協議和關鍵技術
 實時傳輸協議(Real-time Transport Protocol,PRT)是在 Internet 上處理多媒體數據流的一種網絡協議,利用它能夠在一對一(unicast,單播)或者一對多(multicast,多播)的網絡環境中實現傳流媒體數據的實時傳輸。RTP 通常使用 UDP 來進行多媒體數據的傳輸,但如果需要的話可以使用 TCP 或者 ATM 等其它協議。
 協議分析 :每一個RTP數據報都由頭部(Header)和負載(Payload)兩個部分組成,其中頭部前 12 個字節的含義是固定的,而負載則可以是音頻或者視頻數據。

      RTP 是目前解決流媒體實時傳輸問題的最好辦法,要在 Linux 平臺上進行實時傳送編程,可以考慮使用一些開放源代碼的 RTP 庫,如 LIBRTP、JRTPLIB 等。JRTPLIB 是一個面向對象的 RTP 庫,它完全遵循 RFC 1889 設計,在很多場合下是一個非常不錯的選擇。JRTPLIB 是一個用 C++ 語言實現的 RTP 庫,這個庫使用socket 機制實現網絡通訊 因此可以運行在 Windows、Linux、FreeBSD、Solaris、Unix和VxWorks 等多種操作系統上。
二、JRTPLIB 庫的使用方法及程序實現
 (1)JRTPLIB  函數 的使用
 a、在使用 JRTPLIB 進行實時流媒體數據傳輸之前,首先應該生成 RTPSession 類的一個實例來表示此次 RTP 會話,然後調用 Create() 方法來對其進行初始化操作。RTPSession 類的 Create() 方法只有一個參數,用來指明此次 RTP 會話所採用的端口號。
 RTPSession sess;  sess.Create(5000); 

 b、設置恰當的時戳單元,是 RTP 會話初始化過程所要進行的另外一項重要工作,這是通過調用 RTPSession 類的 SetTimestampUnit() 方法來實現的,該方法同樣也只有一個參數,表示的是以秒爲單元的時戳單元。
 sess.SetTimestampUnit(1.0/8000.0);

 c、當 RTP 會話成功建立起來之後,接下去就可以開始進行流媒體數據的實時傳輸了。首先需要設置好數據發送的目標地址,RTP 協議允許同一會話存在多個目標地址,這可以通過調用 RTPSession 類的 AddDestination()、DeleteDestination() 和 ClearDestinations() 方法來完成。例如,下面的語句表示的是讓 RTP 會話將數據發送到本地主機的 6000 端口: 

 unsigned long addr = ntohl(inet_addr("127.0.0.1")); 
 sess.AddDestination(addr, 6000);
 
 d、目標地址全部指定之後,接着就可以調用 RTPSession 類的 SendPacket() 方法,向所有的目標地址發送流媒體數據。SendPacket() 是 RTPSession 類提供的一個重載函數
對於同一個 RTP 會話來講,負載類型、標識和時戳增量通常來講都是相同的,JRTPLIB 允許將它們設置爲會話的默認參數,這是通過調用 RTPSession 類的 SetDefaultPayloadType()、SetDefaultMark() 和 SetDefaultTimeStampIncrement() 方法來完成的。爲 RTP 會話設置這些默認參數的好處是可以簡化數據的發送,例如,如果爲 RTP 會話設置了默認參數: 

 sess.SetDefaultPayloadType(0);
  sess.SetDefaultMark(false);  
 sess.SetDefaultTimeStampIncrement(10);
 


之後在進行數據發送時只需指明要發送的數據及其長度就可以了: 

 sess.SendPacket(buffer, 5); 


 e、對於流媒體數據的接收端,首先需要調用 RTPSession 類的 PollData() 方法來接收發送過來的 RTP 或者 RTCP 數據報。由於同一個 RTP 會話中允許有多個參與者(源),你既可以通過調用 RTPSession 類的 GotoFirstSource() 和 GotoNextSource() 方法來遍歷所有的源,也可以通過調用 RTPSession 類的 GotoFirstSourceWithData() 和 GotoNextSourceWithData() 方法來遍歷那些攜帶有數據的源。在從 RTP 會話中檢測出有效的數據源之後,接下去就可以調用 RTPSession 類的 GetNextPacket() 方法從中抽取 RTP 數據報,當接收到的 RTP 數據報處理完之後,一定要記得及時釋放。

JRTPLIB 爲 RTP 數據報定義了三種接收模式,其中每種接收模式都具體規定了哪些到達的 RTP 數據報將會被接受,而哪些到達的 RTP 數據報將會被拒絕。通過調用 RTPSession 類的 SetReceiveMode() 方法可以設置下列這些接收模式: 
? RECEIVEMODE_ALL  缺省的接收模式,所有到達的 RTP 數據報都將被接受; 
? RECEIVEMODE_IGNORESOME  除了某些特定的發送者之外,所有到達的 RTP 數據報都將被接受,而被拒絕的發送者列表可以通過調用 AddToIgnoreList()、DeleteFromIgnoreList() 和 ClearIgnoreList() 方法來進行設置; 
? RECEIVEMODE_ACCEPTSOME  除了某些特定的發送者之外,所有到達的 RTP 數據報都將被拒絕,而被接受的發送者列表可以通過調用 AddToAcceptList ()、DeleteFromAcceptList 和 ClearAcceptList () 方法來進行設置。 下面是採用第三種接收模式的程序示例。
 if (sess.GotoFirstSourceWithData()) {   
  do {   
   sess.AddToAcceptList(remoteIP, allports,portbase);
          sess.SetReceiveMode(RECEIVEMODE_ACCEPTSOME);
 
    RTPPacket *pack;         
    pack = sess.GetNextPacket();            // 處理接收到的數據    
    delete pack;   } 
  while (sess.GotoNextSourceWithData()); 
  }


  (2)程序流程圖
發送:獲得接收端的 IP 地址和端口號        創建 RTP 會話        指定 RTP 數據接收端 設置 RTP 會話默認參數   發送流媒體數據
接收:獲得用戶指定的端口號  創建RTP會話  設置接收模式  接受RTP數據  檢索RTP數據源  獲取RTP數據報  刪除RTP數據報


三、環境搭建及編譯方法
(1)Toolchain的安裝
 首先找到xscale-arm-toolchain.tgz文件,假設該文件包放在/tmp/下
 #cd /
 #tar -zxvf /tmp/xscale-arm-toolchain.tgz
 再設置環境變量
 #export PATH=/usr/local/arm-linux/bin:$PATH
 最後檢查一下交叉編譯工具是否安裝成功
 #arm-linux-g++ --version
 看是否顯示arm-linux-g++的版本,如有則安裝成功。
(2)JRTPLIB 庫的交叉編譯及安裝
 首先從 JRTPLIB 的網站(
http://lumumba.luc.ac.be/jori/jrtplib/jrtplib.htmll) 下載最新的源碼包,此處使用的是jrtplib-2.8.tar,假設下載後的源碼包放在/tmp下,執 行下面的命令對其解壓縮:
 #cd /tmp
 #tar -zxvf jrtplib-2.8.tar
 然後要對jrtplib進行配置和編譯
 #cd jrtplib-2.8
 #./configure CC=arm-linux-g++ cross-compile=yes
 修改Makefile文件
 將鏈接命令ld 和ar改爲arm-linux-ld和 arm-linux-ar
 #make
 最後再執行如下命令就可以完成 JRTPLIB 的安裝:
 #make install
(3)程序編譯
 a、配置編譯環境
 可以用export來配置,也可以用編寫Makefile的方法。這裏採用Makefile。
 編寫Makefile&:
INCL = -I/usr/local/include
CFLAGS = -pipe -O2 -fno-strength-reduce
LFLAGS = /usr/local/lib/libjrtp.a -L/usr/X11R6/lib
LIBS = -LX11 -LXext /usr/local/lib/libjrtp.a
CC = arm-linux-g++

main:main.o
 $(CC) $(LFLAGS) $(INCL) -o main main.o $(LIBS)
main.o:main.cpp

clean:
 rm -f main
 rm -f *.o
 
.SUFFIXES:.cpp
.cpp.o:
 $(CC) -c $(CFLAGS) $(INCL) -o $@ $<         /*  $@表示目標的完整名字      */
          /* $<表示第一個依賴文件的名字 */
 b、編譯
 假設發送和接收程序分別放在/tmp/send和/tmp/receive目錄下
 #cd /tmp/send
 #make
 #cd /tmp/receive
 #make

四、易出錯誤及注意問題
 1、找不到一些標準的最 基本的一些頭文件。
  主要是因爲Toolchain路徑沒安裝對,要 嚴格按照步驟安裝。
 2、找不到使用的jrtplib庫中的一些頭文件。
  在 jrtplib的安裝目錄下,include路徑下不能再有別的目錄。
 3、recieve函數接收數據包不能正確提出所要數據。
  由於每一個RTP數據報都由頭部(Header)和負載(Payload)兩個部分組成,若使用getrawdata()是返回整個數據包的數據,包含傳輸媒體的類型、格式、序列號、時間戳以及是否有附加數據等信息。getpayload()函數是返回所發送的數據。兩者一定要分清。
 4、設置RECEIVEMODE_ACCEPTSOME  接收模式後,運行程序接收端不能接包。
  IP地址格式出了問題。iner_addr()與ntohl()函數要用對,否則參數傳不進去,接受列表中無值,當然接收不了數據包。
 5、編譯通過,但測試時接收端不能接收到數據。
  可能是接收機防火牆未關閉。運行:
  #iptables -F
  也可能是IP地址沒有設置好。運行:
  #ifocnfig eth0  *.*.*.*  netmask *.*.*.*
 6、使用jrtolib庫時,在程序中include 後最好加上庫所在的路徑。
五、程序

send:

#include <stdio.h>
#include <string.h>
#include "rtpsession.h"

// 錯誤處理函數
void checkerror(int err)
{
  if (err < 0) {
    char* errstr = RTPGetErrorString(err);
    printf("Error:%s\\n", errstr);
    exit(-1);
  }
}

int main(int argc, char** argv)
{
  RTPSession sess;
  unsigned long destip;
  int destport;
  int portbase = 6000;
  int status, index;
  char buffer[128];

  if (argc != 3) {
    printf("Usage: ./sender destip destport\\n");
    return -1;
  }

  // 獲得接收端的IP地址和端口號
  destip = inet_addr(argv[1]);
  if (destip == INADDR_NONE) {
    printf("Bad IP address specified.\\n");
    return -1;
  }
  destip = ntohl(destip);
  destport = atoi(argv[2]);

  // 創建RTP會話
  status = sess.Create(portbase);
  checkerror(status);

  // 指定RTP數據接收端
  status = sess.AddDestination(destip, destport);
  checkerror(status);

  // 設置RTP會話默認參數
  sess.SetDefaultPayloadType(0);
  sess.SetDefaultMark(false);
  sess.SetDefaultTimeStampIncrement(10);

  // 發送流媒體數據
  index = 1;
  do {
    sprintf(buffer, "%d: RTP packet", index ++);
    sess.SendPacket(buffer, strlen(buffer));
    printf("Send packet !\\n");
  } while(1);

  return 0;
}






receive:

#include <stdio.h>
#include "rtpsession.h"
#include "rtppacket.h"

// 錯誤處理函數
void checkerror(int err)
{
  if (err < 0) {
    char* errstr = RTPGetErrorString(err);
    printf("Error:%s\\n", errstr);
    exit(-1);
  }
}

int main(int argc, char** argv)
{
  RTPSession sess;
  int localport,portbase;
  int status;
  unsigned long remoteIP;
  if (argc != 4) {
    printf("Usage: ./sender localport\\n");
    return -1;
  }

   // 獲得用戶指定的端口號
   
  remoteIP = inet_addr(argv[1]);
  localport = atoi(argv[2]);
  portbase = atoi(argv[3]);
  // 創建RTP會話
  status = sess.Create(localport);
  checkerror(status);
  
  //RTPHeader *rtphdr;
  unsigned long timestamp1;
  unsigned char * RawData;
  unsigned char temp[30];
  int lengh ,i;
  bool allports = 1;
  
  sess.AddToAcceptList(remoteIP, allports,portbase);
  
     do {
 //設置接收模式
        sess.SetReceiveMode(RECEIVEMODE_ACCEPTSOME);
   sess.AddToAcceptList(remoteIP, allports,portbase);

    // 接受RTP數據
    status = sess.PollData();

    
 // 檢索RTP數據源
    if (sess.GotoFirstSourceWithData()) {
      do {
        
        RTPPacket* packet;
        // 獲取RTP數據報
        while ((packet = sess.GetNextPacket()) != NULL) {
          printf("Got packet !\n");

   timestamp1 = packet->GetTimeStamp();
   lengh=packet->GetPayloadLength();
   RawData=packet->GetPayload();
   
   for(i=0;i<lengh;i++){
      temp[i]=RawData[i];
  printf("%c",temp[i]);
   }
   temp[i]='\0';
   printf("  timestamp: %d lengh=%d data:%s\n",timestamp1,lengh,&temp);
          // 刪除RTP數據報
   
          delete packet;
        }
      } while (sess.GotoNextSourceWithData());
    }
  } while(1);

  return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章