当前位置: 首页 > news >正文

OpenVela 之 UI 应用开发

在嵌入式开发领域,打造一个美观且功能完善的 UI 应用是提升用户体验的关键。本文将以 OpenVela 为例,详细介绍如何开发一个简单的音乐播放器 UI 应用,涵盖从环境搭建到代码实现、编译运行的全过程。

一、前提条件

在开始开发之前,需要完成以下准备工作:

  • 搭建开发环境,具体步骤可参考 OpenVela 开发环境的前置安装教程。
  • 下载 OpenVela 源码,可参照 OpenVela 快速入门 相关指引。
  • 从 music_player 获取示例代码,该代码将作为本教程开发的基础。

二、前置概念

为了顺利完成开发任务,建议先了解以下基础知识和工具:

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

对这些概念的基本理解,能让开发过程更加高效。

三、项目简介

本文主要介绍如何在 OpenVela 中编写一个简单的音乐播放器(music_player)。该音乐播放器具备播放、暂停、切换歌曲、显示歌曲信息、调节音量等基本功能,通过这个实例,能让开发者掌握 OpenVela 中 UI 应用开发的核心流程和方法。

四、项目结构

项目的代码和资源组织有序,便于管理和开发。以下是 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:字体文件目录,存放应用使用的字体,如 MiSans-Normal.ttf、MiSans-Semibold.ttf。
    • icons:图标文件目录,包含界面显示所需的各种图标,像 album_picture.png、play.png 等。
    • musics:音乐资源目录,包含音频文件和相应的配置信息,有 manifest.json、UnamedRhythm.wav 等。
    • 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:程序入口文件,负责初始化音乐播放器并启动主要运行逻辑。

  • KconfigMake.defsMakefile:构建系统文件。

    • Kconfig 用于定义项目的配置信息和构建选项;
    • Make.defs 包含编译相关的变量定义和依赖项规则;
    • Makefile 定义项目的构建过程和依赖管理。

五、核心设计详解

音乐播放器的核心设计包括数据结构、UI 结构和业务逻辑,三者相互配合实现完整功能。

1. UI 结构设计

播放器的 UI 采用分组方式组织,层次清晰,便于维护。整体结构如下:

TIME GROUP:时间和日期显示区域
PLAYER GROUP:播放器核心区域├── ALBUM GROUP:专辑信息(封面、名称、艺术家)├── PROGRESS GROUP:播放进度(当前时间/总时长、进度条)└── CONTROL GROUP:控制按钮(上一首、播放/暂停、下一首等)
TOP Layer:顶层界面├── VOLUME BAR:音量控制条└── PLAYLIST GROUP:播放列表(包含歌曲列表及标题)

2. 数据结构设计

为了管理应用状态和资源,设计了三个核心数据结构:

  1. 应用配置(struct conf_s):存储环境参数(如 Wi-Fi 配置),敏感信息(如 ssidpsk)需通过安全方式加载(避免明文存储)。
struct conf_s {
#if WIFI_ENABLEDwifi_conf_t wifi;
#endif
};
  • 如果启用 Wi-Fi 功能(WIFI_ENABLED 宏定义),将允许配置 Wi-Fi 的 ssidpsk
  • 避免在代码中硬编码 ssidpsk,确保配置敏感信息时引用外部加密存储或动态加载机制。
  1. 运行时状态(struct ctx_s):记录动态信息,包括当前播放状态(播放 / 暂停 / 停止)、音量、播放进度、音频控制句柄等。
// 唱片信息
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;            // 音频控制句柄,用于音频操作  
};
  1. 资源组件(struct resource_s):管理所有 ui 控件、fonts字体、styles样式和images图片资源,统一维护界面元素和数据关联。
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;      // 专辑数量  
};

3. 业务逻辑设计

主启动流程

应用启动过程由 app_create 函数主导,步骤如下:

  1. 初始化资源和运行时状态结构体。
  2. 读取配置文件(如 Wi-Fi 信息、音乐列表)。
  3. 检查资源完整性(字体、图标、音频文件)。
  4. 创建主界面和顶层控件(如音量条、播放列表)。
  5. 初始化播放状态(默认停止),加载第一个专辑并设置默认音量。
  6. 启动后台任务(如时间更新、播放进度刷新)。

播放状态机

播放状态的切换是核心逻辑,由 app_refresh_play_status 函数实现,根据状态(停止 / 播放 / 暂停)更新 UI 和音频控制:

  • 停止状态:显示「播放」图标,暂停进度计时器,释放音频资源。
  • 播放状态:显示「暂停」图标,启动进度计时器,初始化并启动音频播放。
  • 暂停状态:显示「播放」图标,暂停进度计时器,暂停音频播放。
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;  }  
}

六、接口设计

1. 初始化函数

初始化函数负责在应用启动时执行资源配置、界面创建和配置文件加载等任务。以下是主要函数接口:

/* 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);

2. 定时器启动函数

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

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

3. 专辑操作接口

专辑操作是音乐播放器的核心功能,支持专辑排序、切换和播放相关处理。

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

4. 播放器状态接口

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

/* 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);

5. UI 刷新功能接口

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

/* 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);

6. 事件处理接口

事件处理是用户交互的重要组成部分,负责对按钮、播放列表、音量条等的事件进行处理:

/* 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);

7. 定时器回调函数接口

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

/* 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);

七、编写项目配置文件

  • 配置编译系统配置文件目的是针对目录下的所有源代码,将其编译成可执行产物。
  • 增加了新的应用程序,对应的应用程序需要有新的配置项来来决定是否启用应用程序、分配多少栈、进程执行额优先级以及应用的名字等信息。
  • 为了新增音乐播放器,需要更新编译系统的配置文件,包括 Kconfig、Makefile 和 Make.defs 文件。

Kconfig 文件

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

config LVX_USE_DEMO_MUSIC_PLAYERbool "Music Player"default nif LVX_USE_DEMO_MUSIC_PLAYERconfig LVX_MUSIC_PLAYER_DATA_ROOTstring "Music Player Data Root"default "/sdcard"
endif

Makefile 文件
Makefile 控制应用的编译规则及资源。

include $(APPDIR)/Make.defsifeq ($(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
endifinclude $(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 配置和编译开发板的代码。

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. 编译项目

  1. 切换到 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
  1. 成功执行后,将得到以下文件:
./nuttx
├── vela_ap.elf
├── vela_ap.bin

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

  1. 切换到 openvela 仓库的根目录,启动模拟器:
./emulator.sh vela
  1. 使用模拟器支持的 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

http://www.dtcms.com/a/277754.html

相关文章:

  • kettle从入门到精通 第102课 ETL之kettle xxl-job调度kettle的两种方式
  • 【Linux系统】进程状态 | 进程优先级
  • 手写std::optional:告别空指针的痛苦
  • java + groovy : 动态解析groovy脚本,并与java交互
  • MacBook Air M4 安装 VMware Fusion Pro
  • 问题记录:Fastjson序列化-空值字段处理
  • CA复习功课
  • Appdynamic 配置 PostgreSQL 收集器
  • 复习笔记 34
  • 【VSCode+LaTeX】科研写作环境搭建
  • 内存池(C++)v3 | 简历写法 | 相关面试题
  • 浏览器本地存储——使用localStorage实现电商系统商品收藏功能实战
  • 在网站学装机
  • SCTP协议网络编程
  • 从源码看Nginx:Nginx事件驱动架构深度拆解来了
  • linux上的软挂载操作方法
  • Docker 快速上手
  • 【elementUI踩坑记录】解决 el-table 固定列 el-table__fixed 导致部分滚动条无法拖动的问题
  • 全星质量管理QMS软件系统——汽车零部件制造业数字化转型的质量管理中枢
  • 【设计模式】备忘录模式(标记(Token)模式)
  • 设计模式:软件开发的高效解决方案(单例、工厂、适配器、代理)
  • 从 Intel MacBook 迁移到 ARM MacBook 的完整指南
  • Cursor的使用
  • Pandas 中 stack 和 unstack 方法在数据重塑中的应用
  • 日记-生活随想
  • 信号量机制,互斥的避免自旋锁的实现方法(操作系统)
  • SQL141 试卷完成数同比2020年的增长率及排名变化
  • 《棒球知识科普》体育健将有什么特点·棒球1号位
  • SQL155 大小写混乱时的筛选统计
  • C++进阶-二叉搜索树(二叉排序树)