前一篇文章对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实例
-
- typedef struct OutputDescriptor {
-
- unsigned char* to; unsigned maxSize;
- FramedSource::afterGettingFunc* fAfterGettingFunc;
- void* afterGettingClientData;
- FramedSource::onCloseFunc* fOnCloseFunc;
- void* onCloseClientData;
-
-
-
- unsigned frameSize; struct timeval presentationTime;
- class SavedData;
- SavedData* savedDataHead;
- SavedData* savedDataTail;
- unsigned savedDataTotalSize;
-
-
-
- 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) {
-
-
- if (useSavedData(streamIdTag, to, maxSize,
- afterGettingFunc, afterGettingClientData)) {
- return;
- }
-
-
-
-
- registerReadInterest(streamIdTag, to, maxSize,
- afterGettingFunc, afterGettingClientData,
- onCloseFunc, onCloseClientData);
-
-
-
-
- if (fNumPendingReads == 1 || fHaveUndeliveredData) {
- fHaveUndeliveredData = 0;
- continueReadProcessing();
- }
- }
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];
-
-
-
- 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;
-
-
-
- ++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) {
-
- struct OutputDescriptor& newOut = fOutput[acquiredStreamIdTag];
- newOut.isCurrentlyAwaitingData = False;
-
-
-
-
-
-
-
- if (newOut.fAfterGettingFunc != NULL) {
- (*newOut.fAfterGettingFunc)(newOut.afterGettingClientData,
- newOut.frameSize, 0 ,
- newOut.presentationTime,
- 0 );
- --fNumPendingReads;
- }
- } else {
-
-
-
-
-
- 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 ) {
- #ifdef DEBUG
- fprintf(stderr, "MPEGProgramStreamParser::parse() EXCEPTION (This is normal behavior - *not* an error)\n");
- fflush(stderr);
- #endif
- return 0;
- }
- }
上面的函数中,最有catch语句,所以前面的函数调用必定会抛出异常。parsePESPacket函数返回流标识作为循环条件,所以这个函数最重要
- unsigned char MPEGProgramStreamParser::parsePESPacket() {
-
-
- ...
-
-
-
-
- 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;
-
-
-
- acquiredStreamIdTag = stream_id;
- PES_packet_length -= numBytesToCopy;
- } else if (out.isCurrentlyActive) {
-
-
-
-
-
- restoreSavedParserState();
- fUsingDemux->fHaveUndeliveredData = True;
- throw READER_NOT_READY;
- } else if (out.isPotentiallyReadable &&
- out.savedDataTotalSize + PES_packet_length < 1000000 ) {
-
-
-
-
-
- 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);
- }
-
-
-
- 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时,保存数据到缓存中,这样是不是更好些呢?