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) 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、创建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, MediaSubsession*pSubsession);
7、根据流媒体不同类型,实例化具体的RTP会话的Source和Sink对象。
8、RTSPClient对象发送SETUP和PLAY命令,服务端开始传输流媒体数据。
Ø 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);
9、TaskScheduler开始事件处理循环,通过select监听数据包到达并调用注册函数进行处理。
env->taskScheduler().doEventLoop(&c);
(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/<媒体文件名>
利用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" (不推荐)
如果需要自己调试修改源码,采用编译器的方式会更好些,这种方式也更利于源码分析,步骤如下:
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
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;
}
};
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
Medium<-MediaSource<-FramedSource<-RTPSource<-MultiFramedRTPSource<-H254VideoRTPSource
Source 是生产数据源的对象。如通过RTP读取数据。
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( )调用
};
实际调试中的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;
}
解析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包
}
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:
}
}
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*的类成员。
数据经过多个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
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()中,看名字很明显是一个消息循坏,执行到里面后不停地转圈,生名不息,转圈不止。
TaskScheduler* scheduler = BasicTaskScheduler::CreateNew();
ClassBasicTaskScheduler : public BasicTaskScheduler0
{
//创建对象,不能在外面通过构造函数创建对象
public:
static BasicTaskScheduler*CreateNew(unsigned maxSchedulerGranularity = 10000);
protected:
BasicTaskScheduler(unsignedmaxSchedulerGranularity);
}
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;
}
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);
}
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的数量
}
认证: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();)
}
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 );
}
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
关闭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);
Ø 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;
Ø RTSPServer 类用于构建一个RTSP 服务器。该类内部定义了一个RTSPClientSession类,用于处理单独的客户会话。
Ø 首先创建RTSP 服务器( 具体实现类是DynamicRTSPServer) , 在创建过程中, 先建立Socket(ourSocket)在TCP 的554 端口进行监听;
Ø 然后把连接处理函数句柄(RTSPServer::incomingConnectionHandler)和socket 句柄传给任务调度器(taskScheduler)。