RFC3199定义了MP3的RTP打包规则。首先来看看处理*.mp3的sesseion是如何创建的

static ServerMediaSession* createNewSMS(UsageEnvironment& env,char const* fileName, FILE* /*fid*/) {
...else if (strcmp(extension, ".mp3") == 0) {// Assumed to be a MPEG-1 or 2 Audio file:NEW_SMS("MPEG-1 or 2 Audio");//去注释STREAM_USING_ADUS宏,传输时使用ADUs,而不是MP3裸帧// To stream using 'ADUs' rather than raw MP3 frames, uncomment the following:
//#define STREAM_USING_ADUS 1//去注释INTERLEAVE_ADUS宏,在传输之前奖ADUs重排序(交错)// To also reorder ADUs before streaming, uncomment the following:
//#define INTERLEAVE_ADUS 1// (For more information about ADUs and interleaving,//  see <http://www.live555.com/rtp-mp3/>)Boolean useADUs = False;Interleaving* interleaving = NULL;
#ifdef STREAM_USING_ADUSuseADUs = True;
#ifdef INTERLEAVE_ADUSunsigned char interleaveCycle[] = {0,2,1,3}; // or choose your own...unsigned const interleaveCycleSize= (sizeof interleaveCycle)/(sizeof (unsigned char));interleaving = new Interleaving(interleaveCycleSize, interleaveCycle);  //创建一个用于交错的filter
#endif
#endifsms->addSubsession(MP3AudioFileServerMediaSubsession::createNew(env, fileName, reuseSource, useADUs, interleaving)); //注意这里传递的参数}
}

上面的代码打开宏STREAM_USING_ADUS,则会将MP3帧打包成ADU后,再发送。打开宏INTERLEAVE_ADUS,MP3打包成ADU后,将进行交错排列。可以看到默认情况下,这两个选项都是关闭的。在MP3AudioFileServerMediaSubsession::createNewStreamSource函数中会调用一个createNewStreamSourceCommon函数,处理ADU的打包操作。

FramedSource* MP3AudioFileServerMediaSubsession
::createNewStreamSourceCommon(FramedSource* baseMP3Source, unsigned mp3NumBytes, unsigned& estBitrate) {FramedSource* streamSource;fFileDuration = 0.0;do {streamSource = baseMP3Source; // by defaultif (streamSource == NULL) break;// Use the MP3 file size, plus the duration, to estimate the stream's bitrate:if (mp3NumBytes > 0 && fFileDuration > 0.0) {estBitrate = (unsigned)(mp3NumBytes/(125*fFileDuration) + 0.5); // kbps, rounded} else {estBitrate = 128; // kbps, estimate}if (fGenerateADUs) {  //判断是否打包成ADU后发送// Add a filter that converts the source MP3s to ADUs:streamSource = ADUFromMP3Source::createNew(envir(), streamSource);if (streamSource == NULL) break;if (fInterleaving != NULL) {// Add another filter that interleaves the ADUs before packetizing:streamSource = MP3ADUinterleaver::createNew(envir(), *fInterleaving,streamSource);if (streamSource == NULL) break;}} else if (fFileDuration > 0.0) {   ////注意了,对于不需要打包成ADU的情况,这里有一个打包再解包的过程,为是的方便定位//// Because this is a seekable file, insert a pair of filters: one that// converts the input MP3 stream to ADUs; another that converts these// ADUs back to MP3.  This allows us to seek within the input stream without// tripping over the MP3 'bit reservoir':streamSource = ADUFromMP3Source::createNew(envir(), streamSource);if (streamSource == NULL) break;streamSource = MP3FromADUSource::createNew(envir(), streamSource);if (streamSource == NULL) break;}} while (0);return streamSource;  //返回的是最外层的source
}

上面的代码处理了两种情况:一是根据fGenerateADUs的值决定是否生成ADU后再发送,二是对于不需要打包成ADU的情况,这里有一个打包再解包的过程,为是的方便定位。   下面来看ADU的打包过程

void ADUFromMP3Source::doGetNextFrame() {if (!fAreEnqueueingMP3Frame) {    //分支1// Arrange to enqueue a new MP3 frame:fTotalDataSizeBeforePreviousRead = fSegments->totalDataSize();fAreEnqueueingMP3Frame = True;fSegments->enqueueNewSegment(fInputSource, this);} else {  //分支2// Deliver an ADU from a previously-read MP3 frame:fAreEnqueueingMP3Frame = False;if (!doGetNextFrame1()) {// An internal error occurred; act as if our source went away:FramedSource::handleClosure(this);}}
}

fAreEnqueueingMP3Frame初始直为False, 首先会执行第1个分支。从后面的分析中,我们会发现fSegments->enqueueNewSegmen函数最后又调用了ADUFromMP3Source::doGetNextFrame函数,这时将执行第二个分支。

void SegmentQueue::enqueueNewSegment(FramedSource* inputSource,FramedSource* usingSource) {if (isFull()) {usingSource->envir() << "SegmentQueue::enqueueNewSegment() overflow\n";FramedSource::handleClosure(usingSource);return;}fUsingSource = usingSource;Segment& seg = nextFreeSegment();//从source获取mp3数据inputSource->getNextFrame(seg.buf, sizeof seg.buf,sqAfterGettingSegment, this,FramedSource::handleClosure, usingSource);
}

从source中获取mp3数据的过程就不关注了,直接看sqAfterGettingSegment函数的处理

void SegmentQueue::sqAfterGettingSegment(void* clientData,unsigned numBytesRead,unsigned /*numTruncatedBytes*/,struct timeval presentationTime,unsigned durationInMicroseconds) {SegmentQueue* segQueue = (SegmentQueue*)clientData;Segment& seg = segQueue->nextFreeSegment();       //获取刚才读入数据的segmentseg.presentationTime = presentationTime;seg.durationInMicroseconds = durationInMicroseconds;if (segQueue->sqAfterGettingCommon(seg, numBytesRead)) {  //分析读取到的mp3 frame
#ifdef DEBUGchar const* direction = segQueue->fDirectionIsToADU ? "m->a" : "a->m";fprintf(stderr, "%s:read frame %d<-%d, fs:%d, sis:%d, dh:%d, (descriptor size: %d)\n", direction, seg.aduSize, seg.backpointer, seg.frameSize, seg.sideInfoSize, seg.dataHere(), seg.descriptorSize);
#endif}// Continue our original calling source where it left off:segQueue->fUsingSource->doGetNextFrame();  //又一次调用了doGetNextFrame函数
}

获取到的数据存储在segment中,先调用sqAfterGettingCommon函数对frame进行分析。最后又一次调用了ADUFromMP3Source::doGetNextFrame函数,只不过这次将会执行第2个分支。先来看看sqAfterGettingCommon函数的细节。

// Common code called after a new segment is enqueued
Boolean SegmentQueue::sqAfterGettingCommon(Segment& seg,unsigned numBytesRead) {unsigned char* fromPtr = seg.buf;//是否已经包含了ADU标识if (fIncludeADUdescriptors) {  //对ADUFromMP3Source中的SegmentQueue,这个值必然为False// The newly-read data is assumed to be an ADU with a descriptor// in front////getRemainingFrameSize中根据fromPtr第1个字节决定了descriptor为1个或者两个字节//(void)ADUdescriptor::getRemainingFrameSize(fromPtr); seg.descriptorSize = (unsigned)(fromPtr-seg.buf);} else {seg.descriptorSize = 0;}// parse the MP3-specific info in the frame to get the ADU paramsunsigned hdr;  //4字节的frame头MP3SideInfo sideInfo;  //side info//分析frame,获取相关信息if (!GetADUInfoFromMP3Frame(fromPtr, numBytesRead,hdr, seg.frameSize,sideInfo, seg.sideInfoSize,seg.backpointer, seg.aduSize)) {return False;}// If we've just read an ADU (rather than a regular MP3 frame), then use the// entire "numBytesRead" data for the 'aduSize', so that we include any// 'ancillary data' that may be present at the end of the ADU:if (!fDirectionIsToADU) { //默认值为Trueunsigned newADUSize= numBytesRead - seg.descriptorSize - 4/*header size*/ - seg.sideInfoSize;if (newADUSize > seg.aduSize) seg.aduSize = newADUSize;}fTotalDataSize += seg.dataHere();fNextFreeIndex = nextIndex(fNextFreeIndex);   //更新空闲segment索引return True;
}

上面代码中的fIncludeADUdescriptors变量,表示读入到segment中的数据是否包含ADU标识符,显然ADUFromMP3Source处理的原始数据肯定是不包含ADU标识符的。

GetADUInfoFromMP3Frame函数从mp3帧中获取ADU相关的信息

Boolean GetADUInfoFromMP3Frame(unsigned char const* framePtr,unsigned totFrameSize,unsigned& hdr, unsigned& frameSize,MP3SideInfo& sideInfo, unsigned& sideInfoSize,unsigned& backpointer, unsigned& aduSize) {if (totFrameSize < 4) return False; // there's not enough dataMP3FrameParams fr;    //MP3FrameParams类专门用来分析mp3帧信息//前4个字节是mp3的帧头fr.hdr =   ((unsigned)framePtr[0] << 24) | ((unsigned)framePtr[1] << 16)| ((unsigned)framePtr[2] << 8) | (unsigned)framePtr[3];fr.setParamsFromHeader(); //分析4字节头部fr.setBytePointer(framePtr + 4, totFrameSize - 4); // skip hdrframeSize = 4 + fr.frameSize;//非mp3帧(mp2或者mp1)if (fr.layer != 3) {// Special case for non-layer III framesbackpointer = 0;sideInfoSize = 0;aduSize = fr.frameSize;return True;}sideInfoSize = fr.sideInfoSize;if (totFrameSize < 4 + sideInfoSize) return False; // not enough datafr.getSideInfo(sideInfo);hdr = fr.hdr;  //4字节头backpointer = sideInfo.main_data_begin;  //数据开始位置unsigned numBits = sideInfo.ch[0].gr[0].part2_3_length;numBits += sideInfo.ch[0].gr[1].part2_3_length;numBits += sideInfo.ch[1].gr[0].part2_3_length;numBits += sideInfo.ch[1].gr[1].part2_3_length;aduSize = (numBits+7)/8;  //adu字节数
#ifdef DEBUGfprintf(stderr, "mp3GetADUInfoFromFrame: hdr: %08x, frameSize: %d, part2_3_lengths: %d,%d,%d,%d, aduSize: %d, backpointer: %d\n", hdr, frameSize, sideInfo.ch[0].gr[0].part2_3_length, sideInfo.ch[0].gr[1].part2_3_length, sideInfo.ch[1].gr[0].part2_3_length, sideInfo.ch[1].gr[1].part2_3_length, aduSize, backpointer);
#endifreturn True;
}

来看MP3 frame头的分析

void MP3FrameParams::setParamsFromHeader() {if (hdr & (1<<20)) {isMPEG2 = (hdr & (1<<19)) ? 0x0 : 0x1;isMPEG2_5 = 0;}else {isMPEG2 = 1;isMPEG2_5 = 1;}layer = 4-((hdr>>17)&3);if (layer == 4) layer = 3; // layer==4 is not allowedbitrateIndex = ((hdr>>12)&0xf);if (isMPEG2_5) {samplingFreqIndex = ((hdr>>10)&0x3) + 6;} else {samplingFreqIndex = ((hdr>>10)&0x3) + (isMPEG2*3);}hasCRC = ((hdr>>16)&0x1)^0x1;padding   = ((hdr>>9)&0x1);extension = ((hdr>>8)&0x1);mode      = ((hdr>>6)&0x3);mode_ext  = ((hdr>>4)&0x3);copyright = ((hdr>>3)&0x1);original  = ((hdr>>2)&0x1);emphasis  = hdr & 0x3;stereo    = (mode == MPG_MD_MONO) ? 1 : 2;if (((hdr>>10)&0x3) == 0x3) {
#ifdef DEBUG_ERRORSfprintf(stderr,"Stream error - hdr: 0x%08x\n", hdr);
#endif}bitrate = live_tabsel[isMPEG2][layer-1][bitrateIndex];samplingFreq = live_freqs[samplingFreqIndex];isStereo = (stereo > 1);isFreeFormat = (bitrateIndex == 0);frameSize= ComputeFrameSize(bitrate, samplingFreq, padding, isMPEG2, layer); //计算frame的大小sideInfoSize = computeSideInfoSize();}

标准的mp3 frame(立体声不带CRC), 附加信息大小32位
frameSize的计算方式

unsigned ComputeFrameSize(unsigned bitrate, unsigned samplingFreq,Boolean usePadding, Boolean isMPEG2,unsigned char layer) {if (samplingFreq == 0) return 0;unsigned const bitrateMultiplier = (layer == 1) ? 12000*4 : 144000;unsigned framesize;framesize = bitrate*bitrateMultiplier;framesize /= samplingFreq<<isMPEG2;framesize = framesize + usePadding - 4;return framesize;
}

这里的bitrate单位为kbps
MP3帧长取决于位率和频率,计算公式为:
. mpeg1.0 layer1 : 帧长= (48000*bitrate)/sampling_freq + padding
layer2&3: 帧长= (144000*bitrate)/sampling_freq + padding
. mpeg2.0 layer1 : 帧长= (24000*bitrate)/sampling_freq + padding
layer2&3 : 帧长= (72000*bitrate)/sampling_freq + padding
根据公式,位率为128kbps,采样频率为44.1kHz,padding(帧长调节)为0时,帧长为417字节。

奇怪的是,padding为0,最后几个bit不是丢弃掉了吗?

到此,ADUFromMP3Source::doGetNextFrame()函数第1个分支分析完了,现在来看其第2个分支。分支2主要调用了ADUFromMP3Source::doGetNextFrame1函数。

Boolean ADUFromMP3Source::doGetNextFrame1() {// First, check whether we have enough previously-read data to output an// ADU for the last-read MP3 frame:unsigned tailIndex;Segment* tailSeg;Boolean needMoreData;if (fSegments->isEmpty()) {needMoreData = True;tailSeg = NULL; tailIndex = 0; // unneeded, but stops compiler warnings} else {tailIndex = SegmentQueue::prevIndex(fSegments->nextFreeIndex());  //获取上一个填充了数据的segmenttailSeg = &(fSegments->s[tailIndex]);needMoreData= fTotalDataSizeBeforePreviousRead < tailSeg->backpointer // bp points back too far|| tailSeg->backpointer + tailSeg->dataHere() < tailSeg->aduSize; // not enough data}if (needMoreData) {  //// We don't have enough data to output an ADU from the last-read MP3// frame, so need to read another one and try again:doGetNextFrame();  //没有足够的数据,则重新读取数据return True;}//从尾部的segment中获取一个ADU// Output an ADU from the tail segment:fFrameSize = tailSeg->headerSize+tailSeg->sideInfoSize+tailSeg->aduSize;fPresentationTime = tailSeg->presentationTime;fDurationInMicroseconds = tailSeg->durationInMicroseconds;unsigned descriptorSize= fIncludeADUdescriptors ? ADUdescriptor::computeSize(fFrameSize) : 0;if (descriptorSize + fFrameSize > fMaxSize) {envir() << "ADUFromMP3Source::doGetNextFrame1(): not enough room ("<< descriptorSize + fFrameSize << ">"<< fMaxSize << ")\n";fFrameSize = 0;return False;}unsigned char* toPtr = fTo;//输出ADU描述符// output the ADU descriptor:if (fIncludeADUdescriptors) {  //默认值为FalsefFrameSize += ADUdescriptor::generateDescriptor(toPtr, fFrameSize); }//输出header和side info// output header and side info:memmove(toPtr, tailSeg->dataStart(),tailSeg->headerSize + tailSeg->sideInfoSize);toPtr += tailSeg->headerSize + tailSeg->sideInfoSize;//输出数据// go back to the frame that contains the start of our data:unsigned offset = 0;unsigned i = tailIndex;unsigned prevBytes = tailSeg->backpointer;while (prevBytes > 0) {i = SegmentQueue::prevIndex(i);unsigned dataHere = fSegments->s[i].dataHere();if (dataHere < prevBytes) {prevBytes -= dataHere;} else {offset = dataHere - prevBytes;break;}}// dequeue any segments that we no longer need:while (fSegments->headIndex() != i) {fSegments->dequeue(); // we're done with it}unsigned bytesToUse = tailSeg->aduSize;while (bytesToUse > 0) {Segment& seg = fSegments->s[i];unsigned char* fromPtr= &seg.dataStart()[seg.headerSize + seg.sideInfoSize + offset];unsigned dataHere = seg.dataHere() - offset;unsigned bytesUsedHere = dataHere < bytesToUse ? dataHere : bytesToUse;memmove(toPtr, fromPtr, bytesUsedHere);bytesToUse -= bytesUsedHere;toPtr += bytesUsedHere;offset = 0;i = SegmentQueue::nextIndex(i);}if (fFrameCounter++%fScale == 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 {// Don't use this frame; get another one:doGetNextFrame();}return True;
}

live555源码分析----关于mp3的处理相关推荐

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

    live555源码分析系列 live555源码分析(一)live555初体验 live555源码分析(二)基本组件上 live555源码分析(三)基本组件下 live555源码分析(四)RTSPSer ...

  2. live555 源码分析:播放启动

    本文分析 live555 中,流媒体播放启动,数据开始通过 RTP/RTCP 传输的过程. 如我们在 live555 源码分析:子会话 SETUP 中看到的,一个流媒体子会话的播放启动,由 Strea ...

  3. live555 源码分析:子会话 SDP 行生成

    如我们在前文 live555 源码分析:ServerMediaSession 中看到的,H264VideoFileServerMediaSubsession 的继承层次体系如下图: 在这个继承层次体系 ...

  4. live555 源码分析:ServerMediaSession

    在 live555 中,用一个 ServerMediaSession 表示流媒体会话,它连接了 RTSPServer 和下层流媒体传输逻辑.ServerMediaSession 和 ServerMed ...

  5. live555 源码分析:RTSPServer 组件结构

    前面几篇文章分析了 live555 中 RTSP 的处理逻辑,RTSP 处理有关组件的处理逻辑有点复杂,本文就再来梳理一下它们之间的关系. live555 中 RTSP 处理有关组件关系如下图: 事件 ...

  6. live555 源码分析: PLAY 的处理

    在 SETUP 请求之后,客户端会发起 PLAY 请求,以请求服务器开始传输音视频数据.在 PLAY 请求执行时,一定是已经执行过 SETUP 请求,建立好了客户端会话,因而会与其它要求客户端会话已经 ...

  7. live555 源码分析: SETUP 的处理

    SETUP 请求在 RTSP 的整个工作流程中,用于建立流媒体会话.本文分析 live555 对 SETUP 请求的处理. 在 RTSPServer::RTSPClientConnection::ha ...

  8. live555 源码分析: DESCRIBE 的处理

    前面在 live555 源码分析:RTSPServer 中分析了 live555 中处理 RTSP 请求的大体流程,并分析了处理起来没有那么复杂的一些方法,如 OPTIONS,GET_PARAMETE ...

  9. live555 源码分析:MediaSever

    位于 live555 项目 mediaServer 目录下的是 "LIVE555 Media Server",它是一个完整的 RTSP 服务器应用程序.它可以把多种媒体文件转为流, ...

最新文章

  1. display:inline-block 的使用
  2. ECJia如何配置两个网站访问共同的数据库和附件资源
  3. docker 镜像名 tag 为none 的解决方案
  4. java多线程模型_1、java线程模型
  5. 拖链电缆 机器人电缆_尼龙拖链在机器中起着电缆的作用
  6. 2022-01-06
  7. go语言中的闭包结构
  8. vim程序编译器使用(整理)
  9. python 服务端框架_GitHub - edisonlz/fastor: Python服务端开发框架-极易上手,超出你的想象!...
  10. 微服务 SpringBoot 通过jdbcTemplate配置Oracle数据源
  11. 数据库分页LIMIT
  12. python清洗数据去除停用词_python之NLP数据清洗
  13. RL论文阅读【一】Playing Atari with Deep Reinforcement Learning
  14. 【Leetcode】精选算法top200道(二)
  15. Day 7(云计算-zsn)
  16. PDF复制乱码 -- 原因及解决方案
  17. 服务器 最大连接数:
  18. 2019年ACM-ICPC关注的一些比赛
  19. 浅析云原生模型推理服务框架KServe
  20. DecimalFormat保留小数位

热门文章

  1. 可视化篇:R可视化--迁徙/通勤图
  2. make menuconfig缺少ncurses
  3. JAVA简单快速排序讲解
  4. mabatis报错:Result type not match for select id=XXX
  5. ryu---北向接口(利用socket对外通信)
  6. 计算机科学专辑算什么,计算机科学专辑
  7. 概率论与数理统计笔记系列之第二章:随机变量及其分布
  8. Inpaint如何去水印?Inpaint图片去水印教程
  9. 51单片机串口通信,及波特率计算
  10. 使用AndroidStudio与RN 运行ract-native run-android遇到的那些坑