楼主: 邹起浩
51 0

[学科前沿] openvela 开发 UI 应用 [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

小学生

71%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
60 点
帖子
5
精华
0
在线时间
0 小时
注册时间
2018-6-21
最后登录
2018-6-21

楼主
邹起浩 发表于 2025-11-14 07:45:28 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

一、前提条件

下载源码,请参见快速入门

在开始本教程之前,请从获取示例代码。

二、前置概念

在开始教程之前,推荐先了解以下基础知识和工具,以便顺利完成相关开发工作:

  • Makefile:熟悉 Makefile 的基础概念与使用方式。Makefile 是构建自动化工具 make 使用的配置文件,常用于定义项目的编译规则和依赖管理。
  • Kconfig:了解 Kconfig 的基本原理和用法。Kconfig 是 Linux 内核及嵌入式开发中常用的配置系统,帮助开发者灵活地定义和选择软件配置选项。
  • :学习使用 LVGL 嵌入式图形库。LVGL 是一个开源的嵌入式图形库,广泛用于开发高性能的用户界面。相关文档可参考LVGL 官方文档

对上述概念的基本理解将帮助您更高效地完成教程中的开发任务。

三、简介

本文介绍如何在 openvela 中编写一个简单的音乐播放器。

四、项目结构

项目的代码和资源被整齐地组织在各目录和模块中,便于高效管理和开发。以下是 music_player 项目的目录结构和文件组成说明。

1、目录结构

项目的核心目录和文件结构如下:

packages/demos/music_player
├── res
│  ├── fonts
│  │  ├── MiSans-Normal.ttf
│  │  └── MiSans-Semibold.ttf
│  ├── icons
│  │  ├── album_picture.png
│  │  ├── audio.png
│  │  ├── music.png
│  │  ├── mute.png
│  │  ├── next.png
│  │  ├── nocover.png
│  │  ├── pause.png
│  │  ├── play.png
│  │  ├── playlist.png
│  │  └── previous.png
│  ├── musics
│  │  ├── manifest.json
│  │  ├── UnamedRhythm.png
│  │  └── UnamedRhythm.wav
│  └── config.json
├── audio_ctl.c
├── audio_ctl.h
├── Kconfig
├── Make.defs
├── Makefile
├── music_player.c
├── music_player.h
├── music_player_main.c
├── wifi.c
└── wifi.h

2、文件组成

各目录与文件的作用如下:

  • res:资源目录,包含项目运行所需的静态资源文件。
  •     fonts:字体文件目录,包含应用使用的字体。
  •     icons:图标文件目录,包含用于界面显示的各种图标。
  •     musics:音乐资源目录,包含音频文件和相应的配置信息。
  • config.json:全局配置文件,存储项目的配置参数。
  • audio_ctl.c / audio_ctl.h:音频控制模块,负责实现音频相关的功能,包括音频输入、输出及音量调节等操作。
  • wifi.c / wifi.h:Wi-Fi 控制模块,负责实现 Wi-Fi 的连接管理、初始化等功能。
  • music_player.c / music_player.h:音乐播放器的核心逻辑,定义和实现音乐播放的主要功能。
  • music_player_main.c:程序入口文件,负责初始化音乐播放器并启动主要运行逻辑。
  • Kconfig、Make.defs、Makefile 构建系统文件:
  •     Kconfig:定义项目的配置信息和构建选项。
  •     Make.defs:编译相关的变量定义和依赖项规则。
  •     Makefile:定义项目的构建过程和依赖管理。

五、UI 应用开发

1、UI 结构概览

目标是制作一个这样的音乐播放器界面。

音乐播放器的用户界面 (UI) 采用分组的方式组织为多个模块。以下是完整的 UI 结构层次:

TIME GROUP:
     TIME: 00:00:00
     DATE: 2024/03/21

PLAYER GROUP:
     ALBUM GROUP:
         ALBUM PICTURE
         ALBUM INFO:
             ALBUM NAME
             ALBUM ARTIST
     PROGRESS GROUP:
         CURRENT TIME: 00:00/00:00
         PLAYBACK PROGRESS BAR
     CONTROL GROUP:
         PLAYLIST
         PREVIOUS
         PLAY/PAUSE
         NEXT
         AUDIO

TOP Layer:
     VOLUME BAR
     PLAYLIST GROUP:
         TITLE
         LIST:
             ICON
             ALBUM NAME
             ALBUM ARTIST
  • TIME GROUP:时间显示区域。
  • PLAYER GROUP:播放器核心区域。
  • ALBUM GROUP:专辑信息区域。
  • PROGRESS GROUP:播放进度区域。
  • CONTROL GROUP:播放控制区域。
  • TOP Layer:顶层界面。
  • VOLUME BAR:音量控制条。
  • PLAYLIST GROUP:播放列表区域。

2、数据结构设计

应用内配置主要用于初始化所需的环境参数,例如 Wi-Fi 网络配置。需要注意的是,敏感信息如 Wi-Fi 的 ssid(服务集标识)和 psk(预共享密钥)不要明文保存,建议通过安全方法(例如环境变量或外部配置文件)方式加载。

struct conf_s {
#if WIFI_ENABLED
    wifi_conf_t wifi;
#endif
};

如果启用 Wi-Fi 功能(WIFI_ENABLED 宏定义),将允许配置 Wi-Fi 的 ssid 和 psk。

避免在代码中硬编码 ssid 和 psk,确保配置敏感信息时引用外部加密存储或动态加载机制。

运行时状态

运行时状态数据是应用的动态内容,主要记录播放控制与专辑信息。以下是相关数据结构设计:

  • 唱片(album_info_t)信息
  • 唱片状态切换(switch_album_mode_t)
  • 播放状态(play_status_t)
// 唱片信息
typedef struct _album_info_t {   
    const char* name;               // 专辑名称  
    const char* artist;             // 艺术家  
    char path[LV_FS_MAX_PATH_LENGTH];  // 音频文件路径  
    char cover[LV_FS_MAX_PATH_LENGTH]; // 专辑封面路径  
    uint64_t total_time;            // 总时长(单位:毫秒)  
    lv_color_t color;               // 专辑主题颜色  
} album_info_t;  

// 唱片状态切换
typedef enum _switch_album_mode_t {   
    SWITCH_ALBUM_MODE_PREV,  // 切换到上一张  
    SWITCH_ALBUM_MODE_NEXT,  // 切换到下一张  
} switch_album_mode_t;  

// 播放状态
typedef enum _play_status_t {
    PLAY_STATUS_STOP,  // 播放停止  
    PLAY_STATUS_PLAY,  // 正在播放 
    PLAY_STATUS_PAUSE, // 暂停播放  
} play_status_t;

// 播放器运行时的状态信息
struct ctx_s {  
    bool resource_healthy_check;     // 系统资源检查  
    album_info_t* current_album;     // 当前播放的专辑信息  
    lv_obj_t* current_album_related_obj; // 关联到专辑的 UI 对象  

    uint16_t volume;                 // 当前音量  

    play_status_t play_status_prev;  // 上一次播放状态  
    play_status_t play_status;       // 当前播放状态  
    uint64_t current_time;           // 当前播放时长  

    struct {  
        lv_timer_t* volume_bar_countdown;        // 音量条自动隐藏计时器  
        lv_timer_t* playback_progress_update;   // 播放进度更新计时器  
    } timers;  

    audioctl_s* audioctl;            // 音频控制句柄,用于音频操作  
};

组件树结构

根据 UI 结构及其分组设计,resource_s 数据结构将包含所有 UI 控件、字体、样式以及图片资源。

struct resource_s {
    struct {
        lv_obj_t* time;         // 时间显示
        lv_obj_t* date;         // 日期显示
        lv_obj_t* player_group; // 播放器容器  

        lv_obj_t* volume_bar;   // 音量条  
        lv_obj_t* volume_bar_indic; // 音量指示器  
        lv_obj_t* audio;        // 音频对象  
        lv_obj_t* playlist_base; // 播放列表基础区域  

        lv_obj_t* album_cover;  // 专辑封面  
        lv_obj_t* album_name;   // 专辑名称  
        lv_obj_t* album_artist; // 艺术家名称  

        lv_obj_t* play_btn;       // 播放键  
        lv_obj_t* playback_group; // 播放进度容器  
        lv_obj_t* playback_progress; // 播放进度条  
        lv_span_t* playback_current_time; // 当前播放时间  
        lv_span_t* playback_total_time;   // 总时长  

        lv_obj_t* playlist; // 播放列表对象  
    } ui;  

    struct {   
        struct { lv_font_t* normal; } size_16;   
        struct { lv_font_t* bold; } size_22;   
        struct { lv_font_t* normal; } size_24;   
        struct { lv_font_t* normal; } size_28;   
        struct { lv_font_t* bold; } size_60;   
    } fonts;  

    struct {   
        lv_style_t button_default;                // 按钮默认样式  
        lv_style_t button_pressed;                // 按钮按下样式  
        lv_style_transition_dsc_t button_transition_dsc; // 按钮过渡效果  
        lv_style_transition_dsc_t transition_dsc;       // 通用过渡效果  
    } styles;  

    struct {   
        const char* playlist;   // 播放列表图标路径  
        const char* previous;   // 上一首图标路径  
        const char* play;       // 播放图标路径  
        const char* pause;      // 暂停图标路径  
        const char* next;       // 下一首图标路径  
        const char* audio;      // 音频图标路径  
        const char* mute;       // 静音图标路径  
        const char* music;      // 音乐图标路径  
        const char* nocover;    // 无封面占位图标路径  
    } images;  

    album_info_t* albums;     // 所有专辑信息  
    uint8_t album_count;      // 专辑数量  
};
  • ui 模块:定义了所有界面控件的属性及其层次结构。
  • fonts 模块:不同大小和粗细的字体设置。
  • styles 模块:封装按钮效果及样式。
  • images 模块:图片资源集中管理,便于动态加载.

3、业务逻辑设计

主启动流程

函数 app_create 是音乐播放器应用的初始化入口,用于完成以下任务:

  • 初始化资源和运行上下文结构体。
  • 加载配置文件。
  • 执行组件初始化(如资源健康检查、Wi-Fi 连接等)。
  • 创建主界面并设置默认状态。
  • 启动必要的后台任务(如日期和时间更新功能)。

以下是 app_create 的完整实现及解析:

void app_create(void)
 {
     // 初始化资源和上下文结构体
    lv_memzero(&R, sizeof(R));          // 清空资源结构体 Resource
    lv_memzero(&C, sizeof(C));          // 清空运行上下文结构体 Context
    lv_memzero(&CF, sizeof(CF));        // 清空配置结构体 Config
    read_configs();                     // 读取应用所需的配置文件  

    #if WIFI_ENABLED
        CF.wifi.conn_delay = 2000000;       // 设置 Wi-Fi 延迟(单位:微秒,2 秒)
        wifi_connect(&CF.wifi);             // 进行 Wi-Fi 连接
    #endif

    C.resource_healthy_check = init_resource(); // 检查和初始化资源  

    if (!C.resource_healthy_check) {    // 如果资源检查失败  
        app_create_error_page();        // 创建错误页面提醒用户  
        return;  
    }  

    app_create_main_page();             // 创建主页面  
    app_set_play_status(PLAY_STATUS_STOP); // 设置初始播放状态为 “停止”  
    app_switch_to_album(0);             // 切换到第一个专辑  
    app_set_volume(30);                 // 设置默认音量为 30  

    app_refresh_album_info();           // 更新专辑信息显示  
    app_refresh_playlist();             // 更新播放列表显示  
    app_refresh_volume_bar();           // 更新音量条显示  

    app_start_updating_date_time();     // 启动日期和时间的更新任务  
}

运行时状态机

app_refresh_play_status? 是音乐播放器的运行时状态机核心函数。该函数的主要功能是根据播放状况(PLAY_STATUS_STOPPLAY_STATUS_PLAY?PLAY_STATUS_PAUSE)更新用户界面和音频控制器的状态,从而实现播放、暂停及停止等功能的处理。以下是完整函数及其关键逻辑的具体解释:

static void app_refresh_play_status(void)
 {
    if (C.timers.playback_progress_update == NULL) {
        C.timers.playback_progress_update = lv_timer_create(app_playback_progress_update_timer_cb, 1000, NULL);
    }
    switch (C.play_status) {  
    case PLAY_STATUS_STOP:  
        // 停止播放状态处理  
        lv_image_set_src(R.ui.play_btn, R.images.play); // 更新播放按钮图标为“播放”  
        lv_timer_pause(C.timers.playback_progress_update); // 暂停计时器  
        if (C.audioctl) {  
            audio_ctl_stop(C.audioctl);         // 停止音频播放  
            audio_ctl_uninit_nxaudio(C.audioctl); // 释放音频控制器资源  
            C.audioctl = NULL;                // 清空音频控制器句柄  
        }  
        break;  
    
    case PLAY_STATUS_PLAY:  
        // 播放状态处理  
        lv_image_set_src(R.ui.play_btn, R.images.pause); // 更新播放按钮图标为“暂停”  
        lv_timer_resume(C.timers.playback_progress_update); // 恢复计时器  
        if (C.play_status_prev == PLAY_STATUS_PAUSE) {  
            audio_ctl_resume(C.audioctl); // 恢复音频播放  
        } else if (C.play_status_prev == PLAY_STATUS_STOP) {  
            C.audioctl = audio_ctl_init_nxaudio(C.current_album->path); // 初始化音频控制器  
            audio_ctl_start(C.audioctl); // 开始播放音频  
        }  
        break;  
    
    case PLAY_STATUS_PAUSE:  
        // 暂停播放状态处理  
        lv_image_set_src(R.ui.play_btn, R.images.play); // 更新播放按钮图标为“播放”  
        lv_timer_pause(C.timers.playback_progress_update); // 暂停计时器  
        audio_ctl_pause(C.audioctl); // 暂停音频播放  
        break;  
    
    default:  
        break;  
    }  
}

4、接口设计

初始化功能。

初始化功能负责在应用启动时执行资源设置、界面创建与配置文件加载等任务。以下为主要的功能接口:

/* Init functions */
static void read_configs(void);
static bool init_resource(void);
static void reload_music_config(void);
static void app_create_error_page(void);
static void app_create_main_page(void);
static void app_create_top_layer(void);

定时器启动功能。

定时器控制任务用于激活后台进程,支持动态更新用户界面功能,例如时间显示、播放进度更新等。

/* Timer starting functions */
static void app_start_updating_date_time(void);

专辑操作接口。

专辑操作是音乐播放器的核心特性之一,提供专辑排序、切换和播放等相关处理。

/* Album operations */
static int32_t app_get_album_index(album_info_t* album);
static void app_switch_to_album(int index);

播放器状态接口。

播放状态接口用于设定播放器的运作状况,如播放、暂停、调整音量或播放时间等。以下提供的接口实现了这些功能:

/* Album operations */
static void app_set_play_status(play_status_t status);
static void app_set_playback_time(uint32_t current_time);
static void app_set_volume(uint16_t volume);

用户界面刷新功能接口。

用户界面刷新接口负责动态更新界面组件,例如专辑信息、播放状态、音量条和播放进度的实时展示。

/* UI refresh functions */
static void app_refresh_album_info(void);
static void app_refresh_date_time(void);
static void app_refresh_play_status(void);
static void app_refresh_playback_progress(void);
static void app_refresh_playlist(void);
static void app_refresh_volume_bar(void);
static void app_refresh_volume_countdown_timer(void);

事件处理接口。

事件处理是用户体验的关键部分,负责对按钮、播放列表、音量条等的操作进行响应:

/* Event handler functions */
static void app_audio_event_handler(lv_event_t* e);
static void app_play_status_event_handler(lv_event_t* e);
static void app_playlist_btn_event_handler(lv_event_t* e);
static void app_playlist_event_handler(lv_event_t* e);
static void app_switch_album_event_handler(lv_event_t* e);
static void app_volume_bar_event_handler(lv_event_t* e);
static void app_playback_progress_bar_event_handler(lv_event_t* e);

定时器回调函数接口。

与定时器相关的回调函数用于在固定的间隔内触发特定任务:

/* Timer callback functions */
static void app_refresh_date_time_timer_cb(lv_timer_t* timer);
static void app_playback_progress_update_timer_cb(lv_timer_t* timer);
static void app_volume_bar_countdown_timer_cb(lv_timer_t* timer);

5、创建项目配置文件

配置编译系统配置文件的目的是针对目录下的所有源代码,将其编译成可执行程序。

新增了新的应用程序后,需要有新的设置项来决定是否启用该应用程序、分配多少栈空间、进程执行优先级以及应用名称等信息。

为了添加音乐播放器,需更新编译系统的配置文件,包括 KconfigMakefileMake.defs 文件。

Kconfig 文件

以下为新增应用项目的 Kconfig 文件,用于启用功能及定义音乐播放器数据路径:

config LVX_USE_DEMO_MUSIC_PLAYER
        bool "Music Player"
        default n
        
if LVX_USE_DEMO_MUSIC_PLAYER
        config LVX_MUSIC_PLAYER_DATA_ROOT
                string "Music Player Data Root"
                default "/sdcard"
endif

Makefile 文件

Makefile 控制应用程序的编译规则和资源。

include $(APPDIR)/Make.defs

ifeq ($(CONFIG_LVX_USE_DEMO_MUSIC_PLAYER), y)
PROGNAME = music_player
PRIORITY = 100
STACKSIZE = 32768
MODULE = $(CONFIG_LVX_USE_DEMO_MUSIC_PLAYER)

CSRCS = music_player.c audio_ctl.c wifi.c
MAINSRC = music_player_main.c
endif

include $(APPDIR)/Application.mk

Make.defs 文件

Make.defs 文件将新增的音乐播放器模块集成到系统构建过程中。

ifneq ($(CONFIG_LVX_USE_DEMO_MUSIC_PLAYER),)
CONFIGURED_APPS += $(APPDIR)/packages/demos/music_player
endif

六、编译运行

1、配置项目

切换至 openvela 仓库的根目录,执行以下命令来设置音乐播放器。

模拟器配置文件(defconfig)位于 vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap/ 目录下,使用 build.sh 来配置和编译开发板的代码。

./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap menuconfig

build.sh: 编译脚本,用来配置和编译 openvela 代码

vendor/openvela/boards/vela/configs/*: 配置路径

menuconfig: 打开 menuconfig 页面,调整项目代码的配置。

执行后会显示如下界面:

按下 / 键逐项搜索并修改以下设置:

LVX_USE_DEMO_MUSIC_PLAYER=y
LVX_MUSIC_PLAYER_DATA_ROOT="/data"

LVX_USE_DEMO_MUSIC_PLAYER 为例进行操作,其余配置方式类似。

输入待查找的配置 LVX_USE_DEMO_MUSIC_PLAYER,支持模糊匹配,例如输入 music_player 即可找到对应的设置,按回车键进入该设置项。

按下空格键,在 [ ] 中出现 * 表示启用该配置。

LVX_MUSIC_PLAYER_DATA_ROOT 设置为 /data,修改后按回车键保存当前设置项。

按下 Q 键,弹出以下退出并保存界面。

按字母 Y 键保存配置,并退出修改设置页面。

2、编译项目

切换至 openvela 仓库的根目录,在终端依次执行以下命令:

# 清理构建产物
./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap distclean -j8

# 开始构建
./build.sh vendor/openvela/boards/vela/configs/goldfish-armeabi-v7a-ap -j8

成功执行后,将生成以下文件:

./nuttx
├── vela_ap.elf
├── vela_ap.bin

3、启动模拟器并推送资源

音乐播放器运行过程中需要用到的字体和图像资源位于 apps/packages/demos/music_player/res 中。要将这些资源推送到模拟器挂载的相关文件路径,可以按照以下步骤操作。

切换至 openvela 仓库的根目录,启动模拟器:

./emulator.sh vela

使用模拟器支持的 ADB 将资源推送至设备,在 openvela 仓库的根目录下打开一个新终端窗口,输入 adb push 后跟随文件路径,即可将资源传输到指定位置。

# 安装adb
sudo apt install android-tools-adb

# 推送资源
adb push apps/packages/demos/music_player/res /data/

4、启动音乐播放器

在模拟器的终端环境 openvela-ap> 中输入以下命令:

music_player &

5、退出 Demo

关闭模拟器以退出 Demo,如图所示:

七、常见问题

1、如何自定义音乐播放器

调整apps/packages/demos/music_player/res下的相关设置,在res/musics文件夹中增加新的音乐媒体,目前仅支持*.wav格式。您可以自行将*.mp3/aac/m4a等格式的音频转换为*.wav格式。接着,编辑该文件夹下的res/musics/manifest.json文件:

{
  "musics": [
    {
      "path": "UnamedRhythm.wav",
      "name": "UnamedRhythm",
      "artist": "Benign X",
      "cover": "UnamedRhythm.png",
      "total_time": 12000,
      "color": "#114514"
    }
  ]
}

把希望播放的媒体加入到这个配置文件中,参照以下格式:

参数 参数说明
path 待播放音频的文件路径
name 媒体名称
artist 艺术家名称
cover 封面路径,若未提供封面,则显示默认封面。
total_time 媒体的总播放时间,单位为毫秒。
color 主题色彩,当前未使用。

例如:添加一首名为Happiness.wav、播放时间为186,507 ms的音乐,可以按以下方式进行修改。

{
  "musics": [
    {
      "path": "UnamedRhythm.wav",
      "name": "UnamedRhythm",
      "artist": "Benign X",
      "cover": "UnamedRhythm.png",
      "total_time": 12000,
      "color": "#114514"
    },
    {
      "path": "Happiness.wav",
      "name": "Xin",
      "artist": "Tang",
      "cover": "Good.png",
      "total_time": 186507,
      "color": "#252525"
    },
  ]
}

完成配置修改后,需要重新上传资源,执行以下命令:

# 推送资源
adb push apps/packages/demos/music_player/res /data/

退出模拟器。

重新进行如下操作:
启动模拟器并上传资源

启动音乐播放器

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:Open ELA Pen 嵌入式开发 Linux

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
jg-xs1
拉您进交流群
GMT+8, 2025-12-31 15:10