live555支持的文件格式多为单流的文件,仅支持*.mpg、*.mkv、*.webm几种音视频混合类型的文件。其实我的目的是扩展其支持的格式,如avi等, 所以来分析一下mpg文件的处理。
mpg文件处理涉及的类相当多,类间的关系比较复杂,对于RTP打包的过程(在RTPSink中完成)所以有的媒体类型比较相似(细节上有差异,这些都是在特定媒体相关的***RTPSink中完成的),所以主要分析的是从source中获取数据的过程。一个subsession对应一个流,当一个session中拥有多个subsession时,需要对每一个subsession进行单独的控制。我们可以看到,对处理“PLAY”命令时,对session中的每个subsession都调用了一次startStream操作,如下:
PLAY命令的处理过程前面的文章已经分析过了,通过subsession->startStream调用启动每个流上的播放,其从source获取源数据是将在MultiFramedRTPSink::packFrame()中进行的。
对于mpeg来讲,fSource是一个MPEG1or2VideoStreamFramer或者MPEG1or2AudioStreamFramer实例(根据live555中的源码,还有一种AAC格式音频,这里为了简便不作分析)。它们的继承关系如下:
MPEG1or2VideoStreamFramer->MPEGVideoStreamFramer->FramedFilter->FramedSource->MediaSource
MPEG1or2AudioStreamFramer->FramedSource->MediaSource
先来分析mpeg video的处理。
MPEG1or2VideoStreamFramer类的实现简单,主要是创建对应的语法分析器MPEG1or2VideoStreamParser实例。显然MPEG1or2VideoStreamFramer(是FrameSource的直接子类)是一个Filter,跟它的创建过程可以发现,它的输入source是一个MPEG1or2DemuxedElementaryStream类实例。对于单流的文件来讲, 一般包装的是ByteStreamFileSource类实例,从后面我们可以发现,最终直接读取文件的还是ByteStreamFileSource实例。对于语法分析部分,不作分析,我们只关心如何从文件中解析出音视频数据,所以直接跟踪Filter所包装的MPEG1or2DemuxedElementaryStream类。在语法分析器中,将会调用MPEG1or2DemuxedElementaryStream的getNextFrame函数。
getNextFrame是定义在FramedSource中的非虚函数,实现如下
FramedSource::getNextFrame完成了一些员变量的初始化工作,其它工作交给dogetNextFrame函数,它是FrameSource上的一个纯虚函数,在子类MPEG1or2DemuxedElementaryStream中重新实现。
fOurSourceDemux,被定义为MPEG1or2Demux(直接继承自Medium)对像的引用。为什么是引用而不是指针?我们想想,前面涉及到的类MPEG1or2DemuxedServerMediaSubsession、MPEG1or2VideoRTPSink、MPEG1or2VideoStreamFramer、MPEG1or2VideoStreamParser,这些对于文件中的每一个流(当然这里只是列出了视频相关的类)都需要创建一个实例,但是所有的流对应的文件却只有一个,最后读取文件的过程必然只有一份实现。文件中的每个流会对应一个MPEG1or2DemuxedElementaryStream实例,但是却对应同一个MPEG1or2Demux实例。这里用引用而不是指针,就是为了说明fOurSourceDemux并属于某个MPEG1or2DemuxedElementaryStream实例。
在创建MPEG1or2Demux实例之前,会创建一个ByteStreamFileSource,来看MPEG1or2Demux::getNextFrame函数的定义
上面的代码中首先调用useSavedData函数,检查缓存中是否有相应流的数据存在,若是不存在,就只有再从文件中读取了。现在可以做一个猜测,缓存是必需的,文件中的不同流数据交错排列,而读取时一般会按顺序来读。现在要读取一个视频frame,但是当前文件指针处读取到的刚好是音视频frame,我们就需要将这个音频frame保存到缓存中,这样一直到读取视频数据为止。实际情况是不是这样呢?
先来看MPEG1or2Demux::useSavedData函数的实现
来分析一下上面的代码。缓存被定义为一个OutputDescriptor类型数组,看其定义
OutputDescriptor_t fOutput[256];
竟然定义了一个256个实例,不过也只有这个样才能使用流索引streamIdTag直接访问数组元素,streamIdTag应该小于256。每一个流将对应一个OutputDescriptor实例。实际数据保存在 OutputDescriptor::SavedData类型的链表中。
现在继续来看MPEG1or2Demux::getNextFrame函数,我们需要知道是怎么分离出不同媒体流的,来看continueReadProcessing函数
MPEG1or2Demux::continueReadProcessing的实现似乎很眼熟悉啊!是的,其与MPEGVideoStreamFramer::continueReadProcessing()有些相似。两个函数中均调用了语法分析器进行语法分析,不过应注意前者的语法分析器是MPEGProgramStreamParser类实例,完成了对复合文件的解析,主要目的是从中分离出音视频流数据(其实就是demux过程),而后者是一个MPEG1or2VideoStreamParser,对指定的流数据进一步分析。MPEGProgramStreamParser的数据源,是一个ByteStreamFileSource实例,它是MPEG1or2Demux构造函数中传递下来的参数。若未读取到所需要的数据,fParser->parse()将返回0。
来看MPEGProgramStreamParser::parse函数实现
对于mpeg文件格式不熟悉,就不看了,只看获取流数据包的函数MPEGProgramStreamParser::parsePESPacket
MPEGProgramStreamParser::parsePESPacket函数会读取流中的数据,若读取到的流数据不是当前需要的就保存到缓存中,也就是其对应的OutputDescriptor实例。需要注意局部变量acquiredStreamIdTag,它的值被初始化为0, 只有当读取到当前所需要的流数据时才会被赋值。所以若未读取到所需要的数据,这个函数将返回0值。
mpg文件处理涉及的类相当多,类间的关系比较复杂,对于RTP打包的过程(在RTPSink中完成)所以有的媒体类型比较相似(细节上有差异,这些都是在特定媒体相关的***RTPSink中完成的),所以主要分析的是从source中获取数据的过程。一个subsession对应一个流,当一个session中拥有多个subsession时,需要对每一个subsession进行单独的控制。我们可以看到,对处理“PLAY”命令时,对session中的每个subsession都调用了一次startStream操作,如下:
- void RTSPServer::RTSPClientSession
- ::handleCmd_PLAY(ServerMediaSubsession* subsession, char const* cseq,
- char const* fullRequestStr) {
- //现在终于开始媒体数据传输了
- // Now, start streaming:
- for (i = 0; i < fNumStreamStates; ++i) {
- if (subsession == NULL /* means: aggregated operation */
- || subsession == fStreamStates[i].subsession) {
- unsigned short rtpSeqNum = 0;
- unsigned rtpTimestamp = 0;
- //开始各个subsession上的数据传输, 即开始播放了
- fStreamStates[i].subsession->startStream(fOurSessionId,
- fStreamStates[i].streamToken,
- (TaskFunc*)noteClientLiveness, this,
- rtpSeqNum, rtpTimestamp,
- handleAlternativeRequestByte, this);
- ...
- }
- }
- ...
- }
PLAY命令的处理过程前面的文章已经分析过了,通过subsession->startStream调用启动每个流上的播放,其从source获取源数据是将在MultiFramedRTPSink::packFrame()中进行的。
- void MultiFramedRTPSink::packFrame() {
- if (fOutBuf->haveOverflowData()) {
- ...
- } else {
- ...
- //
- //从source中获取下一个frame
- //
- fSource->getNextFrame(fOutBuf->curPtr(), fOutBuf->totalBytesAvailable(),
- afterGettingFrame, this, ourHandleClosure, this);
- }
- }
对于mpeg来讲,fSource是一个MPEG1or2VideoStreamFramer或者MPEG1or2AudioStreamFramer实例(根据live555中的源码,还有一种AAC格式音频,这里为了简便不作分析)。它们的继承关系如下:
MPEG1or2VideoStreamFramer->MPEGVideoStreamFramer->FramedFilter->FramedSource->MediaSource
MPEG1or2AudioStreamFramer->FramedSource->MediaSource
先来分析mpeg video的处理。
MPEG1or2VideoStreamFramer类的实现简单,主要是创建对应的语法分析器MPEG1or2VideoStreamParser实例。显然MPEG1or2VideoStreamFramer(是FrameSource的直接子类)是一个Filter,跟它的创建过程可以发现,它的输入source是一个MPEG1or2DemuxedElementaryStream类实例。对于单流的文件来讲, 一般包装的是ByteStreamFileSource类实例,从后面我们可以发现,最终直接读取文件的还是ByteStreamFileSource实例。对于语法分析部分,不作分析,我们只关心如何从文件中解析出音视频数据,所以直接跟踪Filter所包装的MPEG1or2DemuxedElementaryStream类。在语法分析器中,将会调用MPEG1or2DemuxedElementaryStream的getNextFrame函数。
getNextFrame是定义在FramedSource中的非虚函数,实现如下
- void FramedSource::getNextFrame(unsigned char* to, unsigned maxSize,
- afterGettingFunc* afterGettingFunc,
- void* afterGettingClientData,
- onCloseFunc* onCloseFunc,
- void* onCloseClientData) {
- // Make sure we're not already being read:
- if (fIsCurrentlyAwaitingData) {
- envir() << "FramedSource[" << this << "]::getNextFrame(): attempting to read more than once at the same time!\n";
- envir().internalError();
- }
- fTo = to;
- fMaxSize = maxSize;
- fNumTruncatedBytes = 0; // by default; could be changed by doGetNextFrame()
- fDurationInMicroseconds = 0; // by default; could be changed by doGetNextFrame()
- fAfterGettingFunc = afterGettingFunc;
- fAfterGettingClientData = afterGettingClientData;
- fOnCloseFunc = onCloseFunc;
- fOnCloseClientData = onCloseClientData;
- fIsCurrentlyAwaitingData = True;
- doGetNextFrame(); //获取下一个frame
- }
FramedSource::getNextFrame完成了一些员变量的初始化工作,其它工作交给dogetNextFrame函数,它是FrameSource上的一个纯虚函数,在子类MPEG1or2DemuxedElementaryStream中重新实现。
- void MPEG1or2DemuxedElementaryStream::doGetNextFrame() {
- fOurSourceDemux.getNextFrame(fOurStreamIdTag, fTo, fMaxSize,
- afterGettingFrame, this,
- handleClosure, this);
- }
fOurSourceDemux,被定义为MPEG1or2Demux(直接继承自Medium)对像的引用。为什么是引用而不是指针?我们想想,前面涉及到的类MPEG1or2DemuxedServerMediaSubsession、MPEG1or2VideoRTPSink、MPEG1or2VideoStreamFramer、MPEG1or2VideoStreamParser,这些对于文件中的每一个流(当然这里只是列出了视频相关的类)都需要创建一个实例,但是所有的流对应的文件却只有一个,最后读取文件的过程必然只有一份实现。文件中的每个流会对应一个MPEG1or2DemuxedElementaryStream实例,但是却对应同一个MPEG1or2Demux实例。这里用引用而不是指针,就是为了说明fOurSourceDemux并属于某个MPEG1or2DemuxedElementaryStream实例。
在创建MPEG1or2Demux实例之前,会创建一个ByteStreamFileSource,来看MPEG1or2Demux::getNextFrame函数的定义
- void MPEG1or2Demux::getNextFrame(u_int8_t streamIdTag,
- unsigned char* to, unsigned maxSize,
- FramedSource::afterGettingFunc* afterGettingFunc,
- void* afterGettingClientData,
- FramedSource::onCloseFunc* onCloseFunc,
- void* onCloseClientData) {
- // First, check whether we have saved data for this stream id:
- //
- // 检查缓存中是否已经存在streamIdTag流的数据
- //
- if (useSavedData(streamIdTag, to, maxSize,
- afterGettingFunc, afterGettingClientData)) {
- return;
- }
- //注意,这里设置了回调用函数
- // Then save the parameters of the specified stream id:
- registerReadInterest(streamIdTag, to, maxSize,
- afterGettingFunc, afterGettingClientData,
- onCloseFunc, onCloseClientData);
- // Next, if we're the only currently pending read, continue looking for data:
- if (fNumPendingReads == 1 || fHaveUndeliveredData) {
- fHaveUndeliveredData = 0;
- continueReadProcessing(); //继续读取数据
- } // otherwise the continued read processing has already been taken care of
- }
上面的代码中首先调用useSavedData函数,检查缓存中是否有相应流的数据存在,若是不存在,就只有再从文件中读取了。现在可以做一个猜测,缓存是必需的,文件中的不同流数据交错排列,而读取时一般会按顺序来读。现在要读取一个视频frame,但是当前文件指针处读取到的刚好是音视频frame,我们就需要将这个音频frame保存到缓存中,这样一直到读取视频数据为止。实际情况是不是这样呢?
先来看MPEG1or2Demux::useSavedData函数的实现
- Boolean MPEG1or2Demux::useSavedData(u_int8_t streamIdTag,
- unsigned char* to, unsigned maxSize,
- FramedSource::afterGettingFunc* afterGettingFunc,
- void* afterGettingClientData) {
- struct OutputDescriptor& out = fOutput[streamIdTag]; //fOutput是一个缓存数组
- //正常情况,缓存中没有数据,直接返回了
- if (out.savedDataHead == NULL) return False; // common case
- unsigned totNumBytesCopied = 0;
- //
- //从OutputDescriptor类型的缓存中读取全部数据
- //
- while (maxSize > 0 && out.savedDataHead != NULL) {
- OutputDescriptor::SavedData& savedData = *(out.savedDataHead);
- unsigned char* from = &savedData.data[savedData.numBytesUsed];
- unsigned numBytesToCopy = savedData.dataSize - savedData.numBytesUsed;
- if (numBytesToCopy > maxSize) numBytesToCopy = maxSize;
- memmove(to, from, numBytesToCopy);
- to += numBytesToCopy;
- maxSize -= numBytesToCopy;
- out.savedDataTotalSize -= numBytesToCopy;
- totNumBytesCopied += numBytesToCopy;
- savedData.numBytesUsed += numBytesToCopy;
- if (savedData.numBytesUsed == savedData.dataSize) {
- out.savedDataHead = savedData.next;
- if (out.savedDataHead == NULL) out.savedDataTail = NULL;
- savedData.next = NULL;
- delete &savedData;
- }
- }
- out.isCurrentlyActive = True;
- if (afterGettingFunc != NULL) {
- struct timeval presentationTime;
- presentationTime.tv_sec = 0; presentationTime.tv_usec = 0; // should fix #####
- (*afterGettingFunc)(afterGettingClientData, totNumBytesCopied,
- 0 /* numTruncatedBytes */, presentationTime,
- 0 /* durationInMicroseconds ?????#####*/);
- }
- return True;
- }
来分析一下上面的代码。缓存被定义为一个OutputDescriptor类型数组,看其定义
OutputDescriptor_t fOutput[256];
竟然定义了一个256个实例,不过也只有这个样才能使用流索引streamIdTag直接访问数组元素,streamIdTag应该小于256。每一个流将对应一个OutputDescriptor实例。实际数据保存在 OutputDescriptor::SavedData类型的链表中。
现在继续来看MPEG1or2Demux::getNextFrame函数,我们需要知道是怎么分离出不同媒体流的,来看continueReadProcessing函数
- void MPEG1or2Demux::continueReadProcessing() {
- while (fNumPendingReads > 0) {
- unsigned char acquiredStreamIdTag = fParser->parse(); //文件语法分析
- if (acquiredStreamIdTag != 0) { //若未读取到所需要的数据,这个值为0
- //我们从输入源获取到一个frame
- // We were able to acquire a frame from the input.
- struct OutputDescriptor& newOut = fOutput[acquiredStreamIdTag];
- newOut.isCurrentlyAwaitingData = False; //指示我们可以读下一个frame了,parse中会根据这个值判断是否正在处理的流
- // indicates that we can be read again
- // (This needs to be set before the 'after getting' call below,
- // in case it tries to read another frame)
- //
- //调用“after getting”函数
- //
- // Call our own 'after getting' function. Because we're not a 'leaf'
- // source, we can call this directly, without risking infinite recursion.
- if (newOut.fAfterGettingFunc != NULL) {
- (*newOut.fAfterGettingFunc)(newOut.afterGettingClientData,
- newOut.frameSize, 0 /* numTruncatedBytes */,
- newOut.presentationTime,
- 0 /* durationInMicroseconds ?????#####*/);
- --fNumPendingReads;
- }
- } else {
- // We were unable to parse a complete frame from the input, because:
- // - we had to read more data from the source stream, or
- // - we found a frame for a stream that was being read, but whose
- // reader is not ready to get the frame right now, or
- // - the source stream has ended.
- break;
- }
- }
- }
MPEG1or2Demux::continueReadProcessing的实现似乎很眼熟悉啊!是的,其与MPEGVideoStreamFramer::continueReadProcessing()有些相似。两个函数中均调用了语法分析器进行语法分析,不过应注意前者的语法分析器是MPEGProgramStreamParser类实例,完成了对复合文件的解析,主要目的是从中分离出音视频流数据(其实就是demux过程),而后者是一个MPEG1or2VideoStreamParser,对指定的流数据进一步分析。MPEGProgramStreamParser的数据源,是一个ByteStreamFileSource实例,它是MPEG1or2Demux构造函数中传递下来的参数。若未读取到所需要的数据,fParser->parse()将返回0。
来看MPEGProgramStreamParser::parse函数实现
- unsigned char MPEGProgramStreamParser::parse() {
- unsigned char acquiredStreamTagId = 0;
- try {
- do {
- switch (fCurrentParseState) {
- case PARSING_PACK_HEADER: {
- parsePackHeader(); //分析包头
- break;
- }
- case PARSING_SYSTEM_HEADER: {
- parseSystemHeader(); //分析系统头
- break;
- }
- case PARSING_PES_PACKET: {
- acquiredStreamTagId = parsePESPacket(); //分析流数据
- break;
- }
- }
- } while(acquiredStreamTagId == 0);
- return acquiredStreamTagId;
- } catch (int /*e*/) {
- #ifdef DEBUG
- fprintf(stderr, "MPEGProgramStreamParser::parse() EXCEPTION (This is normal behavior - *not* an error)\n");
- fflush(stderr);
- #endif
- return 0; // the parsing got interrupted
- }
- }
对于mpeg文件格式不熟悉,就不看了,只看获取流数据包的函数MPEGProgramStreamParser::parsePESPacket
- unsigned char MPEGProgramStreamParser::parsePESPacket() {
- ...
- //
- //检查source是否正在等待这个流类型,如果是就把数据交给它
- //
- // Check whether our using source is interested in this stream type.
- // If so, deliver the frame to him:
- MPEG1or2Demux::OutputDescriptor_t& out = fUsingDemux->fOutput[stream_id];
- if (out.isCurrentlyAwaitingData) {
- unsigned numBytesToCopy;
- if (PES_packet_length > out.maxSize) {
- numBytesToCopy = out.maxSize;
- } else {
- numBytesToCopy = PES_packet_length;
- }
- getBytes(out.to, numBytesToCopy); //拷贝数据
- out.frameSize = numBytesToCopy;
- // set out.presentationTime later #####
- acquiredStreamIdTag = stream_id;
- PES_packet_length -= numBytesToCopy;0
- } else if (out.isCurrentlyActive) {
- //
- //这个流是需要用到的,但是不是现在,当需要的时候我们才能把数据交出去
- //
- // Someone has been reading this stream, but isn't right now.
- // We can't deliver this frame until he asks for it, so punt for now.
- // The next time he asks for a frame, he'll get it.
- restoreSavedParserState(); // so we read from the beginning next time
- fUsingDemux->fHaveUndeliveredData = True;
- throw READER_NOT_READY; //抛出异常了
- } else if (out.isPotentiallyReadable &&
- out.savedDataTotalSize + PES_packet_length < 1000000 /*limit*/) {
- //
- //这个流也是需要用到的,只是当前没有读取而已,因此将其保存到缓存中(OutputDescriptor)
- //
- // Someone is interested in this stream, but hasn't begun reading it yet.
- // Save this data, so that the reader will get it when he later asks for it.
- unsigned char* buf = new unsigned char[PES_packet_length];
- getBytes(buf, PES_packet_length);
- MPEG1or2Demux::OutputDescriptor::SavedData* savedData
- = new MPEG1or2Demux::OutputDescriptor::SavedData(buf, PES_packet_length); //新建一个SavedData实例
- if (out.savedDataHead == NULL) {
- out.savedDataHead = out.savedDataTail = savedData;
- } else {
- out.savedDataTail->next = savedData;
- out.savedDataTail = savedData;
- }
- out.savedDataTotalSize += PES_packet_length;
- PES_packet_length = 0;
- }
- skipBytes(PES_packet_length);
- }
- // Check for another PES Packet next:
- setParseState(PARSING_PES_PACKET);
- return acquiredStreamIdTag;
- }
MPEGProgramStreamParser::parsePESPacket函数会读取流中的数据,若读取到的流数据不是当前需要的就保存到缓存中,也就是其对应的OutputDescriptor实例。需要注意局部变量acquiredStreamIdTag,它的值被初始化为0, 只有当读取到当前所需要的流数据时才会被赋值。所以若未读取到所需要的数据,这个函数将返回0值。
前一篇文章对mpg文件处理的分析中,有个一个比较严重的错误,因为有些重要的细节没有注意到。mpg文件是音视频交错排列的,如果需要读取的是视频数据,但当前文件位置却是音频数据该怎么办?前面分析时说,将把遇到的音频数据保存到缓存中,直到读取到视频数据,live555中并非如此处理的。
先简单的说明一下mpg处理过程涉及的几个类的作用
MPEG1or2Demux, 对应一个session,完成文件的解复用操作。其读取文件的过程是通过ByteStreamFileSource实现的
MPEG1or2DemuxedElementaryStream,作为source,对应一个流,一个或多个实例对应一个MPEG1or2Demux
MPEG1or2DemuxedServerMediaSubsession,作为subsession
MPEG1or2FileServerDemux, 一个服务类,整个程序只有一个实例,MPEG1or2Demux、MPEG1or2DemuxedElementaryStream、MPEG1or2DemuxedServerMediaSubsession三个类将通过本类进行联系。完成的工作如下:
1)创建subsession实例(MPEG1or2DemuxedServerMediaSubsession);
2)为每一个session创建MPEG1or2Demux实例,用于解复用。
3)创建source实例(MPEG1or2DemuxedElementaryStream), subsession中
再来看一个重要的结构体OutputDescriptor,它被定义在MPEG1or2Demux类中,文件中的每一个流对应一个OutputDescriptor实例
需要注意以下3个成员:
isPotentiallyReadable,对应的流存在,即存在对应流的MPEG1or2DemuxedElementaryStream实例
isCurrentlyActive,对应的流是活动的,当从第一次从这个流中读取数据时就置为True, 停止读取后置为False
isCurrentlyAwaitingData,当前正需要从对应的流中读取数据。从文件中读取数据并分析后,通过此标志判断读取到的数据是否是当前需要的流数据,如果不是则需要数据存在到缓存中
我们来看MPEG1or2Demux::getNextFrame函数
useSavedData函数从缓存中读取数据,没什么好说的。简单看一下registerReadInterest函数
registerReadInterest函数中,最后三条语句应注意。isCurrentlyAwaitingData为True表示对应流需要读取数据,fNumPendingReads表示等读取数据的流计数。
现在再来看MPEG1or2Demux::getNextFrame函数中后面的if条件语句
条件一fNumPendingReads == 1,fNumPendingReads表示未读取到数据的流的个数。为什么同时读取数据的流会超过一个呢?对于mpg文件来说,需要读取音频时当前文件位置可能正好是一个视频包,这时无法读取到所需要的数据,就会递增fNumPendingReads变量。条件二fHaveUndeliveredData,表示前面的处理过程中,某一个流未能如愿读取到需要的数据。
上面的两个条件,我觉得有点问题,fNumPendingReads = 0是不可能的, fNumPendingReads>1时,表示前面的处理过程中,某一个流未能如愿读取到需要的数据,这时fHaveUndeliveredData就应该为True,这样的话if语句是不是永远为真呢?这个疑问有待确定。
继续来看MPEG1or2Demux::continueReadProcessing函数的实现
这个函数中有一个while循环,fParser->parse()返回当前读取到的数据的流标号,第一次循环过程的返回值必定是当前所需要的流数据,第二次循环返回值则是先前未读取到的流数据,当所有需要读取数据的流均已经读取数据后,fNumPendingReads值递减为0,循环结束。
再来看MPEGProgramStreamParser::parse函数
上面的函数中,最有catch语句,所以前面的函数调用必定会抛出异常。parsePESPacket函数返回流标识作为循环条件,所以这个函数最重要
我来看上面有中文注释的if结构,
第一个条件out.isCurrentlyAwaitingData为True时,表明读取到的数据正是需要读取的。这应该是正常的情况
第二个条件out.isCurrentlyActive为True时,这个条件是在表明对应的流是活动的,但当前并没有要求读取数据。这个条件只要流已经开始播放,在其停止之前都为True。这个条件处理中,将对应流的fHaveUndeliveredData标记为True,最后抛出了异常,这个异常在MPEGProgramStreamParser::parse中处理的
第三个条件out.isPotentiallyReadable为True时,保存数据到缓存中。isPotentiallyReadable是在MPEG1or2DemuxedElementaryStream类构造函数中就置为True的。
根据上面的分析,当流正在播放时,无论如何都不会将数据保存到缓冲中,只有在流的播放停止的情况下才会进行。
从上面的分析中,我们还可以发现其它一些东西,这里指明一下。当未能读取到所以的流数据时,就不会调用after函数进行处理,但是after函数中完成了一个重要的功能,每一个after调用最后都会将下一次数据处理的任务注册到任务管理器中。那不调用after函数流的处理是不是会中断呢?当然不会。例如,当前处理过程没能读取到需要的视频数据,但在下一次处理音频数据时,会把视频数据也读取了,并调用视频处理的after函数,这就是MPEG1or2Demux::continueReadProcessing() 函数中循环的作用。
一个需要进一步思考的问题:
1).按上述方式,当前需要处理的数据会推迟(直到其它流读取完数据),这样是不是会导致延时的时间比正常情况下要久,从而使得发送变慢呢?
2).out.isCurrentlyActive为True时,保存数据到缓存中,这样是不是更好些呢?
先简单的说明一下mpg处理过程涉及的几个类的作用
MPEG1or2Demux, 对应一个session,完成文件的解复用操作。其读取文件的过程是通过ByteStreamFileSource实现的
MPEG1or2DemuxedElementaryStream,作为source,对应一个流,一个或多个实例对应一个MPEG1or2Demux
MPEG1or2DemuxedServerMediaSubsession,作为subsession
MPEG1or2FileServerDemux, 一个服务类,整个程序只有一个实例,MPEG1or2Demux、MPEG1or2DemuxedElementaryStream、MPEG1or2DemuxedServerMediaSubsession三个类将通过本类进行联系。完成的工作如下:
1)创建subsession实例(MPEG1or2DemuxedServerMediaSubsession);
2)为每一个session创建MPEG1or2Demux实例,用于解复用。
3)创建source实例(MPEG1or2DemuxedElementaryStream), subsession中
再来看一个重要的结构体OutputDescriptor,它被定义在MPEG1or2Demux类中,文件中的每一个流对应一个OutputDescriptor实例
- // A descriptor for each possible stream id tag:
- typedef struct OutputDescriptor {
- // input parameters
- unsigned char* to; unsigned maxSize;
- FramedSource::afterGettingFunc* fAfterGettingFunc;
- void* afterGettingClientData;
- FramedSource::onCloseFunc* fOnCloseFunc;
- void* onCloseClientData;
- // output parameters
- unsigned frameSize; struct timeval presentationTime;
- class SavedData; // forward
- SavedData* savedDataHead;
- SavedData* savedDataTail;
- unsigned savedDataTotalSize;
- // status parameters
- Boolean isPotentiallyReadable;
- Boolean isCurrentlyActive;
- Boolean isCurrentlyAwaitingData;
- } OutputDescriptor_t;
- OutputDescriptor_t fOutput[256];
需要注意以下3个成员:
isPotentiallyReadable,对应的流存在,即存在对应流的MPEG1or2DemuxedElementaryStream实例
isCurrentlyActive,对应的流是活动的,当从第一次从这个流中读取数据时就置为True, 停止读取后置为False
isCurrentlyAwaitingData,当前正需要从对应的流中读取数据。从文件中读取数据并分析后,通过此标志判断读取到的数据是否是当前需要的流数据,如果不是则需要数据存在到缓存中
我们来看MPEG1or2Demux::getNextFrame函数
- void MPEG1or2Demux::getNextFrame(u_int8_t streamIdTag,
- unsigned char* to, unsigned maxSize,
- FramedSource::afterGettingFunc* afterGettingFunc,
- void* afterGettingClientData,
- FramedSource::onCloseFunc* onCloseFunc,
- void* onCloseClientData) {
- // First, check whether we have saved data for this stream id:
- //从缓存中读取数据
- if (useSavedData(streamIdTag, to, maxSize,
- afterGettingFunc, afterGettingClientData)) {
- return;
- }
- // Then save the parameters of the specified stream id:
- // 将信息保存到流对应的OutputDescriptor_t实例中
- registerReadInterest(streamIdTag, to, maxSize,
- afterGettingFunc, afterGettingClientData,
- onCloseFunc, onCloseClientData);
- // Next, if we're the only currently pending read, continue looking for data:
- // 进一步处理,将从文件中读取数据
- if (fNumPendingReads == 1 || fHaveUndeliveredData) {
- fHaveUndeliveredData = 0;
- continueReadProcessing();
- } // otherwise the continued read processing has already been taken care of
- }
useSavedData函数从缓存中读取数据,没什么好说的。简单看一下registerReadInterest函数
- void MPEG1or2Demux::registerReadInterest(u_int8_t streamIdTag,
- unsigned char* to, unsigned maxSize,
- FramedSource::afterGettingFunc* afterGettingFunc,
- void* afterGettingClientData,
- FramedSource::onCloseFunc* onCloseFunc,
- void* onCloseClientData) {
- struct OutputDescriptor& out = fOutput[streamIdTag];
- // Make sure this stream is not already being read:
- if (out.isCurrentlyAwaitingData) {
- envir() << "MPEG1or2Demux::registerReadInterest(): attempt to read stream id "
- << (void*)streamIdTag << " more than once!\n";
- envir().internalError();
- }
- out.to = to; out.maxSize = maxSize;
- out.fAfterGettingFunc = afterGettingFunc;
- out.afterGettingClientData = afterGettingClientData;
- out.fOnCloseFunc = onCloseFunc;
- out.onCloseClientData = onCloseClientData;
- out.isCurrentlyActive = True; //当前流处理活动中
- out.isCurrentlyAwaitingData = True; //当前流需要读取数据
- // out.frameSize and out.presentationTime will be set when a frame's read
- ++fNumPendingReads; //等待读取数据的流计数
- }
registerReadInterest函数中,最后三条语句应注意。isCurrentlyAwaitingData为True表示对应流需要读取数据,fNumPendingReads表示等读取数据的流计数。
现在再来看MPEG1or2Demux::getNextFrame函数中后面的if条件语句
- if (fNumPendingReads == 1 || fHaveUndeliveredData) {
- fHaveUndeliveredData = 0;
- continueReadProcessing();
- }
条件一fNumPendingReads == 1,fNumPendingReads表示未读取到数据的流的个数。为什么同时读取数据的流会超过一个呢?对于mpg文件来说,需要读取音频时当前文件位置可能正好是一个视频包,这时无法读取到所需要的数据,就会递增fNumPendingReads变量。条件二fHaveUndeliveredData,表示前面的处理过程中,某一个流未能如愿读取到需要的数据。
上面的两个条件,我觉得有点问题,fNumPendingReads = 0是不可能的, fNumPendingReads>1时,表示前面的处理过程中,某一个流未能如愿读取到需要的数据,这时fHaveUndeliveredData就应该为True,这样的话if语句是不是永远为真呢?这个疑问有待确定。
继续来看MPEG1or2Demux::continueReadProcessing函数的实现
- void MPEG1or2Demux::continueReadProcessing() {
- while (fNumPendingReads > 0) {
- unsigned char acquiredStreamIdTag = fParser->parse();
- if (acquiredStreamIdTag != 0) {
- // We were able to acquire a frame from the input.
- struct OutputDescriptor& newOut = fOutput[acquiredStreamIdTag];
- newOut.isCurrentlyAwaitingData = False;
- // indicates that we can be read again
- // (This needs to be set before the 'after getting' call below,
- // in case it tries to read another frame)
- // Call our own 'after getting' function. Because we're not a 'leaf'
- // source, we can call this directly, without risking infinite recursion.
- if (newOut.fAfterGettingFunc != NULL) {
- (*newOut.fAfterGettingFunc)(newOut.afterGettingClientData,
- newOut.frameSize, 0 /* numTruncatedBytes */,
- newOut.presentationTime,
- 0 /* durationInMicroseconds ?????#####*/);
- --fNumPendingReads;
- }
- } else {
- // We were unable to parse a complete frame from the input, because:
- // - we had to read more data from the source stream, or
- // - we found a frame for a stream that was being read, but whose
- // reader is not ready to get the frame right now, or
- // - the source stream has ended.
- break;
- }
- }
- }
这个函数中有一个while循环,fParser->parse()返回当前读取到的数据的流标号,第一次循环过程的返回值必定是当前所需要的流数据,第二次循环返回值则是先前未读取到的流数据,当所有需要读取数据的流均已经读取数据后,fNumPendingReads值递减为0,循环结束。
再来看MPEGProgramStreamParser::parse函数
- unsigned char MPEGProgramStreamParser::parse() {
- unsigned char acquiredStreamTagId = 0;
- try {
- do {
- switch (fCurrentParseState) {
- case PARSING_PACK_HEADER: {
- parsePackHeader();
- break;
- }
- case PARSING_SYSTEM_HEADER: {
- parseSystemHeader();
- break;
- }
- case PARSING_PES_PACKET: {
- acquiredStreamTagId = parsePESPacket();
- break;
- }
- }
- } while(acquiredStreamTagId == 0);
- return acquiredStreamTagId;
- } catch (int /*e*/) {
- #ifdef DEBUG
- fprintf(stderr, "MPEGProgramStreamParser::parse() EXCEPTION (This is normal behavior - *not* an error)\n");
- fflush(stderr);
- #endif
- return 0; // the parsing got interrupted
- }
- }
上面的函数中,最有catch语句,所以前面的函数调用必定会抛出异常。parsePESPacket函数返回流标识作为循环条件,所以这个函数最重要
- unsigned char MPEGProgramStreamParser::parsePESPacket() {
- ...
- // Check whether our using source is interested in this stream type.
- // If so, deliver the frame to him:
- MPEG1or2Demux::OutputDescriptor_t& out = fUsingDemux->fOutput[stream_id];
- if (out.isCurrentlyAwaitingData) { //当前流正在等待数据
- unsigned numBytesToCopy;
- if (PES_packet_length > out.maxSize) {
- fUsingDemux->envir() << "MPEGProgramStreamParser::parsePESPacket() error: PES_packet_length ("
- << PES_packet_length
- << ") exceeds max frame size asked for ("
- << out.maxSize << ")\n";
- numBytesToCopy = out.maxSize;
- } else {
- numBytesToCopy = PES_packet_length;
- }
- getBytes(out.to, numBytesToCopy);
- out.frameSize = numBytesToCopy;
- // set out.presentationTime later #####
- acquiredStreamIdTag = stream_id;
- PES_packet_length -= numBytesToCopy;
- } else if (out.isCurrentlyActive) { //当前流是活动的
- // Someone has been reading this stream, but isn't right now.
- // We can't deliver this frame until he asks for it, so punt for now.
- // The next time he asks for a frame, he'll get it.
- restoreSavedParserState(); // so we read from the beginning next time
- fUsingDemux->fHaveUndeliveredData = True; //有未投递的数据
- throw READER_NOT_READY; //直接抛出异常,
- } else if (out.isPotentiallyReadable && //当前流需要读取数据
- out.savedDataTotalSize + PES_packet_length < 1000000 /*limit*/) {
- // Someone is interested in this stream, but hasn't begun reading it yet.
- // Save this data, so that the reader will get it when he later asks for it.
- //
- // 从文件中读取的数据,当前并不需要,所以就把它保存到存在中
- //
- unsigned char* buf = new unsigned char[PES_packet_length];
- getBytes(buf, PES_packet_length);
- MPEG1or2Demux::OutputDescriptor::SavedData* savedData
- = new MPEG1or2Demux::OutputDescriptor::SavedData(buf, PES_packet_length);
- if (out.savedDataHead == NULL) {
- out.savedDataHead = out.savedDataTail = savedData;
- } else {
- out.savedDataTail->next = savedData;
- out.savedDataTail = savedData;
- }
- out.savedDataTotalSize += PES_packet_length;
- PES_packet_length = 0;
- }
- skipBytes(PES_packet_length);
- }
- // Check for another PES Packet next:
- setParseState(PARSING_PES_PACKET);
- return acquiredStreamIdTag;
- }
我来看上面有中文注释的if结构,
第一个条件out.isCurrentlyAwaitingData为True时,表明读取到的数据正是需要读取的。这应该是正常的情况
第二个条件out.isCurrentlyActive为True时,这个条件是在表明对应的流是活动的,但当前并没有要求读取数据。这个条件只要流已经开始播放,在其停止之前都为True。这个条件处理中,将对应流的fHaveUndeliveredData标记为True,最后抛出了异常,这个异常在MPEGProgramStreamParser::parse中处理的
第三个条件out.isPotentiallyReadable为True时,保存数据到缓存中。isPotentiallyReadable是在MPEG1or2DemuxedElementaryStream类构造函数中就置为True的。
根据上面的分析,当流正在播放时,无论如何都不会将数据保存到缓冲中,只有在流的播放停止的情况下才会进行。
从上面的分析中,我们还可以发现其它一些东西,这里指明一下。当未能读取到所以的流数据时,就不会调用after函数进行处理,但是after函数中完成了一个重要的功能,每一个after调用最后都会将下一次数据处理的任务注册到任务管理器中。那不调用after函数流的处理是不是会中断呢?当然不会。例如,当前处理过程没能读取到需要的视频数据,但在下一次处理音频数据时,会把视频数据也读取了,并调用视频处理的after函数,这就是MPEG1or2Demux::continueReadProcessing() 函数中循环的作用。
一个需要进一步思考的问题:
1).按上述方式,当前需要处理的数据会推迟(直到其它流读取完数据),这样是不是会导致延时的时间比正常情况下要久,从而使得发送变慢呢?
2).out.isCurrentlyActive为True时,保存数据到缓存中,这样是不是更好些呢?