有损音频利用人类听觉对声音中的某些频率成分不敏感的特性,从原始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
3、MP3播放
MP3音频的播放涉及到音频驱动、SD卡读写文件的实现,可以参考前面的章节。播放实现主要流程如下:
a. MDK工程定义libmad的编译选项,FPM_DEFAULT、OPT_SPEED。
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如下:
4. 附录
MDK工程,包含SD卡文件读写代码,I2S音频播放驱动,MP3文件播放的实现、<<同一首歌>>MP3文件the samesong.mp3。
源码:http://pan.baidu.com/s/1bOlDEY