Live555 RTSP播放分析(一)--基本模块介绍

以testRTSPClient.cpp测试程序来对Live555 RTSP播放进行一个简单的分析。同时对Live555几大模块的功能及使用进行简单描述。
因为我对Live555使用的比较多的是在客户端播放场景下,所以可能有些不足或者错误,请指正。

RTSPClient处于Live555 liveMedia模块,这部分是Live555流媒体的核心部分,主要是实现了各种流媒体交互流程。我们先了解一些重要的类,以帮助后面分析代码。(RTSP播放流程分析)

GroupSock

GroupSock是Live555对网络接口的封装,支持UDP/TCP,同时也支持单播/组播。

udpGroupsock = new Groupsock(*env, udpIP, udpPort, ttl);

像这样通过IP及端口,就可以创建一个GroupSock,liveMedia中的模块就可以通过该socket进行网络数据的读写。

Source、Filter 和Sink

  • Source 发送端,流的起点, 可直观理解为生产者,负责读取文件或网络流的信息。
  • Filter 本质上也是Source,因为可以由多级Source,Filter可以对上级Source进行处理。
  • Sink 接收端, 流的终点,可理解为是消费者。

数据流向简单来说如下:
Live555 RTSP播放分析(一)--基本模块介绍_第1张图片

  • Source 基于FramedSource,必须实现virtual void doGetNextFrame() = 0;函数;
  • Filter基于FramedFilterFramedFilter实际上也是基于FramedSource,Filter一般是会以上一级的Source作为传入参数,其实就是从上一级Source中获取数据在进行处理。第三级、第四级也一样;
  • Sink基于MediaSink,必须实现virtual Boolean continuePlaying() = 0;函数。

具体如下:
Source的创建需要有GroupSock作为参数,指明其读取的socket。
例如:

udpSource = BasicUDPSource::createNew(*env, udpGroupsock);
rtpSource = SimpleRTPSource::createNew(*env, rtpGroupsock, 33, 90000, "video/MP2T", 0, False /*no 'M' bit*/);

如果还有Filter,就以Source为参数,创建Filter,当然Filter也需要实现virtual void doGetNextFrame() = 0;函数;在其中进行Filter的实现处理。以流媒体中最常见的MPEG2TransportStreamFramer为例,在其自身的afterGettingFrame函数中做了一些TS对齐、解析等操作后,调用的上级Source的doGetNextFrame。

readSource= MPEG2TransportStreamFramer::createNew(*env, rtpSource);

Sink的创建需要Source 作为参数,并调用startPlaying函数启动,当启动后,会调用continuePlaying。如下:

Boolean MediaSink::startPlaying(MediaSource& source,
				afterPlayingFunc* afterFunc,
				void* afterClientData) {
  // Make sure we're not already being played:
  if (fSource != NULL) {
    envir().setResultMsg("This sink is already being played");
    return False;
  }

  // Make sure our source is compatible:
  if (!sourceIsCompatibleWithUs(source)) {
    envir().setResultMsg("MediaSink::startPlaying(): source is not compatible!");
    return False;
  }
  fSource = (FramedSource*)&source;

  fAfterFunc = afterFunc;
  fAfterClientData = afterClientData;
  return continuePlaying();
}

要注意这里面定义了fAfterFunc 就是最后播放结束调用的函数。
例子如下:

sink->startPlaying((FramedSource&)(*readSource), afterPlaying, sink);

continuePlaying中,最基本需要实现的操作就是从Source中读取下一帧数据getNextFrame,进而会调用到Source的doGetNextFrame函数。
getNextFrame中传入的几个参数,fBuffer是存放读取数据的内存,fBufferSize是读取的数据大小,afterGettingFrame是读完一帧后的处理函数。

Boolean FileSink::continuePlaying() {
  if (fSource == NULL) return False;

  fSource->getNextFrame(fBuffer, fBufferSize,
			afterGettingFrame, this,
			onSourceClosure, this);

  return True;
}

每种类型Source的doGetNextFrame函数读取每一帧数据都会有不同的处理,但最后,都会调用父类FramedSource的afterGetting(this);函数,最后调用到在continuePlaying实现中传进来的获取每一帧后的处理函数,例如上面的例子就是afterGettingFrame函数。

void FramedSource::afterGetting(FramedSource* source) {
  source->fIsCurrentlyAwaitingData = False;
      // indicates that we can be read again
      // Note that this needs to be done here, in case the "fAfterFunc"
      // called below tries to read another frame (which it usually will)

  if (source->fAfterGettingFunc != NULL) {
    (*(source->fAfterGettingFunc))(source->fAfterGettingClientData,
				   source->fFrameSize, source->fNumTruncatedBytes,
				   source->fPresentationTime,
				   source->fDurationInMicroseconds);
  }
}

一般来说,afterGettingFrame函数会完成各种Sink定义的操作后(如播放帧数据),然后消费完这一帧后,又会调用continuePlaying函数,从而完成一个闭环。

例如:

void FileSink::afterGettingFrame(unsigned frameSize,
				 unsigned numTruncatedBytes,
				 struct timeval presentationTime) {
  if (numTruncatedBytes > 0) {
    envir() << "FileSink::afterGettingFrame(): The input frame data was too large for our buffer size ("
	    << fBufferSize << ").  "
            << numTruncatedBytes << " bytes of trailing data was dropped!  Correct this by increasing the \"bufferSize\" parameter in the \"createNew()\" call to at least "
            << fBufferSize + numTruncatedBytes << "\n";
  }
  addData(fBuffer, frameSize, presentationTime);

  if (fOutFid == NULL || fflush(fOutFid) == EOF) {
    // The output file has closed.  Handle this the same way as if the input source had closed:
    if (fSource != NULL) fSource->stopGettingFrames();
    onSourceClosure(this);
    return;
  }

  if (fPerFrameFileNameBuffer != NULL) {
    if (fOutFid != NULL) { fclose(fOutFid); fOutFid = NULL; }
  }

  // Then try getting the next frame:
  continuePlaying();
}

最后,这样Souce和Sink就形成一个闭环,不断生产->消费->生产->消费…

TaskScheduler和BasicTaskScheduler

这两个是Live555中任务调度的模块,TaskScheduler定义了接口,BasicTaskScheduler继承BasicTaskScheduler0,BasicTaskScheduler0继承TaskScheduler。

在sink调用startPlaying后,最后必须调用TaskScheduler的doEventLoop,来让整个程序跑起来。

介绍重要的函数及概念:

BasicTaskScheduler0中doEventLoop为程序循环函数,实际看SingleStep中的实现。

void BasicTaskScheduler0::doEventLoop(char volatile* watchVariable) {
  // Repeatedly loop, handling readble sockets and timed events:
  while (1) {
    if (watchVariable != NULL && *watchVariable != 0) break;
    SingleStep();
  }
}

SingleStep中根据任务的类别,分成三类的来处理,每一次循环都会按照下面顺序来完成调用。

1、首先处理的是Socket-Event。
负责I/O复用,使用select函数等待指定的描述字准备好读、写或有异常条件处理。若select返回值大于-1,则转到相应的处理函数;否则表明发生异常,程序将转到错误处理代码中去。该类型适合于有I/O操作的任务。

像UDP/RTP Source从Socket的读取便是这种类型。
相关函数:

turnOnBackgroundReadHandling	//加入到后台IO
setBackgroundHandling	//加入到后台IO
disableBackgroundHandling	//禁止后台IO,即清空
moveSocketHandling 	//移除指定后台IO

2、接着是处理触发器事件(Trigger-Event)。
Live555定义了一个32位的位图来实现触发事件,当某一位设置为1则表明要触发该位对应的事件。若同时有多个(3个或以上)触发事件,它们触发的先后还会跟事件创建的先后有关,因此这一类型仅适合于没有顺序依赖关系的任务。

使用方法:
Trigger-Event的使用需先创建一个EventTrigger,指定其处理函数,需要触发时,调用triggerEvent。如:

testEventID = scheduler->createEventTrigger(testEventHandler);
void testEventHandler(void* user_data)
{
	ALOGD("%s\n", __FUNCTION__);
}
scheduler->triggerEvent(testEventID , this);

相关函数:

createEventTrigger	//创建触发器
deleteEventTrigger	//删除触发器
triggerEvent	//触发

3、最后一个是定时任务(Delayed Task)。
它是一个带有时间的任务。当剩余时间不为0,则任务不执行。通过调整任务的剩余时间,可以灵活地安排任务。

使用方法:
创建一个定时任务,给定时间及处理函数即可,如:

if (scs.duration > 0) {
  unsigned const delaySlop = 2; // number of seconds extra to delay, after the stream's expected duration.  (This is optional.)
  scs.duration += delaySlop;
  unsigned uSecsToDelay = (unsigned)(scs.duration*1000000);
  scs.streamTimerTask = env.taskScheduler().scheduleDelayedTask(uSecsToDelay, (TaskFunc*)streamTimerHandler, rtspClient);
}
    
void streamTimerHandler(void* clientData) {
  ourRTSPClient* rtspClient = (ourRTSPClient*)clientData;
  StreamClientState& scs = rtspClient->scs; // alias

  scs.streamTimerTask = NULL;

  // Shut down the stream:
  shutdownStream(rtspClient);
}

相关函数:

scheduleDelayedTask	//添加定时任务
unscheduleDelayedTask	//移除定时任务
rescheduleDelayedTask	//重置定时任务

BasicTaskScheduler0实现以及触发事件和延迟任务。
BasicTaskScheduler类实现剩下的I/O操作任务接口和三类任务的实际调度(singleStep函数)。

总结

至此,我们了解了Live555的基本模块使用,那么接下来进入主题,RTSPClient的分析

你可能感兴趣的:(live555,流媒体)