live555代碼分析

版權聲明:本文爲博主原創文章,遵循 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

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