既然libmad已经可以顺利的移植到win mobile平台上了,那么如何使用这个libmad库呢?很遗憾,linux平台下面对于libmad的文档描述不是非常清晰。呵呵,按照他们的思维就是一切都在代码里面了。但是对于普通的开发者来说,可能只是涉及到对库的使用,而不是为了使用一个库去学习mp3的编码、解码原理(如果能够通过这个库弄明白了这些,自然再好不过了)。
我仔细的查看了一下这个libmad的相关开源工程。发现了一个很有趣的项目,它就是——madlld-1.1p1(我下载的最新版本,不知道现在是否又有更新的版本出来了)可以google一下,然后直接下载源代码。这个项目的说明如下: This program is a "how to" about libmad's low-level API. The intended use of this program is to serve as tutorial or demonstration for programmers wiling to learn how the currently undocumented library API can be used. As a side effect the program can be used to decode MPEG audio streams to raw PCM samples. 这个项目本身就是做为一个向导或者入门的文件,教会大家如何使用libmad的底层API函数的。因此,把这个项目的代码拿过来仔细分析一下,libmad库的使用方法就变得非常简单了。它里面的代码写得非常不错,很精炼,可以很容易的移植到win mobile平台上去。madlld项目的核心部分由3个.c文件组成,最关键的代码在madlld.c这个文件中的MpegAudioDecoder函数,该函数的定义如下:
static int MpegAudioDecoder(FILE *InputFp, FILE *OutputFp)
现在就把libmad库的API核心部分使用的代码贴出来,简单分析一下备忘:
- // 这里用8K的输出缓冲区,这对于mp3转pcm16仅限于文件操作的程序来说是足够的
- // 但是对于编解码边播放的应用场合而言就太小了,根本不够
- #define INPUT_BUFFER_SIZE (5*8192)
- #define OUTPUT_BUFFER_SIZE 8192 /* Must be an integer multiple of 4. */
- static int MpegAudioDecoder(FILE *InputFp, FILE *OutputFp)
- {
- struct mad_stream Stream;
- struct mad_frame Frame;
- struct mad_synth Synth;
- mad_timer_t Timer;
- unsigned char InputBuffer[INPUT_BUFFER_SIZE+MAD_BUFFER_GUARD],
- OutputBuffer[OUTPUT_BUFFER_SIZE],
- *OutputPtr=OutputBuffer,
- *GuardPtr=NULL;
- const unsigned char *OutputBufferEnd=OutputBuffer+OUTPUT_BUFFER_SIZE;
- int Status=0,
- i;
- unsigned long FrameCount=0;
- bstdfile_t *BstdFile;
- /* First the structures used by libmad must be initialized. */
- mad_stream_init(&Stream);
- mad_frame_init(&Frame);
- mad_synth_init(&Synth);
- mad_timer_reset(&Timer);
- /* Decoding options can here be set in the options field of the
- * Stream structure.
- */
- /* {1} When decoding from a file we need to know when the end of
- * the file is reached at the same time as the last bytes are read
- * (see also the comment marked {3} bellow). Neither the standard
- * C fread() function nor the POSIX read() system call provides
- * this feature. We thus need to perform our reads through an
- * interface having this feature, this is implemented here by the
- * bstdfile.c module.
- */
- // 这里说了那么多其实就是想要一个文件读取函数,在读取的同时知道是否到达文件末尾。
- // 普通的fread是不行的,只有当前读取是否成功,对于在当前的读取完成后是否到达文件
- // 末尾没有任何提示。所以这里采用了自己封装的文件操作函数。
- BstdFile=NewBstdFile(InputFp);
- if(BstdFile==NULL)
- {
- fprintf(stderr,"%s: can't create a new bstdfile_t (%s).\n",
- ProgName,strerror(errno));
- return(1);
- }
- /* This is the decoding loop. */
- do
- {
- /* The input bucket must be filled if it becomes empty or if
- * it's the first execution of the loop.
- */
- if(Stream.buffer==NULL || Stream.error==MAD_ERROR_BUFLEN)
- {
- size_t ReadSize,
- Remaining;
- unsigned char *ReadStart;
- /* {2} libmad may not consume all bytes of the input
- * buffer. If the last frame in the buffer is not wholly
- * contained by it, then that frame's start is pointed by
- * the next_frame member of the Stream structure. This
- * common situation occurs when mad_frame_decode() fails,
- * sets the stream error code to MAD_ERROR_BUFLEN, and
- * sets the next_frame pointer to a non NULL value. (See
- * also the comment marked {4} bellow.)
- *
- * When this occurs, the remaining unused bytes must be
- * put back at the beginning of the buffer and taken in
- * account before refilling the buffer. This means that
- * the input buffer must be large enough to hold a whole
- * frame at the highest observable bit-rate (currently 448
- * kb/s). XXX=XXX Is 2016 bytes the size of the largest
- * frame? (448000*(1152/32000))/8
- */
- if(Stream.next_frame!=NULL)
- {
- // 这里使用了内存分块使用的方法,写得非常精练
- // 主要用于处理当前数据不够一帧,在读入新数据的时候
- // 要把没有用完的数据放到开头,然后接着读入新的数据
- // 编写过网络程序的朋友应该一目了然了,这就是数据包的
- // “拼接”操作嘛
- Remaining=Stream.bufend-Stream.next_frame;
- memmove(InputBuffer,Stream.next_frame,Remaining);
- ReadStart=InputBuffer+Remaining;
- ReadSize=INPUT_BUFFER_SIZE-Remaining;
- }
- else
- ReadSize=INPUT_BUFFER_SIZE,
- ReadStart=InputBuffer,
- Remaining=0;
- /* Fill-in the buffer. If an error occurs print a message
- * and leave the decoding loop. If the end of stream is
- * reached we also leave the loop but the return status is
- * left untouched.
- */
- // 读取数据,没什么可说的,读取的长度如果小于等于零,就报错
- ReadSize=BstdRead(ReadStart,1,ReadSize,BstdFile);
- if(ReadSize<=0)
- {
- if(ferror(InputFp))
- {
- fprintf(stderr,"%s: read error on bit-stream (%s)\n",
- ProgName,strerror(errno));
- Status=1;
- }
- if(feof(InputFp))
- fprintf(stderr,"%s: end of input stream\n",ProgName);
- break;
- }
- /* {3} When decoding the last frame of a file, it must be
- * followed by MAD_BUFFER_GUARD zero bytes if one wants to
- * decode that last frame. When the end of file is
- * detected we append that quantity of bytes at the end of
- * the available data. Note that the buffer can't overflow
- * as the guard size was allocated but not used the the
- * buffer management code. (See also the comment marked
- * {1}.)
- *
- * In a message to the mad-dev mailing list on May 29th,
- * 2001, Rob Leslie explains the guard zone as follows:
- *
- * "The reason for MAD_BUFFER_GUARD has to do with the
- * way decoding is performed. In Layer III, Huffman
- * decoding may inadvertently read a few bytes beyond
- * the end of the buffer in the case of certain invalid
- * input. This is not detected until after the fact. To
- * prevent this from causing problems, and also to
- * ensure the next frame's main_data_begin pointer is
- * always accessible, MAD requires MAD_BUFFER_GUARD
- * (currently 8) bytes to be present in the buffer past
- * the end of the current frame in order to decode the
- * frame."
- */
- // 这里是判断文件是否结束,并且加入所谓的MAD_BUFFER_GUARD
- // 用于明确地告诉解码器,已经没有数据了
- if(BstdFileEofP(BstdFile))
- {
- GuardPtr=ReadStart+ReadSize;
- memset(GuardPtr,0,MAD_BUFFER_GUARD);
- ReadSize+=MAD_BUFFER_GUARD;
- }
- /* Pipe the new buffer content to libmad's stream decoder
- * facility.
- */
- mad_stream_buffer(&Stream,InputBuffer,ReadSize+Remaining);
- Stream.error=0;
- }
- /* Decode the next MPEG frame. The streams is read from the
- * buffer, its constituents are break down and stored the the
- * Frame structure, ready for examination/alteration or PCM
- * synthesis. Decoding options are carried in the Frame
- * structure from the Stream structure.
- *
- * Error handling: mad_frame_decode() returns a non zero value
- * when an error occurs. The error condition can be checked in
- * the error member of the Stream structure. A mad error is
- * recoverable or fatal, the error status is checked with the
- * MAD_RECOVERABLE macro.
- *
- * {4} When a fatal error is encountered all decoding
- * activities shall be stopped, except when a MAD_ERROR_BUFLEN
- * is signaled. This condition means that the
- * mad_frame_decode() function needs more input to complete
- * its work. One shall refill the buffer and repeat the
- * mad_frame_decode() call. Some bytes may be left unused at
- * the end of the buffer if those bytes forms an incomplete
- * frame. Before refilling, the remaining bytes must be moved
- * to the beginning of the buffer and used for input for the
- * next mad_frame_decode() invocation. (See the comments
- * marked {2} earlier for more details.)
- *
- * Recoverable errors are caused by malformed bit-streams, in
- * this case one can call again mad_frame_decode() in order to
- * skip the faulty part and re-sync to the next frame.
- */
- // 这里就是真正的解码部分了,含有对错误的处理方法
- if(mad_frame_decode(&Frame,&Stream))
- {
- if(MAD_RECOVERABLE(Stream.error))
- {
- /* Do not print a message if the error is a loss of
- * synchronization and this loss is due to the end of
- * stream guard bytes. (See the comments marked {3}
- * supra for more informations about guard bytes.)
- */
- if(Stream.error!=MAD_ERROR_LOSTSYNC ||
- Stream.this_frame!=GuardPtr)
- {
- fprintf(stderr,"%s: recoverable frame level error (%s)\n",
- ProgName,MadErrorString(&Stream));
- fflush(stderr);
- }
- continue;
- }
- else
- // 这里的这个错误其实不是错误,只是当前帧的数据不完整,需要再次读入
- // 剩下的部分与新读入的部分“拼接”成为一个完整的帧再解码
- if(Stream.error==MAD_ERROR_BUFLEN)
- continue;
- else
- {
- fprintf(stderr,"%s: unrecoverable frame level error (%s).\n",
- ProgName,MadErrorString(&Stream));
- Status=1;
- break;
- }
- }
- /* The characteristics of the stream's first frame is printed
- * on stderr. The first frame is representative of the entire
- * stream.
- */
- if(FrameCount==0)
- // 这里的PrintFrameInfo用于在解码器接触第一帧的时候,输出一些
- // mp3的采样率、通道个数之类的信息用的
- if(PrintFrameInfo(stderr,&Frame.header))
- {
- Status=1;
- break;
- }
- /* Accounting. The computed frame duration is in the frame
- * header structure. It is expressed as a fixed point number
- * whole data type is mad_timer_t. It is different from the
- * samples fixed point format and unlike it, it can't directly
- * be added or subtracted. The timer module provides several
- * functions to operate on such numbers. Be careful there, as
- * some functions of libmad's timer module receive some of
- * their mad_timer_t arguments by value!
- */
- FrameCount++;
- mad_timer_add(&Timer,Frame.header.duration);
- /* Between the frame decoding and samples synthesis we can
- * perform some operations on the audio data. We do this only
- * if some processing was required. Detailed explanations are
- * given in the ApplyFilter() function.
- */
- // 这里是自定义filter用的,可以用于混音或者特殊音效之类的操作
- // 如果只是单纯的mp3解码的话,这个filter完全可以不用
- if(DoFilter)
- ApplyFilter(&Frame);
- /* Once decoded the frame is synthesized to PCM samples. No errors
- * are reported by mad_synth_frame();
- */
- mad_synth_frame(&Synth,&Frame);
- /* Synthesized samples must be converted from libmad's fixed
- * point number to the consumer format. Here we use unsigned
- * 16 bit big endian integers on two channels. Integer samples
- * are temporarily stored in a buffer that is flushed when
- * full.
- */
- // 这里就是把short类型数据存入output缓冲区
- for(i=0;i<Synth.pcm.length;i++)
- {
- signed short Sample;
- /* Left channel */
- Sample=MadFixedToSshort(Synth.pcm.samples[0][i]);
- // ---------> 这里需要特别注意endian的问题
- *(OutputPtr++)=Sample>>8;
- *(OutputPtr++)=Sample&0xff;
- /* Right channel. If the decoded stream is monophonic then
- * the right output channel is the same as the left one.
- */
- if(MAD_NCHANNELS(&Frame.header)==2)
- Sample=MadFixedToSshort(Synth.pcm.samples[1][i]);
- // --------->这里需要特别注意endian的问题
- *(OutputPtr++)=Sample>>8;
- *(OutputPtr++)=Sample&0xff;
- /* Flush the output buffer if it is full. */
- if(OutputPtr==OutputBufferEnd)
- {
- // 这里就是把结果写入pcm文件了,直接的raw数据写入
- if(fwrite(OutputBuffer,1,OUTPUT_BUFFER_SIZE,OutputFp)!=OUTPUT_BUFFER_SIZE)
- {
- fprintf(stderr,"%s: PCM write error (%s).\n",
- ProgName,strerror(errno));
- Status=2;
- break;
- }
- OutputPtr=OutputBuffer;
- }
- }
- }while(1);
- /* The input file was completely read; the memory allocated by our
- * reading module must be reclaimed.
- */
- BstdFileDestroy(BstdFile);
- /* Mad is no longer used, the structures that were initialized must
- * now be cleared.
- */
- mad_synth_finish(&Synth);
- mad_frame_finish(&Frame);
- mad_stream_finish(&Stream);
- /* If the output buffer is not empty and no error occurred during
- * the last write, then flush it.
- */
- if(OutputPtr!=OutputBuffer && Status!=2)
- {
- size_t BufferSize=OutputPtr-OutputBuffer;
- // 这里就是如果还剩下一部分数据,但是mp3文件又结束了
- // 就在这个地方把已经解出来的数据写入到pcm文件中,从这里可以看出
- // 作者做程序态度是很严谨的,hoho,其实这块数据丢掉,一般人听不出来
- if(fwrite(OutputBuffer,1,BufferSize,OutputFp)!=BufferSize)
- {
- fprintf(stderr,"%s: PCM write error (%s).\n",
- ProgName,strerror(errno));
- Status=2;
- }
- }
- /* Accounting report if no error occurred. */
- if(!Status)
- {
- char Buffer[80];
- /* The duration timer is converted to a human readable string
- * with the versatile, but still constrained mad_timer_string()
- * function, in a fashion not unlike strftime(). The main
- * difference is that the timer is broken into several
- * values according some of it's arguments. The units and
- * fracunits arguments specify the intended conversion to be
- * executed.
- *
- * The conversion unit (MAD_UNIT_MINUTES in our example) also
- * specify the order and kind of conversion specifications
- * that can be used in the format string.
- *
- * It is best to examine libmad's timer.c source-code for details
- * of the available units, fraction of units, their meanings,
- * the format arguments, etc.
- */
- // 最后输出一下总结信息,例如共解压了多少帧,用了多长时间之类的
- mad_timer_string(Timer,Buffer,"%lu:%02lu.%03u",
- MAD_UNITS_MINUTES,MAD_UNITS_MILLISECONDS,0);
- fprintf(stderr,"%s: %lu frames decoded (%s).\n",
- ProgName,FrameCount,Buffer);
- }
- /* That's the end of the world (in the H. G. Wells way). */
- return(Status);
- }
注意需要特别说明的地方是在代码中,我做了标记的地方: // ---------> 这里需要特别注意endian的问题 *(OutputPtr++)=Sample>>8; *(OutputPtr++)=Sample&0xff; 在win mobile开发中,这里需要特别注意,如果直接用这个在unix下面开发的代码的话,它解码出来的pcm码流就是little endian的,而对于win mobile的waveout api来说,他们需要big endian的(呵呵,对啦,就是这个问题害得我用了整整一个上午的时间来调试各个环节,总算找到了!!)
同样的解码文件在little endian和big endian的波形显示效果如下图所示:
显然,只有把output_buffer中的little endian数据改为big endian才能够被win mobile接受。 修改的方法其实很简单(呵呵,找到这个问题还是很复杂的!汗。。。) // ---------> 这里需要特别注意endian的问题 *(OutputPtr++)=Sample&0xff; *(OutputPtr++)=Sample>>8; 把这两句前后位置修改一下即可!
哈哈,简单吧!!libmad学会使用以后,就可以很方便的把mp3文件转换成为pcm码流。下一篇就是如何在win mobile 5里面播放pcm码流的方法了。
加油,还差一步就可以通过自己的程序在win mobile上面播放mp3文件了
|