您的位置:首页 > 编程语言 > C语言/C++

win mobile 5播放mp3音乐的方法(2)--libmad库的使用篇

2013-01-16 14:03 344 查看

win mobile 5播放mp3音乐的方法(2)--libmad库的使用篇  

2010-07-16 15:14:26|  分类:
wince
|  标签:
|字号大中小 订阅

既然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(BstdFi
12bcd
le==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文件了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言 VC WIndows 播放器