版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/wellima/article/details/77978716
Live555
Live555是一個跨平臺的C++開源項目,爲流媒體提供解決方案,實現了RTP/RTCP、RTSP、SIP等標準流媒體傳輸協議。
Live555實現了音視頻數據的流化、接收和處理。支持包括MPEG、H.253+、DV、JPEG等視頻編碼格式、及多種音頻編碼。目前,Live555已經被用於多款播放器的流媒體播放功能的實現,如VLC(VideoLan)、MPlayer。
http://blog.csdn.net/nkmnkm (道長的文章,分析的很不錯)
http://blog.csdn.net/gavinr (這裏面的文章容易讓人理清思路)
該項目的源代碼包括四個基本的庫,各種測試代碼以及LIVE555 Media Server。
四個基本的庫分別是UsageEnvironment&TaskScheduler,groupsock,liveMedia,BasicUsageEnvironment。
1、Live555 Streaming Media整體框架
(1) UsageEnvironment模塊是對系統環境的抽象,包括抽象類 UsageEnvironment和TaskScheduler,用於事件的調度。
<1> UsageEnvironment主要用於消息的輸入輸出和用戶交互功能。
<2> TaskScheduler實現事件的異步處理、事件處理函數的註冊等,它通過維護一個異步讀取源實現對諸如通信消息到達等事件的處理,通過使用DelayQueue實現對其他註冊函數的延時調度。
程序設計者通過自定義該抽象了類UsageEnvironment和TaskScheduler類的子類,就可以在特定環境(如GUI環境)中運行,不需要進行過多的修改。
(2) BasicUsageEnvironment模塊是UsageEnvironment的一個控制檯應用的實現。它針對控制檯的輸入輸出和信號響應進行具體實現,利用select 實現事件獲取和處理。
(3) GroupSock模塊,對網絡接口進行封裝、用於實現數據包的發送和接收。GroupSock主要被設計用以支持多播,但它也完全支持單播通信。
(4) LiveMedia模塊是Live555中最重要的模塊。該模塊聲明瞭一個抽象類Medium,其他所有類都派生自該類。
<1> RTSPClient:該類實現RTSP請求的發送和響應的解析,同時根據解析的結果創建對應的RTP會話。
<2> MediaSession:用於表示一個RTP會話,一個MediaSession可能包含
多個子會話(MediaSubSession),子會話可以是音頻子會話、視頻子會話等。
<3> RTCPInstance:該類實現RTCP協議的通信。
<4> Source和Sink:這兩個概念類似DirectShow中的Filter。
①Source抽象了數據源,比如通過RTP讀取數據。
②Sink是數據消費者的抽象,比如把接收到數據存儲到文件,該文件就是一個Sink。
③數據的流動可能經過多個Source和Sink。
④MediaSink是各種類型的Sink的基類。
⑤MediaSource是各種類型Source的基類。
⑥Source和Sink通過RTP子會話(MediaSubSession)聯繫在一起。
基於liveMedia 的程序,需要通過繼承UsageEnvironment 抽象類和TaskScheduler 抽象類,定義相應的類來處理事件調度,數據讀寫以及錯誤處理。
(5) Media Server 是一個純粹的RTSP 服務器。支持多種格式的媒體文件:
l TS 流文件,擴展名ts。
l PS 流文件,擴展名mpg。
l MPEG-4視頻基本流文件,擴展名m4e。
l MP3文件,擴展名mp3。
l WAV 文件(PCM),擴展名wav。
l AMR 音頻文件,擴展名.amr。
l AAC 文件,ADTS 格式,擴展名aac。
1.1 OpenRTSP客戶端流程
1、創建TaskScheduler和BasicUsageEnvironment類的對象。
Ø TaskScheduler *scheduler =BasicTaskScheduler::createNew();
Ø UsageEnvironment *env = BasicUsageEnvironment::createNew(*scheduler);
2、命令行解析,獲取流媒體地址和其他選項。
3、創建RTSPClient對象。
Ø RTSPClient *rtspClient = RTSPClient::createNew(*env, rtspUri, 0, NULL, 0, -1);
4、如果需要,RTSPClient對象發送OPTIONS命令並解析服務端響應,獲取可以使用命令集。
5、RTSPClient對象發送DESCRIBE命令,並從獲服務端反饋中獲取流媒體相關描述SDP字串。
Ø Authenticator authenticator(admin, 12345, IsMd5); //數字驗證
Ø rtspClient->sendDescribeCommand(continueAferDescribe,& authenticator);
Ø env->taskScheduler().doEventLoop(&c);
Ø //處理describe請求返回的結果
void continueAferDescribe(RTSPClient *rtspClient,
int resultCode,
char* resultString); //sdp描述字符串
5、創建MediaSession對象,解析SDP字串,創建了相應的子會話對象。在這個過程中還完成了RTP和RTCP通信使用的GroupSock對象的創建,包括協議和端口的選擇。
Ø 在continueAferDescribe函數中創建MediaSession對象:
Ø UsageEnvironment& env = rtspClient->envir();
Ø char* const sdpDescription = resultString;
Ø MediaSession *pMediaSession = MediaSession::createNew(env,sdpDescription);
Ø bool hasSubSession = pMediaSession->hasSubsession();
Ø MediaSubsessionIterator *iter = //子會話迭代器
newMediaSubsessionIterator(*pMediaSession);
Ø MediaSubsession *pSubsession = iter->next();
Ø pSubsession->initiate();
Ø unsigned short num = pSubsession->clientPortNum(); //本地rtp端口
Ø 本體rtcp端口:num + 1
Ø //發送setup命令
Ø void setUpRequest(RTSPClient rtspClient, MediaSubsessionpSubsession);
7、根據流媒體不同類型,實例化具體的RTP會話的Source和Sink對象。
8、RTSPClient對象發送SETUP和PLAY命令,服務端開始傳輸流媒體數據。
Ø void setUpRequest(RTSPClient rtspClient, MediaSubsessionpSubsession);
Ø rtspClient->sendSetupCommand(pSubsession,conitueAfterSetup, false, false);
Ø void conitueAfterSetup(RTSPClient rtspClient, intresultCode, char resultStr)
Ø rtspClient->sendPlayCommand(*pMediaSession,continueAfterPlay,
start_time,
end_time, 1.0f, null);
9、TaskScheduler開始事件處理循環,通過select監聽數據包到達並調用註冊函數進行處理。
env->taskScheduler().doEventLoop(&c);
1.2 rtsp直播基於live555的實現
(1) 在mediaSever目錄下面有個live555MediaServer.exe,這是live555自帶生成的服務器端。
<1>將一個254文件比如test.254拷貝到exe文件所在的目錄下(就是mediaSever目錄下);
<2>雙擊打開這個exe服務器端;
<3>在另外一臺機器上打開vlc,使用“媒體–>打開網絡串流”,輸入服務器的dos窗口中的URL。
(2) 在目錄testProgs中有實例代碼,對於改寫你自己需要的程序一定會有很大的借鑑作用。
<1> 編譯live555之後會產生testOnDemandRTSPServer.exe,這也是一個服務器端。後面設計的基於live555的直播的服務端就是借鑑於testOnDemandRTSPServer.cpp 來改寫的。
<2>使用directshow採集的視頻,沒有加音頻採集,後期可以繼續加入音頻採集部分,然後進行編碼,在testOnDemandRTSPServer.cpp中通過sms->addSubsession加入音頻流; directshow不可以跨平臺,所以可以考慮所以opencv進行採集視頻。
(3)live555 + ffplay
<1>把媒體文件放到和live555MediaServer.exe同一目錄
<2>運行live555MediaServer.exe,彈出的dos框裏面有地址,如下圖
<3>客戶端,dos下進入到ffplay所在文件夾下,然後輸入如下命令
ffplay.exe rtsp://10.120.2.18/<媒體文件名>
2、 編譯live555
2.1 方法1
利用genWindowsMakefiles.cmd生成VS可用的makefile
1 修改win32config。打開live\win32config文件,修改如下
TOOLS32 = c:\Program Files\DevStudio\Vc
TOOLS32 = E:\Program Files\Microsoft Visual Studio 10.0\VC
將TOOLS32修改爲你的VS2010路徑
LINK_OPTS_0 = $(linkdebug) msvcirt.lib
LINK_OPTS_0 = $(linkdebug) msvcrt.lib
編譯器索要的LINK運行庫不同,原本以爲可以改爲msvcrt100.lib,但沒找着
2 新增Makefile設定。打開live\groupsock\Makefile.head,修改如下
INCLUDES = -Iinclude -I…/UsageEnvironment/include
INCLUDES = -Iinclude -I…/UsageEnvironment/include -DNO_STRSTREAM
3 建立makefile
方法:運行live\genWindowsMakefiles.cmd,生成VS能夠編譯的*.mak文件
4 建立build.bat命令
新建live\complie.bat,並添加內容如下:
call “E:\ProgramFiles\Microsoft Visual Studio 10.0\VC\vcvarsall.bat”
cd liveMedia
nmake /B -f liveMedia.mak
cd …/groupsock
nmake /B -f groupsock.mak
cd …/UsageEnvironment
nmake /B -f UsageEnvironment.mak
cd …/BasicUsageEnvironment
nmake /B -f BasicUsageEnvironment.mak
cd …/testProgs
nmake /B -f testProgs.mak
cd …/mediaServer
nmake /B -fmediaServer.mak
5 開始編譯:(命令行下)執行complie.bat
5 編譯結果:
5-1 在對應的文件下,如下圖
① 生成與cpp文件對應的obj文件(Object File中間代碼文件,源文件complie生成,在linux下爲o文件)
② 生成lib庫: libBasicUsageEnvironment.lib、libgroupsock.lib、libUsageEnvironment.lib、libliveMedia.lib
說明:若要用VS2010對代碼進行調試跟蹤,那麼編譯時需要做相應修改,修改方法如下:
方法一:修改*.mak文件下的NODEBUG 。不帶DEBUG,NODEBUG=1(默認);帶DEBUG,DEBUG=1
方法二:在win32config加入一行 “NODEBUG=1” (不推薦)
2.2 方法2(Win7+VS2010方式)
如果需要自己調試修改源碼,採用編譯器的方式會更好些,這種方式也更利於源碼分析,步驟如下:
0 綜述:分別爲每個庫單獨編譯生成lib
1 新建解決方案和lib工程,分別如下:
E:\My Document\Visual Studio2010\Projects\myLive555\BasicUsageEnvironment
E:\My Document\Visual Studio 2010\Projects\myLive555\liveMedia
E:\My Document\Visual Studio 2010\Projects\myLive555\groupsock
E:\My Document\Visual Studio 2010\Projects\myLive555\BasicUsageEnvironment
完整解決方案的結構如下圖
2 添加頭文件
方法1:採用全局包含方式(絕對路徑)。需要添加的include文件包括
E:\My Document\Visual Studio2010\Projects\myLive555\BasicUsageEnvironment\include
E:\My Document\Visual Studio 2010\Projects\myLive555\liveMedia\include
E:\My Document\Visual Studio 2010\Projects\myLive555\groupsock\include
E:\My Document\Visual Studio 2010\Projects\myLive555\BasicUsageEnvironment\include
方法2:採用局部(當前工程)包含方式(相對路徑)。推薦
描述:工程->屬性->配置屬性->C/C+±>常規->附加包含目錄
…\BasicUsageEnvironment\include
…\groupsock\include
…\liveMedia\include
…\UsageEnvironment\include
3 添加文件。
在上述lib工程中添加對應的所有的cpp文件。
4 設置工程的輸出目錄。
路徑:E:\My Document\Visual Studio2010\Projects\myLive555\lib
方法:項目-》屬性-》常規-》輸出目錄
5 編譯解決方案
結果:在lib目錄下生成 BasicUsageEnvironment.lib、groupsock.lib、UsageEnvironment.lib、liveMedia.lib
2.3 MediaSession
1) MediaSession表示一個RTP會話;一個MediaSession可能包含多個子會話(MediaSubSession),子會話可以是音頻子會話、視頻子會話等。
2)通過SDP生成一個RTP會話描述信息(使用MediaSession對象表示);通常一個SDP中包含“音頻”“視頻”相關描述信息,使用不同的SubMediaSesion對象表示。
class MediaSession : public Medium
{
public:
staticMediaSession* CreateNew(UsageEnvironment& env, char const* sdpDescription);
BooleanhasSubsessions() const { return fSubsessionHead != NULL; };
protected:
BooleaninitializeWithSDP(char const* sdpDescription);
BooleanparseSDPLine(char const* inputLine, char const*& nextLine)
{
nextLine =NULL;
for(char const*ptr = inputLine; *ptr != ‘\0’; ++ptr)
{
if( *ptr== ‘\r’ || *ptr == ‘\n’ )
{ ++ptr;}
while(*ptr== ‘\r’ || *ptr == ‘\n’ )
{
++ptr;
}
nextLine =ptr; //即是下一行的開始
if(nextLine[0]== ‘\0’)
{
nextLine = NULL;
}
break;
}
if(inputLine[0] == ‘\r’ || inputLine [0] == ‘\n’)
{ returntrue; } //空格行
if(strlen(inputLine)< 2 || inputLine[1] != ‘=’ ||inputLine[0] < ‘a’ || inputLine[0]> ‘z’)
{
enivr().setResultMsg(“Invalide SDP line : ”,inputLine);
return false;
}
returnture;
}
};
2.4 SDP詳解
v = 0 //版本號
o = - 1109152014219182 0 IN 0.0.0.0 //owner
s = HIK Media Server V3.1.1 //sessionname會話名
i = HIK Media Server Session Description : standard //會話描述
u = * (URI of description)
e = * (email address) //Email地址
p = * (phone number)
c = IN IP4 0.0.0.0 //連接信息
b =* (表示寬度信息,值爲0或者bandwidth information)
t = * (time the session is active,有效時間)
r = * (zero or more repeat times)
a = control:*
a = range:clock= 20150120T180515Z – 20150405T151210Z
m = video 0 RTP/AVP 95 //視頻
i = video media
a = rtpmap: 95 H264/90000
a = fmtp:95 profile-level-id = 4D0014; packetization-mode = 0
a = control : trackID = video //音頻
m = audio 0 RTP/AVP 98
i = Audio media
a = rtpmap : 98 G7221/15000
a = control : trackID = audio
3、 Source和Sink及Filter
Medium<-MediaSource<-FramedSource<-RTPSource<-MultiFramedRTPSource<-H254VideoRTPSource
3.1 source
Source 是生產數據源的對象。如通過RTP讀取數據。
3.1.1類FrameSource,抽象類
class MediaSource : public Medium;
class FrameSource : public MediaSource
{
//回調函數
typedef void (afterGettingFunc)(void*clientData, unsigned framesize, unsigned numTruncatedBytes,
struct timeval presentationTime, unsigneddurationInMicrosenconds);
typedef void (onCloseFunc)(void* clientData);
//從上一個source中獲取一幀數據,幀數據的類型如何判斷?
void getNextFrame(unsigned char* to,
unsigned maxSize,
afterGettingFunc* afterGettingFunc,
void* afterGettingClientData,
onCloseFunc* onCloseFunc,
void* onCloseClientData);
virtual voiddoGetNextFrame() = 0; //被getNextFrame( )調用
};
3.1.2類RTPSource
實際調試中的bug:int socknum =m_pVideoSubsession->rtpSource()->RTPgs()->socketNum(); ? 崩潰
class RTPSource :public FramedSource
{
public:
Groupsock* RTPgs() const { returnfRTPInterface.gs(); }
virtual voidsetPacketRecorderingThresholdTime(unsigned uSeconds) = 0;
protected:
RTPSource(UsageEnvironment& env,Groupsock* RTPgs, unsigned char rtpPayloadFormat,
u_int32_t rtpTimestampFrequency);
protected:
RTPInterface fRTPInterface;
private:
unsigned char fRTPPayloadFormat;
}
classRTPInterface
{
public:
RTPInterface(Medium* owner, Groupsock* gs);
Groupsock* gs() const { return fGS; }
Boolean sendPacket(unsigned char* packet,unsigned packetsize); //網絡發送
void startNetworkReading(TaskScheduler::BackgroundHandlerProc*handlerProc); //網絡讀取
private:
Groupsock* fGS;
}
3.1.3類MultiFramedRTPSource
解析RTP數據包
class MultiFramedRTPSource: public RTPSource
{
protected:
MultiFramedRTPSource(UsageEnvironment&env, Groupsock* RTPgs, unsigned char rtpPayloadFormat,
unsigned char rtpTimestampFrequency,BufferedPacketFactory* packetFactory = NULL);
private:
virtual void doGetNextFrame();
void networkReadHandler1(); //解析收到的rtp包
}
3.1.4類H254VideoRTPSource
class H254VideoRTPSource: public MultiFramedRTPSource
{
public :
static H254VideoRTPSource* CreateNew(UsageEnvironment&env, Groupsock* RTPgs,
unsigned char rtpPayloadFormat,
unsigned rtpTimestampFrequency = 90000);
protected:
H254VideoRTPSource(UsageEnvironment&env, Groupsock* RTPgs,
unsigned charrtpPayloadFormat,
unsignedrtpTimestampFrequency = 90000);
private:
friend class H254BufferedPacket;
unsigned char fCurPacketNALUnitType; //h254中的nalu的類型
}
//處理h254的分片包
Boolean H254VideoRTPSource::processSpecialHeader(BufferedPacket*packet, unsigned& resultSpecialHeaderSize)
{
unsigned char* headerStart =packet->data;
unsigned packetSize = packet->dataSize();
fCurPacketNALUnitType = (headerStart[0]& 0x1F ); //5爲I幀,7爲sps,8爲pps
switch(fCurPacketNALUnitType)
{
case 24: //STAP-A
numByteToSkip = 1; //丟棄type字節
break;
case 25: //STAP-B, MTAP15, MTAP24
case 25:
case 27:
numByteToSkip = 3; //丟棄type字節,和初始化DON
break;
case 28: //FU-A, FU-B。NALU的分片包
case 29:
}
}
3.2 sink
Sink 是數據消費的對象。如把接收到的數據存文件,則這個文件就是sink。
Ø 數據接收的終點是Sink 類,MediaSink 是所有Sink 類的基類。
Ø Sink 類實現對數據的處理是通過實現純虛函數continuePlaying( )。
Ø 通常情況下continuePlaying 調用fSource->getNextFrame來爲Source 設置數據緩衝區、處理數據的回調函數等。
voidFramedSource::getNextFrame(unsigned char* to, //緩存數據的地址
unsigned maxSize, //緩衝區的最大長度
afterGettingFunc* afterGettingFunc, //數據回調函數
void* afterGettingClientData, //向數據回調函數中傳入的參數
onCloseFunc* onCloseFunc, //數據源關閉回調函數
void* onCloseClientData); //向數據源關閉回調函數中傳入的參數
數據回調函數:
void afterGettingFunc( void*clientData, //向回調函數中傳入的參數
unsigned frameSize, //數據幀的實際大小
unsigned numTruncatedBytes,
struct timeval presentationTime,
unsigned durationInMicroseseconds
);
Ø fSource是MediaSink 中的類型爲FramedSource*的類成員。
3.3 數據流
數據經過多個Source到Sink。
Source1 -> Source2(a filter) -> … -> Sourcen(a filter) -> sink
從其他Source接收數據的Source也叫Filter。
Ø Module 是一個sink 或者一個filter。
Ø 一個Module 需要獲取數據都通過調用剛好在它之前的那個Module 的FramedSource::getNextFrame()方法。這是通過純虛函數FramedSource::doGetNextFrame() 實現的,每一個Source module 都有相應的實現。
Medium<-MediaSource<-FramedSource<-RTPSource<-MultiFramedRTPSource<-H254VideoRTPSource
4、重要的類
http://blog.csdn.net/niu_gao/article/details/5911130
BasicUsageEnvironment和UsageEnvironment中的類都是用於整個系統的基礎功能類.比如UsageEnvironment代表了整個系統運行的環境,它提供了錯誤記錄和錯誤報告的功能,無論哪一個類要輸出錯誤,就需要保存UsageEnvironment的指針.而TaskScheduler則提供了任務調度功能.整個程序的運行發動機就是它,它調度任務,執行任務(任務就是一個函數).TaskScheduler由於在全局中只有一個,所以保存在了UsageEnvironment中.而所有的類又都保存了UsageEnvironment的指針,所以誰想把自己的任務加入調度中,那是很容易的.在此還看到一個結論:整個live555(服務端)只有一個線程.
類DelayQueue:譯爲"延遲隊列",它是一個隊列,每一項代表了一個要調度的任務(在它的fToken變量中保存).同時保存了這個任務離執行時間點的剩餘時間.可以預見,它就是在TaskScheduler中用於管理調度任務的東西.注意,此隊列中的任務只被執行一次!執行完後這一項即被無情拋棄!
類Groupsock:這個是放在單獨的庫Groupsock中。它封裝了socket操作,增加了多播放支持和一對多單播的功能.但我只看到它對UDP的支持,好像不支持TCP。它管理着一個本地socket和多個目的地址,因爲是UDP,所以只需知道對方地址和端口即可發送數據。Groupsock的構造函數有一個參數是struct in_addr const& groupAddr,在構造函數中首先會調用父類構造函數創建socket對象,然後判斷這個地址,若是多播地址,則加入多播組。Groupsock的兩個成員變量destRecord* fDests和DirectedNetInterfaceSetfMembers都表示目的地址集和,但我始終看不出DirectedNetInterfaceSetfMembers有什麼用,且DirectedNetInterfaceSet是一個沒有被繼承的虛類,看起來fMembers沒有什麼用。僅fDesk也夠用了,在addDestination()和removeDestination()函數中就是操作fDesk,添加或刪除目的地址。
看服端的主體:live555MediaServer.cpp中的main()函數,可見其創建一個RTSPServer類實例後,即進入一個函數env->taskScheduler().doEventLoop()中,看名字很明顯是一個消息循壞,執行到裏面後不停地轉圈,生名不息,轉圈不止。
5、live555開發
5.1 rtsp client
5.1.1創建任務調度類TaskScheduler的對象
TaskScheduler* scheduler = BasicTaskScheduler::CreateNew();
ClassBasicTaskScheduler : public BasicTaskScheduler0
{
//創建對象,不能在外面通過構造函數創建對象
public:
static BasicTaskScheduler*CreateNew(unsigned maxSchedulerGranularity = 10000);
protected:
BasicTaskScheduler(unsignedmaxSchedulerGranularity);
}
5.1.2創建類UsageEnvironment的對象
class UsageEnvironment 是一個純虛抽象類,包含純虛函數。
{
public:
virtual UsageEnvironment& operate<<(char const* str) = 0;
protected:
UsageEnvironment(TaskScheduler&scheduler);
private:
TaskScheduler& fScheduler;
}
class BasicUsageEnvironment : publicBasicUsageEnvironment0
{
}
UsageEnvironment& BasicUsageEnvironment::operate<<(charconst* str)
{
if(str == NULL) str = “(NULL)”;
fprintf(stderr,“%s”, str);
return*this;
}
5.1.3創建RtspClient類
class RTSPClient : public Medium
{
public:
static RTSPClient* createNew( UsageEnvironment& env,
char const* rtspUrl,
int verbositylevel = 0,
char const*applicationName = NULL,
portNumBitstunnelOverHTTPPortNum = 0,
int socketNumToServer = -1);
}
5.1.4啓動消息循環(循環接收網絡數據)
doEventLoop是阻塞函數,因此需要在doEventLoop之前創建RTSPClient對象。
char eventloopWatchVariable = 0;
env->taskScheduler().doEventLoop(&eventloopWatchVariable);
void BasicTaskScheduler0::doEventLoop(charvolatile* watchVariable)
{
while(1)
{
if(watchVariable != NULL &&*watchVariable != 0)
{
SingleStep();
}
}
}
void BasicTaskScheduler::SingleStep(unsignedmaxDelayTime)
{
採用select模型進行網絡通信
fd_set readSet = fReadSet;
fd_set writeSet = fWriteSet;
fd_set exceptionSet =fExceptionSet;
struct timeval tv_timeToDelay;
tv_timeToDelay.tv_sec= 秒;
tv_timeToDelay.tv_usec = 毫秒;
int selectResult = select(fMaxNumSocket,&readSet, &writeSet, &exceptionSet, & tv_timeToDelay);
selectResult = 0; 表示超時
selectResult < 0; 表示錯誤
selectResult > 0; 表示就緒的socket的數量
}
5.1.5發送Describe命令及處理響應
認證:Basic和Digest
Authenticator authenticator(用戶名,密碼, 是否使用MD5);
RtspClient->sendDescribeCommand(DealDescribeResp,& authenticator);
用於Digest鑑權的類
class Authenticator
{
public:
// passwordIsMD5 爲true時,使用MD5加密:md5(::)
// realm和nonce字符串由服務器返回,401 Unauthorized response
Authenticator(char const* username, char const* password, BooleanpasswordIsMD5 = False );
};
處理結果:
void DealDescribeResp(RTSPClient* rtspClient, intresultCode, char* resultString)
{
if resultCode == 401; 未鑑權
resultString是SDP的描述。
通過SDP創建MediaSession對象
MediaSession mediaSession =MediaSession::CreateNew(env, resultString);
判斷是否有子會話
Bool hasSubSession = mediaSession->hasSubsessions();
if hasSubSession == true
得到mediaSession中的MediaSubsession迭代器
MediaSubsessionIterator it = newMediaSubsessionIterator(mediaSession);
創建MediaSubsession
MediaSubsession* pMediaSubsession = it->next();
pMediaSubsession->initiate();
判斷是音頻還是視頻
char* mediaName = pMediaSubsession->mediumName();
視頻爲video 音頻爲audio. (char const* mediumName()😉
}
5.1.6發送Setup命令及處理響應
RtspClient->sendSetupCommand(); 建立rtp連接
函數原型:
typedef void (responseHandler)(RTSPClient* rtspClient,int resultCode, char* resultString)
unsigned RTSPClient::sendSetupCommand(MediaSubsession&subsession, responseHandler* responseHandler,
Boolean streamOutgoing = False,
Boolean streamUsingTcp = False,
Boolean forceMulticastOnUnspecified = False,
Authenticator* authenticator = NULL);
創建Sink來接收DVR發送過來的視頻數據:
class Medium (所有LiveMedia中的類的基類)
{
public :
static Boolean lookupByName(UsageEnvironment& env, charconst* mediumName, Medium*& resultMedium);
static void close(UsageEnvironment& env, char const* mediumName);
static void close(char const* mediumName);
UsageEnvironment&envir( ) const { return fEnviron; }
charconst* name( ) { return fMediumName; }
virtualBoolean isSource( ) const;
virtualBoolean isSink( ) const;
virtualBoolean isRTCPInstance( ) const;
virtual Boolean isRTSPClient( ) const;
virtual Boolean isRTSPServer( ) const;
virtual Boolean isMediaSession( ) const;
virtual Boolean isServerMediaSession( ) const;
}
class MediaSink : public Medium (sink類的基類)
{
public:
staticBoolean lookupByName(UsageEnvironment& env, char const* sinkName,Medium*& resultSink);
typedefvoid (afterPlayingFunc)(void *clientData);
BooleanstartPlaying( MediaSource& source, afterplayingFunc* afterFunc, void*afterClientData);
virtual voidstopPlaying( );
virtualBoolean isRTPSink( ) const;
FrameSource*source( ) const { return fSource; }
protected:
MediaSink(UsageEnvironment&env);
virtualBoolean continuePlaying( ) = 0;
staticvoid onSourceClosure( void clientData);
FrameSource*fSource;
private:
afterPlayingFunc*fAfterFunc;
void*fAfterClientData;
}
媒體數據從source流向sink
class FrameSouce : public MediaSource
{
public:
staticBoolean lookupByName(UsageEnvironment& env, char const* sourceName, FrameSource*&resultSource);
typedefvoid (afterGettingFunc)(void* clientData, unsigned framesize, unsignednumTruncatedBytes,
struct timeval presentationTime, unsigned durationInMicroseconds);
voidgetNextFrame( unsigned char* to, unsigned maxSize, afterGettingFunc*afterGettingFunc,
void* afterGettingClientData, onCloseFunconCLoseFunc, void onCloseClientData );
}
class DummySink : public MediaSink
{
Boolean MediaSink::startPlaying( MediaSource& source, afterPlayingFuncafterFunc, void afterClientData)
{
調用continuePlaying( );
}
}
Ø 創建sink
DummySink sink;
Ø 將sink傳入MediaSubsession
MediaSubsession m_pMediaSubsession;
m_pMediaSubsession->sink = sink;
class MediaSession : public Medium
{
public :
//通過SDP創建MediaSession對象, 包含(音頻)(視頻)的MediaSubsession
static MediaSession* CreateNew(UsageEnvironment&env, char const*sdpDescription);
Boolean hasSubsessions( ) const { … }
解析SDP
}
class MediaSubsession
{
public:
MediaSession& parentSession() {return fParent; }
RTPSource* rtpSource( ) { returnfRtpSource; }
RTCPInstance* rtcpInstance( ) {return fRtcpInstance; }
FrameSource* readSource( ) { returnfReadSource; }
MediaSink* sink; //數據向下的目的地
private:
RTPSource* fRtpSource;
FramedSource* fReadSouce; //幀數據源,RTP拆包後的H254幀數據
};
//獲得接收緩存的大小
unsigned getReceiveBufferSize(UsageEnvironment& env, int socket)
{
int curSize;
int length = sizeof curSize;
getsockopt(socket, SOL_SOCKET,SO_RCVBUF, (char*)&curSize, &length);
}
//設置接收緩存的大小
unsigned setReceiveBufferTo(UsageEnvironment&env, int socket, unsigned requestSize)
{
int length = sizeof requestSize;
setsockopt( socket, SOL_SOCKET,SO_RCVBUF, (char*)&requestSize, length );
}
5.1.7發送Play命令及處理響應
unsigned sendPlayCommand(MediaSubsession&subsession, responseHandler* responseHandler,
double start = 0.0f, double end = -1.0f, float scale = 1.0f,
Authenticator* authenticator = NULL);
unsigned sendPlayCommand(MediaSubsession&subsession, responseHandler* responseHandler,
char const* absStartTime,
char const* absEndTime = NULL,
float scale = 1.0f,
Authenticator* authenticator = NULL);
absStartTime字符串的格式:20150120T180950Z yyyyMMddTHHmmss
scale = 1.0f 表示正常速度播放;
start 爲開始時間
end 表示結束時間
歷史錄像回放:absStartTime 按照格式yyyyMMddTHHmmss填寫值
實時預覽:absStartTime值爲NULL
5.1.8發送Teardown命令關閉視頻
關閉MediaSink
Medium::close(MediaSink*sink);
終止RTP傳輸,使用RTCP發送Bye
subsession->rtcpInstance()->setByeHandler();處理服務器發送的Bye命令
void setByeHandler(TaskFunc* handlerTask, void* clientData, BooleanhandleActiveParticipantsOnly = True);
發送Teardown命令
unsigned sendTeardownCommand(MediaSubsession&subsession,
requestHanlder* reqHandler,
Authenticator* authenticator= NULL );
unsigned sendTeardownCommand(MediaSession&session,
requestHanlder* reqHandler,
Authenticator* authenticator= NULL );
關閉RTSPClient
Medium::close(RTSPClient* rtspClient);
5.1.9資源釋放
Ø MediaSession* session;
Medium::close(session);
Ø RTSPClient *rtspClient;
Medium::close(rtspClient);
Ø MediaSink* sink;
Medium::close(sink);
sink = NULL;
Ø MediaSubsessionIterator *it;
delete it;
Ø TaskScheduler* scheduler =BasicTaskScheduler::createNew( );
UsageEnvironment* env = BasicUsageEnvironment::CreateNew(*scheduler);
…
資源釋放
env->reclaim( );
env = NULL;
delete scheduler;
scheduler = NULL;
5.2 rtsp server
Ø RTSPServer 類用於構建一個RTSP 服務器。該類內部定義了一個RTSPClientSession類,用於處理單獨的客戶會話。
Ø 首先創建RTSP 服務器( 具體實現類是DynamicRTSPServer) , 在創建過程中, 先建立Socket(ourSocket)在TCP 的554 端口進行監聽;
Ø 然後把連接處理函數句柄(RTSPServer::incomingConnectionHandler)和socket 句柄傳給任務調度器(taskScheduler)。
————————————————
版權聲明:本文爲CSDN博主「馬踏四方」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/wellima/article/details/77978716