有损音频利用人类听觉对声音中的某些频率成分不敏感的特性,从原始PCM数据中将这不敏感的一部分信息去除,以达到压缩的目的。其具有体积小,便于传输的特点,得到了广泛的应用。

1. MP3概述

MP3格式1991年由德国夫琅和费集成电路研究所(FraunhoferInstitute for Integrated Circuits)的一个工程师团队发明,并将其标准化。它丢弃原始PCM音频数据中的高频分量,可以按照不同的位速进行压缩,提供了在数据大小和声音质量之间进行权衡的一个范围,从而达到足够小、音质出色的音频格式文件。

MP3在问世之初相比其他存储音频的格式要高效得多,这样的文件非常有利于在那个网络还不发达的年代持续传播,在容量有限的储存设备上保存更多的音频文件。MP3音乐的免费下载以及各大随身听播放器的支持,使得MP3被广泛传播,时至今日,也仍是应用最广泛的有损压缩音频格式之一。

随着音频研究的深入,相关技术和知识不断完备,新的音频有损压缩格式,如ogg、aac、opus等等已经出现,它们承载的信息量相比MP3更加丰富,更加科学,相信不久的将来,MP3会慢慢淡出历史的舞台。

2、libmad

libmad是一个开源的高精度 MPEG 音频解码库,支持 MPEG-1,提供 24-bit 的 PCM 输出,完全是定点计算,非常适合没有浮点支持的平台上使用。使用 libmad 提供的一系列 API,就可以非常简单地实现 MP3 数据解码工作。libmad源码可以从下面网址下载:

http://www.linuxfromscratch.org/blfs/view/svn/multimedia/libmad.html

把源码包中的C文件、头文件和数据文件拷贝作为库源码加入工程编译即可,MP3解码实现可以参考minimad.c

08_MP3播放 - 图1

3、MP3播放

MP3音频的播放涉及到音频驱动、SD卡读写文件的实现,可以参考前面的章节。播放实现主要流程如下:

a. MDK工程定义libmad的编译选项,FPM_DEFAULT、OPT_SPEED。

08_MP3播放 - 图2

b. 打开MP3音频文件,用minimad.c中的参考解码流程进行解码。MP3解码mad_decoder_run()过程中,会通过input()回调函数加载更多的码流用于解码,解码完一帧,会通过output()回调函数播放等处理解码出来的音频数据,如果解码出现错误,通过error()回调函数进行错误的处理。

static int decode(void)

{
       structmad_decoder decoder;

       intresult;



       /*configure input, output, and error functions */

       mad_decoder_init(&decoder,0,

              input,0 /* header */, 0 /* filter */, output,

              error,0 /* message */);



       /*start decoding */

       result= mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);

       /*release the decoder */

       mad_decoder_finish(&decoder);

       returnresult;

}

c. input()回调函数加载更多的码流用于解码,从SD卡加载码流填充满缓存MadInputBuffer,libmad会读取一帧的码流长度数据用于解码,剩余的数据会被留作下一帧的解码,通过mad_stream_buffer()上报码流的位置及可用大小。文件结束后,停止播放,关闭文件,返回MAD_FLOW_STOP告知码流结束。

static enum mad_flow input(void *data,

                  struct mad_stream *stream)

{
unsigned char *ReadStart;

unsigned int ReadSize, ReturnSize;

int Remaining;

FRESULT Res;     



switch (FileState) {
case 0:

       ReadStart= MadInputBuffer;

       ReadSize= FILE_IO_BUFFER_SIZE;

       Remaining= 0;

       FileState= 1;

       break;

case 1:

       /*Get the remaining frame */

       Remaining= stream->bufend - stream->next_frame;

       memmove(MadInputBuffer,stream->next_frame, Remaining);

       ReadStart= MadInputBuffer + Remaining;

       ReadSize= FILE_IO_BUFFER_SIZE - Remaining;        

       break;

default:

       I2S_TxStop();

       f_close(&file);

       returnMAD_FLOW_STOP;

}



/* read the file from SDCard */

Res = f_read(&file, ReadStart,ReadSize, &ReturnSize);

if (Res != RES_OK) {
       f_close(&file);

       returnMAD_FLOW_BREAK;

}

/* if the file is over */

if (ReadSize > ReturnSize) {
       FileState= 2;

}

mad_stream_buffer(stream,MadInputBuffer, ReturnSize+Remaining);

return MAD_FLOW_CONTINUE;

}

d. output()回调函数把解码出来的音频数据加载到音频输出流进行播放。第一帧解码完成后,可以从mad_header结构体获取MP3的采样率、通道数等等音频格式,对I2S音频驱动初始化。解码的左声道数据放在pcm->samples[0]缓存,右声道数据放在pcm->samples[1]缓存,每次解码一帧包含pcm->length个音频数据,一个一个填充到音频输出缓存,如果输出缓存满,则等待播放完一帧后,继续填充。

static enum mad_flow output(void *data,

                   struct mad_header const *header,

                   struct mad_pcm *pcm)

{
unsigned int nchannels, nsamples;

mad_fixed_t const *left_ch, *right_ch;  

static int Index;



if (!Playing) {
       PRINTF("Mode:%s\r\n", header->mode==1?"Mono":"Stereo");

       PRINTF("Samplerate:%d Hz\r\n", header->samplerate);

       PRINTF("Bitrate:%d bps\r\n", header->bitrate);

       I2S_SetSamplerate(header->samplerate);

       I2S_TxStart();

       WriteIndex= I2SState.TxWriteIndex + 1;

       Index= 0;

       Playing= 1;

}

/* pcm->samplerate contains thesampling frequency */

nchannels = pcm->channels;

nsamples = pcm->length;

left_ch  = pcm->samples[0];

right_ch = pcm->samples[1];



while (nsamples--) {
       signedshort letf_sample, right_sample;

       /*output sample(s) in 16-bit signed little-endian PCM */

       letf_sample= scale(*left_ch++);

       if(nchannels == 2) {
              right_sample= scale(*right_ch++);

       }else {
              right_sample= letf_sample;

       }

       while(WriteIndex == I2SState.TxReadIndex) {


       }

       //Samplepair is 4 bytes, 16-bit mode

       if(WriteIndex != I2SState.TxReadIndex) {
              I2SState.TxBuffer[I2SState.TxWriteIndex][Index]= (letf_sample&0xffff)

 | (right_sample<<16);

              Index++;

              if(Index >= AUDIO_FRAME_SIZE) {
                     Index= 0;

                     I2SState.TxWriteIndex= WriteIndex;

                     if(WriteIndex >= AUDIO_NUM_BUFFERS-1) {
                            WriteIndex= 0;

                     }else {
                            WriteIndex++;

                     }

              }

       }

}

return MAD_FLOW_CONTINUE;

}

e. error()回调函数进行错误的处理。

static enum mad_flow error(void *data,

                  struct mad_stream *stream,

                  struct mad_frame *frame)

{
       returnMAD_FLOW_CONTINUE;

}

播放<<同一首歌>>MP3文件the same song.mp3如下:

08_MP3播放 - 图3

4. 附录

MDK工程,包含SD卡文件读写代码,I2S音频播放驱动,MP3文件播放的实现、<<同一首歌>>MP3文件the samesong.mp3。
源码:http://pan.baidu.com/s/1bOlDEY