05-基于GEC6818多线程实现音乐播放

基于GEC6818多线程实现音乐播放

本文主要涉及在GEC6818上实现多线程的音乐播放,并且可以通过左右滑动进行音乐的切换。

一、 系统

系统一般分为:分时系统和实时系统。

1.1 分时系统

  • 概念: 分时系统是一种允许多个用户或任务共享同一计算机资源的系统。这些任务按照时间片的方式进行调度,即每个任务被分配一个固定的时间片,当时间片结束时,操作系统会切换到下一个任务。
  • 调度: 时间片调度允许多个任务并发地运行,从而提高系统的整体效率和响应性。
  • 例子: WindowsLinuxUnix都是分时系统。这些系统通过多任务调度器管理多个进程和线程,确保它们在多核或多处理器系统中高效运行。

1.2 实时系统

  • 概念: 实时系统是一种能够在特定的时间约束内提供响应的系统。这些系统需要满足严格的时间限制和响应时间要求,通常用于控制和嵌入式应用
  • 调度: 实时系统通常使用中断驱动的方式进行任务调度。当发生重要事件或任务时,系统会立即中断当前的操作,执行高优先级的任务,然后再返回原来的操作。
  • 例子: 您提到的STM32是一系列广泛使用的微控制器,常用于嵌入式和实时应用。STM32具有实时操作系统(RTOS)支持,如FreeRTOS,允许开发人员创建实时、可靠的应用程序。

选择哪种系统取决于应用的需求和约束。
例如,需要快速响应和确保严格时间限制的应用通常选择实时系统,而需要高效处理和共享资源的通用应用可能更适合分时系统

二、 线程与进程的区别

分时系统中,CPU调度的主体是基于进程(process)而不是线程。但是,线程是进程的一部分,因此在线程的调度上确实涉及到线程的管理。

线程(Thread)和进程(Process)是操作系统中两个关键的概念,它们之间存在一些明显的区别。以下是线程与进程的主要区别:

2.1 定义

  • 进程:进程是程序的实例。它有自己的独立内存空间、资源和执行环境。一个进程可以看作是一个独立的程序,它可以被操作系统调度和管理。
  • 线程:线程是进程中的一个执行路径。一个进程可以包含多个线程,所有线程共享相同的内存空间和资源,但每个线程有自己的程序计数器和栈。(线程是基于进程,属于进程里面的并发分支,一个线程里面的多个线程都是共享进程的内存空间,所以资源的共享可以通过全局变量来解决)

2.2 资源使用

  • 进程:每个进程有自己的独立内存空间、代码、数据、文件资源、系统资源等。
  • 线程:线程共享所属进程的内存空间和资源。线程之间的通信和数据共享相对简单,但需要使用同步机制来确保数据的一致性和正确性。

2.3 创建与销毁

  • 进程:创建和销毁进程的开销相对较大,涉及复制父进程的内存空间、资源和状态。
  • 线程:创建和销毁线程的开销相对较小,因为线程共享所属进程的资源。线程的创建和切换通常比进程快速。

2.4 调度与切换

  • 进程:进程调度和上下文切换的开销较大,因为需要保存和恢复进程的完整状态。
  • 线程:线程调度和上下文切换的开销相对较小,因为线程共享大部分资源和状态。这也使得线程间的切换更加高效。

2.5 独立性与协作

  • 进程:进程是独立的执行单位,通常不能直接共享数据和资源。进程间的通信(IPC)通常需要使用特定的机制(如管道、消息队列、共享内存等)。
  • 线程线程是进程的一个组成部分,可以直接共享相同进程的数据和资源。线程间的通信相对简单,但需要适当的同步和协调。

2.6 灵活性与效率

  • 进程:进程提供了更高的隔离性和灵活性,但可能存在更多的开销和复杂性。
  • 线程:线程提供了更高的效率和资源利用率,但可能需要更复杂的同步和协作机制。

三、 多线程编程

接口函数:

3.1 pthread_create() 创建线程

NAME
pthread_create - create a new thread

SYNOPSIS
#include <pthread.h>

   int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                      void *(*start_routine) (void *), void *arg);

   Compile and link with -pthread.
    //gcc main.c -o main -l pthread//链接pthread库

pthread_create函数的SYNOPSIS部分,这是在Linux和其他一些UNIX-like系统中使用的多线程编程库的一部分。这个函数用于创建一个新的线程

简要地解析这个函数的参数和作用:

  • pthread_t *thread: 这是一个指向pthread_t类型的指针,用于存储新创建线程的IDpthread_t是一个不透明的结构,用于标识线程

  • const pthread_attr_t *attr: 这是一个指向pthread_attr_t类型的指针,它定义了新线程的属性。如果您不想指定任何特定的属性,可以将其设置为NULL,这将使用默认属性。

  • void *(*start_routine) (void *): 这是一个指向函数的指针,该函数是新线程开始执行时要运行的函数。这个函数接受一个void *类型的参数,并返回一个void *类型的指针。

  • void *arg: 这是传递给start_routine函数的参数。这是一个void *类型的指针,所以它可以指向任何类型的数据。通常,开发者会将其指向一个结构,该结构包含线程函数需要的所有参数。

为了编译使用这个库的程序,您需要使用-pthread标志。这确保了程序正确地链接到pthread库,并且可以使用所有与线程相关的功能。

这个函数的工作原理是创建一个新的线程,该线程会从指定的start_routine函数开始执行,并且可以接受一个参数arg

3.2 pthread_join() 释放资源

线程需要资源,如栈空间、寄存器等.但是线程死亡时资源不会自动回收.

如果不进行资源的释放,则会占用资源不做事情

所以需要使用函数释放这部分的资源:
NAME

   pthread_join - join with a terminated thread

SYNOPSIS
#include <pthread.h>

   int pthread_join(pthread_t thread, void **retval);

   Compile and link with -pthread.
参数:

    thread:要等待的线程的==标识符==。
    retval:一个指向指针的指针,用于存储目标线程的返回值。如果不关心返回值,可以将其设置为 `NULL`。
返回值:

    成功时返回0。
    失败时返回一个非0值,代表错误代码。
示例使用

void *thread_function(void *arg) {
    // 线程执行的代码...
    return NULL; // 或者其他返回值
}

int main() {
    pthread_t tid;
    if (pthread_create(&tid, NULL, thread_function, NULL) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 等待线程结束
    void *ret_val;
    if (pthread_join(tid, &ret_val) != 0) {
        perror("pthread_join");
        return 1;
    }

    // 线程结束后,可以通过ret_val获取线程的返回值
    printf("Thread returned: %p\n", ret_val);

    return 0;
}

3.3 pthread_detach() 线程自己释放资源

另一个方法就是要线程在完成任务后自己释放内存

NAME
pthread_detach - detach a thread

SYNOPSIS
#include <pthread.h>

   int pthread_detach(pthread_t thread);

   Compile and link with -pthread.

DESCRIPTION

   The  pthread_detach() function marks the thread identified by thread as detached.  When a detached
   thread terminates, its resources are automatically released back to the system  without  the  need
   for another thread to join with the terminated thread.

   Attempting to detach an already detached thread results in unspecified behavior.

RETURN VALUE
On success, pthread_detach() returns 0; on error, it returns an error number.

这个函数的主要作用是将一个线程标记为“分离”状态。当一个线程被标记为“分离”状态后,当它结束执行时,其相关资源会自动被系统回收,而不需要其他线程调用 pthread_join 来获取它的退出状态。

使用实例
void *thread_function(void *arg) {
    // 线程执行的代码...
    return NULL; // 或者其他返回值
}

int main() {
    pthread_t tid;
    if (pthread_create(&tid, NULL, thread_function, NULL) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 标记线程为分离状态
    if (pthread_detach(tid) != 0) {
        perror("pthread_detach");
        return 1;
    }

    // 此时,线程被标记为分离状态,不再需要其他线程进行等待

    // ...其他代码...

    return 0;
}

3.4 pthread_self()获取自己的进程

一个用于多线程编程的函数,它的主要作用是获取调用它的线程的ID。每当一个线程被创建,它都会被分配一个唯一的ID,这个ID可以被用来标识这个线程。

NAME
pthread_self - obtain ID of the calling thread

SYNOPSIS
#include <pthread.h>

   pthread_t pthread_self(void);

   Compile and link with -pthread.

DESCRIPTION

   The  pthread_self() function returns the ID of the calling thread.  This is the same value that is
   returned in *thread in the pthread_create(3) call that created this thread.

RETURN VALUE

   This function always succeeds, returning the calling thread's ID.

四、 音乐播放相关命令——播放器–mplayer

mplayer是一个流行的开源媒体播放器,支持多种音频和视频格式。

下面是您给出的命令的简要说明:

  1. madplay -Q 1.mp3:

    • 使用 madplay 命令播放 1.mp3 文件。
    • -Q 选项通常用于“静默”或“安静”模式,这意味着命令将不会打印其他信息或输出。
  2. madplay -Q 1.mp3 &:

    • 这里的 & 符号将命令发送到后台运行,使得 madplay 在后台播放 1.mp3 文件。
    • 这允许您在后台继续执行其他命令或任务,而不会阻塞 madplay
  3. killall -9 madplay:

    • 使用 killall 命令来结束所有名为 madplay 的进程。
    • -9 选项表示发送一个强制的终止信号,这将立即结束进程而不会尝试进行任何清理或资源释放。

这些命令允许您在 Linux 或类似的操作系统中使用 mplayermadplay 命令进行音频播放,并提供了一些控制和后台运行的选项。

五、 要在代码中进行控制就需要shell命令

但是我们需要在代码中使用,所以需要使用一些接口

NAME

   system - execute a shell command

SYNOPSIS
#include <stdlib.h>

   int system(const char *command);

DESCRIPTION

   The  system() library function uses fork(2) to create a child process that executes the shell com‐
   mand specified in command using execl(3) as follows:

       execl("/bin/sh", "sh", "-c", command, (char *) 0);

   system() returns after the command has been completed.q

command : 要执行的命令,直接写字符串就可以了。成功返回0 ,失败返回-1 ,同时error被设置

system会在命令执行完毕之后才会返回,除非这个这个进程死掉或者在乎太,他就会返回。

system("madplau -Q 1.mp3");->这个代码会卡死在这里,直到音乐播放完毕或者被种植或意外死亡
system("madplau -Q 1.mp3 &");-->system 会直接返回

我想要这个歌还没有播放完就切换到下一首

system("madplau -Q 1.mp3");-->这个还没有播放完
//执行下一首
//另一个线程
system("killall -9 madplay);//每次都先杀死这个进程
system("madplau -Q 2.mp3 ");-->另一个线程执行

六、 练习:滑动切换音乐

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <linux/input.h>
#include <pthread.h>


int music_i = 1;

//开一个线程去播放音乐
void * playmusic(void *arg)
{
    //让其分离
    pthread_detach(pthread_self());

    char music_buf[128] = {0};
    while(1)
    {
        system("killall -9 madplay");//保证别的madplay死掉
        sprintf(music_buf,"madplay -Q %d.mp3",music_i);
        printf("%s\n",music_buf);
        system(music_buf);
        //music_i++;
        //if(music_i > 2)
            //music_i = 1;
    }

}

//
int main()
{   
    //1 打开这个触摸屏
    int fd = open("/dev/input/event0",O_RDWR);
    if(-1 == fd)
    {
        perror("open event0 error");
        return -1;
    }

    //操作这个触摸屏 一般触摸屏的操作是不会死掉的  因此我们需要实现死循环
    struct input_event ev;//
    int flag = 0;//超时的flag

    int ev_x0,ev_y0,ev_x,ev_y;

    //开一个线程  去播放音乐
    pthread_t thread;
    if(pthread_create(&thread,NULL,playmusic,NULL) != 0)
    {
        perror("pthread create error");
        return -2;
    }

    while(1)
    {
        //我们想获取坐标值  那么就是从这个文件里面读取内容
        //printf("%s %d\n",__FUNCTION__,__LINE__);
        int r = read(fd,&ev,sizeof(ev));
        if(sizeof(ev) != r)//读出问题出来了
        {
            usleep(10);
            flag++;
            if(10 <= flag)
            {
                //超时太多了  不行了
                perror("read ev error");
                break;
            }
            continue;
        }
        flag = 0;
        //将数据打印出来看看
        //printf("type:%d code:%d value:%d\n",ev.type,ev.code,ev.value);
        //获取坐标
        if(EV_ABS == ev.type)//接下来的数据就是坐标
        {
            if(REL_X == ev.code)//x轴
            {
                ev_x = ev.value;
            }
            else if(REL_Y == ev.code)//y轴
            {
                ev_y = ev.value;
            }
        }
        if(0x01 == ev.type && BTN_TOUCH == ev.code && 0x01 == ev.value)//手按下去的时候
        {
            ev_x0 = ev_x;
            ev_y0 = ev_y;
        }
        else if(0x01 == ev.type && BTN_TOUCH == ev.code && 0x00 == ev.value)//手抬起来
        {
            //实现点击和方向判断
            if(ev_x0 == ev_x && ev_y0 == ev_y)//你的手没有动
            {
                printf("点击\n");
            }
            else//滑动 滑动就会有方向
            {
                if(ev_x > ev_x0 && abs(ev_x - ev_x0) > abs(ev_y - ev_y0))
                {
                    printf("右滑\n");
                    music_i++;
                    if(music_i > 2)
                        music_i = 1;
                    printf("music_i = %d\n",music_i);
                    system("killall -9 madplay");
                }
                else{printf("其它方向\n");}
            }
        }

    }

    //3 关闭这个文件
    close(fd);
    return 0;
}

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
【资源说明】 项目大致框架: ``` (1)GEC6818开发板LCD屏幕的初始化 (2)读取BMP图片文件信息,并显示在LCD屏幕上 (3)获取GEC6818开发板输入子事件 (4)判断输入子事件,实现点击功能 (5)打地鼠的基础玩法,多线程实现地鼠的自主出现和消失,记录分数和游戏结束条件判断 (6)主菜单界面 (7)排行榜界面 (8)程序退出 ``` # 项目程序和函数模块: ## (1)项目源码文件: ``` main.c:主函数,实现屏幕的初始化和主界面的加载 thread_pool.c:实现线程池 thread_pool.h:线程池的头文件 whack_mole.c:打地鼠游戏的功能实现 whack_mole.h:打地鼠游戏功能头文件 program:打地鼠游戏可执行程序(开发板) ``` 项目待开发功能: ``` (1)游戏得分(score)和血量(hp)可视化 (2)游戏界面的暂停和退出功能 (3)排行榜可视化 (4)退出功能插入退出成功图片 ``` 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载使用,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

写的什么石山代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值