Linux下的實時流媒體程式設計(RTP,RTCP,RTSP)2
(2010-04-30 20:07:18)

轉載▼
标簽: 雜談 | 分類: RTP |
RTP 是目前解決流媒體實時傳輸問題的最好辦法,如果需要在Linux平台上進行實時流媒體程式設計,可以考慮使用一些開放源代碼的RTP庫,如LIBRTP、 JRTPLIB等。JRTPLIB是一個面向對象的RTP庫,它完全遵循RFC 1889設計,在很多場合下是一個非常不錯的選擇,下面就以JRTPLIB為例,講述如何在Linux平台上運用RTP協定進行實時流媒體程式設計。
3.1 環境搭建
JRTPLIB 是一個用C++語言實作的RTP庫,目前已經可以運作在Windows、Linux、FreeBSD、 Solaris、Unix和VxWorks等多種作業系統上。要為Linux 系統安裝JRTPLIB,首先從JRTPLIB的網站(http: //lumumba.luc.ac.be/jori/jrtplib/jrtplib.html)下載下傳最新的源碼包,此處使用的是jrtplib- 2.7b.tar.bz2。假設下載下傳後的源碼包儲存在/usr/local/src目錄下,執行下面的指令可以對其進行解壓縮:
[[email protected] src]# bzip2 -dc jrtplib-2.7b.tar.bz2 | tar xvf -
接下去需要對JRTPLIB進行配置和編譯:
[[email protected] src]# cd jrtplib-2.7
[[email protected] jrtplib-2.7b]# ./configure
[[email protected] jrtplib-2.7b]# make
最後再執行如下指令就可以完成JRTPLIB的安裝:
[[email protected] jrtplib-2.7b]# make install
3.2 初始化
在使用JRTPLIB進行實時流媒體資料傳輸之前,首先應該生成RTPSession類的一個執行個體來表示此次RTP會話,然後調用Create()方法來對其進行初始化操作。RTPSession類的Create()方法隻有一個參數,用來指明此次RTP會話所采用的端口号。清單1給出了一個最簡單的初始化架構,它隻是完成了RTP會話的初始化工作,還不具備任何實際的功能。
#include "rtpsession.h"
int main(void)
{
RTPSession sess;
sess.Create(5000);
return 0;
}
如果RTP會話建立過程失敗,Create()方法将會傳回一個負數,通過它雖然可以很容易地判斷出函數調用究竟是成功的還是失敗的,但卻很難明白出錯的原因到底什麼。JRTPLIB采用了統一的錯誤處理機制,它提供的所有函數如果傳回負數就表明出現了某種形式的錯誤,而具體的出錯資訊則可以通過調用 RTPGetErrorString()函數得到。RTPGetErrorString()函數将錯誤代碼作為參數傳入,然後傳回該錯誤代碼所對應的錯誤資訊。清單2給出了一個更加完整的初始化架構,它可以對RTP會話初始化過程中所産生的錯誤進行更好的處理:
#include <stdio.h>
#include "rtpsession.h"
int main(void)
{
RTPSession sess;
int status;
char* msg;
sess.Create(6000);
msg = RTPGetErrorString(status);
printf("Error String: %s\\n", msg);
return 0;
}
設定恰當的時戳單元,是RTP會話初始化過程所要進行的另外一項重要工作,這是通過調用RTPSession類的 SetTimestampUnit()方法來實作的,該方法同樣也隻有一個參數,表示的是以秒為單元的時戳單元。例如,當使用RTP會話傳輸8000Hz 采樣的音頻資料時,由于時戳每秒鐘将遞增8000,是以時戳單元相應地應該被設定成1/8000:
sess.SetTimestampUnit(1.0/8000.0);
3.3 資料發送
當RTP 會話成功建立起來之後,接下去就可以開始進行流媒體資料的實時傳輸了。首先需要設定好資料發送的目标位址, RTP協定允許同一會話存在多個目标位址,這可以通過調用RTPSession類的AddDestination()、 DeleteDestination()和ClearDestinations()方法來完成。例如,下面的語句表示的是讓RTP會話将資料發送到本地主機的6000端口:
unsigned long addr = ntohl(inet_addr("127.0.0.1"));
sess.AddDestination(addr, 6000);
目标位址全部指定之後,接着就可以調用RTPSession類的SendPacket()方法,向所有的目标位址發送流媒體資料。SendPacket()是RTPSession類提供的一個重載函數,它具有下列多種形式:
int SendPacket(void *data,int len)
int SendPacket(void *data,int len,unsigned char pt,bool mark,unsigned long timestampinc)
int SendPacket(void *data,int len,unsigned short hdrextID,void *hdrextdata,int numhdrextwords)
int SendPacket(void *data,int len,unsigned char pt,bool mark,unsigned long timestampinc,
unsigned short hdrextID,void *hdrextdata,int numhdrextwords)
SendPacket()最典型的用法是類似于下面的語句,其中第一個參數是要被發送的資料,而第二個參數則指明将要發送資料的長度,再往後依次是RTP負載類型、辨別和時戳增量。
sess.SendPacket(buffer, 5, 0, false, 10);
對于同一個RTP會話來講,負載類型、辨別和時戳增量通常來講都是相同的,JRTPLIB允許将它們設定為會話的預設參數,這是通過調用 RTPSession類的SetDefaultPayloadType()、SetDefaultMark()和 SetDefaultTimeStampIncrement()方法來完成的。為RTP會話設定這些預設參數的好處是可以簡化資料的發送,例如,如果為 RTP會話設定了預設參數:
sess.SetDefaultPayloadType(0);
sess.SetDefaultMark(false);
sess.SetDefaultTimeStampIncrement(10);
之後在進行資料發送時隻需指明要發送的資料及其長度就可以了:
sess.SendPacket(buffer, 5);
3.4 資料接收
對于流媒體資料的接收端,首先需要調用RTPSession類的PollData()方法來接收發送過來的RTP或者 RTCP資料報。由于同一個RTP會話中允許有多個參與者(源),你既可以通過調用RTPSession類的GotoFirstSource()和 GotoNextSource()方法來周遊所有的源,也可以通過調用RTPSession類的GotoFirstSourceWithData()和 GotoNextSourceWithData()方法來周遊那些攜帶有資料的源。在從RTP會話中檢測出有效的資料源之後,接下去就可以調用 RTPSession類的GetNextPacket()方法從中抽取RTP資料報,當接收到的RTP資料報處理完之後,一定要記得及時釋放。下面的代碼示範了該如何對接收到的RTP資料報進行處理:
if (sess.GotoFirstSourceWithData()) {
do {
RTPPacket *pack;
pack = sess.GetNextPacket();
// 處理接收到的資料
delete pack;
} while (sess.GotoNextSourceWithData());
}
JRTPLIB為RTP資料報定義了三種接收模式,其中每種接收模式都具體規定了哪些到達的RTP資料報将會被接受,而哪些到達的RTP資料報将會被拒絕。通過調用RTPSession類的SetReceiveMode()方法可以設定下列這些接收模式:
RECEIVEMODE_ALL 預設的接收模式,所有到達的RTP資料報都将被接受;
RECEIVEMODE_IGNORESOME 除了某些特定的發送者之外,所有到達的RTP資料報都将被接受,而被拒絕的發送者清單可以通過調用AddToIgnoreList()、DeleteFromIgnoreList()和ClearIgnoreList()方法來進行設定;
RECEIVEMODE_ACCEPTSOME 除了某些特定的發送者之外,所有到達的RTP資料報都将被拒絕,而被接受的發送者清單可以通過調用AddToAcceptList ()、DeleteFromAcceptList和ClearAcceptList ()方法來進行設定。
3.5 控制資訊
JRTPLIB 是一個高度封裝後的RTP庫,程式員在使用它時很多時候并不用關心RTCP資料報是如何被發送和接收的,因為這些都可以由JRTPLIB自己來完成。隻要 PollData()或者SendPacket()方法被成功調用,JRTPLIB就能夠自動對到達的 RTCP資料報進行處理,并且還會在需要的時候發送RTCP資料報,進而能夠確定整個RTP會話過程的正确性。
而另一方面,通過調用RTPSession類提供的SetLocalName()、SetLocalEMail()、 SetLocalLocation()、SetLocalPhone()、SetLocalTool()和SetLocalNote()方法, JRTPLIB又允許程式員對RTP會話的控制資訊進行設定。所有這些方法在調用時都帶有兩個參數,其中第一個參數是一個char型的指針,指向将要被設定的資料;而第二個參數則是一個int型的數值,表明該資料中的前面多少個字元将會被使用。例如下面的語句可以被用來設定控制資訊中的電子郵件位址:
sess.SetLocalEMail("[email protected]@linuxgam.com",19);
在RTP 會話過程中,不是所有的控制資訊都需要被發送,通過調用RTPSession類提供的 EnableSendName()、EnableSendEMail()、EnableSendLocation()、EnableSendPhone ()、EnableSendTool()和EnableSendNote()方法,可以為目前RTP會話選擇将被發送的控制資訊。
3.6 實際應用
最後通過一個簡單的流媒體發送-接收執行個體,介紹如何利用JRTPLIB來進行實時流媒體的程式設計。清單3給出了資料發送端的完整代碼,它負責向使用者指定的IP位址和端口,不斷地發送RTP資料包:
#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;
}
清單4則給出了資料接收端的完整代碼,它負責從指定的端口不斷地讀取RTP資料包:
#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;
int status;
if (argc != 2) {
printf("Usage: ./sender localport\\n");
return -1;
}
// 獲得使用者指定的端口号
localport = atoi(argv[1]);
// 建立RTP會話
status = sess.Create(localport);
checkerror(status);
do {
// 接受RTP資料
status = sess.PollData();
// 檢索RTP資料源
if (sess.GotoFirstSourceWithData()) {
do {
RTPPacket* packet;
// 擷取RTP資料報
while ((packet = sess.GetNextPacket()) != NULL) {
printf("Got packet !\\n");
// 删除RTP資料報
delete packet;
}
} while (sess.GotoNextSourceWithData());
}
} while(1);
return 0;
}
随着多媒體資料在Internet上所承擔的作用變得越來越重要,需要實時傳輸音頻和視訊等多媒體資料的場合也将變得越來越多,如IP電話、視訊點播、線上會議等。RTP是用來在Internet上進行實時流媒體傳輸的一種協定,目前已經被廣泛地應用在各種場合,JRTPLIB是一個面向對象的RTP封裝庫,利用它可以很友善地完成Linux平台上的實時流媒體程式設計