STM32H750XB_RT-THREAD/38-SAI—音频/SAI—MP3播放器/User/mp3Player/mp3Player.c
2025-07-21 14:34:29 +08:00

266 lines
7.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
******************************************************************************
* @file recorder.c
* @author fire
* @version V1.0
* @date 2015-xx-xx
* @brief WM8978放音功能测试+mp3解码
******************************************************************************
* @attention
*
* 实验平台:野火 STM32 H750 开发板
* 论坛 :http://www.chuxue123.com
* 淘宝 :http://firestm32.taobao.com
*
******************************************************************************
*/
#include <stdio.h>
#include <string.h>
#include "./usart/bsp_debug_usart.h"
#include "./wm8978/bsp_wm8978.h"
#include "ff.h"
#include "./mp3Player/mp3Player.h"
#include "mp3dec.h"
#include "./sai/bsp_sai.h"
/* 推荐使用以下格式mp3文件
* 采样率44100Hz
* 声 道2
* 比特率320kbps
*/
/* 处理立体声音频数据时输出缓冲区需要的最大大小为2304*16/8字节(16为PCM数据为16位)
* 这里我们定义MP3BUFFER_SIZE为2304实际输出缓冲区为MP3BUFFER_SIZE*2个字节
*/
#define MP3BUFFER_SIZE 2304
#define INPUTBUF_SIZE 3000
static HMP3Decoder Mp3Decoder; /* mp3解码器指针 */
static MP3FrameInfo Mp3FrameInfo; /* mP3帧信息 */
static MP3_TYPE mp3player; /* mp3播放设备 */
volatile uint8_t Isread=0; /* DMA传输完成标志 */
static uint8_t bufflag=0; /* 数据缓存区选择标志 */
uint32_t led_delay=0;
uint8_t inputbuf[INPUTBUF_SIZE]={0}; /* 解码输入缓冲区1940字节为最大MP3帧大小 */
__attribute__((at(0x30000000))) short outbuffer[2][MP3BUFFER_SIZE]; /* 解码输出缓冲区也是I2S输入数据实际占用字节数RECBUFFER_SIZE*2 */
FIL file; /* file objects */
FRESULT result;
UINT bw; /* File R/W count */
/**
* @brief MP3格式音频播放主程序
* @param 无
* @retval 无
*/
int times = 0;
void mp3PlayerDemo(const char *mp3file)
{
uint8_t *read_ptr=inputbuf;
uint32_t frames=0;
int err=0, i=0, outputSamps=0;
int read_offset = 0; /* 读偏移指针 */
int bytes_left = 0; /* 剩余字节数 */
mp3player.ucFreq = SAI_AUDIOFREQ_DEFAULT;
mp3player.ucStatus = STA_IDLE;
mp3player.ucVolume = 40;
result=f_open(&file,mp3file,FA_READ);
if(result!=FR_OK)
{
printf("Open mp3file :%s fail!!!->%d\r\n",mp3file,result);
result = f_close (&file);
return; /* 停止播放 */
}
printf("当前播放文件 -> %s\n",mp3file);
//初始化MP3解码器
Mp3Decoder = MP3InitDecoder();
if(Mp3Decoder==0)
{
printf("初始化helix解码库设备\n");
return; /* 停止播放 */
}
printf("初始化中...\n");
Delay_ms(10); /* 延迟一段时间等待I2S中断结束 */
wm8978_Reset(); /* 复位WM8978到复位状态 */
/* 配置WM8978芯片输入为DAC输出为耳机 */
wm8978_CfgAudioPath(DAC_ON, EAR_LEFT_ON | EAR_RIGHT_ON);
/* 调节音量,左右相同音量 */
wm8978_SetOUT1Volume(mp3player.ucVolume);
/* 配置WM8978音频接口为飞利浦标准I2S接口16bit */
wm8978_CfgAudioIF(SAI_I2S_STANDARD, 16);
/* 初始化并配置I2S */
SAI_Play_Stop();
SAI_GPIO_Config();
// SAIxA_Tx_Config(SAI_I2S_STANDARD,SAI_PROTOCOL_DATASIZE_16BIT,mp3player.ucFreq);
// SAIA_TX_DMA_Init();
bufflag=0;
Isread=0;
mp3player.ucStatus = STA_PLAYING; /* 放音状态 */
result=f_read(&file,inputbuf,INPUTBUF_SIZE,&bw);
if(result!=FR_OK)
{
printf("读取%s失败 -> %d\r\n",mp3file,result);
MP3FreeDecoder(Mp3Decoder);
return;
}
read_ptr=inputbuf;
bytes_left=bw;
/* 进入主程序循环体 */
while(mp3player.ucStatus == STA_PLAYING)
{
read_offset = MP3FindSyncWord(read_ptr, bytes_left); //寻找帧同步,返回第一个同步字的位置
if(read_offset < 0) //没有找到同步字
{
result=f_read(&file,inputbuf,INPUTBUF_SIZE,&bw);
if(result!=FR_OK)
{
printf("读取%s失败 -> %d\r\n",mp3file,result);
break;
}
read_ptr=inputbuf;
bytes_left=bw;
continue; //跳出循环2回到循环1
}
read_ptr += read_offset; //偏移至同步字的位置
bytes_left -= read_offset; //同步字之后的数据大小
if(bytes_left < 1024) //补充数据
{
/* 注意这个地方因为采用的是DMA读取所以一定要4字节对齐 */
i=(uint32_t)(bytes_left)&3; //判断多余的字节
if(i) i=4-i; //需要补充的字节
memcpy(inputbuf+i, read_ptr, bytes_left); //从对齐位置开始复制
read_ptr = inputbuf+i; //指向数据对齐位置
result = f_read(&file, inputbuf+bytes_left+i, INPUTBUF_SIZE-bytes_left-i, &bw);//补充数据
if(result!=FR_OK)
{
printf("读取%s失败 -> %d\r\n",mp3file,result);
break;
}
bytes_left += bw; //有效数据流大小
}
err = MP3Decode(Mp3Decoder, &read_ptr, &bytes_left, outbuffer[bufflag], 0); //bufflag开始解码 参数mp3解码结构体、输入流指针、输入流大小、输出流指针、数据格式
frames++;
if (err != ERR_MP3_NONE) //错误处理
{
switch (err)
{
case ERR_MP3_INDATA_UNDERFLOW:
printf("ERR_MP3_INDATA_UNDERFLOW\r\n");
result = f_read(&file, inputbuf, INPUTBUF_SIZE, &bw);
read_ptr = inputbuf;
bytes_left = bw;
break;
case ERR_MP3_MAINDATA_UNDERFLOW:
/* do nothing - next call to decode will provide more mainData */
printf("ERR_MP3_MAINDATA_UNDERFLOW\r\n");
break;
default:
printf("UNKNOWN ERROR:%d\r\n", err);
// 跳过此帧
if (bytes_left > 0)
{
bytes_left --;
read_ptr ++;
}
break;
}
Isread=1;
}
else //解码无错误准备把数据输出到PCM
{
MP3GetLastFrameInfo(Mp3Decoder, &Mp3FrameInfo); //获取解码信息
/* 输出到DAC */
outputSamps = Mp3FrameInfo.outputSamps; //PCM数据个数
if (outputSamps > 0)
{
if (Mp3FrameInfo.nChans == 1) //单声道
{
//单声道数据需要复制一份到另一个声道
for (i = outputSamps - 1; i >= 0; i--)
{
outbuffer[bufflag][i * 2] = outbuffer[bufflag][i];
outbuffer[bufflag][i * 2 + 1] = outbuffer[bufflag][i];
}
outputSamps *= 2;
}//if (Mp3FrameInfo.nChans == 1) //单声道
}//if (outputSamps > 0)
/* 根据解码信息设置采样率 */
if (Mp3FrameInfo.samprate != mp3player.ucFreq) //采样率
{
mp3player.ucFreq = Mp3FrameInfo.samprate;
printf(" \r\n Bitrate %dKbps", Mp3FrameInfo.bitrate/1000);
printf(" \r\n Samprate %dHz", mp3player.ucFreq);
printf(" \r\n BitsPerSample %db", Mp3FrameInfo.bitsPerSample);
printf(" \r\n nChans %d", Mp3FrameInfo.nChans);
printf(" \r\n Layer %d", Mp3FrameInfo.layer);
printf(" \r\n Version %d", Mp3FrameInfo.version);
printf(" \r\n OutputSamps %d", Mp3FrameInfo.outputSamps);
printf("\r\n");
if(mp3player.ucFreq >= SAI_AUDIOFREQ_DEFAULT) //I2S_AudioFreq_Default = 2正常的帧每次都要改速率
{
SAIxA_Tx_Config(SAI_I2S_STANDARD,SAI_PROTOCOL_DATASIZE_16BIT,mp3player.ucFreq); //根据采样率修改iis速率
SAIA_TX_DMA_Init((uint32_t)(&outbuffer[0]),(uint32_t)&outbuffer[1],outputSamps);
}
SAI_Play_Start();
}
}//else 解码正常
if(file.fptr==file.fsize) //mp3文件读取完成退出
{
printf("END\r\n");
break;
}
while(Isread==0)
{
led_delay++;
if(led_delay==0xffffff)
{
led_delay=0;
// LED4_TOGGLE;
}
//Input_scan(); //等待DMA传输完成此间可以运行按键扫描及处理事件
}
Isread=0;
}
SAI_Play_Stop();
mp3player.ucStatus=STA_IDLE;
MP3FreeDecoder(Mp3Decoder);
f_close(&file);
}
/* DMA发送完成中断回调函数 */
/* 缓冲区内容已经播放完成,需要切换缓冲区,进行新缓冲区内容播放
同时读取WAV文件数据填充到已播缓冲区 */
void MusicPlayer_SAI_DMA_TX_Callback(void)
{
if(DMA_Instance->CR&(1<<19)) //当前使用Memory1数据
{
bufflag=0; //可以将数据读取到缓冲区0
}
else //当前使用Memory0数据
{
bufflag=1; //可以将数据读取到缓冲区1
}
Isread=1; // DMA传输完成标志
}
/***************************** (END OF FILE) *********************************/