live555学习笔记10-h264 RTP传输详解(2)

来源:互联网 发布:淘宝网汽车模型 编辑:程序博客网 时间:2024/06/05 02:37

十 h264 RTP传输详解(2)


上一章并没有把打开文件分析文件的代码找到,因为发现它隐藏得比较深,而且H264的Source又有多个,形成了连环计。所以此章中就将文件处理与H264的Source们并在一起分析吧。
从哪里开始呢?从source开始吧!为什么要从它开始呢?我就想从这里开始,行了吧?

FramedSource* H264VideoFileServerMediaSubsession::createNewStreamSource(unsigned /*clientSessionId*/,unsigned& estBitrate){estBitrate = 500; // kbps, estimate// Create the video source:ByteStreamFileSource* fileSource = ByteStreamFileSource::createNew(envir(),fFileName);if (fileSource == NULL)return NULL;fFileSize = fileSource->fileSize();// Create a framer for the Video Elementary Stream:return H264VideoStreamFramer::createNew(envir(), fileSource);}
先创建一个ByteStreamFileSource,显然这是一个从文件按字节读取数据的source,没什么可细说的。但是,打开文件,读写文件操作的确就在其中。最终来处理h264文件,分析其格式,解析出帧或nal的应是这个source: H264VideoStreamFramer。打开文件的地方找到了,但分析文件的代码才是更有价值的。那我们只能来看H264VideoStreamFramer。
H264VideoStreamFramer继承自MPEGVideoStreamFramer,MPEGVideoStreamFramer继承自FramedFilter,FramedFilter继承自FramedSource。
啊,中间又冒出个Filter。看到它,是不是联想到了DirectShow的filter?或者说Photoshop中的filter?它们的意义应该都差不多吧?即插入到source和render(sink)之间的处理媒体数据的东东?如果这样理解,还是更接近于photoshop中的概念。唉,说实话,我估计自己说的也不全对,反正就这样认识吧,谬不了一千里。既然我们这样认识了,那么我们就有理由相信可能会出现多个filter们一个连一个,然后高唱:手牵着脚脚牵着手一起向前走...
H264VideoStreamFramer继承自MPEGVideoStreamFramer,MPEGVideoStreamFramer比较简单,它只是把一些工作交给了MPEGVideoStreamParser(又出来个parser,这可是个新东西哦,先不要管它吧),重点来看一下。
构造函数:
H264VideoStreamFramer::H264VideoStreamFramer(UsageEnvironment& env,FramedSource* inputSource,Boolean createParser,Boolean includeStartCodeInOutput): MPEGVideoStreamFramer(env, inputSource),fIncludeStartCodeInOutput(includeStartCodeInOutput),fLastSeenSPS(NULL),fLastSeenSPSSize(0),fLastSeenPPS(NULL),fLastSeenPPSSize(0){fParser = createParser ?new H264VideoStreamParser(this, inputSource,includeStartCodeInOutput) :NULL;fNextPresentationTime = fPresentationTimeBase;fFrameRate = 25.0; // We assume a frame rate of 25 fps, //unless we learn otherwise (from parsing a Sequence Parameter Set NAL unit)}
由于createParser肯定为真,所以主要内容是创建了H264VideoStreamParser对象(先不管这个parser)。
其它的函数就没什么可看的了,都集中在所保存的PPS与SPS上。看来分析工作移到了H264VideoStreamParser,Parser嘛,就是分析器。分析器的基类是StreamParser。StreamParser做了不少的工作,那我们就先搞明白StreamParser做了哪些工作吧,并且可能为继承者提供什么样的调用框架呢?.....我看完了,呵呵。直接说分析结果吧:
StreamParser的主要工作是实现了对数据以位为单位进行访问。因为在处理媒体格式时,按位分析是很常见的情况。这两个函数skipBits(unsigned numBits)和unsigned getBits(unsigned numBits)很明显是基于位的操作。unsigned char* fBank[2]这个变量中的两个缓冲区被轮换使用。这个类中保存了一个Source,理所当然地它应该保存ByteStreamFileSource的实例,而不是FramedFilter的。那些getBytes()或getBits()最终会导致读文件的操作。从文件读取一次数据后,StreamParser::afterGettingBytes1()被调用,StreamParser::afterGettingBytes1()中做一点简单的工作后便调用fClientContinueFunc这个回调函数。fClientContinueFunc可能指向Frame的函数体也可能是指向RtpSink的函数体--因为Framer完全可以把RtpSink的函数体传给Parser。至于到底指向哪个,只能在进一步分析之后才得知。

下面再来分析StreamParser的儿子:MPEGVideoStreamParser。

MPEGVideoStreamParser::MPEGVideoStreamParser(MPEGVideoStreamFramer* usingSource,FramedSource* inputSource): StreamParser(inputSource,FramedSource::handleClosure, usingSource,&MPEGVideoStreamFramer::continueReadProcessing,usingSource),fUsingSource(usingSource){}

MPEGVideoStreamParser的构造函数中有很多有意思的东西。
首先参数usingSource是什么意思?表示正在使用这个Parser的Source? inputSource 很明确,就是能获取数据的source,也就是 ByteStreamFileSource。而且很明显的,StreamParser中保存的source是ByteStreamFileSource。从传入给StreamParser的回调函数以及它们的参数可以看出,这些回调函数全是指向的StreamParser的子类的函数(为啥不用虚函数的方式?哦,回调函数全是静态函数,不能成为虚函数)。这说明在每读一次数据后,MPEGVideoStreamFramer::continueReadProcessing()被调用,在其中对帧进行界定和分析,完成后再调用RTPSink的相应函数,RTPSink中对帧进行打包和发送(还记得吗,不记得了请回头看以前的章节)。
MPEGVideoStreamParser的fTo是RTPSink传入的缓冲指针,其saveByte(),save4Bytes()是把数据从StreamParser的缓冲把数据复制到fTo中,是给继承类使用的。saveToNextCode()是复制数据直到遇到一个同步字节串(比如h264中分隔nal的那一陀东东,当然此处的跟h264还不一样),也是给继承类使用的。纯虚函数parse()很明显是留继承类去写帧分析代码的地方。registerReadInterest()被调用者用来告诉MPEGVideoStreamParser其接收帧的缓冲地址与容量。
现在应该来分析一下MPEGVideoStreamFramer,以明确MPEGVideoStreamFramer与MPEGVideoStreamParser是怎样配合的。
MPEGVideoStreamFramer中用到Parser的重要的函数只有两个,一是:

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

很简单,只是告诉了Parser保存帧的缓冲和缓冲的大小,然后执行continueReadProcessing(),那么来看一下continueReadProcessing():

void MPEGVideoStreamFramer::continueReadProcessing(){unsigned acquiredFrameSize = fParser->parse();if (acquiredFrameSize > 0) {// We were able to acquire a frame from the input.// It has already been copied to the reader's space.fFrameSize = acquiredFrameSize;fNumTruncatedBytes = fParser->numTruncatedBytes();// "fPresentationTime" should have already been computed.// Compute "fDurationInMicroseconds" now:fDurationInMicroseconds =(fFrameRate == 0.0 || ((int) fPictureCount) < 0) ?0 : (unsigned) ((fPictureCount * 1000000) / fFrameRate);fPictureCount = 0;// Call our own 'after getting' function.  Because we're not a 'leaf'// source, we can call this directly, without risking infinite recursion.afterGetting(this);} else {// We were unable to parse a complete frame from the input, because:// - we had to read more data from the source stream, or// - the source stream has ended.}}
先利用Parser进行分析(应该是解析出一帧吧?),分析完成后,帧数据已到了MPEGVideoStreamFramer的缓冲fTo中。计算出帧的持续时间后,调用FrameSource的afterGetting(),最终会调用RTPSink的函数。
看到这里,可以总结一下,其实看来看去,Parser直正被外部使用的函数几乎只有一个:parse()。

下面可以看H264VideoStreamParser了。其实也很简单,多了一些用于分析h264格式的函数,当然是非公开的,只供自己使用的。在哪里使用呢?当然是在parser()中使用。至于H264VideoStreamFramer前面已经说过了,没什么太多的东西,所以就不看了。总结起来也就是这样:RTPSink向H264VideoStreamFramer要下一帧(其实h264中肯定不是一帧了,而是一个NAL Unit),H264VideoStreamFramer告诉H264VideoStreamParser输出缓冲和容内的字节数,然后调用H264VideoStreamParser的parser()函数,parser()中调用ByteStreamFileSource从文件中读取数据,直到parser()获得完整的一帧,parser()返回,H264VideoStreamFramer进行一些自己的处理后把这一帧返回给了RTPSink(当然是以回调函数的方式返回)。
还有一个东西,H264FUAFragmenter,被H264VideoRTPSink所使用,继承自FramedFilter。它最初在RTPSink开始播放后创建,如下:

Boolean H264VideoRTPSink::continuePlaying(){// First, check whether we have a 'fragmenter' class set up yet.// If not, create it now:if (fOurFragmenter == NULL) {fOurFragmenter = new H264FUAFragmenter(envir(), fSource,OutPacketBuffer::maxSize,ourMaxPacketSize() - 12/*RTP hdr size*/);fSource = fOurFragmenter;}// Then call the parent class's implementation:return MultiFramedRTPSink::continuePlaying();}
并且它取代了H264VideoStreamFramer成为直接与RTPSink发生关系的source.如此一来,RTPSink要获取帧时,都是从它获取的.看它最主要的一个函数吧:

void H264FUAFragmenter::doGetNextFrame(){if (fNumValidDataBytes == 1) {// We have no NAL unit data currently in the buffer.  Read a new one:fInputSource->getNextFrame(&fInputBuffer[1], fInputBufferSize - 1,afterGettingFrame, this, FramedSource::handleClosure, this);} else {// We have NAL unit data in the buffer.  There are three cases to consider:// 1. There is a new NAL unit in the buffer, and it's small enough to deliver//    to the RTP sink (as is).// 2. There is a new NAL unit in the buffer, but it's too large to deliver to//    the RTP sink in its entirety.  Deliver the first fragment of this data,//    as a FU-A packet, with one extra preceding header byte.// 3. There is a NAL unit in the buffer, and we've already delivered some//    fragment(s) of this.  Deliver the next fragment of this data,//    as a FU-A packet, with two extra preceding header bytes.if (fMaxSize < fMaxOutputPacketSize) { // shouldn't happenenvir() << "H264FUAFragmenter::doGetNextFrame(): fMaxSize ("<< fMaxSize << ") is smaller than expected\n";} else {fMaxSize = fMaxOutputPacketSize;}fLastFragmentCompletedNALUnit = True; // by defaultif (fCurDataOffset == 1) { // case 1 or 2if (fNumValidDataBytes - 1 <= fMaxSize) { // case 1memmove(fTo, &fInputBuffer[1], fNumValidDataBytes - 1);fFrameSize = fNumValidDataBytes - 1;fCurDataOffset = fNumValidDataBytes;} else { // case 2// We need to send the NAL unit data as FU-A packets.  Deliver the first// packet now.  Note that we add FU indicator and FU header bytes to the front// of the packet (reusing the existing NAL header byte for the FU header).fInputBuffer[0] = (fInputBuffer[1] & 0xE0) | 28; // FU indicatorfInputBuffer[1] = 0x80 | (fInputBuffer[1] & 0x1F); // FU header (with S bit)memmove(fTo, fInputBuffer, fMaxSize);fFrameSize = fMaxSize;fCurDataOffset += fMaxSize - 1;fLastFragmentCompletedNALUnit = False;}} else { // case 3// We are sending this NAL unit data as FU-A packets.  We've already sent the// first packet (fragment).  Now, send the next fragment.  Note that we add// FU indicator and FU header bytes to the front.  (We reuse these bytes that// we already sent for the first fragment, but clear the S bit, and add the E// bit if this is the last fragment.)fInputBuffer[fCurDataOffset - 2] = fInputBuffer[0]; // FU indicatorfInputBuffer[fCurDataOffset - 1] = fInputBuffer[1] & ~0x80; // FU header (no S bit)unsigned numBytesToSend = 2 + fNumValidDataBytes - fCurDataOffset;if (numBytesToSend > fMaxSize) {// We can't send all of the remaining data this time:numBytesToSend = fMaxSize;fLastFragmentCompletedNALUnit = False;} else {// This is the last fragment:fInputBuffer[fCurDataOffset - 1] |= 0x40; // set the E bit in the FU headerfNumTruncatedBytes = fSaveNumTruncatedBytes;}memmove(fTo, &fInputBuffer[fCurDataOffset - 2], numBytesToSend);fFrameSize = numBytesToSend;fCurDataOffset += numBytesToSend - 2;}if (fCurDataOffset >= fNumValidDataBytes) {// We're done with this data.  Reset the pointers for receiving new data:fNumValidDataBytes = fCurDataOffset = 1;}// Complete delivery to the client:FramedSource::afterGetting(this);}}

如果输入缓冲中没有数据,调用fInputSource->getNextFrame(),fInputSource是H264VideoStreamFramer,H264VideoStreamFramer的getNextFrame()会调用H264VideoStreamParser的parser(),parser()又调用ByteStreamFileSource获取数据,然后分析,parser()完成后会调用:

void H264FUAFragmenter::afterGettingFrame1(unsigned frameSize,unsigned numTruncatedBytes,struct timeval presentationTime,unsigned durationInMicroseconds){fNumValidDataBytes += frameSize;fSaveNumTruncatedBytes = numTruncatedBytes;fPresentationTime = presentationTime;fDurationInMicroseconds = durationInMicroseconds;// Deliver data to the client:doGetNextFrame();}
然后又调用回H264FUAFragmenter::doGetNextFrame(),此时输入缓冲中有数据了,H264FUAFragmenter就进行分析处理.H264FUAFragmenter又对数据做了什么呢?



原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 奶瓶吸奶费力不顺畅怎么办 宝宝吃奶粉太勤怎么办 香蕉和地瓜一起吃了怎么办 贝亲奶瓶泡沫多怎么办 四个多月的宝宝拉肚子怎么办 宝宝四个月了拉肚子怎么办 四个月宝宝火大怎么办 刚出生的宝宝便秘怎么办 小宝宝破腹产吸了几口羊水怎么办 换奶粉不拉屎了怎么办 婴儿吃奶粉不拉屎怎么办 1岁半突然不喝奶怎么办 6个月宝宝不吃奶粉怎么办 7个月宝宝不吃奶粉怎么办 5个月宝宝不吃奶粉怎么办 一岁两个月宝宝不长肉怎么办 7个月宝宝肚子疼怎么办 奶喝一半凉了怎么办 5个月孩子厌奶怎么办 怀孕后特别不爱吃水果怎么办 宝宝吃了无比滴怎么办 婴儿上火怎么办吃什么可以去火 肚子胀怎么办最快的方法 40天婴儿拉水怎么办 8个月宝宝坐不稳怎么办 宝宝段奶不吃奶粉怎么办 3个月宝宝头睡偏了怎么办 2个月婴儿抱着睡怎么办 两个半月的宝宝睡眠少怎么办 七个月宝宝不愿意坐怎么办 一个多月的宝宝老是哭闹怎么办 宝宝头老往后仰怎么办 8个月宝宝不会爬怎么办 孩子个头长得慢怎么办 宝宝个头长得慢怎么办 婴儿个头长得慢怎么办 11个月宝宝认生怎么办 3个月宝宝认人怎么办 3个月的宝宝认生怎么办 两个月的宝宝睡觉一惊一惊怎么办 六个月宝宝不喜欢喝水怎么办