天天看點

linux下基于jrtplib庫的實時傳送實作

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 的網站(

linux下基于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;

}

繼續閱讀