live555代码分析

来源:互联网 发布:win10安装连接网络出错 编辑:程序博客网 时间:2024/05/30 23:21

Live555

Live555是一个跨平台的C++开源项目,为流媒体提供解决方案,实现了RTP/RTCPRTSPSIP等标准流媒体传输协议。

Live555实现了音视频数据的流化、接收和处理。支持包括MPEGH.253+DVJPEG等视频编码格式、及多种音频编码。目前,Live555已经被用于多款播放器的流媒体播放功能的实现,如VLC(VideoLan)MPlayer

http://blog.csdn.net/nkmnkm (道长的文章,分析的很不错)

http://blog.csdn.net/gavinr (这里面的文章容易让人理清思路)

该项目的源代码包括四个基本的库,各种测试代码以及LIVE555 Media Server

四个基本的库分别是UsageEnvironment&TaskSchedulergroupsockliveMediaBasicUsageEnvironment

1、Live555 Streaming Media整体框架

(1) UsageEnvironment模块是对系统环境的抽象,包括抽象类 UsageEnvironmentTaskScheduler,用于事件的调度。

<1> UsageEnvironment主要用于消息的输入输出和用户交互功能。

<2> TaskScheduler实现事件的异步处理、事件处理函数的注册等,它通过维护一个异步读取源实现对诸如通信消息到达等事件的处理,通过使用DelayQueue实现对其他注册函数的延时调度

程序设计者通过自定义该抽象了类UsageEnvironmentTaskScheduler类的子类,就可以在特定环境(如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> SourceSink:这两个概念类似DirectShow中的Filter

Source抽象了数据源,比如通过RTP读取数据。

Sink是数据消费者的抽象,比如把接收到数据存储到文件,该文件就是一个Sink

数据的流动可能经过多个SourceSink

MediaSink是各种类型的Sink的基类。

MediaSource是各种类型Source的基类。

SourceSink通过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、创建TaskSchedulerBasicUsageEnvironment类的对象。

Ø  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命令并解析服务端响应,获取可以使用命令集。

 

5RTSPClient对象发送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字串,创建了相应的子会话对象。在这个过程中还完成了RTPRTCP通信使用的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, MediaSubsession*pSubsession);

 

7、根据流媒体不同类型,实例化具体的RTP会话的SourceSink对象。

8RTSPClient对象发送SETUPPLAY命令,服务端开始传输流媒体数据。

Ø  void setUpRequest(RTSPClient *rtspClient, MediaSubsession*pSubsession);

Ø  rtspClient->sendSetupCommand(pSubsession,conitueAfterSetup, false, false);

Ø  void conitueAfterSetup(RTSPClient *rtspClient, intresultCode, char* resultStr)

Ø  rtspClient->sendPlayCommand(*pMediaSession,continueAfterPlay,

start_time,

end_time, 1.0f, null);

9TaskScheduler开始事件处理循环,通过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.libgroupsock.libUsageEnvironment.libliveMedia.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(<username>:<realm>:<actual-password>)

 // realmnonce字符串由服务器返回,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, onCloseFunc*onCLoseFunc, void* onCloseClientData );

 

}

 

class DummySink : public MediaSink

{

Boolean MediaSink::startPlaying( MediaSource& source, afterPlayingFunc*afterFunc, 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)。