【开源】嵌入式Linux(IMX6U)应用层综合项目(4)--音乐播放器APP

news2025/1/6 19:50:43

1.简介

此文章并不是教程,只能当作笔者的学习分享,只会做一些简单的介绍,其他的各位结合着代码和运行现象自己分析吧,相信通过函数名和注释,基本上是不难看懂代码的,其中涉及到的一些技术栈,也请各位学习到的时候多查阅资料。

本篇的内容为嵌入式Linux应用层的一个综合性比较强的项目,结尾会将源码放在网盘中开源出来,笔者能力有限,只是简单的把功能实现了,代码开源供大家一起交流学习,有什么好的建议,请各位一定不吝赐教!!!

1.1功能介绍

项目包括了四个app:

1.云平台的调试窗口,用于查看订阅主题所下发的数据,另一个为输入Json格式的数据来控制STM32单片机上的外设。

2.智能家居的界面,有4个图片按钮用于控制STM32板子上的LED灯、门(舵机)、蜂鸣器,量计分别为温度、湿度和亮度的值,同样是STM32获取发布到云平台的。

3.通过一个摄像头模块做的一个相机功能,可以拍照、录像,以及查看拍摄的照片,和播放录制视频的回放。

4.简易的音乐播放器:能够切换歌曲,以及暂停播放音乐。

1.2技术栈介绍

虽然项目简单,但是所涉及到的技术栈还是比较杂,我简单在此列出:

 1.LVGL库用于绘制UI。

2.MQTT协议,连接阿里云平台与STM32通讯。

3.alsa库用于音频处理。

4.LED、BEEP

5.V4L2 摄像头应用编程

1.3演示视频

【开源】Linux应用综合项目|云平台调试工具+智能家居+相机+音乐播放器_哔哩哔哩_bilibili

1.4硬件介绍

硬件使用的是正点原子的阿尔法开发板,芯片是IMX6U,类似开发板应该都可以运行。

2.软件设计

2.1.asla音频应用编程

此文件初始化了声卡和混音器,创建了一个线程来播放我们所选的音乐,在切换和暂停歌曲时用到了互斥量来保护所要访问的资源,以防切换时出现错误。总共包括了4首歌曲,可以通过左右两个按键来切换上一首和下一首歌曲,可以调节音量和暂停开始播放。

#include "ds_music.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <alsa/asoundlib.h>

/************************************
 宏定义
 ************************************/
#define PCM_PLAYBACK_DEV "hw:0,0"
#define MIXER_DEV "hw:0"
/************************************
 WAV音频文件解析相关数据结构申明
 ************************************/
typedef struct WAV_RIFF
{
    char ChunkID[4];     /* "RIFF" */
    u_int32_t ChunkSize; /* 从下一个地址开始到文件末尾的总字节数 */
    char Format[4];      /* "WAVE" */
} __attribute__((packed)) RIFF_t;

typedef struct WAV_FMT
{
    char Subchunk1ID[4];     /* "fmt " */
    u_int32_t Subchunk1Size; /* 16 for PCM */
    u_int16_t AudioFormat;   /* PCM = 1*/
    u_int16_t NumChannels;   /* Mono = 1, Stereo = 2, etc. */
    u_int32_t SampleRate;    /* 8000, 44100, etc. */
    u_int32_t ByteRate;      /* = SampleRate * NumChannels * BitsPerSample/8 */
    u_int16_t BlockAlign;    /* = NumChannels * BitsPerSample/8 */
    u_int16_t BitsPerSample; /* 8bits, 16bits, etc. */
} __attribute__((packed)) FMT_t;
static FMT_t wav_fmt;

typedef struct WAV_DATA
{
    char Subchunk2ID[4];     /* "data" */
    u_int32_t Subchunk2Size; /* data size */
} __attribute__((packed)) DATA_t;

/************************************
 static静态全局变量定义
 ************************************/
static snd_pcm_t *pcm = NULL;                      // pcm句柄
static snd_mixer_t *mixer = NULL;                  // 混音器句柄
static snd_mixer_elem_t *playback_vol_elem = NULL; // 播放<音量控制>元素
static unsigned int buf_bytes;                     // 应用程序缓冲区的大小(字节为单位)
static void *buf = NULL;                           // 指向应用程序缓冲区的指针
static int fd = -1;                                // 指向WAV音频文件的文件描述符
static snd_pcm_uframes_t period_size = 1024;       // 周期大小(单位: 帧)
static unsigned int periods = 16;                  // 周期数(设备驱动层buffer的大小)
static struct termios old_cfg;                     // 用于保存终端当前的配置参数

static pthread_t g_play_music_thread = NULL;
static pthread_t g_mixer_music_thread = NULL;

bool is_playing = false;
int music_select = 1;
char *music_name, *music_singer;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t volume_mutex = PTHREAD_MUTEX_INITIALIZER;

static int snd_pcm_init(void)
{
    snd_pcm_hw_params_t *hwparams = NULL;
    int ret;

    /* 打开PCM设备 */
    ret = snd_pcm_open(&pcm, PCM_PLAYBACK_DEV, SND_PCM_STREAM_PLAYBACK, 0);
    if (0 > ret)
    {
        fprintf(stderr, "snd_pcm_open error: %s: %s\n",
                PCM_PLAYBACK_DEV, snd_strerror(ret));
        return -1;
    }

    /* 实例化hwparams对象 */
    snd_pcm_hw_params_malloc(&hwparams);

    /* 获取PCM设备当前硬件配置,对hwparams进行初始化 */
    ret = snd_pcm_hw_params_any(pcm, hwparams);
    if (0 > ret)
    {
        fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(ret));
        goto err2;
    }

    /**************
     设置参数
    ***************/
    /* 设置访问类型: 交错模式 */
    ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (0 > ret)
    {
        fprintf(stderr, "snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));
        goto err2;
    }

    /* 设置数据格式: 有符号16位、小端模式 */
    ret = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
    if (0 > ret)
    {
        fprintf(stderr, "snd_pcm_hw_params_set_format error: %s\n", snd_strerror(ret));
        goto err2;
    }

    /* 设置采样率 */
    ret = snd_pcm_hw_params_set_rate(pcm, hwparams, wav_fmt.SampleRate, 0);
    if (0 > ret)
    {
        fprintf(stderr, "snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));
        goto err2;
    }

    /* 设置声道数: 双声道 */
    ret = snd_pcm_hw_params_set_channels(pcm, hwparams, wav_fmt.NumChannels);
    if (0 > ret)
    {
        fprintf(stderr, "snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));
        goto err2;
    }

    /* 设置周期大小: period_size */
    ret = snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
    if (0 > ret)
    {
        fprintf(stderr, "snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));
        goto err2;
    }

    /* 设置周期数(驱动层buffer的大小): periods */
    ret = snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0);
    if (0 > ret)
    {
        fprintf(stderr, "snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));
        goto err2;
    }

    /* 使配置生效 */
    ret = snd_pcm_hw_params(pcm, hwparams);
    snd_pcm_hw_params_free(hwparams); // 释放hwparams对象占用的内存
    if (0 > ret)
    {
        fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(ret));
        goto err1;
    }

    buf_bytes = period_size * wav_fmt.BlockAlign; // 变量赋值,一个周期的字节大小

    return 0;

err2:
    snd_pcm_hw_params_free(hwparams); // 释放内存
err1:
    snd_pcm_close(pcm); // 关闭pcm设备
    return -1;
}

static int snd_mixer_init(void)
{
    snd_mixer_elem_t *elem = NULL;
    const char *elem_name;
    long minvol, maxvol;
    int ret;

    /* 打开混音器 */
    ret = snd_mixer_open(&mixer, 0);
    if (0 > ret)
    {
        fprintf(stderr, "snd_mixer_open error: %s\n", snd_strerror(ret));
        return -1;
    }

    /* 关联一个声卡控制设备 */
    ret = snd_mixer_attach(mixer, MIXER_DEV);
    if (0 > ret)
    {
        fprintf(stderr, "snd_mixer_attach error: %s\n", snd_strerror(ret));
        goto err;
    }

    /* 注册混音器 */
    ret = snd_mixer_selem_register(mixer, NULL, NULL);
    if (0 > ret)
    {
        fprintf(stderr, "snd_mixer_selem_register error: %s\n", snd_strerror(ret));
        goto err;
    }

    /* 加载混音器 */
    ret = snd_mixer_load(mixer);
    if (0 > ret)
    {
        fprintf(stderr, "snd_mixer_load error: %s\n", snd_strerror(ret));
        goto err;
    }

    /* 遍历混音器中的元素 */
    elem = snd_mixer_first_elem(mixer); // 找到第一个元素
    while (elem)
    {

        elem_name = snd_mixer_selem_get_name(elem); // 获取元素的名称
        /* 针对开发板出厂系统:WM8960声卡设备 */
        if (!strcmp("Speaker", elem_name) ||   // 耳机音量<对喇叭外音输出有效>
            !strcmp("Headphone", elem_name) || // 喇叭音量<对耳机输出有效>
            !strcmp("Playback", elem_name))
        { // 播放音量<总的音量控制,对喇叭和耳机输出都有效>
            if (snd_mixer_selem_has_playback_volume(elem))
            {                                                                      // 是否是音量控制元素
                snd_mixer_selem_get_playback_volume_range(elem, &minvol, &maxvol); // 获取音量可设置范围
                printf("minvol = %d, maxvol = %d\n", minvol, maxvol);
                snd_mixer_selem_set_playback_volume_all(elem, (maxvol - minvol) * 0.9 + minvol); // 全部设置为90%

                if (!strcmp("Playback", elem_name))
                    playback_vol_elem = elem;
            }
        }

        elem = snd_mixer_elem_next(elem);
    }

    return 0;

err:
    snd_mixer_close(mixer);
    return -1;
}

static int open_wav_file(const char *file)
{
    RIFF_t wav_riff;
    DATA_t wav_data;
    int ret;

    fd = open(file, O_RDONLY);
    if (0 > fd)
    {
        fprintf(stderr, "open error: %s: %s\n", file, strerror(errno));
        return -1;
    }

    /* 读取RIFF chunk */
    ret = read(fd, &wav_riff, sizeof(RIFF_t));
    if (sizeof(RIFF_t) != ret)
    {
        if (0 > ret)
            perror("read error");
        else
            fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }

    if (strncmp("RIFF", wav_riff.ChunkID, 4) || // 校验
        strncmp("WAVE", wav_riff.Format, 4))
    {
        fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }

    /* 读取sub-chunk-fmt */
    ret = read(fd, &wav_fmt, sizeof(FMT_t));
    if (sizeof(FMT_t) != ret)
    {
        if (0 > ret)
            perror("read error");
        else
            fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }

    if (strncmp("fmt ", wav_fmt.Subchunk1ID, 4))
    { // 校验
        fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }

    /* 打印音频文件的信息 */
    printf("<<<<音频文件格式信息>>>>\n\n");
    printf("  file name:     %s\n", file);
    printf("  Subchunk1Size: %u\n", wav_fmt.Subchunk1Size);
    printf("  AudioFormat:   %u\n", wav_fmt.AudioFormat);
    printf("  NumChannels:   %u\n", wav_fmt.NumChannels);
    printf("  SampleRate:    %u\n", wav_fmt.SampleRate);
    printf("  ByteRate:      %u\n", wav_fmt.ByteRate);
    printf("  BlockAlign:    %u\n", wav_fmt.BlockAlign);
    printf("  BitsPerSample: %u\n\n", wav_fmt.BitsPerSample);

    /* sub-chunk-data */
    if (0 > lseek(fd, sizeof(RIFF_t) + 8 + wav_fmt.Subchunk1Size,
                  SEEK_SET))
    {
        perror("lseek error");
        close(fd);
        return -1;
    }

    while (sizeof(DATA_t) == read(fd, &wav_data, sizeof(DATA_t)))
    {
        /* 找到sub-chunk-data */
        if (!strncmp("data", wav_data.Subchunk2ID, 4)) // 校验
            return 0;

        if (0 > lseek(fd, wav_data.Subchunk2Size, SEEK_CUR))
        {
            perror("lseek error");
            close(fd);
            return -1;
        }
    }

    fprintf(stderr, "check error: %s\n", file);
    return -1;
}

long vol = 229;
void *play_music_thread(void *args)
{
    int ret;

    /* 播放 */
    for (;;)
    {
        pthread_mutex_lock(&mutex);
        while (!is_playing)
        {
            // 在暂停时关闭PCM设备
            if (pcm != NULL)
            {
                snd_pcm_close(pcm);
                pcm = NULL;
                printf("PCM设备已暂停并关闭\n");
            }
            pthread_cond_wait(&cond, &mutex);
        }
        // 在恢复播放时重新初始化PCM设备
        if (pcm != NULL)
        {
            snd_pcm_close(pcm);
            pcm = NULL;
        }
        if (fd != -1)
        {
            close(fd);
            fd = -1;
        }
        switch (music_select)
        {
        case 0:
            if (open_wav_file("/home/root/wav_music/keximeiruguo.wav"))
                goto err2;
            break;
        case 1:
            if (open_wav_file("/home/root/wav_music/seeyoulater.wav"))
                goto err2;
            break;
        case 2:
            if (open_wav_file("/home/root/wav_music/xihuanni.wav"))
                goto err2;
            break;
        case 3:
            if (open_wav_file("/home/root/wav_music/xiyangwuxianhao.wav"))
                goto err2;
            break;
        default:
            printf("select music false\n");
            goto err2;
            break;
        }

        /* 初始化PCM Playback设备 */
        if (snd_pcm_init())
            goto err1;

        /* 初始化混音器 */
        if (snd_mixer_init())
            goto err2;

        /* 申请读缓冲区 */
        buf = malloc(buf_bytes);
        if (NULL == buf)
        {
            perror("malloc error");
            goto err2;
        }
        pthread_mutex_unlock(&mutex);

        while (is_playing)
        {
            memset(buf, 0x00, buf_bytes);   // buf清零
            ret = read(fd, buf, buf_bytes); // 从音频文件中读取数据
            if (0 >= ret)                   // 如果读取出错或文件读取完毕
                break;

            ret = snd_pcm_writei(pcm, buf, period_size);
            if (0 > ret)
            {
                fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));
                break;
            }
            else if (ret < period_size)
            { // 实际写入的帧数小于指定的帧数
                // 此时我们需要调整下音频文件的读位置
                // 将读位置向后移动(往回移)(period_size-ret)*frame_bytes个字节
                // frame_bytes表示一帧的字节大小
                if (0 > lseek(fd, (ret - period_size) * wav_fmt.BlockAlign, SEEK_CUR))
                {
                    perror("lseek error");
                    break;
                }
            }
            // 设置音量
            pthread_mutex_lock(&volume_mutex);
            long volume;
            pthread_mutex_unlock(&volume_mutex);
            snd_mixer_selem_get_playback_volume(playback_vol_elem,
                                                SND_MIXER_SCHN_FRONT_LEFT, &volume);
            printf("volume = %d\n", volume);
            volume = vol;
            snd_mixer_selem_set_playback_volume_all(playback_vol_elem, volume);
        }
        free(buf);
        buf = NULL;
    }
err2:
    if (buf)
        free(buf); // 释放内存
err1:
    if (pcm != NULL)
    {
        snd_pcm_close(pcm); // 关闭pcm设备
        pcm = NULL;
        printf("已关闭pcm设备\r\n");
    }
    if (fd != -1)
    {
        close(fd); // 关闭打开的音频文件
        fd = -1;
        printf("音频已被关闭\r\n");
    }
    pthread_exit(NULL);
}

void *mixer_music_thread(void *args)
{
}

void clean_music_file(void)
{
    // free(buf);          // 释放内存
    snd_pcm_close(pcm); // 关闭pcm设备
    printf("已关闭pcm设备\r\n");
    close(fd); // 关闭打开的音频文件
    printf("音频已被关闭\r\n");
}

void ds_music_init(void)
{
    int32_t res;
    res = pthread_create(&g_play_music_thread, NULL, play_music_thread, NULL);
    if (res < 0)
    {
        printf("pthread_create play_music_thread failed: %d\n", res);
        return -1;
    }
    printf("play_music_thread created successfully\n");

    res = pthread_create(&g_mixer_music_thread, NULL, mixer_music_thread, NULL);
    if (res < 0)
    {
        printf("pthread_create mixer_music_thread failed: %d\n", res);
        return -1;
    }
    printf("mixer_music_thread created successfully\n");

    return 0;
}

2.2.音乐播放器UI设计

以下为音乐播放器的UI设计,基本上在前面了解了LVGL控件的设计之后,这部分看起来应该就没有什么难度了。

#include "ui_app_music.h"

#define MUSIC_NUM 4
// LV_IMG_DECLARE(disc_img);
LV_IMG_DECLARE(img_next_64);
LV_IMG_DECLARE(img_play_64);
LV_IMG_DECLARE(img_prev_64);
LV_IMG_DECLARE(img_stop_64);
LV_IMG_DECLARE(img_disc_256);
LV_IMG_DECLARE(img_love_48);
LV_IMG_DECLARE(img_love_off_48);
LV_IMG_DECLARE(img_sound_48);

static lv_obj_t *disc_img;
static lv_obj_t *prev_btn;
static lv_obj_t *play_pause_btn;
static lv_obj_t *next_btn;
static lv_obj_t *love_btn;
static lv_obj_t *sound_btn;

lv_obj_t *bg_click_area;
lv_obj_t *sound_slider;
lv_obj_t *sound_slider_label;
bool slider_visible = false; // 用于记录滑块是否可见

extern bool is_playing;
bool is_love;

extern char *music_name, *music_singer;
extern pthread_mutex_t mutex;
extern pthread_cond_t cond;
extern int music_select;

static void play_pause_event_cb(lv_event_t *e);
static void prev_event_cb(lv_event_t *e);
static void next_event_cb(lv_event_t *e);
static void love_event_cb(lv_event_t *e);
static void sound_event_cb(lv_event_t *e);
static void sound_slider_event_cb(lv_event_t *e);
static void bg_click_event_cb(lv_event_t *e);

void show_music_info(const char *name, const char *singer, const lv_font_t *font)
{
    lv_obj_t *music_name_label = lv_label_create(lv_scr_act());
    lv_label_set_text(music_name_label, name);
    static lv_style_t music_name_label_style;
    lv_style_init(&music_name_label_style);
    lv_style_set_text_font(&music_name_label_style, font);                    // 设置字体
    lv_style_set_text_color(&music_name_label_style, lv_color_hex(0x515151)); // 设置字体颜色
    lv_obj_add_style(music_name_label, &music_name_label_style, LV_PART_MAIN);
    lv_obj_align(music_name_label, LV_ALIGN_TOP_LEFT, 460, 130);

    lv_obj_t *music_singer_label = lv_label_create(lv_scr_act());
    lv_label_set_text(music_singer_label, singer);
    static lv_style_t music_singer_label_style;
    lv_style_init(&music_singer_label_style);
    lv_style_set_text_font(&music_singer_label_style, font);                    // 设置字体
    lv_style_set_text_color(&music_singer_label_style, lv_color_hex(0xbfbfbf)); // 设置字体颜色
    lv_obj_add_style(music_singer_label, &music_singer_label_style, LV_PART_MAIN);
    lv_obj_align(music_singer_label, LV_ALIGN_TOP_LEFT, 460, 160);
}

void show_music_info_select(int select)
{
    clear_area(460, 130, 200, 60, lv_color_hex(MY_UI_COLOR_WHITE));

    switch (select)
    {
    case 0:
        show_music_info("可惜没有如果", "林俊杰", &lv_font_SiYuanHeiTi_Light_28);
        break;

    case 1:
        show_music_info("See you later", "marmixer", &lv_font_montserrat_24);
        break;

    case 2:
        show_music_info("喜欢你", "邓紫棋", &lv_font_SiYuanHeiTi_Light_28);
        break;

    case 3:
        show_music_info("夕阳无限好", "陈奕迅", &lv_font_SiYuanHeiTi_Light_28);
        break;
    }
}

void create_music_player_ui(void)
{
    int btn_x = 340, btn_y = 380;
    music_name = "See you later";
    music_singer = "marmixer";
    show_music_info(music_name, music_singer, &lv_font_montserrat_24);

    disc_img = lv_imgbtn_create(lv_scr_act());
    lv_obj_set_size(disc_img, 256, 256);
    lv_obj_add_event_cb(disc_img, prev_event_cb, LV_EVENT_CLICKED, NULL);
    lv_img_set_src(disc_img, &img_disc_256);
    lv_obj_align(disc_img, LV_ALIGN_TOP_LEFT, 145, 80);

    prev_btn = lv_imgbtn_create(lv_scr_act());
    lv_obj_set_size(prev_btn, 64, 64);
    lv_obj_add_event_cb(prev_btn, prev_event_cb, LV_EVENT_CLICKED, NULL);
    lv_img_set_src(prev_btn, &img_prev_64);
    lv_obj_align(prev_btn, LV_ALIGN_TOP_LEFT, btn_x, btn_y);

    play_pause_btn = lv_imgbtn_create(lv_scr_act());
    lv_obj_set_size(play_pause_btn, 64, 64);
    lv_obj_add_event_cb(play_pause_btn, play_pause_event_cb, LV_EVENT_CLICKED, NULL);
    lv_img_set_src(play_pause_btn, &img_play_64);
    lv_obj_align(play_pause_btn, LV_ALIGN_TOP_LEFT, btn_x + 80, btn_y);

    next_btn = lv_imgbtn_create(lv_scr_act());
    lv_obj_set_size(next_btn, 64, 64);
    lv_obj_add_event_cb(next_btn, next_event_cb, LV_EVENT_CLICKED, NULL);
    lv_img_set_src(next_btn, &img_next_64);
    lv_obj_align(next_btn, LV_ALIGN_TOP_LEFT, btn_x + 80 * 2, btn_y);

    love_btn = lv_imgbtn_create(lv_scr_act());
    lv_obj_set_size(love_btn, 48, 48);
    lv_obj_add_event_cb(love_btn, love_event_cb, LV_EVENT_CLICKED, NULL);
    lv_img_set_src(love_btn, &img_love_48);
    lv_obj_align(love_btn, LV_ALIGN_TOP_LEFT, 460, 260);

    sound_btn = lv_imgbtn_create(lv_scr_act());
    lv_obj_set_size(sound_btn, 48, 48);
    lv_obj_add_event_cb(sound_btn, sound_event_cb, LV_EVENT_CLICKED, NULL);
    lv_img_set_src(sound_btn, &img_sound_48);
    lv_obj_align(sound_btn, LV_ALIGN_TOP_LEFT, 680, 260);

    sound_slider = lv_slider_create(lv_scr_act());
    lv_bar_set_range(sound_slider, 180, 255);
    lv_obj_set_size(sound_slider, 10, 120);
    lv_obj_align(sound_slider, LV_ALIGN_TOP_LEFT, 698, 110);
    lv_obj_add_event_cb(sound_slider, sound_slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
    lv_obj_add_flag(sound_slider, LV_OBJ_FLAG_HIDDEN);
    lv_slider_set_value(sound_slider, 229, LV_ANIM_OFF);

    // 更新标签
    char buf[8];
    int percentage = (229 - 180) * 100 / (255 - 180);
    lv_snprintf(buf, sizeof(buf), "%d%%", percentage);
    sound_slider_label = lv_label_create(lv_scr_act());
    lv_label_set_text(sound_slider_label, buf);
    lv_obj_align_to(sound_slider_label, sound_slider, LV_ALIGN_OUT_TOP_MID, 25, 65);
    lv_obj_add_flag(sound_slider_label, LV_OBJ_FLAG_HIDDEN);

    bg_click_area = lv_obj_create(lv_scr_act());
    lv_obj_set_size(bg_click_area, LV_HOR_RES, LV_VER_RES);
    lv_obj_add_flag(bg_click_area, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_HIDDEN);
    lv_obj_add_event_cb(bg_click_area, bg_click_event_cb, LV_EVENT_CLICKED, NULL);
    lv_obj_move_background(bg_click_area);
}

static void play_pause_event_cb(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    if (code == LV_EVENT_CLICKED)
    {
        printf("Button clicked!\n");
        pthread_mutex_lock(&mutex);
        is_playing = !is_playing;
        if (is_playing)
        {
            lv_img_set_src(play_pause_btn, &img_stop_64); // 切换为暂停图标
            pthread_cond_signal(&cond);
        }
        else
        {
            lv_img_set_src(play_pause_btn, &img_play_64); // 切换为播放图标
        }
        pthread_mutex_unlock(&mutex);
    }
}

static void prev_event_cb(lv_event_t *e)
{
    // Handle previous song action
    lv_event_code_t code = lv_event_get_code(e);
    if (code == LV_EVENT_CLICKED)
    {
        printf("prev_event_cb clicked!\n");
        pthread_mutex_lock(&mutex);
        if (!is_playing)
        {
            music_select--;
            if (music_select == -1)
                music_select = MUSIC_NUM - 1;
        }
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
        printf("music_select = %d\n", music_select);
        show_music_info_select(music_select);
    }
}

static void next_event_cb(lv_event_t *e)
{
    // Handle next song action
    lv_event_code_t code = lv_event_get_code(e);
    if (code == LV_EVENT_CLICKED)
    {
        printf("next_event_cb clicked!\n");
        pthread_mutex_lock(&mutex);
        if (!is_playing)
        {
            music_select++;
            if (music_select == MUSIC_NUM)
                music_select = 0;
        }
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
        printf("music_select = %d\n", music_select);
        show_music_info_select(music_select);
    }
}

static void love_event_cb(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    if (code == LV_EVENT_CLICKED)
    {
        printf("love_event_cb clicked!\n");
        is_love = !is_love;
        if (is_love)
        {
            lv_img_set_src(love_btn, &img_love_48); // 切换为暂停图标
        }
        else
        {
            lv_img_set_src(love_btn, &img_love_off_48); // 切换为播放图标
        }
    }
}

static void sound_event_cb(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    if (code == LV_EVENT_CLICKED)
    {
        printf("sound_event_cb clicked!\n");
        slider_visible = !slider_visible;
        if (slider_visible)
        {
            lv_obj_clear_flag(sound_slider, LV_OBJ_FLAG_HIDDEN);       // 显示滑块
            lv_obj_clear_flag(sound_slider_label, LV_OBJ_FLAG_HIDDEN); // 显示标签
            lv_obj_clear_flag(bg_click_area, LV_OBJ_FLAG_HIDDEN);      // 显示背景点击区域
        }
        else
        {
            lv_obj_add_flag(sound_slider, LV_OBJ_FLAG_HIDDEN);       // 隐藏滑块
            lv_obj_add_flag(sound_slider_label, LV_OBJ_FLAG_HIDDEN); // 隐藏标签
            lv_obj_add_flag(bg_click_area, LV_OBJ_FLAG_HIDDEN);      // 隐藏背景点击区域
        }
    }
}

static void bg_click_event_cb(lv_event_t *e)
{
    if (slider_visible)
    {
        lv_obj_add_flag(sound_slider, LV_OBJ_FLAG_HIDDEN);       // 隐藏滑块
        lv_obj_add_flag(sound_slider_label, LV_OBJ_FLAG_HIDDEN); // 隐藏标签
        lv_obj_add_flag(bg_click_area, LV_OBJ_FLAG_HIDDEN);      // 隐藏背景点击区域
        slider_visible = false;
    }
}

extern long vol;
extern pthread_mutex_t volume_mutex;
static void sound_slider_event_cb(lv_event_t *e)
{
    lv_obj_t *slider = lv_event_get_target(e);
    char buf[8];
    long volume = (int)lv_slider_get_value(slider);

    // 将滑条值转换为百分比
    int percentage = (volume - 180) * 100 / (255 - 180);
    pthread_mutex_lock(&volume_mutex);
    vol = volume;
    pthread_mutex_unlock(&volume_mutex);
    printf("vol = %d\n", vol);

    lv_snprintf(buf, sizeof(buf), "%d%%", percentage);
    lv_label_set_text(sound_slider_label, buf);
    lv_obj_align_to(sound_slider_label, slider, LV_ALIGN_OUT_TOP_MID, 25, 65);
}

void ui_app_music(void)
{
    clear_area(0, 0, 800, 480, lv_color_hex(MY_UI_COLOR_DEEP_WHITE));
    ui_left_app_bar(20, 70);

    lv_obj_t *img = lv_img_create(lv_scr_act());
    lv_img_set_src(img, &img_music_on);
    lv_obj_align(img, LV_ALIGN_TOP_LEFT, 32, 360);

    lv_obj_t *mid_label = lv_label_create(lv_scr_act());
    lv_label_set_text(mid_label, "Music");
    static lv_style_t mid_label_style;
    lv_style_init(&mid_label_style);
    lv_style_set_text_font(&mid_label_style, &lv_font_montserrat_24);           // 设置字体
    lv_style_set_text_color(&mid_label_style, lv_color_hex(MY_UI_COLOR_BLACK)); // 设置字体颜色
    lv_obj_add_style(mid_label, &mid_label_style, LV_PART_MAIN);
    lv_obj_align(mid_label, LV_ALIGN_TOP_MID, 0, 20);

    create_split_background(lv_scr_act(), 135, 70, 640, 280, lv_color_hex(0xfefaec), lv_color_hex(MY_UI_COLOR_WHITE));
    create_music_player_ui();
}

3.结尾(附网盘链接)

链接:百度网盘 请输入提取码

提取码:2jia

--来自百度网盘超级会员V5的分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1987791.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springboot员工健康管理系统论文源码调试讲解

1系统成功运行案例 2 相关技术 2.1 springboot框架介绍 本课题程序开发使用到的框架技术&#xff0c;英文名称缩写是springboot&#xff0c;在JavaWeb开发中使用的流行框架有SSH、SSM、springboot、SpringMVC等&#xff0c;作为一个课题程序采用SSH框架也可以&#xff0c;SSM框…

2002年-至今阿拉斯加北部的北极地区测量数据集(气候、冰层、生态系统和人类活动)

Measurements in the Arctic region north of Alaska in 2002 2002年-至今阿拉斯加北部的北极地区测量数据集 简介 2002年阿拉斯加北部的北极地区测量数据集提供了关于该地区环境变化的重要信息&#xff0c;涵盖气候、冰层、生态系统和人类活动等多个方面。以下是该数据集的…

【socket编程】UDP网络通信 {简单的服务器echo程序;简单的远程控制程序;简单的网络聊天室程序}

今天我们通过以下的几个surver/client模型了解一下&#xff35;&#xff24;&#xff30;网络通信 一、简单的服务器echo程序 以下部分内容转载自「网络编程」简单UDP网络通信程序的实现_socket udp-CSDN博客 1.1 服务端 首先明确&#xff0c;这个简单的UDP网络程序分客户端…

v2rayU 将对你的电脑造成伤害 弹窗问题

如果删除v2rayU软件了还是反复弹窗&#xff0c;网上其他方法都不行的话&#xff0c;不妨试试这个方法&#xff0c;亲测有效&#xff1a; 在系统搜索 登陆&#xff0c;找到登陆项&#xff1a; 将「允许在后台」关闭后&#xff0c;不再提示。

java -- SpringMVC表现层数据封装详解

表现层数据封装 介绍 1. ModelAndView ModelAndView是Spring MVC提供的一个对象&#xff0c;用于封装模型数据和视图信息。当Controller处理完用户请求后&#xff0c;会返回一个ModelAndView对象给DispatcherServlet&#xff0c;DispatcherServlet再解析这个对象&#xff0c…

河南建筑装饰资质延期资料准备步骤详解

河南建筑装饰资质延期资料的准备是一个细致且重要的过程&#xff0c;以下是详细的准备步骤&#xff1a; 一、前期准备与规划 了解政策与要求 访问河南省住房和城乡建设厅的官方网站、政务服务平台等权威渠道&#xff0c;获取最新的建筑装饰资质延期政策、要求、流程和时间节点…

从 Pandas 到 Polars 四十四:Polars 和 数据可视化库Seaborn

在我对Matplotlib感到沮丧并发表帖子时&#xff0c;我的朋友让我试试Seaborn库。近年来我一直在使用Altair&#xff0c;因此并没有过多考虑Seaborn。然而&#xff0c;Seaborn的新界面给我留下了深刻印象&#xff0c;并且我很高兴地发现&#xff0c;Seaborn将直接接受Polars的Da…

4.7.深层循环神经网络

深层循环网络 ​ 就是更深了&#xff0c;因为之前的网络都只有一层隐藏层&#xff0c;弄多一点 ​ 我们将多层循环神经网络堆叠在一起&#xff0c;通过对几个简单层的组合&#xff0c;产生了一个灵活的机制。上图展示了一个具有 L L L个隐藏层的深度循环神经网络&#xff0c;每…

滑动验证码的介绍!

一、什么是滑动验证&#xff1f; 滑动验证&#xff08;通常也被称为滑块验证&#xff09;是一种用户交互式的验证方法。它要求用户通过鼠标或触摸屏&#xff0c;将滑块按照指定的路径或方向滑动到正确的位置&#xff0c;以完成验证过程。这种验证方式不仅增加了自动化攻击的难…

mysql源码编译启动debug

对于没有C语言基础的同学来说&#xff0c;想看看源码&#xff0c;在搞定编辑器做debug的时候就被劝退了&#xff0c;发生点啥了&#xff0c;完全看不懂&#xff0c;不知道从哪里入手去做debug&#xff1b;我为了看看 mysql 的 insert buffer 到底存的是索引页还是数据页&#x…

Tik Tok账号被限流什么原因?怎么解决?

TikTok账号被限流&#xff0c;无疑是让众多内容创作者头疼的问题。浏览量骤减&#xff0c;账号活跃度下降&#xff0c;究竟是什么原因导致的限流&#xff1f;今天&#xff0c;我们将深入探讨TikTok限流的几大成因&#xff0c;大家运营的时候尽量避免这些问题&#xff0c;同时也…

极狐GitLab与无问芯穹达成战略合作,共探AI落地软件智能研发场景新机遇

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门面向中国程序员和企业提供企业级一体化 DevOps 平台&#xff0c;用来帮助用户实现需求管理、源代码托管、CI/CD、安全合规&#xff0c;而且所有的操作都是在一个平台上进行&#xff0c;省事省心省钱。可以一键安装极狐GitL…

教程 | USB-IF USBCV软件工具使用指南

一. 概述 CVTest 是基于 USBCV 软件工具的合规性测试&#xff0c;可以验证设备是否符合 USB 规范和标准。USBCV 则是 USB-IF 提供的一款免费 USB 装置测试软体&#xff0c;简要做 USB2.0 及 USB3.0 装置相关测试。本文将基于 NXP LPC5516 HID 类键盘简要讲解 USBCV 中公有 USB…

两个向量的余弦相似度如何计算

两个向量的余弦相似度是通过测量两个向量在方向上的相似性来计算的。它是向量空间中两个向量夹角的余弦值。如果两个向量的方向相同&#xff0c;它们的余弦相似度接近1&#xff1b;如果两个向量的方向完全相反&#xff0c;它们的余弦相似度接近-1&#xff1b;如果两个向量正交&…

求职Leetcode题目(5)

1.分割回文串 每一个结点表示剩余没有扫描到的字符串&#xff0c;产生分支是截取了剩余字符串的前缀&#xff1b;产生前缀字符串的时候&#xff0c;判断前缀字符串是否是回文。如果前缀字符串是回文&#xff0c;则可以产生分支和结点&#xff1b;如果前缀字符串不是回文&#…

[ERR] 1273 - Unknown collation: ‘utf8mb4_0900_ai_ci‘(已解决)

今天在使用navicate Premium运行sql文件时出现如下错误&#xff1a; 错误&#xff1a;1273 - Unknown collation: utf8mb4_0900_ai_ci 报错原因&#xff1a; 生成转储文件&#xff08;也就是sql文件&#xff09;的数据库版本为8.0,而要运行sql文件的数据库版本为5.6,因为是高版…

Redis 实现消息队列

Redis 实现消息队列 文章目录 Redis 实现消息队列导引1. 基于List结构的消息队列2. 基于PubSub的消息队列3. 基于Stream的消息队列(推荐)3.1 XADD3.2 XREAD3.3 XGROUP 导引 消息队列(Message Queue)&#xff0c;从概念上来理解就是用来存放消息的队列&#xff0c;最简单的消息…

芯片外置电阻RC如何实现振荡器功能?

大家好,这里是大话硬件。 这篇文章来实现DC-DC控制器内部的振荡器模块功能。 在调试DC-DC控制器时,如果要改变其开关频率,通常是修改芯片外围的RC参数, 如下图所示。 结合常用芯片UC3842系列的内部框图,实现方式如下: 实现方案如下: 根据上述的原理,实现的思路:…

springboot整合mybatis-plus和pagehelper插件报错,

在springboot和myabtisplus版本没有冲突的情况下&#xff0c;MybatisPlusAutoConfiguration配置类没有生效&#xff0c;查看该类发现存在生效条件&#xff0c;即&#xff1a; 1.必须存在数据源的Bean对象 2.必须存在SqlSessionFactory和SqlSessionFactoryBean类&#xff08;这…

【QT常用技术讲解】tableWidget右键菜单及多进程编程

前言 本文在QT项目的开发框架的基础上&#xff08;源代码&#xff09;增加tableWidget的右键菜单功能&#xff0c;并使用进程实现ping计算机的功能来讲解&#xff0c;本文不对进程间通信进行讲解。 概述 一个项目在开发过程中&#xff0c;通常面临着引入“第三方应用”&#x…