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

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