概述:
(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 ,将这个文件拷贝到对应目录即可。