您的位置:首页 > 其它

TQ2440 上用多线程实现的mp3 播放器

2010-08-01 02:34 260 查看
概述:

(1) 这是一个基于 TQ2440开发板的, 使用用多线程实现的mp3播放器。

(2) 从软件层次来看, 驱动层包括 按键驱动 和 声卡驱动, 需要自己实现的是 按键驱动; 应用层有 MP3主播放程序 和 Madplay播放器程序。

(3) 除了用madplay 播放器播放MP3, 为了好玩 ,还将蜂鸣器播放音乐的功能加了进去, 不过要用 一段 代码转换 编码数组 为程序可读的文件。
(4) 由于 madplay 编译后 本身会只会生成 madplay这个可执行文件, 为了能将madplay 的方法用到自己的代码中, 还需要修改 madplay 的Makefile 和 一些源代码。 最好是将 main()函数源文件 送到madplay目录中去 一起编译。
(5) 当然 madplay本身 还需要一些库的支持, 这些库都在资源中了。

正文:

一·系统架构:



图一 MP3 系统架构图

基于TQ2440开发板, 利用板上四个按键资源, K1~K4分别实现以下功能:
1. K1: Play /Pause;
2. K2: Stop;
3. K3: Prev Song;
4. K4: Next Song;

二、程序流程图:



图二 MP3 主程序流程图

三、准备工作(略):

在实现MP3 程序代码之前, 必须做好以下工作:

1. 一个可以启动 linux 内核的Uboot,最好是NFS 方式启动内核;
2. 制作好的linux 内核,并将声卡芯片(UDA1341)编译进内核;
3. 根文件系统;
4. Button 驱动,笔者的按键驱动 在按下和抬起时都能读到键值变化,并且4个键值是以 一个整数按 位图方式放回用户空间供读取的, 这样的好处是 可以随时监控按键状态 并且 支持组合键的操作;
5. madplay 的源码和 库文件支持,你需要弄到这些文件 libid3tag-0.15.1b.tar.gz,libmad-0.15.1b.tar.gz,zlib-1.1.4.tar.gz,madplay-0.15.2b.tar.gz;

四、代码实现:

1. 全局变量及预定义:

/************************************************************************
*	@	Specified Global Variable
************************************************************************/
/*
* Delayms
* */
#define Delayms(ms) usleep((ms)<<10)

/*
* 按键驱动相关
* */
#define KEY_UP      1
#define KEY_DOWN    0
#define K1          0x01
#define K2          0x02
#define K3          0x04
#define K4          0x08
#define KEY_PLAY_PAUSE      K1
#define KEY_STOP            K2
#define KEY_PREV            K3
#define KEY_NEXT            K4

unsigned int KeyVal = 0xffffffff;      /*模仿rGPXDAT*/

void *GetKeyVal(void);

/*
* 蜂鸣器驱动相关
* */
#define BUZZER_IOC_MAGIC 'p'   /*幻数,指定设备类型*/

/*定义命令*/
#define BUZZER_IOC_BEEP       _IOW(BUZZER_IOC_MAGIC, 0, int)    /*设置频率及保持时间*/
#define BUZZER_IOC_SETFREQ    _IOW(BUZZER_IOC_MAGIC, 1, int)    /*设置频率*/
#define BUZZER_IOC_STOP       _IO(BUZZER_IOC_MAGIC, 2)

/*将频率和保持时间整合成一个32位 参数,供BUZZER_IOC_BEEP 命令使用*/
#define BUZZER_MKBEEP(freq, ms)        ((freq)<<16|(ms))

/*io操作*/
#define Buzzer_Set_Freq(freq) ioctl(fd_bzr, BUZZER_IOC_SETFREQ, freq)
#define Buzzer_Stop()         ioctl(fd_bzr, BUZZER_IOC_STOP)
#define Beep(freq, ms)        ioctl(fd_bzr, BUZZER_IOC_BEEP, BUZZER_MKBEEP(freq, ms))

/*
* 歌曲play应用程序相关
* */
#define SONGDIR "./Songs/"      /*存放歌曲的目录*/

int fd_bzr;

/*歌曲结构体*/
typedef struct{
int sgid;          /*歌曲编号*/
char name[86];     /*歌曲名*/
int type;          /*类型,mp1或 mp3*/
struct list_head sglist;  /*歌曲列表*/
}SongType;

#define SONG_MP1 0    /*类型*/
#define SONG_MP3 1

SongType *pSong;
/*定义并初始化链表头*/
LIST_HEAD(sghead);

/*播放器状态结构*/
typedef struct{
bool stop;          /*是否是停止状态*/
bool pause;         /*是否是暂停状态*/
bool change;        /*是否换歌*/
}PlayStatus;

PlayStatus PlayStat={   /*播发器状态全局标志*/
.stop  =true,
.pause =false,
.change=false
};

void Init_SgList(void);
void *PlaySong(void);

/*在信号函数中,如果是 pause 将阻塞,直至 play*/
pthread_cond_t  pp_Status_Cond;
pthread_mutex_t pp_Status_Mutex;   /*配合条件变量使用*/

/*音乐播放子线程*/
pthread_t PsId;

/*Play_mp3()中 对声卡设备的操作文件描述符
* 在audio_oss.c 中使用*/
int sfd;


2. 主函数:

/************************************************************************
*	@	main()
************************************************************************/
int main(int argc, char **argv)
{
Init_SgList();
/*初始化歌曲列表后,将第一个歌曲结构赋给 pSong*/
pSong = list_entry((&sghead)->next, typeof(SongType), sglist);

bool lpress= false; /*常按标志*/
int key, keyt;

pthread_t tid;
/*获取键值线程*/
pthread_create(&tid, NULL, (void *)GetKeyVal, NULL);

fd_bzr = open("/dev/mybzr-misc", 0);
if (fd_bzr < 0)
{
perror("open device Buzzer");
exit(1);
}

sfd = open("/dev/dsp", O_WRONLY);
if (sfd < 0)
{
perror("open device Sound");
exit(1);
}

printf("main() sfd=%d/n", sfd);

printf("do_while/n");

while(1)
{
while ( (key=(KeyVal^0xf)&0xf)==0 ); /*等待按键按下*/
printf("get a key = %08X/n", key);
while ( (keyt=(KeyVal^0xf)&0xf)==key ) /*按键处理*/
{
switch ( keyt )
{
case KEY_PLAY_PAUSE:
{
if (lpress==true)   /*长按无效*/
{
break;
}

if (PlayStat.stop)   /*之前状态为 停止,则重新播放歌曲*/
{
pthread_create(&PsId, NULL, (void *)PlaySong, NULL);

PlayStat.stop  = false;
PlayStat.pause = false;

}
else                /*之前状态 不是停止,则根据 pause状态*/
{
if (PlayStat.pause)  /*之前状态为暂停,则向播放子线程发送 继续信号*/
{
/*向所有相关线程广播,使阻塞线程唤醒*/
pthread_cond_broadcast(&pp_Status_Cond);

PlayStat.pause = false;

}
else               /*否则向播放子线程发送 暂停信号*/
{
PlayStat.pause = true;
pthread_kill(PsId, SIGUSR1);

}
}
break;
}
case KEY_STOP:
{

if (lpress==true)   /*长按无效*/
{
break;
}

if (PlayStat.stop)   /*之前状态是 stop,则break*/
{
break;
}
else      /*否则向播放子线程发送 终止信号*/
{
pthread_kill(PsId, SIGUSR2);
PlayStat.stop = true;
}
break;
}
case KEY_PREV:
{
struct list_head *pos = &pSong->sglist;

if (pSong->sglist.prev == &sghead)   /*歌曲链表前驱是 sghead,说明播放到第一首*/
{
pos = pSong->sglist.prev;
}

pSong = list_entry(pos->prev, typeof(SongType), sglist);

printf("do_prev: id=%d, name=%s/n", pSong->sgid, pSong->name);

PlayStat.change = true;

break;
}
case KEY_NEXT:
{
struct list_head *pos = &pSong->sglist;

if (pSong->sglist.next == &sghead)   /*歌曲链表后继是 sghead,说明播放到最后一首*/
{
pos = pSong->sglist.next;
}

pSong = list_entry(pos->next, typeof(SongType), sglist);

printf("do_next: id=%d, name=%s/n", pSong->sgid, pSong->name);

PlayStat.change = true;

break;
}
default:
{
printf("default : keyt=%d/n", keyt);
}
}   /* switch ( keyt )*/

if (lpress) /*如果是常按将是 每58ms 进入一次处理循环*/
{
Delayms(58);
}
else
{
int i;
/*0.9s 还按着 就算是 常按*/
for (i=0; i<50; i++)
{
Delayms(18);

/*键值变化说明键已经松开*/
if ( (keyt=(KeyVal^0xf)&0xf)!=key )
{
break;
}
}
lpress=true;
}
}   /*while ( (keyt=(GetKeyDat()^0xf)&0xf)==key ) */
lpress=false;

if (PlayStat.change==true)
{
pthread_kill(PsId, SIGUSR2);
PlayStat.stop = true;

pthread_create(&PsId, NULL, (void *)PlaySong, NULL);

PlayStat.stop   = false;
PlayStat.pause  = false;
PlayStat.change = false;

}

}   /*while(1)*/

close(fd_bzr);
close(sfd);

return 0;

}


3. 按键扫描子线程:

/*
* 获取键值子线程
* @KeyVal -- 以位图方式 获取键值,这样可以支持组合键
* */
void *GetKeyVal(void)
{
int fd;

fd = open("/dev/mybtn-misc", 0);
if(fd < 0)
{
printf("Open Buttons Device Faild!/n");
exit(1);
}

fd_set rds;
FD_ZERO(&rds);
FD_SET(fd, &rds);

while(1)
{
int ret;

/*监控按键变化*/
ret = select(fd + 1, &rds, NULL, NULL, NULL);
if(ret < 0)
{
printf("Read Buttons Device Faild!/n");
exit(1);
}

if(FD_ISSET(fd, &rds))
{
ret = read(fd, &KeyVal, sizeof(KeyVal));
if(ret == -1)
{
printf("Read Button Device Faild!/n");
exit(1);
}
}
}

close(fd);
return 0;
}


4. Init_SgList()函数实现

/*
* 比较后缀名
* */
int PostfixMatch(char* fn, char* pf)
{
char* tfn;
for (tfn=fn; *tfn!='.' && *tfn!=0; tfn++)
{
;
}

if (*tfn==0 || pf==0)
{
return -1;
}

tfn++;
return strcmp(tfn, pf);
}

/*
* 初始化歌曲列表
* @将歌曲歌曲从 歌曲存放目录载入链表
* */
void Init_SgList(void)
{
DIR *dir;
struct dirent *ptr;
int i=1;
SongType *psong;

dir =opendir(SONGDIR);
while((ptr=readdir(dir))!=NULL)
{
if (PostfixMatch(ptr->d_name, "mp1")==0 || PostfixMatch(ptr->d_name, "mp3")==0)
{
printf("name=%s/n", ptr->d_name);
psong = (SongType*) malloc(sizeof(SongType));
if (psong==NULL)
{
list_for_each_entry(psong, &sghead, sglist)
{
free(psong);
}

exit(1);
}

psong->sgid = i;
strcpy(psong->name, ptr->d_name);
if (PostfixMatch(ptr->d_name, "mp1")==0)
{
psong->type = SONG_MP1;
}
else
{
psong->type = SONG_MP3;
}

list_add_tail(&psong->sglist, &sghead);
i++;

}
}
}


5. 播放子线程

/*
* 蜂鸣器音乐播放
* */
#define SOUND_SPACE  4/5   /*定义普通音符演奏的长度分率*/

const unsigned short 	FreTab[12]  = { 262,277,294,311,330,349,369,392,415,440,466,494 }; /*原始频率表*/
//c 0,   d 2     e 4 f 5     g 7     a 9     b 11
const unsigned char 	SignTab[7]  = { 0,2,4,5,7,9,11 };           //1~7 在频率表中的位置,其他是伴音
const unsigned char 	LengthTab[7]= { 1,2,4,8,16,32,64 };

void BuzzerPlay(unsigned char *Sound,unsigned char Signature,unsigned char Octachord,unsigned short LDiv0)
{
unsigned short NewFreTab[12];  /*新的频率表*/
unsigned char  i,j;
/*Point -- 指向乐谱; LDiv -- 音长; LDiv1,LDiv2 -- 音长中发音与不发音长度*/
/*LDiv4 -- 4分音符长度; */
unsigned short Point, LDiv, LDiv1, LDiv2, LDiv4,CurrentFre, SoundLength;
unsigned char  Tone, Length, SL, SH, SM, SLen, XG, FD;

for(i=0;i<12;i++)     /*根据调号及音阶生成新的频率表*/
{
j = i + Signature;
if(j > 11)
{	j = j-12;NewFreTab[i] = FreTab[j]*2;}
else
{	NewFreTab[i] = FreTab[j];}

if(Octachord == 1)	/*八度 频率差值约为 4倍*/
{	NewFreTab[i]>>=2;}
else if(Octachord == 3)
{	NewFreTab[i]<<=2;}
}

SoundLength = 0;
while(Sound[SoundLength] != 0x00 || Sound[SoundLength+1] != 0x00) /*计算歌曲长度*/
{	SoundLength+=2;}

Point = 0;
Tone   = Sound[Point];
Length = Sound[Point+1];    /*读出第一个音符和它的时值*/

LDiv4 = LDiv0/4;      /*算出4音符的长度*/
LDiv4 = LDiv4-LDiv4*SOUND_SPACE;  /*普通音最长间隔标准*/

while(Point < SoundLength)
{
SL=Tone%10;            /*计算出音符*/
SM=Tone/10%10;         /*计算出高低音*/
SH=Tone/100;           /*计算出是否升半*/
CurrentFre = NewFreTab[SignTab[SL-1]+SH];  /*查出对应音符的频率*/
if(SL!=0)	//为 0 就是停顿
{
if (SM==1)
{	CurrentFre >>= 2;}   /*低音 降八调*/
if (SM==3)
{	CurrentFre <<= 2;}   /*高音 升八调*/
}
SLen=LengthTab[Length%10];   /*算出是几分音符*/
XG=Length/10%10;             /*算出音符类型(0普通1连音2顿音)*/
FD=Length/100;		         /*是否有半音*/
LDiv=LDiv0/SLen;             /*算出连音音符演奏的长度*/
if (FD==1) 	                 /*有半音 再延时一半*/
{	LDiv=LDiv+LDiv/2;}
if (XG!=1)        /*无连音*/
{
if(XG==0)     /*普通音*/
{
if (SLen<=4)
{	LDiv1=LDiv-LDiv4;}
else
{	LDiv1=LDiv*SOUND_SPACE;}
}
else	      /*顿音*/
{	LDiv1=LDiv/2;}
}
else   /*连音*/
{	LDiv1=LDiv;}
if(SL==0)
{	LDiv1=0;}	/*音符为0, 视为休止符*/
LDiv2=LDiv-LDiv1;   /*算出不发音长度*/

if (SL!=0)
{
Beep(CurrentFre, LDiv1);
}
if(LDiv2!=0)
{
usleep(LDiv2<<10);   /*不发音延时*/
}

Point+=2;
Tone=Sound[Point];
Length=Sound[Point+1];
}
//close(fd_bzr);
}

/*
*
* */

/*
* 自定义信号,用于处理 play、pause 播放器,线程挂起和唤醒的应用
* */
void PlayPause(int sign_no)
{
printf("playpause/n");
pthread_mutex_lock(&pp_Status_Mutex);
if (PlayStat.pause==true)
{
pthread_cond_wait(&pp_Status_Cond, &pp_Status_Mutex);
}
pthread_mutex_unlock(&pp_Status_Mutex);

}

/*
* SIGKILL,用于处理线程退出
* */
void PlayExit(int sign_no)
{
printf("playexit/n");
pthread_exit(0);
}

/*
* 从文件fn中,获得 song到 数组sa中
* @文件存储必须 是以 一个 空格字符 作为分割符
* */
int GetMp1(char *fn, unsigned char *sa)
{
int fd;
int size, i, j, k=0;
char tmp[1024];
char *ptmp=tmp;
char td[8];

fd = open(fn, O_RDONLY);
size = read(fd, tmp, 1024);

close(fd);

printf("read size=%d/n", size);

for (i=0; i<size; i++)
{
for (j=0; *ptmp != ' '; ptmp++, j++, i++)
{
td[j]= *ptmp;
}
td[j]=0;
ptmp++;
sa[k++] = atoi(td);

}
return k;
}

/*
*
* */
void PlayMp1(char *pathname)
{
unsigned char psong[1024];

GetMp1(pathname, psong);            /*从文件中获得歌曲数组*/
BuzzerPlay(psong, 7, 2, 2000);       /*播放歌曲*/
}

/*
* 播发mp1子线程
* */
extern int PlayMp3(char *songname);

void *PlaySong(void)
{
char pathname[86];

pthread_cond_init(&pp_Status_Cond, NULL);
pthread_mutex_init(&pp_Status_Mutex, NULL);

printf("play SONG/n");
signal(SIGUSR1, PlayPause);
signal(SIGUSR2, PlayExit);

while(1)
{
printf("### Now Play: No=%d, Song Name= %s, Type= %s/n", /
pSong->sgid, pSong->name, pSong->type==SONG_MP3?"mp3":"mp1" );

#if 1

strcpy(pathname, SONGDIR);
strcat(pathname, pSong->name);

if (pSong->type == SONG_MP1)
{
printf("play mp1/n");
PlayMp1(pathname);       /*播放歌曲*/
}
else /*mp3*/
{
printf("play mp3/n");
PlayMp3(pathname);
}

struct list_head *pos = &pSong->sglist;  /*取得下一首歌曲*/

if (pSong->sglist.next == &sghead)   /*歌曲链表后继是 sghead,说明播放到最后一首*/
{
pos = pSong->sglist.next;
}

pSong = list_entry(pos->next, typeof(SongType), sglist);
#else                /*这段用来测试线程控制是否 正常*/
int i=0;
while(1)
{
printf("i = %d/n", i++);
sleep(1);
}
#endif
}
}


代码说明:

(1). 能够识别Songs/目录下.mp1和 .mp3文件(mp1 实际上是蜂鸣器播放文件,这里可以不用管);

(2). play和pause播放子线程的实现是通过 条件变量pp_Status_Cond。当pause时,主线程向子线程发送一个自定义信号,这个信号触发播放子线程进入信号处理函数,并通过pthread_cond_wait阻塞在其中;当play时,主线程通过pthread_cond_broadcast(&pp_Status_Cond)向子线程唤醒阻塞线程。

(3). PlayMp3(char *songname)是MP3播放的核心代码,它源于开源的 madplay播放器源码,下面说明madplay的移植和编译过程。

五、madplay移植:

(1). 将Madplay所需的3个库和madplay源码包解压。

(2). 编译三个库文件。
这里设 交叉编译器目录为 $(CROSS_COMPILE_DIR)
你的CROSS_COMPILE_DIR 可能 =/usr/local/arm/4.3.3/。
#cd ./zlib-1.1.4

#./configure --prefix=$(CROSS_COMPILE_DIR)/arm-none-linux-gnueabi/libc/usr/lib

修改Makefile

AR=$(CROSS_COMPILE_DIR)/bin/arm-linux-ar rcs

CC=$(CROSS_COMPILE_DIR)/bin/arm-linux-gcc

RANLIB=$(CROSS_COMPILE_DIR)/bin/arm-linux-ranlib

#make
#make install


#cd ./libid3tat-0.15.1d

#./configure --host=arm-linux CC=arm-linux-gcc --disable-debugging --disable-shared --prefix=$(CROSS_COMPILE_DIR)/arm-none-linux-gnueabi/libc/usr/lib

#make
#make install


#cd /mp3/libmad-0.15.1b

#./configure --enable-fpm=arm --host=arm-linux --disable-shared --disable-debugging --prefix=$(CROSS_COMPILE_DIR)/arm-none-linux-gnueabi/libc/usr/lib

修改 Makefile 129行 去掉 –fforce-mem
#make
#make install


(3). 移植madplay。
修改 Makefile.in的am_madplay_OBJECTS,加入Play_mp3.$(OBJEXT) 这一项(就是播放器主程序):
am_madplay_OBJECTS = Play_mp3.$(OBJEXT) madplay.$(OBJEXT) $(am__objects_1) /
version.$(OBJEXT) resample.$(OBJEXT) filter.$(OBJEXT) /
tag.$(OBJEXT) crc.$(OBJEXT) rgain.$(OBJEXT) player.$(OBJEXT) /
$(am__objects_2)

搜索@AMDEP_TRUE@ ,在其中加入@AMDEP_TRUE@ ./$(DEPDIR)/Play_mp3.Po /

修改 audio_oss.c 将“static int sfd; ” 改为 ”extern int sfd;“
注释掉init 和 finish函数中的内容,否则每放完一首歌都会 close 设备文件,每开始一首歌都会 open文件,而这个设备文件我们可以只在主程序中打开一次。

修改 madplay.c 中的内容 , 屏蔽掉main 函数,依照原main函数 做如下修改:
int PlayMp3(char *songname)
{
struct player player;
int result = 0;

int argc=2;
char *argv[2]={
"madplay",
songname
};

//argv0 = argv[0];

/* internationalization support */
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);

/* initialize and get options */

player_init(&player);

get_options(argc, argv, &player);

/* main processing */

if (player.verbosity >= 0)
ver_banner(stderr);

if (player.options & PLAYER_OPTION_CROSSFADE) {
if (!(player.options & PLAYER_OPTION_GAP))
warn(_("cross-fade ignored without gap"));
else if (mad_timer_sign(player.gap) >= 0)
warn(_("cross-fade ignored without negative gap"));
}

if (player.output.replay_gain & PLAYER_RGAIN_ENABLED) {
if (player.options & PLAYER_OPTION_IGNOREVOLADJ)
warn(_("volume adjustment ignored with Replay Gain enabled"));
else
player.options |= PLAYER_OPTION_IGNOREVOLADJ;
}

if ((player.options & PLAYER_OPTION_SHOWTAGSONLY) &&
player.repeat != 1) {
warn(_("ignoring repeat"));
player.repeat = 1;
}

/* make stop time absolute */
if (player.options & PLAYER_OPTION_TIMED)
mad_timer_add(&player.global_stop, player.global_start);

/* get default audio output module */
if (player.output.command == 0 &&
!(player.options & PLAYER_OPTION_SHOWTAGSONLY))
player.output.command = audio_output(0);

/* run the player */

if (player_run(&player, argc - optind, (char const **) &argv[optind]) == -1)
result = 4;

/* finish up */

player_finish(&player);

return result;
}


将Play_mp3.c 拷贝到 madplay目录下编译,先做好配置:
#./configure --host=arm-linux CC=arm-linux-gcc --disable-debugging --disable-shared

然后在生产的Makefile 中在 "LIBS=" 后面加上 "-lpthread -lmad -lid3tag -lm -lz -static"

然后再 #make。

这样,编译后生成的可执行文件仍然是madplay ,将这个文件拷贝到对应目录即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: