首頁 > 評測 > 基于靈動MM32F5270開發(fā)板播放MP3和WAV音頻文件

基于靈動MM32F5270開發(fā)板播放MP3和WAV音頻文件

  
  • 作者:
  • 來源:
  • [導讀]
  • 本帖最后由 春嬌霹靂娃 于 2023-6-16 14:47 編輯 #申請原創(chuàng)# @21小跑堂 一、I2S簡介 I2S(Inter-IC Sound)總線為集成在芯片內的音頻總線,是飛利浦公司為數(shù)字音頻設備之間的音頻數(shù)據傳輸而制定的一種總

本帖最后由 春嬌霹靂娃 于 2023-6-16 14:47 編輯

#申請原創(chuàng)# @21小跑堂

一、I2S簡介
I2S(Inter-IC Sound)總線為集成在芯片內的音頻總線,是飛利浦公司為數(shù)字音頻設備之間的音頻數(shù)據傳輸而制定的一種總線標準。

MM32F5270系列MCU最多支持3個I2S接口,支持半雙工通信和全雙工通信,數(shù)據幀格式可配置為 16 位、24 位或 32 位。數(shù)據傳輸方向始終是MSB優(yōu)先,且每個I2S接口都支持DMA傳輸方式。MM32F5270系列的I2S支持飛利浦標準、MSB向左對齊標準、LSB向右對齊標準和PCM標準四種。

MM32F5270 是一款搭載了安謀科技 Arm China STAR-MC1 內核的 MCU 產品,其工作頻率可達 120MHz,內置多達 256KB Flash 和 192KB RAM。MM32F5270 相較于現(xiàn)有產品全面提升了性能、存儲容量、總線架構和外設配置,旨在覆蓋更廣泛的工業(yè)、汽車和 IoT 應用。

二、本文實現(xiàn)音頻播放的設計框架
本實驗搭載在 PLUS-F5270 開發(fā)板,通過 SDSPI 組件實現(xiàn) FatFs 移植讀取音頻文件,分為 WAV 格式音頻文件解碼和 MP3 格式音頻文件通過 libmad 解碼。組件包含SDSPI、FatFS 和 Libmad 三部分組成,整個設計框圖如下圖:



三、實驗環(huán)境


1.硬件環(huán)境:MM32F5277E9P開發(fā)板,SD卡及讀卡器



I2S 部分電路原理圖:


2.軟件環(huán)境:KEIL軟件,Tera Term 終端軟件,酷狗音樂

本實驗整個設計思路為:
1.通過 SDSPI 接口操作 SD 卡,移植 FatFs 文件系統(tǒng)對音頻文件進行識別,對音頻文件數(shù)據進行讀取操作;
2.通過 DMA 搬運的方式,將存儲在 SD 卡中的音頻文件(WAV 格式 / MP3 格式)通過 I2S 總線,將音頻數(shù)據發(fā)送給 CS4344 音頻數(shù)模轉換芯片,從而播放出音樂;
3.開啟 DMA 傳輸完成中斷和半傳輸完成中斷;
4.使用雙緩沖區(qū),實現(xiàn)流暢的音頻文件播放效果。

四、FatFS簡介
1.FatFs official website: http://elm-chan.org/fsw/ff/00index_e.html
FatFS 負責管理和存儲文件信息的軟件機構,是一種在磁盤上組織文件的方法。其優(yōu)點有:
  • C 語言編寫,支持 FAT12, FAT16 和 FAT32
  • 支持多種存儲媒介,有獨立的緩沖區(qū),可對多個文件進行讀寫
  • 可裁剪的文件系統(tǒng)
  • 免費開源,專門為小型嵌入式系統(tǒng)設計

2.移植FatFS
適配ffconf.h和diskio.c文件,使編譯通過。


FatFS常用的API如下圖:


五、WAV格式音頻文件
1.WAV是 Waveform 的簡寫,也叫波形文件,是一種可以存儲聲音波形的數(shù)字音頻格式。其具有真實記錄聲音波形的特點,基本無數(shù)據壓縮,數(shù)據體量相對較大。典型的WAV文件格式如下圖:

WAV 文件采用的是 RIFF 格式結構,至少由 RIFF 塊、fmt 塊和 data 塊組成。每個 RIFF 文件由若干個塊 (chunk) 組成,每個塊由塊標識、塊長度以及數(shù)據這三部分組成。塊標識由 4 個 ASCII 碼字符組成的,如果不滿 4 個字符則在右邊以空格來補齊。塊長度由 4 個字節(jié)存儲空間,保存的是當前塊數(shù)據的長度,但不包含塊標識和塊長度字段,所以一個塊的實際長度就是塊長度字段的數(shù)值再加上 8 字節(jié)。

WAV文件格式實例解析如下:


2.WAV文件解碼

 

  • 結構體定義:wav_decode.h
  • 結構解析:
  • bool wav_decode_open(wav_decode_obj_t * obj, char * file_name, uint8_t * buf, uint32_t buf_size)
  • bool wav_decode_read(wav_decode_obj_t * obj, uint16_t * buf, uint32_t buf_len)
  • void wav_decode_close(wav_decode_obj_t * obj)
  • 播放實現(xiàn):void app_wav_play(void)


wav_decode_open 打開FatFS 中音頻文件:

  1. <font face="Arial"><span style="background-color: black;"><font color="#fffacd">/* open fatfs file. */
  2. bool wav_decode_open(wav_decode_obj_t * obj,char * file_name, uint8_t * buf, uint32_t buf_size){
  3.      UINT fatfs_br = 0u;
  4.      uint8_t offset = 0u;
  5.      if (FR_OK != f_open(&(obj->fatfs_file), file_name, FA_READ) ){
  6.          return false;}
  7.      obj->fatfs_buf = buf;
  8.      obj->fatfs_buf_size = buf_size;
  9.      if (FR_OK != f_read(&(obj->fatfs_file), obj->fatfs_buf, WAV_DECODE_HEAD_BUFFER_SIZE, &fatfs_br)){
  10.          return false;}
  11.     chunk_riff_def_t *chunk_riff;
  12.     chunk_fmt_def_t  *chunk_fmt;
  13.     chunk_fact_def_t *chunk_fact;
  14.     chunk_data_def_t *chunk_data;
  15.  
  16.     chunk_riff = (chunk_riff_def_t *)(obj->fatfs_buf);
  17.     chunk_fmt  = (chunk_fmt_def_t  *)(obj->fatfs_buf + 12);
  18.     chunk_fact = (chunk_fact_def_t *)(obj->fatfs_buf + 12 + 8 + chunk_fmt->sub_chunk1_size);
  19.  
  20.     if((chunk_fact->chunk_id == 0x74636166) || (chunk_fact->chunk_id == 0x5453494C)){
  21.         chunk_data = (chunk_data_def_t *)(obj->fatfs_buf + 12 + 8 + chunk_fmt->sub_chunk1_size + 8 + chunk_fact->chunk_size);
  22.         offset = 12 + 8 + chunk_fmt->sub_chunk1_size + 8 + chunk_fact->chunk_size;}
  23.     else{
  24.         chunk_data = (chunk_data_def_t *)(obj->fatfs_buf + 12 + 8 + chunk_fmt->sub_chunk1_size);
  25.         offset = 12 + 8 + chunk_fmt->sub_chunk1_size;}
  26.  
  27.     if(chunk_riff->format == 0x45564157 && chunk_data->sub_chunk2_id == 0x61746164){
  28.          obj->audio_format    = chunk_fmt->audio_format;
  29.          obj->channel         = chunk_fmt->num_of_channels;
  30.          obj->sample_rate     = chunk_fmt->sample_rate;
  31.          obj->bits_per_sample = chunk_fmt->bits_per_sample;
  32.          obj->bit_rate        = chunk_fmt->byte_rate * 8;
  33.          obj->block_align     = chunk_fmt->block_align;
  34.  
  35.          obj->data_size  = chunk_data->sub_chunk2_size;
  36.          obj->data_start_offset = offset;
  37.          obj->total_second = obj->data_size;
  38.     }else{
  39.         return false;}
  40.      return true;
  41. }</font></span></font>
復制代碼

wav_decode_read 讀取音頻文件數(shù)據:

  1. <font face="Arial">/* read fatfs file. */
  2. bool wav_decode_read(wav_decode_obj_t * obj,uint16_t * buf,uint32_t buf_len){
  3.      UINT fat_br = 0u;
  4.      if (FR_OK != f_read(&(obj->fatfs_file), buf, buf_len,&fat_br) ){
  5.          return false;}
  6.      obj->current_second+=fat_br;
  7.  
  8.      if((fat_br <= 0) || (fat_br < buf_len)){
  9.      return false;}
  10.      return true;
  11. }</font>
復制代碼

wav_decode_close 解碼數(shù)據結束:

  1. <font face="Arial">/* close fatfs file. */
  2. void wav_decode_close(wav_decode_obj_t * obj){
  3.     f_close(&(obj->fatfs_file) );}
  4. </font>
復制代碼

app_wav_play 播放 WAV:

  1. <font face="Arial">/* play wave audio. */
  2. void app_wav_play(void){
  3.     if( wav_decode_open(&wav_obj, file_path, file_buf, 10240) ){
  4.         if( (wav_obj.bits_per_sample == 16) &&
  5.             (wav_obj.channel == 2) &&
  6.             (wav_obj.sample_rate > 44000) &&
  7.             (wav_obj.sample_rate < 48100)){
  8.             Audio_ClearStatus(AUDIO_STATUS_XFER_HALF | AUDIO_STATUS_XFER_DONE);
  9.             wav_decode_read(&wav_obj, audio_buf[0], 5120);
  10.  
  11.             Audio_SetConf(44100, (uint16_t *)audio_buf[0], 2560);
  12.             Audio_Enable(true);}}
  13.     else{
  14.         return;}
  15.  
  16.     uint8_t next_index = 1;
  17.  
  18.     while(1){
  19.         while((Audio_GetStatus() & AUDIO_STATUS_XFER_HALF) != AUDIO_STATUS_XFER_HALF);
  20.         Audio_ClearStatus(AUDIO_STATUS_XFER_HALF);
  21.         if(next_index == 0){
  22.             if(!wav_decode_read(&wav_obj,audio_buf[0], 5120)){
  23.                 break;}}
  24.         else{
  25.             if(!wav_decode_read(&wav_obj,audio_buf[1], 5120)){
  26.                 break;}}
  27.  
  28.         while((Audio_GetStatus() & AUDIO_STATUS_XFER_DONE) != AUDIO_STATUS_XFER_DONE);
  29.         Audio_ClearStatus(AUDIO_STATUS_XFER_DONE);
  30.  
  31.         if(next_index == 0){
  32.             Audio_SetConf(44100, (uint16_t *)audio_buf[0], 2560);
  33.             next_index = 1;}
  34.         else{
  35.             Audio_SetConf(44100, (uint16_t *)audio_buf[1], 2560);
  36.             next_index = 0;}
  37.         Audio_Enable(true);}
  38.  
  39.     Audio_Enable(false);
  40.     wav_decode_close(&wav_obj);}</font>
復制代碼


六、MP3音頻文件解碼

1.MP3 格式音樂文件 (Moving Picture Experts Group Audio Layer III (MPEG Audio Layer 3) ),經過壓縮后的 MP3 文件數(shù)據由多個幀 (frame) 組成。幀是 MP3 文件的最小組成單位,每個幀由幀頭 、附加信息和聲音數(shù)據組成,幀的長度由不同的 MP3 文件而不同,每個幀包含一段音頻的壓縮數(shù)據,通過解碼庫解碼即可得到對應 PCM 音頻數(shù)據。

2.MP3解碼庫

適合在小型嵌入式控制器移植的有兩個開源 MP3 解碼庫,libmad 解碼庫和 helix 解碼庫,這兩個解碼庫都是以一幀為解碼單位,一次解碼一幀,其區(qū)別見下表:

Libmad Helix
  • 高精度的MPEG音頻解碼庫
  • 支持MPEG-1、MPEG-2、MPEG-2.5標準
  • 提供24 bit PCM輸出
  • 用定點運算模擬浮點運算,不需要處理器有浮點運算功能
  • 對MP3解碼中關鍵部分采用優(yōu)化算法
  • 軟件庫結構清晰,易于開發(fā)和使用
。。。
  • 支持MPEG-1、MPEG-2、MPEG-2.5標準
  • 支持定點和浮點運算
  • 支持可變位恒率、恒定位速率及立體聲、單聲道音頻格式
。。。


3.libmad軟件解碼庫
本實驗使用Libmad解碼庫進行MP3解碼。
libmad 是一個開源 MP3 解碼庫,其對 MP3 解碼算法做了很多優(yōu)化,性能較好,很多播放器如 mplayer、xmms 等都是使用這個開源庫進行解碼的。Download from : https://downloads.sourceforge.net/mad/libmad-0.15.1b.tar.gz

“mad.h” 頭文件定義了 libmad 的數(shù)據結構及 API 函數(shù),libmad 中的主要數(shù)據結構體如下表所示:



4.移植libmad
將 libmad-0.15.1b 文件夾復制到工程目錄,將所有的 .c 文件加入工程,設置好包含路徑。



若編譯出現(xiàn) “FPM not select” 警告,在 KEIL 進行宏定義配置,如下圖:


libmad 代碼中使用 DEBUG 預編譯作為調試開關,需將其屏蔽


libmad 解碼出的數(shù)據為 24-bit PCM 數(shù)據,需進行 24-bit 至 16-bit 的轉換,使用 uint16_t scale(mad_fixed_t sample) 函數(shù)。

解碼用到三個數(shù)據結構,需將其定義為全局變量,防止?臻g不夠:
  • struct mad_frame Frame;
  • struct mad_synth Synth;
  • struct mad_stream Stream;

解碼的緩沖區(qū) audio buffer 要設置為全局變量,注意字節(jié)對齊的問題。

Libmad音頻解碼MP3也分為三個API完成:
  1. bool mp3_decode_open(mp3_decode_obj_t * obj, char * file_name, uint8_t * buf, uint32_t buf_size);
  2. bool mp3_decode_read(mp3_decode_obj_t * obj, uint16_t * buf, uint32_t buf_len);
  3. void mp3_decode_close(mp3_decode_obj_t * obj);
復制代碼
最終主函數(shù)實現(xiàn)WAV和MP3的播放
  1. #include "board_init.h"
  2.  
  3. #include "ff.h"
  4. #include "mp3_decode.h"
  5. #include "audio_port.h"
  6. #include "wav_decode.h"
  7.  
  8. FATFS   fs;
  9. DIR     dir;
  10. char file_path[BOARD_FILE_PATH_MAX_LEN];
  11. const char str_dir_path[] = BOARD_AUDIO_DIR_PATH;
  12.  
  13. /* audio buffer. */
  14. __attribute__((aligned(4))) uint16_t audio_buf[2][5120];
  15. __attribute__((aligned(4))) uint16_t out_buf[2][5120];
  16.  
  17. /* file buffer. */
  18. __attribute__((aligned(4))) uint8_t file_buf[10240];
  19.  
  20. __attribute__((aligned(4))) mp3_decode_obj_t mp3_obj;
  21. __attribute__((aligned(4))) wav_decode_obj_t wav_obj;
  22.  
  23. bool app_fatfs_init(void);
  24. void app_get_next_audio(void);
  25. void app_mp3_play(void);
  26. void app_wav_play(void);
  27.  
  28. int main(void){
  29.     BOARD_Init();
  30.     printf("audio player.\r\n");
  31.  
  32.     app_fatfs_init();
  33.     while (1){
  34.         app_get_next_audio();
  35.  
  36.         /* play mp3 audio. */
  37.         if (strstr(file_path, ".mp3") != NULL || strstr(file_path, ".MP3")) /* mp3 file. */{
  38.             app_mp3_play();
  39.             printf("done.\r\n");}
  40.  
  41.  
復制代碼
最后播放音樂的視頻



酷狗音樂下載網址

https://www.kugou.com/yy/html/rank.html

附件
軟件工程源代碼:
plus-f5270_audio_player_20239615.zip (7.87 MB)

MM32F5270原理圖:
PLUS-F5270 原理圖 V1.2.1.4.pdf (521.64 KB)

 

  • 本文系21ic原創(chuàng),未經許可禁止轉載!

網友評論