本帖最后由 sujingliang 于 2025-2-11 19:41 编辑
为了实现播放MP3文件需要完成以下工作:
1、利用FATFS读取位于EMMC上的mp3文件,这些mp3文件可以将EMMC模拟成U盘方式拷贝到EMMC上。
2、利用ST官方提供的mp3解码软件包实现mp3文件解码:
从st.com上下载X-CUBE-AUDIO开发包
将其中的SpiritDSP_MP3_Dec加入自己的工程。
3、利用SAI驱动EM8994播放音乐。
一、定义一些常量
/* Audio PDM FS frequency = 128KHz = 8 * 16KHz = 8 * FS_Freq */
#define AUDIO_PDM_GET_FS_FREQUENCY(FS) (FS * 8)
#define AUDIO_FREQUENCY SAI_AUDIO_FREQUENCY_16K
#define AUDIO_CHANNEL_NUMBER 2U
#define AUDIO_BUFFER_SIZE 256U
#define AUDIO_PCM_CHUNK_SIZE 32U
//static FILINFO file_information;
static FIL file_object;
__attribute__((aligned(4))) DWORD clmt[2400]; // Cluster link map table buffer
__attribute__((aligned(4))) short audio_output_buffer[8192] __attribute__((section(".bss.ARM.__at_0xD0400000")));
audio_output_buffer是音频缓冲,位于0xD0400000
二、WM8994初始化
static int32_t WM8994_Probe(void)
{
int32_t ret = BSP_ERROR_NONE;
WM8994_IO_t IOCtx;
static WM8994_Object_t WM8994Obj;
uint32_t id;
/* Configure the audio driver */
IOCtx.Address = AUDIO_I2C_ADDRESS;
IOCtx.Init = BSP_I2C4_Init;
IOCtx.DeInit = BSP_I2C4_DeInit;
IOCtx.ReadReg = BSP_I2C4_ReadReg16;
IOCtx.WriteReg = BSP_I2C4_WriteReg16;
IOCtx.GetTick = BSP_GetTick;
if(WM8994_RegisterBusIO (&WM8994Obj, &IOCtx) != WM8994_OK)
{
ret = BSP_ERROR_BUS_FAILURE;
}
else
{
/* Reset the codec */
if(WM8994_Reset(&WM8994Obj) != WM8994_OK)
{
ret = BSP_ERROR_COMPONENT_FAILURE;
}
else if(WM8994_ReadID(&WM8994Obj, &id) != WM8994_OK)
{
ret = BSP_ERROR_COMPONENT_FAILURE;
}
else if(id != WM8994_ID)
{
ret = BSP_ERROR_UNKNOWN_COMPONENT;
}
else
{
Audio_Drv = (AUDIO_Drv_t *) &WM8994_Driver;
Audio_CompObj = &WM8994Obj;
}
}
return ret;
}
三、SAI初始化
static void Playback_Init(void)
{
RCC_PeriphCLKInitTypeDef RCC_PeriphCLKInitStruct;
/* Configure PLLSAI1 prescalers */
/* PLLSAI_VCO: VCO_512M
SAI_CLK(first level) = PLLSAI_VCO/PLLSAIP = 512/125 = 4.096 Mhz */
RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SAI2;
RCC_PeriphCLKInitStruct.Sai23ClockSelection = RCC_SAI2CLKSOURCE_PLL2;
RCC_PeriphCLKInitStruct.PLL2.PLL2P = 125;
RCC_PeriphCLKInitStruct.PLL2.PLL2Q = 1;
RCC_PeriphCLKInitStruct.PLL2.PLL2R = 1;
RCC_PeriphCLKInitStruct.PLL2.PLL2N = 512;
RCC_PeriphCLKInitStruct.PLL2.PLL2FRACN = 0;
RCC_PeriphCLKInitStruct.PLL2.PLL2M = 25;
if(HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct) != HAL_OK)
{
Error_Handler();
}
/* Configure PLLSAI4A prescalers */
RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SAI4A;
RCC_PeriphCLKInitStruct.Sai4AClockSelection = RCC_SAI4ACLKSOURCE_PLL2;
if(HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct) != HAL_OK)
{
Error_Handler();
}
/* SAI PCM Output init */
__HAL_SAI_RESET_HANDLE_STATE(&haudio_out_sai);
haudio_out_sai.Instance = AUDIO_OUT_SAIx;
haudio_out_sai.Init.AudioMode = SAI_MODEMASTER_TX;
haudio_out_sai.Init.Synchro = SAI_ASYNCHRONOUS;
haudio_out_sai.Init.OutputDrive = SAI_OUTPUTDRIVE_ENABLE;
haudio_out_sai.Init.NoDivider = SAI_MASTERDIVIDER_ENABLE;
haudio_out_sai.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_1QF;
haudio_out_sai.Init.AudioFrequency = AUDIO_FREQUENCY;
haudio_out_sai.Init.Protocol = SAI_FREE_PROTOCOL;
haudio_out_sai.Init.DataSize = SAI_DATASIZE_16;
haudio_out_sai.Init.FirstBit = SAI_FIRSTBIT_MSB;
haudio_out_sai.Init.ClockStrobing = SAI_CLOCKSTROBING_FALLINGEDGE;
haudio_out_sai.FrameInit.FrameLength = 128;
haudio_out_sai.FrameInit.ActiveFrameLength = 64;
haudio_out_sai.FrameInit.FSDefinition = SAI_FS_CHANNEL_IDENTIFICATION;
haudio_out_sai.FrameInit.FSPolarity = SAI_FS_ACTIVE_LOW;
haudio_out_sai.FrameInit.FSOffset = SAI_FS_BEFOREFIRSTBIT;
haudio_out_sai.SlotInit.FirstBitOffset = 0;
haudio_out_sai.SlotInit.SlotSize = SAI_SLOTSIZE_DATASIZE;
haudio_out_sai.SlotInit.SlotNumber = 4;
haudio_out_sai.SlotInit.SlotActive = (SAI_SLOTACTIVE_0 | SAI_SLOTACTIVE_2);
/* DeInit SAI PCM input */
HAL_SAI_DeInit(&haudio_out_sai);
/* Init SAI PCM input */
if(HAL_OK != HAL_SAI_Init(&haudio_out_sai))
{
Error_Handler();
}
/* Enable SAI to generate clock used by audio driver */
__HAL_SAI_ENABLE(&haudio_out_sai);
WM8994_Probe();
/* Init PDM Filters */
// AUDIO_IN_PDMToPCM_Init(AUDIO_FREQUENCY, AUDIO_CHANNEL_NUMBER);
}
四、列出所有的mp3文件名
void list_mp3_files(const char *path)
{
DIR dir;
FILINFO fileInfo;
// 挂载文件系统
FRESULT res = f_mount(&MMCFatFs, (TCHAR const*)MMCPath, 0);
if (res != FR_OK) {
printf("Failed to mount filesystem: %d\n", res);
return;
}
// 打开目录
res = f_opendir(&dir, path);
if (res != FR_OK) {
printf("Failed to open directory: %d\n", res);
f_mount(NULL, "", 0); // 卸载文件系统
return;
}
// 遍历目录
while (1) {
res = f_readdir(&dir, &fileInfo);
if (res != FR_OK || fileInfo.fname[0] == 0) {
break; // 遍历完成或出错
}
// 检查是否为 MP3 文件
if (!(fileInfo.fattrib & AM_DIR)) { // 排除目录
char *ext = strrchr(fileInfo.fname, '.'); // 获取文件扩展名
if (ext && strcmp(ext, ".mp3") == 0) { // 检查扩展名是否为 .mp3
strncpy(mp3Files[mp3Count], fileInfo.fname, 255); // 存储文件名
mp3Count++;
if (mp3Count >= 100) {
break; // 达到最大存储数量
}
}
}
}
// 关闭目录
f_closedir(&dir);
// 卸载文件系统
f_mount(NULL, "", 0);
// 打印所有 MP3 文件名
for (int i = 0; i < mp3Count; i++) {
printf("MP3 File: %s\n", mp3Files[i]);
}
}
五、初始化MP3播放器
/**
* [url=home.php?mod=space&uid=247401]@brief[/url] 初始化MP3播放器
* @param mp3_file: MP3文件的名字
* @param vol: 音量
* @param output_device: 0--耳机 1--喇叭
* @retval 1:成功 0:失败
*/
unsigned char st_mp3_player_Init( char *mp3_file, unsigned char vol, unsigned char output_device )
{
unsigned int read_num;
unsigned int ID3_Length, temp1;
unsigned char buf[4];
uint32_t *AudioFreq_ptr;
uwVolume = vol;
memset( SongInfo.FileName, 0, 30 );
/* Register the file system object to the FatFs module */
if(f_mount(&MMCFatFs, (TCHAR const*)MMCPath, 0) != FR_OK)
{
printf("f_mount error\r\n");
return 0;
}
if( f_open( &file_object, mp3_file, FA_OPEN_EXISTING | FA_READ ) != FR_OK )
{
printf("MP3 File Open Err!!!\r\n" );
return 0;
}
/* 使能FATFS的快速读写功能 */
if( f_lseek(&file_object, 4)!= FR_OK )
{
f_close( &file_object );
printf("faftfs fast seek feature err!!!\r\n" );
return 0;
}
file_object.cltbl = clmt; /* Enable fast seek feature (cltbl != NULL) */
clmt[0] = 2400; /* Set table size */
if( f_lseek(&file_object, CREATE_LINKMAP) != FR_OK ) /* Create CLMT */
{
f_close( &file_object );
printf("faftfs fast seek feature err!!!\r\n" );
return 0;
}
if( f_lseek(&file_object, 0)!= FR_OK )
{
f_close( &file_object );
printf("faftfs fast seek feature err!!!\r\n" );
return 0;
}
//计算文件大小
SongInfo.FileSize = f_size( &file_object );
memcpy( SongInfo.FileName, mp3_file, strlen( mp3_file) );
//判断打开的文件是否是mp3文件
if( f_read( &file_object, buf, 3, &read_num ) != FR_OK )
{
f_close( &file_object );
return 0;
}
if( memcmp( buf, (unsigned char *)"ID3", 3 ) != 0 )
{
f_close( &file_object );
printf("It is not mp3 file!!!\r\n" );
return 0;
}
//开始计算ID3标签头大小
f_lseek( &file_object, 6 );
if( f_read( &file_object, buf, 4, &read_num ) != FR_OK )
{
f_close( &file_object );
// printf("读取文件失败。\r\n" );
return 0;
}
ID3_Length = (buf[0] & 0x7f) * 0x200000; //计算ID3标签头的大小
ID3_Length = ID3_Length + ( (buf[1] & 0x7f) * 0x400 );
ID3_Length = ID3_Length + ( (buf[2] & 0x7f) * 0x80 );
ID3_Length = ID3_Length + (buf[3] & 0x7f) + 10;
//跳转到ID3标签结束位置,准备获取mp3文件信息
f_lseek( &file_object, ID3_Length );
SongInfo.file_ptr = ID3_Length;
//初始化MP3解码器
memset( &g_MP3Decoder, 0, sizeof( TSpiritMP3Decoder ) );
SpiritMP3DecoderInit( &g_MP3Decoder, // MP3 decoder object
RetrieveMP3Data, // Input callback function
NULL, // No post-process callback
NULL // Callback parameter
);
//获取mp3文件信息
while( 1 )
{
Audio_Delay_1ms( 3 );
temp1 = SpiritMP3Decode( &g_MP3Decoder, /* Decoder structure */
(short *)&audio_output_buffer[0], /* [OUT] Output PCM buffer */
1152, /* Number of samples to decode (1 sample = 32 bit = 2ch*16 bit) */
&MP3Info /* [OUT, opt] Optional informational structure */
);
//到达文件末尾,没有获取mp3文件信息,退出
if( temp1 < 1152 )
{
f_close( &file_object );
printf("Get MP3 Info Err!!!\r\n");
return 0;
}
//获取到正确的MP3信息
if( MP3Info.IsGoodStream == 1 && MP3Info.nSampleRateHz != 0 && MP3Info.nBitrateKbps != 0 )
{
MPEGAudioFrameInfo.mSamplesPerFrame = MP3Info.nSamplesPerFrame;
MPEGAudioFrameInfo.mBitrate = MP3Info.nBitrateKbps * 1000;
SongInfo.Bitrate = MPEGAudioFrameInfo.mBitrate;
MPEGAudioFrameInfo.mChannelMode = MP3Info.nChannels;
MPEGAudioFrameInfo.mLayer = MP3Info.nLayer;
MPEGAudioFrameInfo.mSamplerate = MP3Info.nSampleRateHz;
//计算歌曲的播放时长
SongInfo.time = (SongInfo.FileSize - ID3_Length) * 8 / SongInfo.Bitrate;
printf("\r\n");
printf( "mp3 SamplesPerFrame:%d\r\n", MPEGAudioFrameInfo.mSamplesPerFrame );
printf( "mp3 Bitrate:%d\r\n", MPEGAudioFrameInfo.mBitrate );
printf( "mp3 Samplerate:%d\r\n", MPEGAudioFrameInfo.mSamplerate );
printf( "mp3 ChannelMode:%d\r\n", MPEGAudioFrameInfo.mChannelMode );
printf( "mp3 Layer:%d\r\n", MPEGAudioFrameInfo.mLayer );
printf( "mp3 FileSize:%d\r\n", SongInfo.FileSize );
printf( "mp3 PlayTime:%d\r\n", SongInfo.time );
Display_SongDescription();
break;
}
}
//重新初始化MP3解码器,准备开始解码
memset( &g_MP3Decoder, 0, sizeof( TSpiritMP3Decoder ) );
SpiritMP3DecoderInit( &g_MP3Decoder, // MP3 decoder object
RetrieveMP3Data, // Input callback function
NULL, // No post-process callback
NULL // Callback parameter
);
//跳转到ID3标签头结束位置,从此位置开始解码
f_lseek( &file_object, ID3_Length );
SongInfo.file_ptr = ID3_Length;
/*
for(int i=0;i<sizeof(AudioFreq);i++)
{
if(abs((int)(AudioFreq[i]-MPEGAudioFrameInfo.mSamplerate))<100)
{
AudioFreq_ptr=&AudioFreq[i];
break;
}
}
*/
AudioFreq[0]=MPEGAudioFrameInfo.mSamplerate;
AudioFreq_ptr=&AudioFreq[0];
//AudioFreq_ptr = &AudioFreq[6]; /*96K*/
AudioPlayInit->Device = AUDIO_OUT_DEVICE_HEADPHONE;
AudioPlayInit->ChannelsNbr = 2;
AudioPlayInit->SampleRate = *AudioFreq_ptr;
AudioPlayInit->BitsPerSample = AUDIO_RESOLUTION_16B;
AudioPlayInit->Volume = uwVolume;
//配置WM8994的工作模式
if( BSP_AUDIO_OUT_Init( 0, AudioPlayInit ) != 0 )
{
f_close( &file_object );
printf("WM8994 Init Err!!!\r\n");
return 0;
}
//if( output_device == 0 ) BSP_AUDIO_OUT_SetAudioFrameSlot(CODEC_AUDIOFRAME_SLOT_02);
//else BSP_AUDIO_OUT_SetAudioFrameSlot(CODEC_AUDIOFRAME_SLOT_13);
return 1;
}
六、播放MP3函数
/**
* [url=home.php?mod=space&uid=247401]@brief[/url] 播放MP3
* @param time: MP3文件开始播放的时间点(不超过MP3播放时间的最大值),单位:s
* @retval 1:成功 0:失败
*/
unsigned char st_mp3play( unsigned short int time )
{
signed int temp = 0;
unsigned int nSamples = 0;
unsigned int addr = 0;
uint8_t index=mp3Current;
SongInfo.state = OK;
SongInfo.time_count = time;
if( time >= (SongInfo.time-2) )
{
BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
f_close( &file_object );
return 1;
}
//根据播放起始时间,计算文件指针起始位置
addr = (time * SongInfo.Bitrate / 8 );
if( addr == 0 )
{
if( f_lseek(&file_object, SongInfo.file_ptr ) != FR_OK )
{
f_close( &file_object );
BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
return 0;
}
}
else
{
addr += SongInfo.file_ptr;
SongInfo.file_ptr = addr;
if( f_lseek(&file_object, SongInfo.file_ptr) != FR_OK )
{
f_close( &file_object );
BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
return 0;
}
}
/* 先解码两帧mp3音频数据,用于填充audio_output_buffer */
temp = 0;
while( temp < 2 )
{
nSamples = SpiritMP3Decode( &g_MP3Decoder, /* Decoder structure */
(short *)&audio_output_buffer[temp*MPEGAudioFrameInfo.mSamplesPerFrame*2], /* [OUT] Output PCM buffer */
MPEGAudioFrameInfo.mSamplesPerFrame, /* Number of samples to decode (1 sample = 32 bit = 2ch*16 bit) */
&MP3Info /* [OUT, opt] Optional informational structure */
);
printf("Samples:%d\r\n", nSamples);
if( MP3Info.IsGoodStream == 1 )
{
temp++;
}
/* 到达文件末尾,则退出 */
if( nSamples < MPEGAudioFrameInfo.mSamplesPerFrame||index!= mp3Current)
{
f_close( &file_object );
BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
return 0;
}
Audio_Delay_1ms( 3 );
}
TxHalfCpltFlag = 0;
TxCpltFlag = 0;
TxBufNum = 0;
AudioOutFlag = 0;
Audio_ErrorFlag = 0;
//开始播放
BSP_AUDIO_OUT_Play( 0,(uint8_t*)audio_output_buffer, (MPEGAudioFrameInfo.mSamplesPerFrame*2*2*2) );
AudioOutFlag = 1;
while( 1 )
{
while( AudioOutFlag == 1 )
{
//播放状态--暂停
if( SongInfo.state == Pause )
{
BSP_AUDIO_OUT_Pause(0);
while( SongInfo.state == Pause )
{
Audio_Delay_1ms( 10 );
}
}
//播放状态--恢复
else if( SongInfo.state == Resume )
{
BSP_AUDIO_OUT_Resume( 0 );
SongInfo.state = OK;
}
//播放状态--停止
else if( SongInfo.state == Stop )
{
BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
f_close( &file_object );
SongInfo.state = OK;
return 1;
}
//播放状态--错误
else if( Audio_ErrorFlag == 1 )
{
BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
f_close( &file_object );
return 0;
}
Audio_Delay_1ms( 3 );
}
//计算当前歌曲时间点
SongInfo.time_count = SongInfo.file_ptr * 8 / SongInfo.Bitrate ;
//printf("SongInfo.time_count:%d\r\n",SongInfo.time_count);
//audio_output_buffer已通过DMA传输一半
if( TxHalfCpltFlag == 1 )
{
//解码MP3音频数据,用来填充audio_output_buffer
while( 1 )
{
nSamples = SpiritMP3Decode( &g_MP3Decoder, /* Decoder structure */
(short *)&audio_output_buffer[0], /* [OUT] Output PCM buffer */
MPEGAudioFrameInfo.mSamplesPerFrame, /* Number of samples to decode (1 sample = 32 bit = 2ch*16 bit) */
&MP3Info /* [OUT, opt] Optional informational structure */
);
/*到达文件末尾, 则退出*/
if( nSamples < MPEGAudioFrameInfo.mSamplesPerFrame ||index!= mp3Current)
{
BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
f_close( &file_object );
return 1;
}
if( !( !MP3Info.IsGoodStream || (MP3Info.nBitrateKbps == 0) || (MP3Info.nSampleRateHz == 0) ) )
{
break;
}
}
}
//audio_output_buffer已通过DMA全部传输完成
if( TxCpltFlag == 1 )
{
while( 1 )
{
nSamples = SpiritMP3Decode( &g_MP3Decoder, /* Decoder structure */
(short *)&audio_output_buffer[MPEGAudioFrameInfo.mSamplesPerFrame*2], /* [OUT] Output PCM buffer */
MPEGAudioFrameInfo.mSamplesPerFrame, /* Number of samples to decode (1 sample = 32 bit = 2ch*16 bit) */
&MP3Info /* [OUT, opt] Optional informational structure */
);
/*到达文件末尾,则退出*/
if( nSamples < MPEGAudioFrameInfo.mSamplesPerFrame )
{
BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
f_close( &file_object );
return 1;
}
if( !( !MP3Info.IsGoodStream || (MP3Info.nBitrateKbps == 0) || (MP3Info.nSampleRateHz == 0) ) )
{
break;
}
}
}
if( TxHalfCpltFlag == 1 && TxCpltFlag == 1 )
{
TxHalfCpltFlag = 0;
TxCpltFlag = 0;
BSP_AUDIO_OUT_Resume(0);
}
else if( TxHalfCpltFlag == 1 ) TxHalfCpltFlag = 0;
else if( TxCpltFlag == 1 ) TxCpltFlag = 0;
AudioOutFlag = 1;
}
return 1;
}
七、中断处理
void DMA2_Stream1_IRQHandler(void)
{
/* USER CODE BEGIN DMA2_Stream1_IRQn 0 */
HAL_DMA_IRQHandler(haudio_out_sai.hdmatx);
/* USER CODE END DMA2_Stream1_IRQn 0 */
/* USER CODE BEGIN DMA2_Stream1_IRQn 1 */
/* USER CODE END DMA2_Stream1_IRQn 1 */
}
回调
void BSP_AUDIO_OUT_TransferComplete_CallBack(uint32_t Instance)
{
TxCpltFlag = 1;
AudioOutFlag = 0;
TxBufNum = 0;
//printf("BSP_AUDIO_OUT_TransferComplete_CallBack\r\n");
if( TxHalfCpltFlag == 1 )
{
BSP_AUDIO_OUT_Pause(0);
TxBufNum = 1;
printf("fault1\r\n");
}
}
void BSP_AUDIO_OUT_HalfTransfer_CallBack(uint32_t Instance)
{
TxHalfCpltFlag = 1;
AudioOutFlag = 0;
TxBufNum = 1;
//printf("BSP_AUDIO_OUT_HalfTransfer_CallBack\r\n");
if( TxCpltFlag == 1 )
{
BSP_AUDIO_OUT_Pause(0);
TxBufNum = 0;
printf("fault2\r\n");
}
}
八、播放DEMO
void mp3_player_demo(void)
{
if( st_mp3_player_Init( mp3Files[mp3Current%mp3Count], 80, 0) == 1 )
{
printf("mp3 init ok\r\n");
st_mp3play( 0 );
}
}
void BSP_PB_Callback(Button_TypeDef Button)
{
printf("BSP_PB_Callback\r\n");
if(Button==BUTTON_USER)
{
mp3Current++;
if(mp3Current>=mp3Count) mp3Current=0;
}
}
按键处理
|