NXP MCU 技术论坛
直播中

刘天

13年用户 132经验值
擅长:微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件 微处理器/微控制 数字及可编程逻辑 模拟与电源 基础元器件
私信 关注
[经验]

【NXP LPC54110试用体验】FLAC无损音频播放

FLAC无损音频播放
象棋小子    1048272975
无损音频不存在音频损失的问题,可以获取到原始PCM采样数据,代表数字音频最高的保真水准。随着储存设备容量的增大,网络传输带宽的提升,无损音频越来越受到人们的追捧。
1. FLAC概述
常用的无损音频格式有WAV、FLAC、APE等等。WAV格式直接保存原始的PCM数据,造成容量过大,不方便使用。FLAC和APE为无损音频压缩格式,通过压缩算法重新对PCM数据进行编码,减小容量,同时可以通过可逆的算法解压还原成PCM数据。在众多的无损音频压缩格式中,FLAC被认为是最具潜力的一种无损音频压缩格式。
FLAC(FreeLossless Audio Codec)是一套自由的音频压缩编解码器,不同于其它有损音频压缩格式,FLAC格式不会破坏任何原有的音频资讯。这种压缩类似于ZIP的方式,但FLAC是专门针对PCM音频特点设计的压缩格式,其压缩率要大于ZIP方式。FLAC是一种非专有的,不受专利影响,开放源码,并且完全免费的无损音频压缩格式,它的编码算法相当成熟,已经通过了严格的测试,被很多软件以及硬件音频产品所支持,如支持大多数的操作系统,包括Windows、Unix类系统、Mac等等,应用在移动多媒体播放器、汽车音响、家用音响等等设备。
2FLAC库移植
FLAC编解码复杂度比较低,对计算要求不高,在普通的硬件平台就可以轻松实现实时解码播放,因此可以在LPC5411x开发平台上实现FLAC音频的播放。FLAC有专门的项目组维护,可以在https://xiph.org/flac下载完整的FLAC编解码源码,对于C开发环境,其对应的库为libFLAC。libFLAC包含了操作系统相关的数据结构,并且对于cortex-m平台来说,功能过于臃肿,需要对该库做较大的改动才能完成移植,并不是最适合的。
此处采用rockbox嵌入式项目中的FLAC解码库,减少移植的工作量。rockbox是一个免费的数字音频播放器框架,支持众多的音频编解码器。其采用的FLAC解码库移植于libffmpeg,与FLAC解码相关的源码有:
bitstream.c/bitstream.h,获取位流源码实现。
decoder.c/decoder.h,FLAC解码实现。
ARM.S/arm.h,针对cortex-m,MDK下修改的LPC解码arm汇编实现,非必须文件,用于优化Level 8的解码速度。在decoder.c中取消定义CPU_ARM宏则取消arm汇编的优化实现。
golomb.h和tables.c为编码头文件和常数表实现。
把该库源码加入LPC5411x工程编译即可。
3FLAC播放
FLAC音频的播放涉及到音频驱动、SD卡读写文件的实现,可以参考前面的章节。播放实现主要流程如下:
a.   用Flac_ParceMetadata()函数打开FLAC音频文件并解析FLAC头,获取采样位数、采样频率、声道数等等音频格式。
static int Flac_ParceMetadata(constTCHAR filePath[], FLACContext* context)
{
UINT s1 = 0;
FIL FLACfile;
int metaDataFlag = 1;
char metaDataChunk[128];      
unsigned long metaDataBlockLength = 0;
char* tagContents;
if(f_open(&FLACfile, filePath,FA_READ) != FR_OK) {
       PRINTF("Couldnot open: %srn", filePath);
       return1;
}
f_read(&FLACfile, metaDataChunk, 4,&s1);
if(s1 != 4) {
       PRINTF("Readfailurern");
       f_close(&FLACfile);
       return1;
}
if(memcmp(metaDataChunk,"fLaC", 4) != 0) {
       PRINTF("Nota FLAC filern");
       f_close(&FLACfile);
       return1;
}
      
// Now we are at the stream block
// Each block has metadata header of 4bytes
do {
f_read(&FLACfile, metaDataChunk, 4,&s1);
if(s1 != 4) {
       PRINTF("Readfailurern");
       f_close(&FLACfile);
       return1;
}
//Check if last chunk
if(metaDataChunk[0] & 0x80)metaDataFlag = 0;
metaDataBlockLength = (metaDataChunk[1]<< 16) | (metaDataChunk[2] << 8)
| metaDataChunk[3];
//STREAMINFO block
if((metaDataChunk[0] & 0x7F) == 0) {
       if(metaDataBlockLength> 128) {
              PRINTF("Metadatabuffer too smallrn");
              f_close(&FLACfile);
              return1;
       }
       f_read(&FLACfile,metaDataChunk, metaDataBlockLength, &s1);
       if(s1!= metaDataBlockLength) {
              PRINTF("Readfailurern");
              f_close(&FLACfile);
              return1;
       }
       /*
       Field in STEAMINFO
       <16>min block size (samples)
       <16>max block size (samples)
       <24>min frams size (bytes)
       <24>max frams size (bytes)
       <20>Sample rate (Hz)
       <3>(number of channels)-1
       <5>(bits per sample)-1.
       <36>Total samples in stream.
       <128>MD5 signature of the unencoded audio data.
       */
       context->min_blocksize= (metaDataChunk[0] << 8) | metaDataChunk[1];
       context->max_blocksize= (metaDataChunk[2] << 8) | metaDataChunk[3];
       context->min_framesize= (metaDataChunk[4] << 16) |
(metaDataChunk[5] << 8) | metaDataChunk[6];
       context->max_framesize= (metaDataChunk[7] << 16) |
(metaDataChunk[8] << 8)| metaDataChunk[9];
       context->samplerate= (metaDataChunk[10] << 12) |
(metaDataChunk[11] << 4) | ((metaDataChunk[12] & 0xf0)>> 4);
       context->channels= ((metaDataChunk[12] & 0x0e) >> 1) + 1;
       context->bps= (((metaDataChunk[12] & 0x01) << 4) |
((metaDataChunk[13] &0xf0)>>4) ) + 1;               
       //Thisfield in FLAC context is limited to 32-bits
       context->totalsamples= (metaDataChunk[14] << 24) | (metaDataChunk[15] << 16) |
(metaDataChunk[16] <<8) | metaDataChunk[17];
      
} else {   
       if(f_lseek(&FLACfile,FLACfile.fptr + metaDataBlockLength) != FR_OK) {
              PRINTF("FileSeek Failedrn");
              f_close(&FLACfile);
              return1;
       }
}
} while(metaDataFlag);
// track length in ms
context->length =(context->totalsamples / context->samplerate) * 1000;
// file size in bytes
context->filesize =f_size(&FLACfile);
// current offset is end of metadata inbytes
context->metadatalength =FLACfile.fptr;
// bitrate of file     
context->bitrate =((context->filesize - context->metadatalength) * 8) / context->length;
f_close(&FLACfile);
return 0;      
}
b.   根据解析的音频格式,对I2S音频驱动初始化。
PRINTF("Playing %srn",filePath);
PRINTF("Mode: %srn",context.channels==1?"Mono":"Stereo");
PRINTF("Samplerate: %d Hzrn",context.samplerate);
PRINTF("SampleBits: %dbitrn", context.bps);
PRINTF("Samples: %drn",context.totalsamples);
I2S_SetSamplerate(context.samplerate);
I2S_TxStart();
c.   调用flac_decode_frame()从fileChunk缓存区读取一块的码流数据进行解码,解码出来的左声道数据放在decodedSamplesLeft缓存区,右声道数据放在decodedSamplesRight缓存区。
if (flac_decode_frame(&context,decodedSamplesLeft, decodedSamplesRight,
fileChunk,bytesLeft, yield) < 0) {
       PRINTF("FLACDecode Failedrn");
       break;
}
d.   解码的左右声道数据填充到音频输出缓存,进行播放。解码一块产生context.blocksize个音频数据,一个一个填充到音频输出缓存,如果输出缓存满,则等待播放完一帧后,继续填充。
i = 0;
while (i < context.blocksize) {
       //LeftChannel
       samplePair[0]= (uint16_t) (decodedSamplesLeft>>sampleShift);
       if(context.channels==2) {
       //RightChannel
              samplePair[1]= (uint16_t) (decodedSamplesRight>>sampleShift);
       }else {
              //RepeatLeft channel if mono
              samplePair[1]= (uint16_t) (decodedSamplesLeft>>sampleShift);
       }
                     
       while(WriteIndex == I2SState.TxReadIndex) {
              
       }
       //Samplepair is 4 bytes, 16-bit mode
       if(WriteIndex != I2SState.TxReadIndex) {
              I2SState.TxBuffer[I2SState.TxWriteIndex][Index]=
(samplePair[0]&0xffff) |(samplePair[1]<<16);
              Index++;
              if(Index >= AUDIO_FRAME_SIZE) {
                     Index= 0;
                     I2SState.TxWriteIndex= WriteIndex;
                     if(WriteIndex >= AUDIO_NUM_BUFFERS-1) {
                            WriteIndex= 0;
                     }else {
                            WriteIndex++;
                     }
              }
       }
       i++;
}
e.   从SD卡读取下一块码流数据到fileChunk缓存区,跳到步骤c,如此循环解码、填充音频输出流、读取码流数据这些过程,直至文件结束,解码完毕。
//calculate the number of valid bytesleft in the fileChunk buffer
bytesUsed = context.gb.index/8;
bytesLeft -= bytesUsed;
//shift the unused stuff to the front ofthe fileChunk buffer
memmove(fileChunk,&fileChunk[bytesUsed], bytesLeft);
//Refill the fileChunk buffer
f_read(&FLACfile,&fileChunk[bytesLeft], MAX_FRAMESIZE - bytesLeft, &s1);
//add however many were read
bytesLeft += s1;
播放<<天空之城>>主题曲sky city.flac如下:
1.png
                              
4. 附录
MDK工程,包含SD卡文件读写代码,I2S播放驱动,FLAC无损音频文件解码播放的实现,FLAC音频文件sky city.flac。
https://pan.baidu.com/s/1boEFZ6R

回帖(1)

可乐丸子

2017-12-23 23:14:12
全码率都可以吗?烧写这个可以播放MP3吗
举报

更多回帖

×
20
完善资料,
赚取积分