fl2440——按键控制madplay音乐播放器

         今日诗词分享:该段诗词描述的是国产武侠游戏《剑侠情缘三》中的职业——天策(游戏中对唐朝军人的称呼)。

                          天策

长河落日东都城,铁马戍边将军坟。

尽诛宵小天策义,长枪独守大唐魂。

==========================================================================

主机操作系统:Centos 6.7 
交叉编译器环境:arm-linux-gcc-4.5.4 (可通过命令/opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc -v查询)
开发板平台: fl2440 
Linux内核版本: linux-3.0 .54

==========================================================================

好了,接下来进入正题。之前,我们通过两种方法移植了madplay播放器,现在,开始尝试通过应用程序来实现用按键控制madplay播放器。按键用的是我们自己写的platform的按键驱动,不是内核自带的。

基于我们的程序,fl2440有四个按键,分别设置为:

KEY1:播放/暂停

KEY2:停止

KEY3:上一首

KEY4:下一首


按键控制播放器代码:

/*********************************************************************************
 *      Copyright:  (C) 2017 qicheng
 *                  All rights reserved.
 *
 *       Filename:  bbb.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(04/27/2017)
 *         Author:  yangni <497049229@qq.com>
 *      ChangeLog:  1, Release initial version on "04/27/2017 07:49:28 PM"
 *                 
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define KEY1  0x1         //1<<1  
#define KEY2  0x2         //1<<2  
#define KEY3  0x4         //1<<3  
#define KEY4  0x8         //1<<4  

/*共享内存申请标记*/
#define PERM S_IRUSR|S_IWUSR

/*双向循环列表:存放歌曲名*/
struct song             
{
    char songname[64];
    struct song *prev;
    struct song *next;
};

/*孙子进程id号*/
pid_t gradchild;

/*子进程id号*/
pid_t pid;

/*共享内存描述标记*/
int shmid;

char *p_addr;

/*播放标记*/
int first_key=1;
int play_flag=0;

/*************************************************
Function name: play
Parameter    : struct song *
Description  : 播放函数
Return       : void
Argument     : void
**************************************************/
void play(struct song *currentsong)
{
    pid_t fd;
    char *c_addr;
    char *p;
    int len;
    char my_song[64]="/mp3/song/";
    while(currentsong)
    {
        /*创建子进程,即孙子进程*/
        fd = fork();
        if(fd == -1)
        {   
            perror("fork");
            exit(1);
        }
        else if(fd == 0)
        {
            /*把歌曲名加上根路径*/
            strcat(my_song,currentsong->songname);
            p = my_song;
            len = strlen(p);

            /*去掉文件名最后的'\n'*/
            my_song[len-1]='\0';

            printf("THIS SONG IS %s\n",my_song);
            execl("/mp3/madplay","madplay",my_song,NULL);
            printf("\n\n\n");
        }
        else
        {
            /*内存映射*/
            c_addr = shmat(shmid,0,0);

            /*把孙子进程的id和当前播放歌曲的节点指针传入共享内存*/
            memcpy(c_addr,&fd,sizeof(pid_t));
            memcpy(c_addr + sizeof(pid_t)+1,¤tsong,4);
            /*使用wait阻塞孙子进程,直到孙子进程播放完才能被唤醒;
              当被唤醒时,表示播放MP3期间没有按键按下,则继续顺序播放下一首MP3*/
            if(fd == wait(NULL))
            {
                currentsong = currentsong->next;
                printf("THE NEXT SONG IS %s\n",currentsong->songname);
            }
        }
    }
}

/*************************************************
Function name: creat_song_list
Parameter    : void
Description  : 创建歌曲名的双向循环链表
Return       : struct song *
Argument     : void
**************************************************/
struct song *creat_song_list(void)
{   
    FILE *fd;
    size_t size;
    size_t len;
    char *line = NULL;
    struct song *head;
    struct song *p1;
    struct song *p2;
    system("ls /mp3/song > song_list");
    fd = fopen("song_list","r");

    p1 = (struct song *)malloc(sizeof(struct song));

    printf("==================================song list=====================================\n");
    system("ls /mp3/song");   
    printf("\n");
    printf("================================================================================\n");
    size = getline(&line,&len,fd);

    strncpy(p1->songname,line,strlen(line));
    head = p1;
    while((size = getline(&line,&len,fd)) != -1)
    {   
        p2 = p1;
        p1 = (struct song *)malloc(sizeof(struct song));
        strncpy(p1->songname,line,strlen(line));
        p2->next = p1;
        p1->prev = p2;  
    }
    p1->next = head;
    head->prev = p1;
    p1 = NULL;
    p2 = NULL;
    system("rm -rf song_list");
    return head;
}
/*************************************************
Function name: startplay
Parameter    : pid_t *,struct song *
Description  : 开始播放函数
Return       : void
Argument     : void
**************************************************/
void startplay(pid_t *childpid,struct song *my_song)
{
    pid_t pid;
    int ret;
    /*创建子进程*/
    pid = fork();

    if(pid > 0)
    {
        *childpid = pid;
        play_flag = 1;
        sleep(1);
        /*把孙子进程的pid传给父进程*/
        memcpy(&gradchild,p_addr,sizeof(pid_t));
    }
    else if(0 == pid)
    {   
        /*子进程播放MP3函数*/
        play(my_song);
    }
}
/*************************************************
Function name: my_pause
Parameter    : pid_t
Description  : 暂停函数
Return       : void
Argument     : void
**************************************************/
void my_pause(pid_t pid)
{
    printf("=======================PAUSE!PRESS K1 TO CONTINUE===================\n");
    kill(pid,SIGSTOP); //对孙子进程发送SKGSTOP信号
    play_flag = 0;
}

/*************************************************
Function name: my_pause
Parameter    : pid_t
Description  : 停止播放函数
Return       : void
Argument     : void
**************************************************/
void my_stop(pid_t g_pid)
{

    printf("=======================STOP!PRESS K1 TO START PLAY===================\n");
    kill(g_pid,SIGKILL); //对孙子进程发送SKGKILL信号
    kill(pid,SIGKILL);   //对子进程发送SKGKILL信号
    first_key=1;

}

/*************************************************
Function name: conti_play
Parameter    : pid_t
Description  : 继续函数
Return       : void
Argument     : void
**************************************************/
void conti_play(pid_t pid)
{
    printf("===============================CONTINUE=============================\n");
    kill(pid,SIGCONT); //对孙子进程发送SIGCONT信号
    play_flag=1;
}

/*************************************************
Function name: next
Parameter    : pid_t
Description  : 下一首函数
Return       : void
Argument     : void
**************************************************/
void next(pid_t next_pid)
{
    struct song *nextsong;

    printf("===============================NEXT MP3=============================\n");
    /*从共享内存获得孙子进程播放歌曲的节点指针*/
    memcpy(&nextsong,p_addr + sizeof(pid_t)+1,4);
    /*指向下首歌曲的节点*/
    nextsong = nextsong->next;
    /*杀死当前歌曲播放的子进程,孙子进程*/
    kill(pid,SIGKILL);
    kill(next_pid,SIGKILL);
    wait(NULL);
    startplay(&pid,nextsong);
}

/*************************************************
Function name: prev
Parameter    : pid_t
Description  : 上一首函数
Return       : void
Argument     : void
Autor & date : yuanhui 09.12.08
**************************************************/
void prev(pid_t prev_pid)
{
    struct song *prevsong;
    /*从共享内存获得孙子进程播放歌曲的节点指针*/
    printf("===============================PRIOR MP3=============================\n");
    memcpy(&prevsong,p_addr + sizeof(pid_t)+1,4);
    /*指向上首歌曲的节点*/
    prevsong = prevsong->prev;
    /*杀死当前歌曲播放的子进程,孙子进程*/
    kill(pid,SIGKILL);
    kill(prev_pid,SIGKILL);
    wait(NULL);
    startplay(&pid,prevsong);
}

/*************************************************
Function name: main
Parameter    : void
Description  : 主函数
Return       : int
Argument     : void
**************************************************/
int main(void)
{
    int buttons_fd;
    int key_value;
    struct song *head;
    /*打开设备文件*/
    buttons_fd = open("/dev/button", 0);
    if (buttons_fd < 0) {
        perror("open device buttons");
        exit(1);
    }


  /*创建播放列表*/
    head = creat_song_list();
    printf("===================================OPTION=======================================\n\n\n\n");
    printf("        K1:START/PAUSE     K2:STOP   K3:NEXT      K4:PRIOR\n\n\n\n");
    printf("================================================================================\n");


  /*共享内存:用于存放子进程ID,播放列表位置*/
    if((shmid = shmget(IPC_PRIVATE,5,PERM))== -1)
        exit(1);
    p_addr = shmat(shmid,0,0);
    memset(p_addr,'\0',1024);
    
    
    while(1) 
    {
        fd_set rds;
        int ret;

        FD_ZERO(&rds);
        FD_SET(buttons_fd, &rds);

        /*监听获取键值*/
        ret = select(buttons_fd + 1, &rds, NULL, NULL, NULL);
        if (ret < 0) 
        {
            perror("select");
            exit(1);
        }
        if (ret == 0) 
            printf("Timeout.\n");
        else if (FD_ISSET(buttons_fd, &rds))
        {
            int ret = read(buttons_fd, &key_value, sizeof key_value);
            if (ret != sizeof key_value) 
            {
                if (errno != EAGAIN)
                    perror("read buttons\n");
                continue;
            } 
            else
            {
                //printf("buttons_value: %d\n", key_value+1);
                
                /*首次播放,必须是按键1*/
                if(first_key){
                    switch(key_value)
                    {   
                    case KEY1:
                        startplay(&pid,head);
                        first_key=0;
                        break;
                    case KEY2:
                    case KEY3:
                    case KEY4:
                        printf("=======================PRESS K1 TO START PLAY===================\n");
                        break;
                    default:
                        printf("=======================PRESS K1 TO START PLAY===================\n");
                        break;
                    } //end switch
                }//end if(first_key)
                /*若不是首次播放,则根据不同键值处理*/
                else if(!first_key){
                    switch(key_value)
                    {
                    case KEY1:
                        //printf("play_flag:%d\n",play_flag);
                        if(play_flag)
                            my_pause(gradchild);
                        else
                            conti_play(gradchild);
                        break;
                    case KEY2:
                        my_stop(gradchild);
                        break;
                    case KEY3:
                        next(gradchild);
                        break;
                    case KEY4:
                        prev(gradchild);
                        break;
                    } //end switch
                }//end if(!first_key)

            }
                
        }
    }

    close(buttons_fd);
    return 0;
}


        从main函数入手看,该程序首先打开我们的按键设备。然后创建播放列表,再分配一段共享内存并初始化(用于存放子进程ID,播放列表位置)。然后通过select函数监听按键,通过播放标记first_key判断是否第一次播放,若first_key=1,则调用startplay()函数并把,first_key设为0,然后程序开始播放。

        用交叉编译器编译完成后,把它下载到开发板上就可以运行了。注意,先得在开发板上下载好你的歌曲,需要提前把名字改为英文,然后创建文件夹,把歌曲放在一个文件夹下。比如我在根目录下创建了一个/mp3目录,/mp3目录下建了一个song目录,并把歌曲下载到song目录下,所以我的歌曲目录就为/mp3/song/,然后把madplay工具放在/mp3目录下,总之,打开的目录一定要对。


主要函数分析:

一、创建双向循环链表:

struct song *creat_song_list(void)
{   
    FILE *fd;
    size_t size;
    size_t len;
    char *line = NULL;
    struct song *head;
    struct song *p1;
    struct song *p2;
    system("ls /mp3/song > song_list");
    fd = fopen("song_list","r");

    p1 = (struct song *)malloc(sizeof(struct song));

    printf("==================================song list=====================================\n");
    system("ls /mp3/song");   
    printf("\n");
    printf("================================================================================\n");
    size = getline(&line,&len,fd);

    strncpy(p1->songname,line,strlen(line));
    head = p1;
    while((size = getline(&line,&len,fd)) != -1)
    {   
        p2 = p1;
        p1 = (struct song *)malloc(sizeof(struct song));
        strncpy(p1->songname,line,strlen(line));
        p2->next = p1;
        p1->prev = p2;  
    }
    p1->next = head;
    head->prev = p1;
    p1 = NULL;
    p2 = NULL;
    system("rm -rf song_list");
    return head;
}

(1)system("ls /mp3/song > song_list");
          这里相当于execl()函数,用于执行系统命令,将/mp3/song下的内容重定向的song_list中。如果没有指定重定向到哪,就默认打印到屏幕上。

(2)size = getline(&line,&len,fd);

        其中fd是FILE数据结构,用于表示一个文件,和文件描述符类似。fd=fopen("song_list","w");这就是说读取song_list文件的数据,将其一行的首地址给&line,line保存读到的文本,len为要读的文件的大小,fd代表文件指针。fd=fopen("song_list","w");其实就是以写的方式打开文件,并返回文件指针给fd,但是这个Line是保存的是一行内容,需要调节指针不断读取文件才能读整个文件的内容。

(3)创建双向循环列表:

         因为歌曲需要实现上一首和下一首,所以链表能够前后访问。双向链表拥有头节点和尾节点。

struct song             //创建一个双向链表
{
    char songname[64];
    struct song *prev;
    struct song *next;
};
    struct song *head;   //分别定义song的结构体
    struct song *p1;
    struct song *p2;
    p2 = p1;
    p1 = (struct song *)malloc(sizeof(struct song));
    strncpy(p1->songname,line,strlen(line));
    p2->next = p1;
    p1->prev = p2; 

   头结点head相当于数组中的首地址,可以通过传参head来访问该链表。

二、开始播放函数:

void startplay(pid_t *childpid,struct song *my_song)
{
    pid_t pid;
    int ret;
    /*创建子进程*/
    pid = fork();

    if(pid > 0)
    {
        *childpid = pid;
        play_flag = 1;
        sleep(1);
        /*把孙子进程的pid传给父进程*/
        memcpy(&gradchild,p_addr,sizeof(pid_t));
    }
    else if(0 == pid)
    {   
        /*子进程播放MP3函数*/
        play(my_song);
    }
}
(1)函数调用在main中:

 startplay(&pid,head);
这里head是双向链表的返回值,也是它的头节点,相当于首地址。

(2)fork()函数:

         fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
        一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

       fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次它可能有三种不同的返回值:

  • 在父进程中,fork返回新创建子进程的进程ID;
  • 在子进程中,fork返回0;
  • 如果出现错误,fork返回一个负值;

       知道fork()函数使用后,我们就可以开始分析这段代码了。首先,创建了一个子进程(克隆一个自己),因为返回值有两次,所以父进程和子进程各做各的事,因为父进程睡眠了1秒。所以子进程就优先完成了调用play函数的工作。接下来我们就来看看play函数。


二、play函数:

void play(struct song *currentsong)
{
    pid_t fd;
    char *c_addr;
    char *p;
    int len;
    char my_song[64]="/mp3/song/";    //指定歌曲存放路径,最后的“/”不能少
    while(currentsong)
    {
        /*创建子进程,即孙子进程*/
        fd = fork();
        if(fd == -1)
        {   
            perror("fork");
            exit(1);
        }
        else if(fd == 0)
        {
            /*把歌曲名加上根路径*/
            strcat(my_song,currentsong->songname);
            p = my_song;
            len = strlen(p);

            /*去掉文件名最后的'\n'*/
            my_song[len-1]='\0';

            printf("THIS SONG IS %s\n",my_song);
            execl("/mp3/madplay","madplay",my_song,NULL);
            printf("\n\n\n");
        }
        else
        {
            /*内存映射*/
            c_addr = shmat(shmid,0,0);

            /*把孙子进程的id和当前播放歌曲的节点指针传入共享内存*/
            memcpy(c_addr,&fd,sizeof(pid_t));
            memcpy(c_addr + sizeof(pid_t)+1,&currentsong,4);
            /*使用wait阻塞孙子进程,直到孙子进程播放完才能被唤醒;
              当被唤醒时,表示播放MP3期间没有按键按下,则继续顺序播放下一首MP3*/
            if(fd == wait(NULL))
            {
                currentsong = currentsong->next;
                printf("THE NEXT SONG IS %s\n",currentsong->songname);
            }
        }
    }
}
      因为执行该函数之前先执行startplay()函数, startplay函数已经创建了一个子进程,本函数由子进程执行过程中调用所以本函数创建的子进程就 相当于孙进程了。

      然后孙进程要做的事就是播放当前的歌曲。而子进程则映射了一段内存,把孙进程的id和当前的歌曲节点指针放入该内存,然后用wait()函数阻塞孙进程,直到孙进程播放结束后才能唤醒,当前歌曲指针指向下一首歌。

(1)内存映射 void *shmat(int shmid, const void *shmaddr, int shmflg);

         shmat()是用来允许本进程访问一块共享内存的函数。
第一个参数:shmget返回的标识符,

第二个参数:如果 shmaddr 是NULL,系统将自动选择一个合适的地址! 如果shmaddr不是NULL 并且没有指定SHM_RND 则此段连接到addr所指定的地址上 如果shmaddr非0 并且指定了SHM_RND 则此段连接到shmaddr -(shmaddr mod SHMLAB)所表示的地址上.SHM_RND命令的意思是取整,SHMLAB的意思是低边界地址的倍数,它总是2的乘方。该算式是将地址向下取最近一个 SHMLAB的倍数。除非只计划在一种硬件上运行应用程序(这在当今是不大可能的),否则不用指定共享段所连接到的地址。所以一般应指定shmaddr为0,以便由内核选择地址。

第三个参数:如果在flag中指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写的方式连接此段shmat返回值是该段所连接的实际地址 如果出错返回-1


(2)  wait(等待子进程中断或结束)

       当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。因为子进程终止是个异步事件,这种信号也是内核向父进程发的异步通知。父进程可以忽略该信号,或者提供一个该信号发生时即被调用执行的函数(信号处理程序)。
父进程同步等待子进程退出时则调用wait函数,此时父进程可能会有如下三种情形:
①    阻塞(如果其所有子进程都还在运行)。
②    带回子进程的终止状态立即返回(如果已有一个子进程终止,正等待父进程取其终止状态)。
③    出错立即返回(如果它没有任何子进程)。


(3)execl()函数:

execl("/mp3/madplay","madplay",my_song,NULL);
   第一个参数为命令的路径,第二个参数为命名的名字,后面跟的是参数(可以为多个),NULL表示结尾。


三、暂停函数:

void my_pause(pid_t pid)
{
    printf("=======================PAUSE!PRESS K1 TO CONTINUE===================\n");
    kill(pid,SIGSTOP);  //对孙子进程发送SKGSTOP信号
    play_flag = 0;
}

       该函数调用了kill函数,发送信号将子进程暂停,并把播放标志设置为0 。my_stop(),conti_play()的用法与之类似。


(1) my_pause(gradchild);   //调用该函数

(2)SIGKILL和SIGSTOP的区别

        SIGKILL提供给管理员杀死进程的权利,SIGSTOP提供给管理员暂停进程的权利, 所以这两个信号不能被忽略和重定义。

(3)int kill(pid_t pid, int sig);

函数说明:kill()可以用来送参数sig 指定的信号给参数pid 指定的进程。参数pid 有几种情况:

  • pid>0 将信号传给进程识别码为pid 的进程.
  • pid=0 将信号传给和目前进程相同进程组的所有进程
  • pid=-1 将信号广播传送给系统内所有的进程
  • pid<0 将信号传给进程组识别码为pid 绝对值的所有进程参数 sig 代表的信号编号可参考附录D
返回值:执行成功则返回0, 如果有错误则返回-1.
错误代码:
1、EINVAL 参数sig 不合法
2、ESRCH 参数pid 所指定的进程或进程组不存在
3、EPERM 权限不够无法传送信号给指定进程


四、切换到下一曲 void next()函数:       

void next(pid_t next_pid)
{
    struct song *nextsong;

    printf("===============================NEXT MP3=============================\n");
    /*从共享内存获得孙子进程播放歌曲的节点指针*/
    memcpy(&nextsong,p_addr + sizeof(pid_t)+1,4);
    /*指向下首歌曲的节点*/
    nextsong = nextsong->next;
    /*杀死当前歌曲播放的子进程,孙子进程*/
    kill(pid,SIGKILL);
    kill(next_pid,SIGKILL);
    wait(NULL);
    startplay(&pid,nextsong);
}

      这里重点是将从共享内存获得孙子进程播放歌曲的节点指针,将指针指向下首歌曲的节点,然后停止当前进程,等待子进程退出,再进行下一轮播放。切换到上一首歌曲的函数和该函数类似,只需要将指针指向前驱即可。


五、memcpy()函数
void *memcpy(void*dest, const void *src, size_t n);


功能:从源src所指的内存地址的起始位置开始,拷贝n个字节的数据到目标dest所指的内存地址的起始位置中。


1)src和dest所指内存区域不能重叠,函数返回指向dest的指针。如果src和dest以任何形式出现了重叠,它的结果是未定义的。


2)与strcpy相比,memcpy遇到’\0’不结束,而且一定会复制完n个字节。只要保证src开始有n字节的有效数据,dest开始有n字节内存空间就行。


3)如果目标数组本身已有数据,执行memcpy之后,将覆盖原有数据(最多覆盖n个)。如果要追加数据,则每次执行memcpy()后,要将目标地址增加到要追加数据的地址。


4)source和destin都不一定是数组,任意的可读写的空间均可。






关于 shmget()和shmat()函数的用法可参考原博客:
本文参考博客:http://blog.sina.com.cn/s/blog_95268f5001016gnf.html



  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值