Rfc3199 defines the RTP packaging rules for MP3. First, let's take a look at how the sesseion of *. MP3 is created.
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 "); // comment out the stream_using_adus macro, use adus for transmission instead of MP3 bare frames // to stream using 'adus' rather than raw MP3 frames, uncomment the following: // # define stream_using_adus 1 // comment out the interleave_adus macro, adus re-sorting (staggered) before transmission // to also reorder adus before streaming, uncomment the following: // # define interleave_adus 1 // (for more information about adus and interleaving, // See
The above code is used to open the macro stream_using_adus. The MP3 frame is packaged into an ADU and then sent. Open the macro interleave_adus. After MP3 is packaged into ADU, it will be staggered. By default, both options are disabled. In the mp3audiofileservermediasubsession: createnewstreamsource function, a createnewstreamsourcecommon function is called to handle ADU packaging.
Framedsource * metadata: createnewstreamsourcecommon (framedsource * basemp3source, unsigned mp3numbytes, unsigned & estbitrate) {framedsource * streamsource; ffileduration = 0.0; do {streamsource = basemp3source; // by default if (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) {// determine whether to package it into ADU and then send it // Add a filter that converts the source MP3s to adus: streamsource = adufromatrix 3source: 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) {// note that if you do not need to package it into ADU, here is a process of package and unpackage again, to facilitate location, /// 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 = adufrommp 3source :: createnew (envir (), streamsource); If (streamsource = NULL) break; streamsource = mp3fromadusource: createnew (envir (), streamsource); If (streamsource = NULL) break ;}} while (0); Return streamsource; // return the outermost source}
The above code processes two situations: one is to determine whether to generate ADU and then send the code based on the value of fgenerateadus, and the other is that the Code does not need to be packaged into ADU, here is a process of package and unpackage, to facilitate positioning. The following describes the Adu packaging process.
Void adufrommp 3source: dogetnextframe () {If (! Bytes) {// branch 1 // arrange to enqueue a new MP3 frame: bytes = fsegments-> totaldatasize (); fareenqueueingmp3frame = true; fsegments-> enqueuenewsegment (finputsource, this);} else {// branch 2 // deliver an ADU from a previusly-read MP3 frame: fareenqueueingmp3frame = false; If (! Dogetnextframe1 () {// an internal error occurred; act as if our source went away: framedsource: handleclosure (this );}}}
The initial value of fareenqueueingmp3frame is false. First, 1st branches are executed. From the subsequent analysis, we will find that the fsegments-> enqueuenewsegmen function finally calls the adufrommp 3source: dogetnextframe function, and then the second branch is executed.
Void segmentqueue: enqueuenewsegment (framedsource * inputsource, framedsource * usingsource) {If (isfull () {usingsource-> envir () <"segmentqueue: Queue () overflow \ n "; framedsource: handleclosure (usingsource); return;} fusingsource = usingsource; segment & seg = nextfreesegment (); // obtain MP3 data from source inputsource-> getnextframe (SEG. buf, sizeof seg. buf, sqaftergettingsegment, this, framedsource: handleclosure, usingsource );}
The process of obtaining MP3 data from the source is not followed. You can directly view the processing of sqaftergettingsegment function.
Void queue: Queue (void * clientdata, unsigned numbytesread, unsigned/* signed */, struct timeval presentationtime, unsigned Queue) {segmentqueue * segqueue = (segmentqueue *) clientdata; segment & seg = segqueue-> nextfreesegment (); // obtain the segment seg. presentationtime = presentationtime; seg. durationinmicroseconds = durationinmicroseconds; If (segque UE-> sqaftergettingcommon (SEG, numbytesread) {// analyze the MP3 frame read # ifdef debug char 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 (); // The dogetnextframe function is called again}
The obtained data is stored in the segment. First, the sqaftergettingcommon function is called to analyze the frame. Finally, the adufromatrix 3source: dogetnextframe function is called again, but 2nd branches will be executed this time. Let's take a look at the details of the sqaftergettingcommon function.
// Common code called after a new segment is enqueuedboolean segmentqueue: sqaftergettingcommon (segment & seg, unsigned numbytesread) {unsigned char * fromptr = seg. buf; // whether the Adu ID if (fincludeadudescriptors) {// to segmentqueue in adufromatrix 3source, this value must be false // The newly-read data is assumed to be an ADU with a descriptor/in front /// getremainingframesize, depending on the fromptr 1st bytes, the descriptor is one or two bytes/ /(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 Params unsigned HDR; // 4-byte frame header mp3sideinfo sideinfo; // side info // analyze the frame and obtain the related information if (! Getaduinfofromatrix 3frame (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) {// The default value is true unsigned newadusize = numbytesread-seg. descriptorsize-4/* Header size */-seg. sideinfosize; If (newadusize> seg. adusize) seg. adusize = newadusize;} ftotaldatasize + = seg. datahere (); fnextfreeindex = nextindex (fnextfreeindex); // update the idle segment index return true ;}
The fincludeadudescriptors variable in the above Code indicates whether the data read into the segment contains the Adu identifier. Obviously, the raw data processed by adufromm3source must not contain the Adu identifier.
The getaduinfofromatrix function obtains Adu-related information from MP3 frames.
Boolean evaluate (unsigned char const * frameptr, unsigned totframesize, unsigned & HDR, unsigned & framesize, struct & sideinfo, unsigned & sideinfosize, unsigned & backpointer, unsigned & adusize) {If (totframesize <4) return false; // there's not enough data mp3frameparams fr; // The mp3frameparams class is used to analyze the MP3 frame information. // the first four bytes are the FR of the MP3 frame header. HDR = (unsigned) frameptr [0] <24) | (unsigned) frameptr [1] <16) | (unsigned) frameptr [2] <8) | (unsigned) frameptr [3]; Fr. setparamsfromheader (); // analyzes the 4-byte header Fr. setbytepointer (frameptr + 4, totframesize-4); // skip HDR framesize = 4 + Fr. framesize; // non-MP3 frame (MP2 or MP1) if (Fr. layer! = 3) {// special case for non-Layer III frames backpointer = 0; sideinfosize = 0; adusize = Fr. framesize; return true;} sideinfosize = Fr. sideinfosize; If (totframesize <4 + sideinfosize) return false; // not enough data Fr. getsideinfo (sideinfo); HDR = Fr. HDR; // 4-byte header backpointer = sideinfo. main_data_begin; // data start position: 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 bytes # ifdef debug fprintf (stderr, "mp3getaduinfofromframe: HDR: % 08x, framesize: % d, part2_3_lengths: % 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); # endif return true ;}
Let's look at the analysis of MP3 frame headers.
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 allowed bitrateindex = (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_errors fprintf (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 ); // calculate the size of the frame sideinfosize = computesideinfosize ();}
Standard MP3 frame (without CRC for stereo sound) with 32-bit Additional information
Framesize Calculation Method
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;}
Here, bitrate is measured in kbps.
The length of an MP3 frame depends on the bit rate and frequency. The formula is as follows:
. Mpeg1.0 layer1: frame length = (48000 * bitrate)/sampling_freq + padding
Layer2 & 3: frame length = (144000 * bitrate)/sampling_freq + padding
. Mpeg2.0 layer1: frame length = (24000 * bitrate)/sampling_freq + padding
Layer2 & 3: frame length = (72000 * bitrate)/sampling_freq + padding
According to the formula, the bit rate is 128 Kbps, the sampling frequency is 44.1 kHz, the padding (frame length adjusted) is 0, and the frame length is 417 bytes.
The strange thing is that the padding is 0, and the last bit is not discarded?
At this point, the adufrommp 3source: dogetnextframe () function has analyzed 1st branches. Now we can see that it has 2nd branches. Branch 2 mainly calls the adufrommp 3source: dogetnextframe1 function.
Boolean adufrommp 3source: dogetnextframe1 () {// first, check whether we have enough previusly-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-> nextfreei Ndex (); // obtain the last segment tailseg = & (fsegments-> S [tailindex]) filled with data; needmoredata = Response <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: Dog Etnextframe (); // if there is not enough data, return true to read the data again;} // obtain an ADU from the tail segment // output an ADU from the tail segment: fframesize = tailseg-> headersize + tailseg-> sideinfosize + tailseg-> adusize; region = tailseg-> presentationtime; region = tailseg-> region; unsigned descriptorsize = bytes? Adudescriptor: computesize (fframesize): 0; If (descriptorsize + fframesize> fmaxsize) {envir () <"adufromatrix 3source: dogetnextframe1 (): not enough room ("<descriptorsize + fframesize <"> "<fmaxsize <") \ n "; fframesize = 0; return false ;} unsigned char * toptr = FTO; // output ADU descriptor // output the Adu descriptor: If (fincludeadudescriptors) {// default value: false fframesize + = adudescriptor: generatede Scriptor (toptr, fframesize);} // output header and side info: memmove (toptr, tailseg-> datastart (), tailseg-> headersize + tailseg-> sideinfosize); toptr + = tailseg-> headersize + tailseg-> sideinfosize; // output data // 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-= bytes; toptr + = bytesusedhere; offset = 0; I = segmentqueue: nextindex (I );} if (fframecounter ++ % fscale = 0) {// fast forward and return operation, discard unwanted frames // 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 ;}