一、前提条件
下载源码,请参见快速入门。
在开始本教程之前,请从
二、前置概念
在开始教程之前,推荐先了解以下基础知识和工具,以便顺利完成相关开发工作:
- 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_STOP、PLAY_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、创建项目配置文件
配置编译系统配置文件的目的是针对目录下的所有源代码,将其编译成可执行程序。
新增了新的应用程序后,需要有新的设置项来决定是否启用该应用程序、分配多少栈空间、进程执行优先级以及应用名称等信息。
为了添加音乐播放器,需更新编译系统的配置文件,包括 Kconfig、Makefile 和 Make.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/
退出模拟器。
重新进行如下操作:
启动模拟器并上传资源
和
启动音乐播放器


雷达卡


京公网安备 11010802022788号







