北京理工大学 20981 陈罡
继续上面一篇的内容。既然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 {
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文件了!