linux 下基于jrtplib库的实时传送实现【转】

原文http://www.rosoo.net/a/201001/8405.html

RTP 是目前解决流媒体实时传输问题的最好办法,要在 Linux 平台上进行实时传送编程,可以考虑使用一些开放源代码的 RTP 库,如LIBRTP、JRTPLIB 等。JRTPLIB 是一个面向对象的 RTP 库,它完全遵循 RFC 1889设计,在很多场合下是一个非常不错的选择。

一、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 () 方法来进行设置。 下面是采用第三种接收模式的程序示例。

  1. if (sess.GotoFirstSourceWithData())   
  2.  do   
  3.   sess.AddToAcceptList(remoteIP, allports,portbase); 
  4.          sess.SetReceiveMode(RECEIVEMODE_ACCEPTSOME); 
  5.  
  6.    RTPPacket *pack;         
  7.    pack sess.GetNextPacket();            // 处理接收到的数据     
  8.    delete pack;   
  9.  while (sess.GotoNextSourceWithData()); 
  10.  


 (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&:

  1. INCL -I/usr/local/include 
  2. CFLAGS -pipe -O2 -fno-strength-reduce 
  3. LFLAGS /usr/local/lib/libjrtp.a -L/usr/X11R6/lib 
  4. LIBS -LX11 -LXext /usr/local/lib/libjrtp.a 
  5. CC arm-linux-g++ 
  6.  
  7. main:main.o 
  8. $(CC) $(LFLAGS) $(INCL) -o main main.o $(LIBS) 
  9. main.o:main.cpp 
  10.  
  11. clean: 
  12. rm -f main 
  13. rm -f *.o 
  14.  
  15. .SUFFIXES:.cpp 
  16. .cpp.o: 
  17. $(CC) -c $(CFLAGS) $(INCL) -o $@ $<          
  18.           


b、编译
假设发送和接收程序分别放在/tmp/send和/tmp/receive目录下

  1. #cd /tmp/send 
  2. #make 
  3. #cd /tmp/receive 
  4. #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;    
} 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值