摘自:http://blog.youkuaiyun.com/nkmnkm/article/details/6906055(对live555感兴趣的friends可以加群:3744371)
一直想研究live555,没有时间,终于因为项目的原因可以深入无间地研究一下了.所以在此著文以记之.
一 如何编译live555
利用mingw环境很容易:在live555文件夹下,
genMakefiles mingw
make
即可.
可以用genWindowsMakefiles.cmd生成VC可用的makefile,但是对比较新的vc版本支持不好,需要自己改很多东西.
用VC编译有一种更好的办法:
手动为每个库都生成一个lib项目,为mediaServer生成exe项目,设置好各库之间的依赖关系,就可以用VC编译了.由于live555代码中没有单独支持gcc的东西,所以编译是相当的容易.这样就可以用VC编译和调试了.
我现在怕麻烦,只用eclipse+mingw.eclipse的调试也很好用了.
二 基础类
讲几个重要的基础类:
BasicUsageEnvironment和UsageEnvironment中的类都是用于整个系统的基础功能类.比如UsageEnvironment代表了整个系统运行的环境,它提供了错误记录和错误报告的功能,无论哪一个类要输出错误,就需要保存UsageEnvironment的指针.而TaskScheduler则提供了任务调度功能.整个程序的运行发动机就是它,它调度任务,执行任务(任务就是一个函数).TaskScheduler由于在全局中只有一个,所以保存在了UsageEnvironment中.而所有的类又都保存了UsageEnvironment的指针,所以谁想把自己的任务加入调度中,那是很容易的.在此还看到一个结论:整个live555(服务端)只有一个线程.
类HashTable:不用多说,实现了哈稀表.
类DelayQueue:译为"延迟队列",它是一个队列,每一项代表了一个要调度的任务(在它的fToken变量中保存).同时保存了这个任务离执行时间点的剩余时间.可以预见,它就是在TaskScheduler中用于管理调度任务的东西.注意,此队列中的任务只被执行一次!执行完后这一项即被无情抛弃!
类HandlerSet:Handler集合.Handler是什么呢?它是一种专门用于执行socket操作的任务(函数),HandlerSet被TaskScheduler用来管理所有的socket任务(增删改查).所以TaskScheduler中现在已调度两种任务了:socket任务(handlerSet)和延迟任务(DelayQueue).其实TaskScheduler还调度第三种任务:Event,介个后面再说.
类Groupsock:这个是放在单独的库Groupsock中。它封装了socket操作,增加了多播放支持和一对多单播的功能.但我只看到它对UDP的支持,好像不支持TCP。它管理着一个本地socket和多个目的地址,因为是UDP,所以只需知道对方地址和端口即可发送数据。Groupsock的构造函数有一个参数是struct in_addr const& groupAddr,在构造函数中首先会调用父类构造函数创建socket对象,然后判断这个地址,若是多播地址,则加入多播组。Groupsock的两个成员变量destRecord* fDests和DirectedNetInterfaceSet fMembers都表示目的地址集和,但我始终看不出DirectedNetInterfaceSet fMembers有什么用,且DirectedNetInterfaceSet是一个没有被继承的虚类,看起来fMembers没有什么用。仅fDesk也够用了,在addDestination()和removeDestination()函数中就是操作fDesk,添加或删除目的地址。
解释一下Groupsock::changeDestinationParameters()函数:
- //改变目的地址的参数
- //newDestAddr是新的目的地址
- //newDestPort是新的目的端口
- //newDestTTL是新的TTL
- void Groupsock::changeDestinationParameters(
- struct in_addr const& newDestAddr,
- Port newDestPort,
- int newDestTTL)
- {
- if (fDests == NULL)
- return;
- //获取第一个目的地址(此处不是很明白:fDest是一个单向链表,每次添加一个目的地址,
- //都会把它插入到最前目,难道这个函数仅改变最后一个添加的目的地址?)
- struct in_addr destAddr = fDests->fGroupEId.groupAddress();
- if (newDestAddr.s_addr != 0) {
- if (newDestAddr.s_addr != destAddr.s_addr
- && IsMulticastAddress(newDestAddr.s_addr))
- {
- //如果目的地址是一个多播地址,则离开老的多播组,加入新的多播组。
- socketLeaveGroup(env(), socketNum(), destAddr.s_addr);
- socketJoinGroup(env(), socketNum(), newDestAddr.s_addr);
- }
- destAddr.s_addr = newDestAddr.s_addr;
- }
- portNumBits destPortNum = fDests->fGroupEId.portNum();
- if (newDestPort.num() != 0) {
- if (newDestPort.num() != destPortNum &&
- IsMulticastAddress(destAddr.s_addr))
- {
- //如果端口也不一样,则先更改本身socket的端口
- //(其实是关掉原先的socket的,再以新端口打开一个socket)。
- changePort(newDestPort);
- //然后把新的socket加入到新的多播组。
- // And rejoin the multicast group:
- socketJoinGroup(env(), socketNum(), destAddr.s_addr);
- }
- destPortNum = newDestPort.num();
- fDests->fPort = newDestPort;
- }
- u_int8_t destTTL = ttl();
- if (newDestTTL != ~0)
- destTTL = (u_int8_t) newDestTTL;
- //目标地址的所有信息都在fGroupEId中,所以改变成员fGroupEId。
- fDests->fGroupEId = GroupEId(destAddr, destPortNum, destTTL);
- //(看起来这个函数好像只用于改变多播时的地址参数,
- //以上分析是否合理,肯请高人指点)
- }
三 消息循环
看服端的主体:live555MediaServer.cpp中的main()函数,可见其创建一个RTSPServer类实例后,即进入一个函数env->taskScheduler().doEventLoop()中,看名字很明显是一个消息循坏,执行到里面后不停地转圈,生名不息,转圈不止。那么在这个人生的圈圈中如何实现RTSP服务和RTP传输呢?别想那么远了,还是先看这个圈圈中实现了什么功能吧。
- void BasicTaskScheduler0::doEventLoop(char* watchVariable) {
- // Repeatedly loop, handling readble sockets and timed events:
- while (1) {
- if (watchVariable != NULL && *watchVariable != 0)
- break;
- SingleStep();
- }
- }
void BasicTaskScheduler0::doEventLoop(char* watchVariable) {
// Repeatedly loop, handling readble sockets and timed events:
while (1) {
if (watchVariable != NULL && *watchVariable != 0)
break;
SingleStep();
}
}
BasicTaskScheduler0从TaskScheduler派生,所以还是一个任务调度对象,所以依然说明任务调度对象是整个程序的发动机。
循环中每次走一步:SingleStep()。这走一步中都做些什么呢?
总结为以下四步:
1为所有需要操作的socket执行select。
2找出第一个应执行的socket任务(handler)并执行之。
3找到第一个应响应的事件,并执行之。
4找到第一个应执行的延迟任务并执行之。
可见,每一步中只执行三个任务队列中的一项。下面详细分析函数SingleStep():
- //循坏中主要执行的函数
- void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {
- fd_set readSet = fReadSet; // make a copy for this select() call
- fd_set writeSet = fWriteSet; // ditto
- fd_set exceptionSet = fExceptionSet; // ditto
- //计算select socket们时的超时时间。
- DelayInterval const& timeToDelay = fDelayQueue.timeToNextAlarm();
- struct timeval tv_timeToDelay;
- tv_timeToDelay.tv_sec = timeToDelay.seconds();
- tv_timeToDelay.tv_usec = timeToDelay.useconds();
- // Very large "tv_sec" values cause select() to fail.
- // Don't make it any larger than 1 million seconds (11.5 days)
- const long MAX_TV_SEC = MILLION;
- if (tv_timeToDelay.tv_sec > MAX_TV_SEC) {
- tv_timeToDelay.tv_sec = MAX_TV_SEC;
- }
- // Also check our "maxDelayTime" parameter (if it's > 0):
- if (maxDelayTime > 0
- && (tv_timeToDelay.tv_sec > (long) maxDelayTime / MILLION
- || (tv_timeToDelay.tv_sec == (long) maxDelayTime / MILLION
- && tv_timeToDelay.tv_usec
- > (long) maxDelayTime % MILLION))) {
- tv_timeToDelay.tv_sec = maxDelayTime / MILLION;
- tv_timeToDelay.tv_usec = maxDelayTime % MILLION;
- }
- //先执行socket的select操作,以确定哪些socket任务(handler)需要执行。
- int selectResult = select(fMaxNumSockets,
- &readSet, &writeSet,&exceptionSet,
- &tv_timeToDelay);
- if (selectResult < 0) {
- //#if defined(__WIN32__) || defined(_WIN32)
- int err = WSAGetLastError();
- // For some unknown reason, select() in Windoze sometimes fails with WSAEINVAL if
- // it was called with no entries set in "readSet". If this happens, ignore it:
- if (err == WSAEINVAL && readSet.fd_count == 0) {
- err = EINTR;
- // To stop this from happening again, create a dummy socket:
- int dummySocketNum = socket(AF_INET, SOCK_DGRAM, 0);
- FD_SET((unsigned) dummySocketNum, &fReadSet);
- }
- if (err != EINTR) {
- //#else
- // if (errno != EINTR && errno != EAGAIN) {
- //#endif
- // Unexpected error - treat this as fatal:
- //#if !defined(_WIN32_WCE)
- // perror("BasicTaskScheduler::SingleStep(): select() fails");
- //#endif
- internalError();
- }
- }
- // Call the handler function for one readable socket:
- HandlerIterator iter(*fHandlers);
- HandlerDescriptor* handler;
- // To ensure forward progress through the handlers, begin past the last
- // socket number that we handled:
- if (fLastHandledSocketNum >= 0) {
- //找到上次执行的socket handler的下一个
- while ((handler = iter.next()) != NULL) {
- if (handler->socketNum == fLastHandledSocketNum)
- break;
- }
- if (handler == NULL) {
- fLastHandledSocketNum = -1;
- iter.reset(); // start from the beginning instead
- }
- }
- //从找到的handler开始,找一个可以执行的handler,不论其状态是可读,可写,还是出错,执行之。
- while ((handler = iter.next()) != NULL) {
- int sock = handler->socketNum; // alias
- int resultConditionSet = 0;
- if (FD_ISSET(sock, &readSet)
- && FD_ISSET(sock, &fReadSet)/*sanity check*/)
- resultConditionSet |= SOCKET_READABLE;
- if (FD_ISSET(sock, &writeSet)
- && FD_ISSET(sock, &fWriteSet)/*sanity check*/)
- resultConditionSet |= SOCKET_WRITABLE;
- if (FD_ISSET(sock, &exceptionSet)
- && FD_ISSET(sock, &fExceptionSet)/*sanity check*/)
- resultConditionSet |= SOCKET_EXCEPTION;
- if ((resultConditionSet & handler->conditionSet)
- != 0 && handler->handlerProc != NULL) {
- fLastHandledSocketNum = sock;
- // Note: we set "fLastHandledSocketNum" before calling the handler,
- // in case the handler calls "doEventLoop()" reentrantly.
- (*handler->handlerProc)(handler->clientData, resultConditionSet);
- break;
- }
- }
- //如果寻找完了依然没有执行任何handle,则从头再找。
- if (handler == NULL && fLastHandledSocketNum >= 0) {
- // We didn't call a handler, but we didn't get to check all of them,
- // so try again from the beginning:
- iter.reset();
- while ((handler = iter.next()) != NULL) {
- int sock = handler->socketNum; // alias
- int resultConditionSet = 0;
- if (FD_ISSET(sock, &readSet)&& FD_ISSET(sock, &fReadSet)/*sanity check*/)
- resultConditionSet |= SOCKET_READABLE;
- if (FD_ISSET(sock, &writeSet)&& FD_ISSET(sock, &fWriteSet)/*sanity check*/)
- resultConditionSet |= SOCKET_WRITABLE;
- if (FD_ISSET(sock, &exceptionSet) && FD_ISSET(sock, &fExceptionSet)/*sanity check*/)
- resultConditionSet |= SOCKET_EXCEPTION;
- if ((resultConditionSet & handler->conditionSet)
- != 0 && handler->handlerProc != NULL) {
- fLastHandledSocketNum = sock;
- // Note: we set "fLastHandledSocketNum" before calling the handler,
- // in case the handler calls "doEventLoop()" reentrantly.
- (*handler->handlerProc)(handler->clientData, resultConditionSet);
- break;
- }
- }
- //依然没有找到可执行的handler。
- if (handler == NULL)
- fLastHandledSocketNum = -1; //because we didn't call a handler
- }
- //响应事件
- // Also handle any newly-triggered event
- // (Note that we do this *after* calling a socket handler,
- // in case the triggered event handler modifies The set of readable sockets.)
- if (fTriggersAwaitingHandling != 0) {
- if (fTriggersAwaitingHandling == fLastUsedTriggerMask) {
- // Common-case optimization for a single event trigger:
- fTriggersAwaitingHandling = 0;
- if (fTriggeredEventHandlers[fLastUsedTriggerNum] != NULL) {
- //执行一个事件处理函数
- (*fTriggeredEventHandlers[fLastUsedTriggerNum])(fTriggeredEventClientDatas[fLastUsedTriggerNum]);
- }
- } else {
- // Look for an event trigger that needs handling
- // (making sure that we make forward progress through all possible triggers):
- unsigned i = fLastUsedTriggerNum;
- EventTriggerId mask = fLastUsedTriggerMask;
- do {
- i = (i + 1) % MAX_NUM_EVENT_TRIGGERS;
- mask >>= 1;
- if (mask == 0)
- mask = 0x80000000;
- if ((fTriggersAwaitingHandling & mask) != 0) {
- //执行一个事件响应
- fTriggersAwaitingHandling &= ~mask;
- if (fTriggeredEventHandlers[i] != NULL) {
- (*fTriggeredEventHandlers[i])(fTriggeredEventClientDatas[i]);
- }
- fLastUsedTriggerMask = mask;
- fLastUsedTriggerNum = i;
- break;
- }
- } while (i != fLastUsedTriggerNum);
- }
- }
- //执行一个最迫切的延迟任务。
- // Also handle any delayed event that may have come due.
- fDelayQueue.handleAlarm();
- }
四 计划任务(TaskScheduler)深入探讨
我们且把三种任务命名为:socket handler,event handler,delay task。
这三种任务的特点是,前两个加入执行队列后会一直存在,而delay task在执行完一次后会立即弃掉。
socket handler保存在队列BasicTaskScheduler0::HandlerSet* fHandlers中;
event handler保存在数组BasicTaskScheduler0::TaskFunc * fTriggeredEventHandlers[MAX_NUM_EVENT_TRIGGERS]中;
delay task保存在队列BasicTaskScheduler0::DelayQueue fDelayQueue中。
下面看一下三种任务的执行函数的定义:
socket handler为
typedef void BackgroundHandlerProc(void* clientData, int mask);
event handler为
typedef void TaskFunc(void* clientData);
delay task 为
typedef void TaskFunc(void* clientData);//跟event handler一样。
再看一下向任务调度对象添加三种任务的函数的样子:
delay task为:
void setBackgroundHandling(int socketNum, int conditionSet ,BackgroundHandlerProc* handlerProc, void* clientData)
event handler为:
EventTriggerId createEventTrigger(TaskFunc* eventHandlerProc)
delay task为:
TaskToken scheduleDelayedTask(int64_t microseconds, TaskFunc* proc,void* clientData)
socket handler添加时为什么需要那些参数呢?socketNum是需要的,因为要select socket(socketNum即是socket()返回的那个socket对象)。conditionSet也是需要的,它用于表明socket在select时查看哪种装态,是可读?可写?还是出错?proc和clientData这两个参数就不必说了(真有不明白的吗?)。再看BackgroundHandlerProc的参数,socketNum不必解释,mask是什么呢?它正是对应着conditionSet,但它表明的是select之后的结果,比如一个socket可能需要检查其读/写状态,而当前只能读,不能写,那么mask中就只有表明读的位被设置。
event handler是被存在数组中。数组大小固定,是32项,用EventTriggerId来表示数组中的项,EventTriggerId是一个32位整数,因为数组是32项,所以用EventTriggerId中的第n位置1表明对应数组中的第n项。成员变量fTriggersAwaitingHandling也是EventTriggerId类型,它里面置1的那些位对应了数组中所有需要处理的项。这样做节省了内存和计算,但降低了可读性,呵呵,而且也不够灵活,只能支持32项或64项,其它数量不被支持。以下是函数体
- EventTriggerId BasicTaskScheduler0::createEventTrigger( TaskFunc* eventHandlerProc)
- {
- unsigned i = fLastUsedTriggerNum;
- EventTriggerId mask = fLastUsedTriggerMask;
- //在数组中寻找一个未使用的项,把eventHandlerProc分配到这一项。
- do {
- i = (i + 1) % MAX_NUM_EVENT_TRIGGERS;
- mask >>= 1;
- if (mask == 0)
- mask = 0x80000000;
- if (fTriggeredEventHandlers[i] == NULL) {
- // This trigger number is free; use it:
- fTriggeredEventHandlers[i] = eventHandlerProc;
- fTriggeredEventClientDatas[i] = NULL; // sanity
- fLastUsedTriggerMask = mask;
- fLastUsedTriggerNum = i;
- return mask; //分配成功,返回值表面了第几项
- }
- } while (i != fLastUsedTriggerNum);//表明在数组中循环一圈
- //数组中的所有项都被占用,返回表明失败。
- // All available event triggers are allocated; return 0 instead:
- return 0;
- }
EventTriggerId BasicTaskScheduler0::createEventTrigger( TaskFunc* eventHandlerProc)
{
unsigned i = fLastUsedTriggerNum;
EventTriggerId mask = fLastUsedTriggerMask;
//在数组中寻找一个未使用的项,把eventHandlerProc分配到这一项。
do {
i = (i + 1) % MAX_NUM_EVENT_TRIGGERS;
mask >>= 1;
if (mask == 0)
mask = 0x80000000;
if (fTriggeredEventHandlers[i] == NULL) {
// This trigger number is free; use it:
fTriggeredEventHandlers[i] = eventHandlerProc;
fTriggeredEventClientDatas[i] = NULL; // sanity
fLastUsedTriggerMask = mask;
fLastUsedTriggerNum = i;
return mask; //分配成功,返回值表面了第几项
}
} while (i != fLastUsedTriggerNum);//表明在数组中循环一圈
//数组中的所有项都被占用,返回表明失败。
// All available event triggers are allocated; return 0 instead:
return 0;
}
可以看到最多添加32个事件,且添加事件时没有传入clientData参数。这个参数在触发事件时传入,见以下函数:
- void BasicTaskScheduler0::triggerEvent(EventTriggerId eventTriggerId,void* clientData)
- {
- // First, record the "clientData":
- if (eventTriggerId == fLastUsedTriggerMask) {
- // common-case optimization:直接保存下clientData
- fTriggeredEventClientDatas[fLastUsedTriggerNum] = clientData;
- } else {
- //从头到尾查找eventTriggerId对应的项,保存下clientData
- EventTriggerId mask = 0x80000000;
- for (unsigned i = 0; i < MAX_NUM_EVENT_TRIGGERS; ++i) {
- if ((eventTriggerId & mask) != 0) {
- fTriggeredEventClientDatas[i] = clientData;
- fLastUsedTriggerMask = mask;
- fLastUsedTriggerNum = i;
- }
- mask >>= 1;
- }
- }
- // Then, note this event as being ready to be handled.
- // (Note that because this function (unlike others in the library)
- // can be called from an external thread, we do this last, to
- // reduce the risk of a race condition.)
- //利用fTriggersAwaitingHandling以bit mask的方式记录需要响应的事件handler们。
- fTriggersAwaitingHandling |= eventTriggerId;
- }
void BasicTaskScheduler0::triggerEvent(EventTriggerId eventTriggerId,void* clientData)
{
// First, record the "clientData":
if (eventTriggerId == fLastUsedTriggerMask) {
// common-case optimization:直接保存下clientData
fTriggeredEventClientDatas[fLastUsedTriggerNum] = clientData;
} else {
//从头到尾查找eventTriggerId对应的项,保存下clientData
EventTriggerId mask = 0x80000000;
for (unsigned i = 0; i < MAX_NUM_EVENT_TRIGGERS; ++i) {
if ((eventTriggerId & mask) != 0) {
fTriggeredEventClientDatas[i] = clientData;
fLastUsedTriggerMask = mask;
fLastUsedTriggerNum = i;
}
mask >>= 1;
}
}
// Then, note this event as being ready to be handled.
// (Note that because this function (unlike others in the library)
// can be called from an external thread, we do this last, to
// reduce the risk of a race condition.)
//利用fTriggersAwaitingHandling以bit mask的方式记录需要响应的事件handler们。
fTriggersAwaitingHandling |= eventTriggerId;
}
看,clientData被传入了,这表明clientData在每次触发事件时是可以变的。
此时再回去看SingleStep()是不是更明了了?
delay task添加时,需要传入task延迟等待的微秒(百万分之一秒)数(第一个参数),这个弱智也可以理解吧?嘿嘿。分析一下介个函数:
- TaskToken BasicTaskScheduler0::scheduleDelayedTask(int64_t microseconds,TaskFunc* proc, void* clientData)
- {
- if (microseconds < 0)
- microseconds = 0;
- //DelayInterval 是表示时间差的结构
- DelayInterval timeToDelay((long) (microseconds / 1000000),(long) (microseconds % 1000000));
- //创建delayQueue中的一项
- AlarmHandler* alarmHandler = new AlarmHandler(proc, clientData,timeToDelay);
- //加入DelayQueue
- fDelayQueue.addEntry(alarmHandler);
- //返回delay task的唯一标志
- return (void*) (alarmHandler->token());
- }
- delay task的执行都在函数fDelayQueue.handleAlarm()中,handleAlarm()在类DelayQueue中实现。看一下handleAlarm():
- void DelayQueue::handleAlarm()
- {
- //如果第一个任务的执行时间未到,则同步一下(重新计算各任务的等待时间)。
- if (head()->fDeltaTimeRemaining != DELAY_ZERO)
- synchronize();
- //如果第一个任务的执行时间到了,则执行第一个,并把它从队列中删掉。
- if (head()->fDeltaTimeRemaining == DELAY_ZERO) {
- // This event is due to be handled:
- DelayQueueEntry* toRemove = head();
- removeEntry(toRemove); // do this first, in case handler accesses queue
- //执行任务,执行完后会把这一项销毁。
- toRemove->handleTimeout();
- }
- }
TaskToken BasicTaskScheduler0::scheduleDelayedTask(int64_t microseconds,TaskFunc* proc, void* clientData)
{
if (microseconds < 0)
microseconds = 0;
//DelayInterval 是表示时间差的结构
DelayInterval timeToDelay((long) (microseconds / 1000000),(long) (microseconds % 1000000));
//创建delayQueue中的一项
AlarmHandler* alarmHandler = new AlarmHandler(proc, clientData,timeToDelay);
//加入DelayQueue
fDelayQueue.addEntry(alarmHandler);
//返回delay task的唯一标志
return (void*) (alarmHandler->token());
}
delay task的执行都在函数fDelayQueue.handleAlarm()中,handleAlarm()在类DelayQueue中实现。看一下handleAlarm():
void DelayQueue::handleAlarm()
{
//如果第一个任务的执行时间未到,则同步一下(重新计算各任务的等待时间)。
if (head()->fDeltaTimeRemaining != DELAY_ZERO)
synchronize();
//如果第一个任务的执行时间到了,则执行第一个,并把它从队列中删掉。
if (head()->fDeltaTimeRemaining == DELAY_ZERO) {
// This event is due to be handled:
DelayQueueEntry* toRemove = head();
removeEntry(toRemove); // do this first, in case handler accesses queue
//执行任务,执行完后会把这一项销毁。
toRemove->handleTimeout();
}
}
可能感觉奇怪,其它的任务队列都是先搜索第一个应该执行的项,然后再执行,这里干脆,直接执行第一个完事。那就说明第一个就是最应该执行的一个吧?也就是等待时间最短的一个吧?那么应该在添加任务时,将新任务跟据其等待时间插入到适当的位置而不是追加到尾巴上吧?猜得对不对还得看fDelayQueue.addEntry(alarmHandler)这个函数是怎么执行的。
- void DelayQueue::addEntry(DelayQueueEntry* newEntry)
- {
- //重新计算各项的等待时间
- synchronize();
- //取得第一项
- DelayQueueEntry* cur = head();
- //从头至尾循环中将新项与各项的等待时间进行比较
- while (newEntry->fDeltaTimeRemaining >= cur->fDeltaTimeRemaining) {
- //如果新项等待时间长于当前项的等待时间,则减掉当前项的等待时间。
- //也就是后面的等待时几只是与前面项等待时间的差,这样省掉了记录插入时的时间的变量。
- newEntry->fDeltaTimeRemaining -= cur->fDeltaTimeRemaining;
- //下一项
- cur = cur->fNext;
- }
- //循环完毕,cur就是找到的应插它前面的项,那就插它前面吧
- cur->fDeltaTimeRemaining -= newEntry->fDeltaTimeRemaining;
- // Add "newEntry" to the queue, just before "cur":
- newEntry->fNext = cur;
- newEntry->fPrev = cur->fPrev;
- cur->fPrev = newEntry->fPrev->fNext = newEntry;
- }
void DelayQueue::addEntry(DelayQueueEntry* newEntry)
{
//重新计算各项的等待时间
synchronize();
//取得第一项
DelayQueueEntry* cur = head();
//从头至尾循环中将新项与各项的等待时间进行比较
while (newEntry->fDeltaTimeRemaining >= cur->fDeltaTimeRemaining) {
//如果新项等待时间长于当前项的等待时间,则减掉当前项的等待时间。
//也就是后面的等待时几只是与前面项等待时间的差,这样省掉了记录插入时的时间的变量。
newEntry->fDeltaTimeRemaining -= cur->fDeltaTimeRemaining;
//下一项
cur = cur->fNext;
}
//循环完毕,cur就是找到的应插它前面的项,那就插它前面吧
cur->fDeltaTimeRemaining -= newEntry->fDeltaTimeRemaining;
// Add "newEntry" to the queue, just before "cur":
newEntry->fNext = cur;
newEntry->fPrev = cur->fPrev;
cur->fPrev = newEntry->fPrev->fNext = newEntry;
}
有个问题,while循环中为什么没有判断是否到达最后一下的代码呢?难道肯定能找到大于新项的等待时间的项吗?是的!第一个加入项的等待时间是无穷大的,而且这一项永远存在于队列中。
五 RTSP服务运作
基础基本搞明白了,那么RTSP,RTP等这些协议又是如何利用这些基础机制运作的呢?
首先来看RTSP.
RTSP首先需建立TCP侦听socket。可见于此函数:
- DynamicRTSPServer* DynamicRTSPServer::createNew(UsageEnvironment& env, Port ourPort,
- UserAuthenticationDatabase* authDatabase,
- unsigned reclamationTestSeconds) {
- int ourSocket = setUpOurSocket(env, ourPort); //建立TCP socket
- if (ourSocket == -1)
- return NULL;
- return new DynamicRTSPServer(env, ourSocket, ourPort, authDatabase,
- reclamationTestSeconds);
- }
DynamicRTSPServer* DynamicRTSPServer::createNew(UsageEnvironment& env, Port ourPort,
UserAuthenticationDatabase* authDatabase,
unsigned reclamationTestSeconds) {
int ourSocket = setUpOurSocket(env, ourPort); //建立TCP socket
if (ourSocket == -1)
return NULL;
return new DynamicRTSPServer(env, ourSocket, ourPort, authDatabase,
reclamationTestSeconds);
}
要帧听客户端的连接,就需要利用任务调度机制了,所以需添加一个socket handler。可见于此函数:
- RTSPServer::RTSPServer(UsageEnvironment& env,
- int ourSocket,
- Port ourPort,
- UserAuthenticationDatabase* authDatabase,
- unsigned reclamationTestSeconds) :
- Medium(env),
- fRTSPServerSocket(ourSocket),
- fRTSPServerPort(ourPort),
- fHTTPServerSocket(-1),
- fHTTPServerPort(0),
- fClientSessionsForHTTPTunneling(NULL),
- fAuthDB(authDatabase),
- fReclamationTestSeconds(reclamationTestSeconds),
- fServerMediaSessions(HashTable::create(STRING_HASH_KEYS))
- {
- #ifdef USE_SIGNALS
- // Ignore the SIGPIPE signal, so that clients on the same host that are killed
- // don't also kill us:
- signal(SIGPIPE, SIG_IGN);
- #endif
- // Arrange to handle connections from others:
- env.taskScheduler().turnOnBackgroundReadHandling(
- fRTSPServerSocket,
- (TaskScheduler::BackgroundHandlerProc*) &incomingConnectionHandlerRTSP,
- this);
- }
RTSPServer::RTSPServer(UsageEnvironment& env,
int ourSocket,
Port ourPort,
UserAuthenticationDatabase* authDatabase,
unsigned reclamationTestSeconds) :
Medium(env),
fRTSPServerSocket(ourSocket),
fRTSPServerPort(ourPort),
fHTTPServerSocket(-1),
fHTTPServerPort(0),
fClientSessionsForHTTPTunneling(NULL),
fAuthDB(authDatabase),
fReclamationTestSeconds(reclamationTestSeconds),
fServerMediaSessions(HashTable::create(STRING_HASH_KEYS))
{
#ifdef USE_SIGNALS
// Ignore the SIGPIPE signal, so that clients on the same host that are killed
// don't also kill us:
signal(SIGPIPE, SIG_IGN);
#endif
// Arrange to handle connections from others:
env.taskScheduler().turnOnBackgroundReadHandling(
fRTSPServerSocket,
(TaskScheduler::BackgroundHandlerProc*) &incomingConnectionHandlerRTSP,
this);
}
当收到客户的连接时需保存下代表客户端的新socket,以后用这个socket与这个客户通讯。每个客户将来会对应一个rtp会话,而且各客户的RTSP请求只控制自己的rtp会话,那么最好建立一个会话类,代表各客户的rtsp会话。于是类RTSPServer::RTSPClientSession产生,它保存的代表客户的socket。下为RTSPClientSession的创建过程
- void RTSPServer::incomingConnectionHandler(int serverSocket)
- {
- struct sockaddr_in clientAddr;
- SOCKLEN_T clientAddrLen = sizeof clientAddr;
- //接受连接
- int clientSocket = accept(serverSocket,
- (struct sockaddr*) &clientAddr,
- &clientAddrLen);
- if (clientSocket < 0) {
- int err = envir().getErrno();
- if (err != EWOULDBLOCK) {
- envir().setResultErrMsg("accept() failed: ");
- }
- return;
- }
- //设置socket的参数
- makeSocketNonBlocking(clientSocket);
- increaseSendBufferTo(envir(), clientSocket, 50 * 1024);
- #ifdef DEBUG
- envir() << "accept()ed connection from " << our_inet_ntoa(clientAddr.sin_addr) << "\n";
- #endif
- //产生一个sesson id
- // Create a new object for this RTSP session.
- // (Choose a random 32-bit integer for the session id (it will be encoded as a 8-digit hex number). We don't bother checking for
- // a collision; the probability of two concurrent sessions getting the same session id is very low.)
- // (We do, however, avoid choosing session id 0, because that has a special use (by "OnDemandServerMediaSubsession").)
- unsigned sessionId;
- do {
- sessionId = (unsigned) our_random();
- } while (sessionId == 0);
- //创建RTSPClientSession,注意传入的参数
- (void) createNewClientSession(sessionId, clientSocket, clientAddr);
- }
void RTSPServer::incomingConnectionHandler(int serverSocket)
{
struct sockaddr_in clientAddr;
SOCKLEN_T clientAddrLen = sizeof clientAddr;
//接受连接
int clientSocket = accept(serverSocket,
(struct sockaddr*) &clientAddr,
&clientAddrLen);
if (clientSocket < 0) {
int err = envir().getErrno();
if (err != EWOULDBLOCK) {
envir().setResultErrMsg("accept() failed: ");
}
return;
}
//设置socket的参数
makeSocketNonBlocking(clientSocket);
increaseSendBufferTo(envir(), clientSocket, 50 * 1024);
#ifdef DEBUG
envir() << "accept()ed connection from " << our_inet_ntoa(clientAddr.sin_addr) << "\n";
#endif
//产生一个sesson id
// Create a new object for this RTSP session.
// (Choose a random 32-bit integer for the session id (it will be encoded as a 8-digit hex number). We don't bother checking for
// a collision; the probability of two concurrent sessions getting the same session id is very low.)
// (We do, however, avoid choosing session id 0, because that has a special use (by "OnDemandServerMediaSubsession").)
unsigned sessionId;
do {
sessionId = (unsigned) our_random();
} while (sessionId == 0);
//创建RTSPClientSession,注意传入的参数
(void) createNewClientSession(sessionId, clientSocket, clientAddr);
}
RTSPClientSession要提供什么功能呢?可以想象:需要监听客户端的rtsp请求并回应它,需要在DESCRIBE请求中返回所请求的流的信息,需要在SETUP请求中建立起RTP会话,需要在TEARDOWN请求中关闭RTP会话,等等...
RTSPClientSession要侦听客户端的请求,就需把自己的socket handler加入计划任务。证据如下:
- RTSPServer::RTSPClientSession::RTSPClientSession(
- RTSPServer& ourServer,
- unsigned sessionId,
- int clientSocket,
- struct sockaddr_in clientAddr) :
- fOurServer(ourServer),
- fOurSessionId(sessionId),
- fOurServerMediaSession(NULL),
- fClientInputSocket(clientSocket),
- fClientOutputSocket(clientSocket),
- fClientAddr(clientAddr),
- fSessionCookie(NULL),
- fLivenessCheckTask(NULL),
- fIsMulticast(False),
- fSessionIsActive(True),
- fStreamAfterSETUP(False),
- fTCPStreamIdCount(0),
- fNumStreamStates(0),
- fStreamStates(NULL),
- fRecursionCount(0)
- {
- // Arrange to handle incoming requests:
- resetRequestBuffer();
- envir().taskScheduler().turnOnBackgroundReadHandling(fClientInputSocket,
- (TaskScheduler::BackgroundHandlerProc*) &incomingRequestHandler,
- this);
- noteLiveness();
- }
RTSPServer::RTSPClientSession::RTSPClientSession(
RTSPServer& ourServer,
unsigned sessionId,
int clientSocket,
struct sockaddr_in clientAddr) :
fOurServer(ourServer),
fOurSessionId(sessionId),
fOurServerMediaSession(NULL),
fClientInputSocket(clientSocket),
fClientOutputSocket(clientSocket),
fClientAddr(clientAddr),
fSessionCookie(NULL),
fLivenessCheckTask(NULL),
fIsMulticast(False),
fSessionIsActive(True),
fStreamAfterSETUP(False),
fTCPStreamIdCount(0),
fNumStreamStates(0),
fStreamStates(NULL),
fRecursionCount(0)
{
// Arrange to handle incoming requests:
resetRequestBuffer();
envir().taskScheduler().turnOnBackgroundReadHandling(fClientInputSocket,
(TaskScheduler::BackgroundHandlerProc*) &incomingRequestHandler,
this);
noteLiveness();
}
下面重点讲一下下RTSPClientSession响应DESCRIBE请求的过程:
- void RTSPServer::RTSPClientSession::handleCmd_DESCRIBE(
- char const* cseq,
- char const* urlPreSuffix,
- char const* urlSuffix,
- char const* fullRequestStr)
- {
- char* sdpDescription = NULL;
- char* rtspURL = NULL;
- do {
- //整理一下下RTSP地址
- char urlTotalSuffix[RTSP_PARAM_STRING_MAX];
- if (strlen(urlPreSuffix) + strlen(urlSuffix) + 2
- > sizeof urlTotalSuffix) {
- handleCmd_bad(cseq);
- break;
- }
- urlTotalSuffix[0] = '\0';
- if (urlPreSuffix[0] != '\0') {
- strcat(urlTotalSuffix, urlPreSuffix);
- strcat(urlTotalSuffix, "/");
- }
- strcat(urlTotalSuffix, urlSuffix);
- //验证帐户和密码
- if (!authenticationOK("DESCRIBE", cseq, urlTotalSuffix, fullRequestStr))
- break;
- // We should really check that the request contains an "Accept:" #####
- // for "application/sdp", because that's what we're sending back #####
- // Begin by looking up the "ServerMediaSession" object for the specified "urlTotalSuffix":
- //跟据流的名字查找ServerMediaSession,如果找不到,会创建一个。每个ServerMediaSession中至少要包含一个
- //ServerMediaSubsession。一个ServerMediaSession对应一个媒体,可以认为是Server上的一个文件,或一个实时获取设备。其包含的每个ServerMediaSubSession代表媒体中的一个Track。所以一个ServerMediaSession对应一个媒体,如果客户请求的媒体名相同,就使用已存在的ServerMediaSession,如果不同,就创建一个新的。一个流对应一个StreamState,StreamState与ServerMediaSubsession相关,但代表的是动态的,而ServerMediaSubsession代表静态的。
- ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlTotalSuffix);
- if (session == NULL) {
- handleCmd_notFound(cseq);
- break;
- }
- // Then, assemble a SDP description for this session:
- //获取SDP字符串,在函数内会依次获取每个ServerMediaSubSession的字符串然连接起来。
- sdpDescription = session->generateSDPDescription();
- if (sdpDescription == NULL) {
- // This usually means that a file name that was specified for a
- // "ServerMediaSubsession" does not exist.
- snprintf((char*) fResponseBuffer, sizeof fResponseBuffer,
- "RTSP/1.0 404 File Not Found, Or In Incorrect Format\r\n"
- "CSeq: %s\r\n"
- "%s\r\n", cseq, dateHeader());
- break;
- }
- unsigned sdpDescriptionSize = strlen(sdpDescription);
- // Also, generate our RTSP URL, for the "Content-Base:" header
- // (which is necessary to ensure that the correct URL gets used in
- // subsequent "SETUP" requests).
- rtspURL = fOurServer.rtspURL(session, fClientInputSocket);
- //形成响应DESCRIBE请求的RTSP字符串。
- snprintf((char*) fResponseBuffer, sizeof fResponseBuffer,
- "RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
- "%s"
- "Content-Base: %s/\r\n"
- "Content-Type: application/sdp\r\n"
- "Content-Length: %d\r\n\r\n"
- "%s", cseq, dateHeader(), rtspURL, sdpDescriptionSize,
- sdpDescription);
- } while (0);
- delete[] sdpDescription;
- delete[] rtspURL;
- //返回后会被立即发送(没有把socket write操作放入计划任务中)。
- }
void RTSPServer::RTSPClientSession::handleCmd_DESCRIBE(
char const* cseq,
char const* urlPreSuffix,
char const* urlSuffix,
char const* fullRequestStr)
{
char* sdpDescription = NULL;
char* rtspURL = NULL;
do {
//整理一下下RTSP地址
char urlTotalSuffix[RTSP_PARAM_STRING_MAX];
if (strlen(urlPreSuffix) + strlen(urlSuffix) + 2
> sizeof urlTotalSuffix) {
handleCmd_bad(cseq);
break;
}
urlTotalSuffix[0] = '\0';
if (urlPreSuffix[0] != '\0') {
strcat(urlTotalSuffix, urlPreSuffix);
strcat(urlTotalSuffix, "/");
}
strcat(urlTotalSuffix, urlSuffix);
//验证帐户和密码
if (!authenticationOK("DESCRIBE", cseq, urlTotalSuffix, fullRequestStr))
break;
// We should really check that the request contains an "Accept:" #####
// for "application/sdp", because that's what we're sending back #####
// Begin by looking up the "ServerMediaSession" object for the specified "urlTotalSuffix":
//跟据流的名字查找ServerMediaSession,如果找不到,会创建一个。每个ServerMediaSession中至少要包含一个
//ServerMediaSubsession。一个ServerMediaSession对应一个媒体,可以认为是Server上的一个文件,或一个实时获取设备。其包含的每个ServerMediaSubSession代表媒体中的一个Track。所以一个ServerMediaSession对应一个媒体,如果客户请求的媒体名相同,就使用已存在的ServerMediaSession,如果不同,就创建一个新的。一个流对应一个StreamState,StreamState与ServerMediaSubsession相关,但代表的是动态的,而ServerMediaSubsession代表静态的。
ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlTotalSuffix);
if (session == NULL) {
handleCmd_notFound(cseq);
break;
}
// Then, assemble a SDP description for this session:
//获取SDP字符串,在函数内会依次获取每个ServerMediaSubSession的字符串然连接起来。
sdpDescription = session->generateSDPDescription();
if (sdpDescription == NULL) {
// This usually means that a file name that was specified for a
// "ServerMediaSubsession" does not exist.
snprintf((char*) fResponseBuffer, sizeof fResponseBuffer,
"RTSP/1.0 404 File Not Found, Or In Incorrect Format\r\n"
"CSeq: %s\r\n"
"%s\r\n", cseq, dateHeader());
break;
}
unsigned sdpDescriptionSize = strlen(sdpDescription);
// Also, generate our RTSP URL, for the "Content-Base:" header
// (which is necessary to ensure that the correct URL gets used in
// subsequent "SETUP" requests).
rtspURL = fOurServer.rtspURL(session, fClientInputSocket);
//形成响应DESCRIBE请求的RTSP字符串。
snprintf((char*) fResponseBuffer, sizeof fResponseBuffer,
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
"%s"
"Content-Base: %s/\r\n"
"Content-Type: application/sdp\r\n"
"Content-Length: %d\r\n\r\n"
"%s", cseq, dateHeader(), rtspURL, sdpDescriptionSize,
sdpDescription);
} while (0);
delete[] sdpDescription;
delete[] rtspURL;
//返回后会被立即发送(没有把socket write操作放入计划任务中)。
}
fOurServer.lookupServerMediaSession(urlTotalSuffix)中会在找不到同名ServerMediaSession时新建一个,代表一个RTP流的ServerMediaSession们是被RTSPServer管理的,而不是被RTSPClientSession拥有。为什么呢?因为ServerMediaSession代表的是一个静态的流,也就是可以从它里面获取一个流的各种信息,但不能获取传输状态。不同客户可能连接到同一个流,所以ServerMediaSession应被RTSPServer所拥有。创建一个ServerMediaSession过程值得一观:
- static ServerMediaSession* createNewSMS(UsageEnvironment& env,char const* fileName, FILE* /*fid*/)
- {
- // Use the file name extension to determine the type of "ServerMediaSession":
- char const* extension = strrchr(fileName, '.');
- if (extension == NULL)
- return NULL;
- ServerMediaSession* sms = NULL;
- Boolean const reuseSource = False;
- if (strcmp(extension, ".aac") == 0) {
- // Assumed to be an AAC Audio (ADTS format) file:
- NEW_SMS("AAC Audio");
- sms->addSubsession(
- ADTSAudioFileServerMediaSubsession::createNew(env, fileName,
- reuseSource));
- } else if (strcmp(extension, ".amr") == 0) {
- // Assumed to be an AMR Audio file:
- NEW_SMS("AMR Audio");
- sms->addSubsession(
- AMRAudioFileServerMediaSubsession::createNew(env, fileName,
- reuseSource));
- } else if (strcmp(extension, ".ac3") == 0) {
- // Assumed to be an AC-3 Audio file:
- NEW_SMS("AC-3 Audio");
- sms->addSubsession(
- AC3AudioFileServerMediaSubsession::createNew(env, fileName,
- reuseSource));
- } else if (strcmp(extension, ".m4e") == 0) {
- // Assumed to be a MPEG-4 Video Elementary Stream file:
- NEW_SMS("MPEG-4 Video");
- sms->addSubsession(
- MPEG4VideoFileServerMediaSubsession::createNew(env, fileName,
- reuseSource));
- } else if (strcmp(extension, ".264") == 0) {
- // Assumed to be a H.264 Video Elementary Stream file:
- NEW_SMS("H.264 Video");
- OutPacketBuffer::maxSize = 100000; // allow for some possibly large H.264 frames
- sms->addSubsession(
- H264VideoFileServerMediaSubsession::createNew(env, fileName,
- reuseSource));
- } else if (strcmp(extension, ".mp3") == 0) {
- // Assumed to be a MPEG-1 or 2 Audio file:
- NEW_SMS("MPEG-1 or 2 Audio");
- // To stream using 'ADUs' rather than raw MP3 frames, uncomment the following:
- //#define STREAM_USING_ADUS 1
- // To also reorder ADUs before streaming, uncomment the following:
- //#define INTERLEAVE_ADUS 1
- // (For more information about ADUs and interleaving,
- // see <http://www.live555.com/rtp-mp3/>)
- Boolean useADUs = False;
- Interleaving* interleaving = NULL;
- #ifdef STREAM_USING_ADUS
- useADUs = True;
- #ifdef INTERLEAVE_ADUS
- unsigned char interleaveCycle[] = {0,2,1,3}; // or choose your own...
- unsigned const interleaveCycleSize
- = (sizeof interleaveCycle)/(sizeof (unsigned char));
- interleaving = new Interleaving(interleaveCycleSize, interleaveCycle);
- #endif
- #endif
- sms->addSubsession(
- MP3AudioFileServerMediaSubsession::createNew(env, fileName,
- reuseSource, useADUs, interleaving));
- } else if (strcmp(extension, ".mpg") == 0) {
- // Assumed to be a MPEG-1 or 2 Program Stream (audio+video) file:
- NEW_SMS("MPEG-1 or 2 Program Stream");
- MPEG1or2FileServerDemux* demux = MPEG1or2FileServerDemux::createNew(env,
- fileName, reuseSource);
- sms->addSubsession(demux->newVideoServerMediaSubsession());
- sms->addSubsession(demux->newAudioServerMediaSubsession());
- } else if (strcmp(extension, ".ts") == 0) {
- // Assumed to be a MPEG Transport Stream file:
- // Use an index file name that's the same as the TS file name, except with ".tsx":
- unsigned indexFileNameLen = strlen(fileName) + 2; // allow for trailing "x\0"
- char* indexFileName = new char[indexFileNameLen];
- sprintf(indexFileName, "%sx", fileName);
- NEW_SMS("MPEG Transport Stream");
- sms->addSubsession(
- MPEG2TransportFileServerMediaSubsession::createNew(env,
- fileName, indexFileName, reuseSource));
- delete[] indexFileName;
- } else if (strcmp(extension, ".wav") == 0) {
- // Assumed to be a WAV Audio file:
- NEW_SMS("WAV Audio Stream");
- // To convert 16-bit PCM data to 8-bit u-law, prior to streaming,
- // change the following to True:
- Boolean convertToULaw = False;
- sms->addSubsession(
- WAVAudioFileServerMediaSubsession::createNew(env, fileName,
- reuseSource, convertToULaw));
- } else if (strcmp(extension, ".dv") == 0) {
- // Assumed to be a DV Video file
- // First, make sure that the RTPSinks' buffers will be large enough to handle the huge size of DV frames (as big as 288000).
- OutPacketBuffer::maxSize = 300000;
- NEW_SMS("DV Video");
- sms->addSubsession(
- DVVideoFileServerMediaSubsession::createNew(env, fileName,
- reuseSource));
- } else if (strcmp(extension, ".mkv") == 0) {
- // Assumed to be a Matroska file
- NEW_SMS("Matroska video+audio+(optional)subtitles");
- // Create a Matroska file server demultiplexor for the specified file. (We enter the event loop to wait for this to complete.)
- newMatroskaDemuxWatchVariable = 0;
- MatroskaFileServerDemux::createNew(env, fileName,
- onMatroskaDemuxCreation, NULL);
- env.taskScheduler().doEventLoop(&newMatroskaDemuxWatchVariable);
- ServerMediaSubsession* smss;
- while ((smss = demux->newServerMediaSubsession()) != NULL) {
- sms->addSubsession(smss);
- }
- }
- return sms;
- }
static ServerMediaSession* createNewSMS(UsageEnvironment& env,char const* fileName, FILE* /*fid*/)
{
// Use the file name extension to determine the type of "ServerMediaSession":
char const* extension = strrchr(fileName, '.');
if (extension == NULL)
return NULL;
ServerMediaSession* sms = NULL;
Boolean const reuseSource = False;
if (strcmp(extension, ".aac") == 0) {
// Assumed to be an AAC Audio (ADTS format) file:
NEW_SMS("AAC Audio");
sms->addSubsession(
ADTSAudioFileServerMediaSubsession::createNew(env, fileName,
reuseSource));
} else if (strcmp(extension, ".amr") == 0) {
// Assumed to be an AMR Audio file:
NEW_SMS("AMR Audio");
sms->addSubsession(
AMRAudioFileServerMediaSubsession::createNew(env, fileName,
reuseSource));
} else if (strcmp(extension, ".ac3") == 0) {
// Assumed to be an AC-3 Audio file:
NEW_SMS("AC-3 Audio");
sms->addSubsession(
AC3AudioFileServerMediaSubsession::createNew(env, fileName,
reuseSource));
} else if (strcmp(extension, ".m4e") == 0) {
// Assumed to be a MPEG-4 Video Elementary Stream file:
NEW_SMS("MPEG-4 Video");
sms->addSubsession(
MPEG4VideoFileServerMediaSubsession::createNew(env, fileName,
reuseSource));
} else if (strcmp(extension, ".264") == 0) {
// Assumed to be a H.264 Video Elementary Stream file:
NEW_SMS("H.264 Video");
OutPacketBuffer::maxSize = 100000; // allow for some possibly large H.264 frames
sms->addSubsession(
H264VideoFileServerMediaSubsession::createNew(env, fileName,
reuseSource));
} else if (strcmp(extension, ".mp3") == 0) {
// Assumed to be a MPEG-1 or 2 Audio file:
NEW_SMS("MPEG-1 or 2 Audio");
// To stream using 'ADUs' rather than raw MP3 frames, uncomment the following:
//#define STREAM_USING_ADUS 1
// To also reorder ADUs before streaming, uncomment the following:
//#define INTERLEAVE_ADUS 1
// (For more information about ADUs and interleaving,
// see <http://www.live555.com/rtp-mp3/>)
Boolean useADUs = False;
Interleaving* interleaving = NULL;
#ifdef STREAM_USING_ADUS
useADUs = True;
#ifdef INTERLEAVE_ADUS
unsigned char interleaveCycle[] = {0,2,1,3}; // or choose your own...
unsigned const interleaveCycleSize
= (sizeof interleaveCycle)/(sizeof (unsigned char));
interleaving = new Interleaving(interleaveCycleSize, interleaveCycle);
#endif
#endif
sms->addSubsession(
MP3AudioFileServerMediaSubsession::createNew(env, fileName,
reuseSource, useADUs, interleaving));
} else if (strcmp(extension, ".mpg") == 0) {
// Assumed to be a MPEG-1 or 2 Program Stream (audio+video) file:
NEW_SMS("MPEG-1 or 2 Program Stream");
MPEG1or2FileServerDemux* demux = MPEG1or2FileServerDemux::createNew(env,
fileName, reuseSource);
sms->addSubsession(demux->newVideoServerMediaSubsession());
sms->addSubsession(demux->newAudioServerMediaSubsession());
} else if (strcmp(extension, ".ts") == 0) {
// Assumed to be a MPEG Transport Stream file:
// Use an index file name that's the same as the TS file name, except with ".tsx":
unsigned indexFileNameLen = strlen(fileName) + 2; // allow for trailing "x\0"
char* indexFileName = new char[indexFileNameLen];
sprintf(indexFileName, "%sx", fileName);
NEW_SMS("MPEG Transport Stream");
sms->addSubsession(
MPEG2TransportFileServerMediaSubsession::createNew(env,
fileName, indexFileName, reuseSource));
delete[] indexFileName;
} else if (strcmp(extension, ".wav") == 0) {
// Assumed to be a WAV Audio file:
NEW_SMS("WAV Audio Stream");
// To convert 16-bit PCM data to 8-bit u-law, prior to streaming,
// change the following to True:
Boolean convertToULaw = False;
sms->addSubsession(
WAVAudioFileServerMediaSubsession::createNew(env, fileName,
reuseSource, convertToULaw));
} else if (strcmp(extension, ".dv") == 0) {
// Assumed to be a DV Video file
// First, make sure that the RTPSinks' buffers will be large enough to handle the huge size of DV frames (as big as 288000).
OutPacketBuffer::maxSize = 300000;
NEW_SMS("DV Video");
sms->addSubsession(
DVVideoFileServerMediaSubsession::createNew(env, fileName,
reuseSource));
} else if (strcmp(extension, ".mkv") == 0) {
// Assumed to be a Matroska file
NEW_SMS("Matroska video+audio+(optional)subtitles");
// Create a Matroska file server demultiplexor for the specified file. (We enter the event loop to wait for this to complete.)
newMatroskaDemuxWatchVariable = 0;
MatroskaFileServerDemux::createNew(env, fileName,
onMatroskaDemuxCreation, NULL);
env.taskScheduler().doEventLoop(&newMatroskaDemuxWatchVariable);
ServerMediaSubsession* smss;
while ((smss = demux->newServerMediaSubsession()) != NULL) {
sms->addSubsession(smss);
}
}
return sms;
}
可以看到NEW_SMS("AMR Audio")会创建新的ServerMediaSession,之后马上调用sms->addSubsession()为这个ServerMediaSession添加一个 ServerMediaSubSession 。看起来ServerMediaSession应该可以添加多个ServerMediaSubSession,但这里并没有这样做。如果可以添加多个 ServerMediaSubsession 那么ServerMediaSession与流名字所指定与文件是没有关系的,也就是说它不会操作文件,而文件的操作是放在 ServerMediaSubsession中的。具体应改是在ServerMediaSubsession的sdpLines()函数中打开。