live555源码分析(七)播放过程

live555源码分析系列

live555源码分析(一)live555初体验

live555源码分析(二)基本组件上

live555源码分析(三)基本组件下

live555源码分析(四)RTSPServer分析

live555源码分析(五)DESCRIBE请求的处理

live555源码分析(六)SETUP和PLAY请求的处理

live555源码分析(七)播放过程

live555源码分析(八)多播

live555源码分析(七)播放过程

本文衔接上文,分析播放过程,涉及音视频数据如何获取、如何处理以及如何发送

上一篇文章我们分析到StreamState::startPlaying函数,本文从这里开始分析,其定义如下

void StreamState
::startPlaying(Destinations* dests, unsigned clientSessionId,
	       TaskFunc* rtcpRRHandler, void* rtcpRRHandlerClientData,
	       ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
	       void* serverRequestAlternativeByteHandlerClientData) {
    
    /* 将目的添加到消费者中 */
    if (dests->isTCP) { //TCP
        fRTPSink->addStreamSocket(dests->tcpSocketNum, dests->rtpChannelId);
        fRTCPInstance->addStreamSocket(dests->tcpSocketNum, dests->rtcpChannelId);
    } else { //UDP
        fRTPgs->addDestination(dests->addr, dests->rtpPort, clientSessionId);
        fRTCPgs->addDestination(dests->addr, dests->rtcpPort, clientSessionId);
    }
    
    /* 开始播放 */
    fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this);
}

从上面可以看到,会将目的添加到RTPSink中,然后调用RTPSink开始播放,并指定好它的源为fMediaSource

在我们live555源码分析(四)RTSPServer分析中实现的这个例子中

RTPSink是这样产生的

RTPSink* H264VideoFileServerMediaSubsession
::createNewRTPSink(Groupsock* rtpGroupsock,
		   unsigned char rtpPayloadTypeIfDynamic,
		   FramedSource* /*inputSource*/) {
  return H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
}

H264VideoRTPSink的作用是将H264的NALU进行RTP打包发送

fMediaSource是这样产生的

FramedSource* H264VideoFileServerMediaSubsession::createNewStreamSource(unsigned /*clientSessionId*/, unsigned& estBitrate) {
  estBitrate = 500; // kbps, estimate

  ByteStreamFileSource* fileSource = ByteStreamFileSource::createNew(envir(), fFileName);
  if (fileSource == NULL) return NULL;
  fFileSize = fileSource->fileSize();

  return H264VideoStreamFramer::createNew(envir(), fileSource);
}

live555对数据源的类很多采用了装饰者模式,如上述中,ByteStreamFileSource用于读取文件字节流,H264VideoStreamFramer用于读取h264文件的nalu

搞清楚生产者和消费者对应谁之后,我们继续分析fRTPSink->startPlaying(),它的定义如下

Boolean MediaSink::startPlaying(MediaSource& source,
				afterPlayingFunc* afterFunc,
				void* afterClientData) {
	return continuePlaying();
}

continuePlaying在本例中对应如下

Boolean H264or5VideoRTPSink::continuePlaying() {
	fOurFragmenter = new H264or5Fragmenter(fHNumber, envir(),
                                           fSource, OutPacketBuffer::maxSize,
                                           ourMaxPacketSize() - 12/*RTP hdr size*/);

    fSource = fOurFragmenter;
    
    return MultiFramedRTPSink::continuePlaying();
}

可以看到,这里又会将我们定义的生产者H264VideoStreamFramer对象作为参数传递给H264or5Fragmenter创建新的生产者,然后修改fSource

这里使用的也是装饰者模式,H264or5Fragmenter的作用是将NALU进行特定的RTP打包

从头到尾的生产者关系如下

ByteStreamFileSource //文件字节流
	|
H264VideoStreamFramer //提取NALU
	|
H264or5Fragmenter //对NALU进行RTP打包

到这里,相信你不会觉得定义这么多的生产者很乱,反而会觉得设计得挺巧妙

接着往下看

Boolean MultiFramedRTPSink::continuePlaying() {
	buildAndSendPacket(True);
}
void MultiFramedRTPSink::buildAndSendPacket(Boolean isFirstPacket) {
	unsigned rtpHdr = 0x80000000;
    rtpHdr |= (fRTPPayloadType<<16);
    rtpHdr |= fSeqNo;
    fOutBuf->enqueueWord(rtpHdr);
    
    ...
    packFrame();
}

buildAndSendPacket函数对设置了RTP得头部,其中跳过了时间戳,这个将在稍后设置

RTP头部得定义如下,感兴趣的话可以仔细分析代码

live555源码分析(七)播放过程_第1张图片

接下来分析packFrame函数,其定义如下

void MultiFramedRTPSink::packFrame() {
	fSource->getNextFrame(fOutBuf->curPtr(), fOutBuf->totalBytesAvailable(),
    	afterGettingFrame, this, ourHandleClosure, this);
}

可以看到,上述调用了生产者,获取一帧数据,获取完数据之后会调用afterGettingFrame函数

afterGettingFrame定义如下,这里请记住,我们稍后分析

void MultiFramedRTPSink::afterGettingFrame(void* clientData, unsigned numBytesRead,
		    unsigned numTruncatedBytes,
		    struct timeval presentationTime,
		    unsigned durationInMicroseconds) {
  MultiFramedRTPSink* sink = (MultiFramedRTPSink*)clientData;
  sink->afterGettingFrame1(numBytesRead, numTruncatedBytes,
			   presentationTime, durationInMicroseconds);
}

在此例中fSource对应H264or5Fragmenter,看一看它的getNextFrame函数定义

void H264or5Fragmenter::doGetNextFrame() {
	if (fNumValidDataBytes == 1) { //缓存没有数据
    	fInputSource->getNextFrame(&fInputBuffer[1], fInputBufferSize - 1,
                                   afterGettingFrame, this,
                                   FramedSource::handleClosure, this);
    } else { //缓存有数据
     	/*
     	 * 打包方式
     	 * 1.单NALU模式
     	 * 2.分片模式
     	 * 3.聚合模式
     	 */
        
        FramedSource::afterGetting(this);
    }
}

首先,如果缓存里没有数据,那么就会调用fInputSource来获取一帧数据,此例中fInputSource对应的是H264VideoStreamFramer,调用H264VideoStreamFramergetNextFrame函数将获取一个NALU

如果缓存里有数据,那么就进行RTP打包,这里的数据是位于RTP的载荷位置,这里实现了三种NALU的RTP打包模式,单NALU打包、分片打包、聚合打包

我们首先来分析getNextFrame函数,此例中,其定义如下

void MPEGVideoStreamFramer::doGetNextFrame() {
    fParser->registerReadInterest(fTo, fMaxSize);
    continueReadProcessing();
}

void MPEGVideoStreamFramer::continueReadProcessing() {
    unsigned acquiredFrameSize = fParser->parse();
	afterGetting(this);
}

最终会通过fParser->parse来解析获取数据,然后调用afterGetting

fParserH264or5VideoStreamFramer的构造函数中构造,其作用是解析H264或者H265文件

H264or5VideoStreamFramer
::H264or5VideoStreamFramer(int hNumber, UsageEnvironment& env, FramedSource* inputSource,
			   Boolean createParser, Boolean includeStartCodeInOutput)
{
    fParser = createParser
        ? new H264or5VideoStreamParser(hNumber, this, inputSource, includeStartCodeInOutput)
}

好的,在获取完一帧后,会调用afterGetting函数,其对应

void H264or5Fragmenter::afterGettingFrame(void* clientData, unsigned frameSize,
					  unsigned numTruncatedBytes,
					  struct timeval presentationTime,
					  unsigned durationInMicroseconds) {
  H264or5Fragmenter* fragmenter = (H264or5Fragmenter*)clientData;
  fragmenter->afterGettingFrame1(frameSize, numTruncatedBytes, presentationTime,
				 durationInMicroseconds);
}
void H264or5Fragmenter::afterGettingFrame1(unsigned frameSize,
					   unsigned numTruncatedBytes,
					   struct timeval presentationTime,
					   unsigned durationInMicroseconds) {
    fNumValidDataBytes += frameSize;
    fSaveNumTruncatedBytes = numTruncatedBytes;
    fPresentationTime = presentationTime; //本帧数据的时间戳
    fDurationInMicroseconds = durationInMicroseconds; //下一次获取数据的间隔时间

    doGetNextFrame();
}

可以看到在获取到一帧数据后,又调用了doGetNextFrame函数

void H264or5Fragmenter::doGetNextFrame() {
	if (fNumValidDataBytes == 1) { //缓存没数据
    	fInputSource->getNextFrame(&fInputBuffer[1], fInputBufferSize - 1,
                                   afterGettingFrame, this,
                                   FramedSource::handleClosure, this);
    } else { //缓存有数据
     	/*
     	 * 打包方式
     	 * 1.单NALU模式
     	 * 2.分片模式
     	 * 3.聚合模式
     	 */
        
        FramedSource::afterGetting(this);
    }
}

第二次调用这个函数,现在会进入else分支,对数据进行RTP封装,然后调用FramedSource::afterGetting

如果对于RTP封装感兴趣的话,可以看从零开始写一个RTSP服务器(三)RTP传输H.264

我们接着查看FramedSource::afterGetting,其定义在下面函数调用中指定

void MultiFramedRTPSink::packFrame() {
	fSource->getNextFrame(fOutBuf->curPtr(), fOutBuf->totalBytesAvailable(),
    	afterGettingFrame, this, ourHandleClosure, this);
}

定义如下

void MultiFramedRTPSink::afterGettingFrame1(unsigned frameSize, unsigned numTruncatedBytes,
		     struct timeval presentationTime,
		     unsigned durationInMicroseconds) {
    ...
    
	doSpecialFrameHandling(curFragmentationOffset, frameStart,
                           numFrameBytesToUse, presentationTime,
                           overflowBytes);
    
    sendPacketIfNecessary();
	...
}

这里忽略了许多东西,只保留了其中最主要的部分

首先调用doSpecialFrameHandling对RTP包进行处理,本例中,其定义如下

void H264or5VideoRTPSink::doSpecialFrameHandling(unsigned /*fragmentationOffset*/,
						 unsigned char* /*frameStart*/,
						 unsigned /*numBytesInFrame*/,
						 struct timeval framePresentationTime,
						 unsigned /*numRemainingBytes*/) {
	...
	if()
    	setMarkerBit();
    
    setTimestamp(framePresentationTime);
}

可以看到此函数会设置默写标志位和时间戳

到了这里,一个RTP包就完成了,接下来看sendPacketIfNecessary,其定义如下

void MultiFramedRTPSink::sendPacketIfNecessary() {
	...
    fRTPInterface.sendPacket(fOutBuf->packet(), fOutBuf->curPacketSize()
    
	...
	envir().taskScheduler().scheduleDelayedTask(uSecondsToGo, (TaskFunc*)sendNext, this);
}

首先将数据传送给所有的客户端,然后添加一个定时任务,准备下一次发送

整个过程就是这样,确实有点绕,我将其画为流程图,如下

live555源码分析(七)播放过程_第2张图片

本文到这里就结束了

你可能感兴趣的:(live555源码分析与应用)