Files
player/Project/Src/MP3/mp3play.c

693 lines
18 KiB
C
Raw Normal View History

2025-06-27 00:32:57 +08:00
#include "mp3play.h"
#include "stm32f4xx.h"
#include "dac.h"
#include "delay.h"
#include "mymem.h"
#include "usart.h"
#include "string.h"
#include "rtthread.h"
#include "ftfile.h"
//包含这个文件用于文字编码转换
#include "char_encode.h"
//////////////////////////////////////////////////////////////////////////////////
//本程序移植自helix MP3解码库
//ALIENTEK STM32F407开发板
//MP3 解码代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2014/6/29
//版本V1.0
//********************************************************************************
//V1.0 说明
//1,支持16位单声道/立体声MP3的解码
//2,支持CBR/VBR格式MP3解码
//3,支持ID3V1和ID3V2标签解析
//4,支持所有比特率(MP3最高是320Kbps)解码
//////////////////////////////////////////////////////////////////////////////////
static __mp3ctrl * mp3ctrl; //mp3控制结构体
static __audiodev audiodev; //音频控制结构体
static vu8 g_buff_Invalid=0; //数据无效时要重新填充数据
static vu8 g_buff_useing; //当前DMA在使用哪个缓冲区
static int g_vol=5; //音量 0~10级
#define MP3_VOL_MAX 10
//MP3 DMA发送回调函数
static void dma_tx_callback(DAC_UserStruct *dac)
{
u16 i;
if (dac->buff_useing==1)
{
if((audiodev.status&0X01)==0)//暂停了,填充0
{
for(i=0;i<2304*2;i++)audiodev.i2sbuf1[i]=0;
}
}else
{
if((audiodev.status&0X01)==0)//暂停了,填充0
{
for(i=0;i<2304*2;i++)audiodev.i2sbuf2[i]=0;
}
}
g_buff_useing=dac->buff_useing;
g_buff_Invalid=1;
}
//填充PCM数据到DAC
//buf:PCM数据首地址
//size:pcm数据量(16位为单位)
//nch:声道数(1,单声道,2立体声)
void mp3_fill_buffer(u16* buf,u16 size,u8 nch)
{
u16 i;
s16 *p;
while(g_buff_Invalid==0)//等待传输完成
{
rt_thread_delay(5);//
};
if(g_buff_useing==0)
{
p=(s16*)audiodev.i2sbuf2;
}else
{
p=(s16*)audiodev.i2sbuf1;
}
if(nch==2)
{
for(i=0;i<size;i++)
{
// p[i]=((buf[i]+0x8000)>>4);
int temp=(int16_t)buf[i];
temp=temp*g_vol/MP3_VOL_MAX;
p[i]=((temp+0x8000)>>4);
}
}
else //单声道
{
for(i=0;i<size;i++)
{
int temp=(int16_t)buf[i];
temp=temp*g_vol/MP3_VOL_MAX;
p[2*i]=((temp+0x8000)>>4);
p[2*i+1]=p[2*i];
}
}
//填充了数据之后设置为有效
g_buff_Invalid=0;
}
static void mp3_get_string (u8 type,u8 *out,u8 *in,int size);
//解析ID3V1
//buf:输入数据缓存区(大小固定是128字节)
//pctrl:MP3控制器
//返回值:0,获取正常
// 其他,获取失败
u8 mp3_id3v1_decode(u8* buf,__mp3ctrl *pctrl)
{
ID3V1_Tag *id3v1tag;
id3v1tag=(ID3V1_Tag*)buf;
if (strncmp("TAG",(char*)id3v1tag->id,3)==0)//是MP3 ID3V1 TAG
{
mp3_get_string(0,pctrl->title,id3v1tag->title,30);
mp3_get_string(0,pctrl->artist,id3v1tag->artist,30);
}else return 1;
return 0;
}
static void mp3_get_string (u8 type,u8 *out,u8 *in,int size)
{
if (type==1)//UNICODE16
{
u8 *char_uni=mymalloc (128);
mymemset(char_uni,0,128);
strncpy((char*)char_uni,(char*)(in),size);
//Unicode转GBK
uni2gbk_str(char_uni,out);
myfree(char_uni);
}
else if (type==3)//UTF8
{
u8 *char_utf8=mymalloc (128);
mymemset(char_utf8,0,128);
strncpy((char*)char_utf8,(char*)(in),size);
//UTF8转GBK
utf82gbk_str (char_utf8,out);
myfree(char_utf8);
}
else //(type==0)//ASCII
{
strncpy((char*)out,(char*)(in),size);
}
}
//解析ID3V2
//buf:输入数据缓存区
//size:数据大小
//pctrl:MP3控制器
//返回值:0,获取正常
// 其他,获取失败
u8 mp3_id3v2_decode(u8* buf,u32 size,__mp3ctrl *pctrl)
{
ID3V2_TagHead *taghead;
ID3V23_FrameHead *framehead;
u32 t;
u32 tagsize; //tag大小
u32 frame_size; //帧大小
taghead=(ID3V2_TagHead*)buf;
if(strncmp("ID3",(const char*)taghead->id,3)==0)//存在ID3?
{
tagsize=((u32)taghead->size[0]<<21)|((u32)taghead->size[1]<<14)|((u16)taghead->size[2]<<7)|taghead->size[3];//得到tag 大小
pctrl->datastart=tagsize; //得到mp3数据开始的偏移量
if(tagsize>size)tagsize=size; //tagsize大于输入bufsize的时候,只处理输入size大小的数据
if(taghead->mversion<3)
{
printf("not supported mversion!\r\n");
return 1;
}
t=10;
while(t<tagsize)
{
framehead=(ID3V23_FrameHead*)(buf+t);
frame_size=((u32)framehead->size[0]<<24)|((u32)framehead->size[1]<<16)|((u32)framehead->size[2]<<8)|framehead->size[3];//得到帧大小
if (strncmp("TT2",(char*)framehead->id,3)==0||strncmp("TIT2",(char*)framehead->id,4)==0)//找到歌曲标题帧,不支持unicode格式!!
{
u8 *dat_ptr=(buf+t+sizeof(ID3V23_FrameHead));
int size=AUDIO_MIN(frame_size-1,MP3_TITSIZE_MAX-1);
mp3_get_string(dat_ptr[0],pctrl->title,dat_ptr+1,size);
}
if (strncmp("TP1",(char*)framehead->id,3)==0||strncmp("TPE1",(char*)framehead->id,4)==0)//找到歌曲艺术家帧
{
u8 *dat_ptr=(buf+t+sizeof(ID3V23_FrameHead));
int size=AUDIO_MIN(frame_size-1,MP3_ARTSIZE_MAX-1);
mp3_get_string(dat_ptr[0],pctrl->artist,dat_ptr+1,size);
}
// if (strncmp("APIC",(char*)framehead->id,4)==0)
// {
// //专辑封面
// u8 *dat_ptr=(buf+t+sizeof(ID3V23_FrameHead));
// if (pctrl->jpeg_data) myfree(pctrl->jpeg_data);
// pctrl->jpeg_data=mymalloc(frame_size);
// pctrl->jpeg_len=frame_size;
// mymemcpy (pctrl->jpeg_data,dat_ptr,frame_size);
// }
t+=frame_size+sizeof(ID3V23_FrameHead);
}
return 0;
}else
{ pctrl->datastart=0;//不存在ID3,mp3数据是从0开始
return (u8)-1;
}
}
//获取MP3基本信息
//pname:MP3文件路径
//pctrl:MP3控制信息结构体
//返回值:0,成功
// 其他,失败
u8 mp3_get_info(u8 *data,u32 size,__mp3ctrl* pctrl)
{
HMP3Decoder decoder={0};
MP3FrameInfo frame_info={0};
MP3_FrameXing* fxing=0;
MP3_FrameVBRI* fvbri=0;
u8 *buf=0;
u8 *data_ptr=0;
//u32 br;
u8 res=0;
int offset=0;
u32 p=0;
short samples_per_frame=0; //一帧的采样个数
u32 totframes=0; //总帧数
buf=mymalloc(MP3_FILE_BUF_SZ); //申请5K内存
if(buf)//内存申请成功
{
data_ptr=data;
mymemcpy (buf,data_ptr,MP3_FILE_BUF_SZ);
if(res==0)//读取文件成功,开始解析ID3V2/ID3V1以及获取MP3信息
{
if (mp3_id3v2_decode(buf,MP3_FILE_BUF_SZ,pctrl)!=0) //解析ID3V2数据
{
data_ptr=data+size-128;
mymemcpy (buf,data_ptr,128);
mp3_id3v1_decode(buf,pctrl); //解析ID3V1数据
}
decoder=MP3InitDecoder(); //MP3解码申请内存
data_ptr=data+pctrl->datastart;
mymemcpy (buf,data_ptr,MP3_FILE_BUF_SZ);
offset=MP3FindSyncWord(buf,MP3_FILE_BUF_SZ); //查找帧同步信息
if(offset>=0&&MP3GetNextFrameInfo(decoder,&frame_info,&buf[offset])==0)//找到帧同步信息了,且下一阵信息获取正常
{
p=offset+4+32;
fvbri=(MP3_FrameVBRI*)(buf+p);
if(strncmp("VBRI",(char*)fvbri->id,4)==0)//存在VBRI帧(VBR格式)
{
if (frame_info.version==MPEG1)samples_per_frame=1152;//MPEG1,layer3每帧采样数等于1152
else samples_per_frame=576;//MPEG2/MPEG2.5,layer3每帧采样数等于576
totframes=((u32)fvbri->frames[0]<<24)|((u32)fvbri->frames[1]<<16)|((u16)fvbri->frames[2]<<8)|fvbri->frames[3];//得到总帧数
pctrl->totsec=totframes*samples_per_frame/frame_info.samprate;//得到文件总长度
}else //不是VBRI帧,尝试是不是Xing帧(VBR格式)
{
if (frame_info.version==MPEG1) //MPEG1
{
p=frame_info.nChans==2?32:17;
samples_per_frame = 1152; //MPEG1,layer3每帧采样数等于1152
}else
{
p=frame_info.nChans==2?17:9;
samples_per_frame=576; //MPEG2/MPEG2.5,layer3每帧采样数等于576
}
p+=offset+4;
fxing=(MP3_FrameXing*)(buf+p);
if(strncmp("Xing",(char*)fxing->id,4)==0||strncmp("Info",(char*)fxing->id,4)==0)//是Xng帧
{
if(fxing->flags[3]&0X01)//存在总frame字段
{
totframes=((u32)fxing->frames[0]<<24)|((u32)fxing->frames[1]<<16)|((u16)fxing->frames[2]<<8)|fxing->frames[3];//得到总帧数
pctrl->totsec=totframes*samples_per_frame/frame_info.samprate;//得到文件总长度
}else //不存在总frames字段
{
pctrl->totsec=size/(frame_info.bitrate/8);
}
}else //CBR格式,直接计算总播放时间
{
pctrl->totsec=size/(frame_info.bitrate/8);
}
}
pctrl->bitrate=frame_info.bitrate; //得到当前帧的码率
mp3ctrl->samplerate=frame_info.samprate; //得到采样率.
if(frame_info.nChans==2)mp3ctrl->outsamples=frame_info.outputSamps; //输出PCM数据量大小
else mp3ctrl->outsamples=frame_info.outputSamps*2; //输出PCM数据量大小,对于单声道MP3,直接*2,补齐为双声道输出
}else res=0XFE;//未找到同步帧
MP3FreeDecoder(decoder);//释放内存
}
}else res=0XFF;
myfree(buf);
return res;
}
//得到当前播放时间
//fx:文件指针
//mp3x:mp3播放控制器
//void mp3_get_curtime(FIL*fx, FILINFO *finfo,__mp3ctrl *mp3x)
//{
// u32 fpos=0;
// if(fx->fptr>mp3x->datastart)fpos=fx->fptr-mp3x->datastart; //得到当前文件播放到的地方
// mp3x->cursec=fpos*mp3x->totsec/(finfo->fsize-mp3x->datastart); //当前播放到第多少秒了?
//}
//mp3文件快进快退函数
//pos:需要定位到的文件位置
//返回值:当前文件位置(即定位后的结果)
u32 mp3_file_seek(u32 pos)
{
// if(pos>audiodev.file->fsize)
// {
// pos=audiodev.file->fsize;
// }
// f_lseek(audiodev.file,pos);
// return audiodev.file->fptr;
return pos;
}
//判断mp3是否支持
//读取 MP3_FILE_BUF_SZ 大小的数据就行了
//返回0 支持非0 不支持
int mp3_get_support (u8* data,u32 size)
{
int res=0;
__mp3ctrl *mp3ctrl=mymalloc(sizeof(__mp3ctrl));
mymemset(mp3ctrl,0,sizeof(__mp3ctrl));//数据清零
res=mp3_get_info(data,size,mp3ctrl);
if (mp3ctrl->jpeg_data) myfree(mp3ctrl->jpeg_data);
myfree(mp3ctrl);
mp3ctrl=0;
return res;
}
//返回0 支持非0 不支持
int mp3_get_support_name (const char *name)
{
//定义返回变量
int res=0;
//初始化mp3结构体
__mp3ctrl *mp3ctrl=mymalloc(sizeof(__mp3ctrl));
mymemset(mp3ctrl,0,sizeof(__mp3ctrl));//数据清零
//申请缓存
u32 buf_size=1024*5;
u8 *buf=mymalloc (buf_size);
//打开文件
ft_file_struct *file=ft_if_fopen(name,"rw");
//获取头大小
ft_if_fseek(file,0,SEEK_SET);
ft_if_fread(buf,1,buf_size,file);
ID3V2_TagHead *taghead;
u32 tagsize=0; //tag大小
taghead=(ID3V2_TagHead*)buf;
if(strncmp("ID3",(const char*)taghead->id,3)==0)//存在ID3?
{
tagsize=((u32)taghead->size[0]<<21)|((u32)taghead->size[1]<<14)|((u16)taghead->size[2]<<7)|taghead->size[3];//得到tag 大小
}
//校验格式是否支持
HMP3Decoder decoder={0};
MP3FrameInfo frame_info={0};
int offset=0;
decoder=MP3InitDecoder(); //MP3解码申请内存
ft_if_fseek(file,tagsize,SEEK_SET);
ft_if_fread(buf,1,buf_size,file);
offset=MP3FindSyncWord(buf,buf_size); //查找帧同步信息
if(offset>=0&&MP3GetNextFrameInfo(decoder,&frame_info,&buf[offset])==0)//找到帧同步信息了,且下一阵信息获取正常
{ res=0; }
else res=-1;
MP3FreeDecoder(decoder);//释放内存
//关闭文件
ft_if_fclose(file);
//释放缓存
myfree(buf);
//释放mp3结构体
myfree(mp3ctrl);
//返回结果
return res;
}
//播放一曲MP3音乐
//fname:MP3文件路径.
//返回值:0,正常播放完成
//[b7]:0,正常状态;1,错误状态
//[b6:0]:b7=0时,表示操作码
// b7=1时,表示有错误(这里不判定具体错误,0X80~0XFF,都算是错误)
u8 mp3_play_song(u8* data,u32 size)
{
if (mp3ctrl) return AP_ERR;//不可重入!
HMP3Decoder mp3decoder={0};
MP3FrameInfo mp3frameinfo={0};
u8 res=0;
u8* buffer=0; //输入buffer
u8* readptr=0; //MP3解码读指针
u8 *data_ptr=0;
int offset=0; //偏移量
int outofdata=0;//超出数据范围
int bytesleft=0;//buffer还剩余的有效数据
u32 br=0;
int err=0;
DAC_UserStruct dac={0};
mp3ctrl=mymalloc(sizeof(__mp3ctrl));
buffer=mymalloc(MP3_FILE_BUF_SZ); //申请解码buf大小
audiodev.i2sbuf1=mymalloc(2304*2);
audiodev.i2sbuf2=mymalloc(2304*2);
audiodev.tbuf=mymalloc(2304*2);
audiodev.file_seek=mp3_file_seek;
if(!mp3ctrl||!buffer||!audiodev.i2sbuf1||!audiodev.i2sbuf2||!audiodev.tbuf)//内存申请失败
{
myfree(mp3ctrl);
myfree(buffer);
myfree(audiodev.i2sbuf1);
myfree(audiodev.i2sbuf2);
myfree(audiodev.tbuf);
return AP_ERR; //错误
}
mymemset(audiodev.i2sbuf1,0,2304*2); //数据清零
mymemset(audiodev.i2sbuf2,0,2304*2); //数据清零
mymemset(mp3ctrl,0,sizeof(__mp3ctrl));//数据清零
res=mp3_get_info(data,size,mp3ctrl);
if(res==0)
{
//初始化DAC
dac.buff1=(u32 *)audiodev.i2sbuf1;
dac.buff2=(u32 *)audiodev.i2sbuf2;
//dac.buff_size=2304*2;
dac.buff_size=mp3ctrl->outsamples*2;
dac.rate=DAC_GetRate(mp3ctrl->samplerate);
dac.call_back=dma_tx_callback;
DAC_NormalInit (&dac);
mp3decoder=MP3InitDecoder(); //MP3解码申请内存
audiodev.status|=1<<1; //开启播放
audiodev.status|=1<<0; //非暂停状态
}
if(res==0&&mp3decoder!=0)//打开文件成功
{
//f_lseek(audiodev.file,mp3ctrl->datastart); //跳过文件头中tag信息
data_ptr=data+mp3ctrl->datastart;
//audio_start(); //开始播放
while(res==0)
{
readptr=buffer; //MP3读指针指向buffer
offset=0; //偏移量为0
outofdata=0; //数据正常
bytesleft=0;
//res=f_read(audiodev.file,buffer,MP3_FILE_BUF_SZ,&br);//一次读取MP3_FILE_BUF_SZ字节
br=MP3_FILE_BUF_SZ;
if (br>data+size-data_ptr) br=data+size-data_ptr;
mymemcpy (buffer,data_ptr,br);
data_ptr+=br;
if(res)//读数据出错了
{
res=AP_ERR;
break;
}
if(br==0) //读数为0,说明解码完成了.
{
res=AP_OK; //播放完成
break;
}
bytesleft+=br; //buffer里面有多少有效MP3数据?
err=0;
while(!outofdata)//没有出现数据异常(即可否找到帧同步字符)
{
offset=MP3FindSyncWord(readptr,bytesleft);//在readptr位置,开始查找同步字符
if(offset<0) //没有找到同步字符,跳出帧解码循环
{
outofdata=1;//没找到帧同步字符
}else //找到同步字符了
{
readptr+=offset; //MP3读指针偏移到同步字符处.
bytesleft-=offset; //buffer里面的有效数据个数,必须减去偏移量
err=MP3Decode(mp3decoder,&readptr,&bytesleft,(short*)audiodev.tbuf,0);//解码一帧MP3数据
if(err!=0)
{
//printf("decode error:%d\r\n",err);
break;
}else
{
MP3GetLastFrameInfo(mp3decoder,&mp3frameinfo); //得到刚刚解码的MP3帧信息
if(mp3ctrl->bitrate!=mp3frameinfo.bitrate) //更新码率
{
mp3ctrl->bitrate=mp3frameinfo.bitrate;
}
mp3_fill_buffer((u16*)audiodev.tbuf,mp3frameinfo.outputSamps,mp3frameinfo.nChans);//填充pcm数据
}
if(bytesleft<MAINBUF_SIZE*2)//当数组内容小于2倍MAINBUF_SIZE的时候,必须补充新的数据进来.
{
memmove(buffer,readptr,bytesleft);//移动readptr所指向的数据到buffer里面,数据量大小为:bytesleft
//f_read(audiodev.file,buffer+bytesleft,MP3_FILE_BUF_SZ-bytesleft,&br);//补充余下的数据
br=MP3_FILE_BUF_SZ-bytesleft;
if (br>data+size-data_ptr) br=data+size-data_ptr;
mymemcpy (buffer+bytesleft,data_ptr,br);
data_ptr+=br;
if(br<MP3_FILE_BUF_SZ-bytesleft)
{
mymemset(buffer+bytesleft+br,0,MP3_FILE_BUF_SZ-bytesleft-br);
}
bytesleft=MP3_FILE_BUF_SZ;
readptr=buffer;
}
while(audiodev.status&(1<<1))//正常播放中
{
rt_thread_delay (5);
//mp3_get_curtime(audiodev.file,mp3ctrl);
u32 fpos=0;
if(data_ptr>data)fpos=data_ptr-data; //得到当前文件播放到的地方
mp3ctrl->cursec=fpos*mp3ctrl->totsec/(size-mp3ctrl->datastart); //当前播放到第多少秒了?
audiodev.totsec=mp3ctrl->totsec; //参数传递
audiodev.cursec=mp3ctrl->cursec;
audiodev.bitrate=mp3ctrl->bitrate;
audiodev.samplerate=mp3ctrl->samplerate;
audiodev.bps=16;//MP3仅支持16位
if(audiodev.status&0X01)break;//没有按下暂停
}
if((audiodev.status&(1<<1))==0)//请求结束播放/播放完成
{
res=AP_NEXT;//跳出上上级循环
outofdata=1;//跳出上一级循环
break;
}
}
}
}
//audio_stop();//关闭音频输出
}else res=AP_ERR;//错误
MP3FreeDecoder(mp3decoder); //释放内存
if (mp3ctrl->jpeg_data) myfree(mp3ctrl->jpeg_data);
myfree(mp3ctrl);
myfree(buffer);
myfree(audiodev.i2sbuf1);
myfree(audiodev.i2sbuf2);
myfree(audiodev.tbuf);
DAC_NormalDeInit (&dac);
mp3ctrl=0;
audiodev.cursec=0;
audiodev.totsec=0;
return res;
}
// 获取当前播放的曲目名返回非0成功
char *mp3_get_name(void)
{
if(mp3ctrl)
return (char *)mp3ctrl->title;
else return 0;
}
// 获取当前播放的艺术家返回非0成功
char *mp3_get_artist(void)
{
if(mp3ctrl)
return (char *)mp3ctrl->artist;
else return 0;
}
// 获取当前播放的时间返回1成功
int mp3_get_time(int *totsec,int *cursec)
{
if(mp3ctrl)
{
if(totsec) *totsec=mp3ctrl->totsec;
if(cursec) *cursec=mp3ctrl->cursec;
return 1;
}
else
{
return 0;
}
}
__audiodev *mp3_getAudiodev (void)
{
return &audiodev;
}
__mp3ctrl *mp3_getMp3Info (void)
{
return mp3ctrl;
}
void mp3_stop (void)
{
mp3_getAudiodev()->status=0;
}
void mp3_play (void)
{
//暂停中
if (mp3_getAudiodev()->status&1)
{
mp3_getAudiodev()->status&=(~1);
}
//播放中
else
{
mp3_getAudiodev()->status|=1;
}
}
void mp3_suspend (void)
{
mp3_getAudiodev()->status|=1;
}
// 设置音量
int mp3_set_vol(int vol)
{
if(vol>MP3_VOL_MAX) vol=MP3_VOL_MAX;
else if(vol<0) vol=0;
g_vol=vol;
return 0;
}
// 增加音量
int mp3_add_val(int vol)
{
vol=vol+g_vol;
if(vol>MP3_VOL_MAX) vol=MP3_VOL_MAX;
else if(vol<0) vol=0;
g_vol=vol;
return 0;
}
// 减少音量
int mp3_sub_val(int vol)
{
vol=g_vol-vol;
if(vol>MP3_VOL_MAX) vol=MP3_VOL_MAX;
else if(vol<0) vol=0;
g_vol=vol;
return 0;
}
// 获取音量
int mp3_get_vol(void)
{
return g_vol;
}