本文是一篇实战教程,旨在引导你通过自定义 MCP(Model Context Protocol)工具,结合语音指令控制硬件设备。我们将以 Arcs-mini 开发板为例实现一些 mcp 工具的示例。
实操之前,请确保已根据文档《Arcs-Mini 开发环境搭建指南》 搭建开发环境。
本篇示例在 2.2.1 版本的 SDK 进行修改
可通过命令git log查看版本,如果版本不一致,请使用命令git checkout mini-v2.2.1切到指定SDK版本
参考文档《烧录工具安装教程》安装ADB工具
执行命令adb shell kv set int user.disable_app_update 1关闭云端OTA更新,防止云端更新把刚烧录的固件刷掉了。
如果您不想重新编译代码而希望直接体验本固件,可点击下载。
下载链接:mcp_tool_play_offline_music.lpk
下载后,可以参考文档《恢复出厂固件 & 升级固件教程》进行烧录
源码下载:apps.zip
资源文件下载:res.zip
下载后,将其替换 arcs_mini 项目的
apps与res文件夹
1.离线音频资源:music.zip
2.执行命令
python3 ./tools/romfs.py pack -i ./res/arcs-mini/music -o ./res/arcs-mini/music.bin
3.使用 cskburn desktop 将 music.bin 烧录到地址 0x00A00000

整条链路分两段:
端侧服务层 service_offline_music.c
0x00A00000 挂载 ROMFS(让设备知道音频文件放在那里)track_id/artist/titleplay_next() 和 play_by_track_id() 接口MCP 工具层 mcp_tool_offline_music.c
tools/list 动态上报完整曲库映射tools/call 只接收 index 并调用服务层播放apps/arcs-mini/services/service_offline_music.c
离线音乐核心服务:扫描、建索引、轮播、按编号播放
apps/arcs-mini/services/service_offline_music.h
对外接口和 service_offline_music_track_t 结构体
apps/arcs-mini/main.c
按键四击触发 service_offline_music_play_next()
apps/arcs-mini/mcp-tools/mcp_tool_offline_music.c
MCP 工具定义:tools/list + tools/call
res/arcs-mini/music/
音乐源文件目录(文件名决定编号和元信息)
res/arcs-mini/music.bin
ROMFS 镜像,烧录到 0xA00000
res/arcs-mini/partition_table.json
分区表,声明 music 分区地址
文件名格式:
NNN_歌手_歌名.扩展名
示例(当前工程):
000_周杰伦_晴天.mp3001_周杰伦_枫.mp3002_方大同_春风吹.mp3003_陈奕迅_十年.mp3004_陈奕迅_葡萄成熟时.mp3解析规则:
track_id_ 到第 2 个 _ -> artist_ 到扩展名前 -> title不满足前 3 位数字规则的文件会被忽略。
新建文件:apps/arcs-mini/services/service_offline_music.h
复制以下内容:
#ifndef SERVICE_OFFLINE_MUSIC_H
#define SERVICE_OFFLINE_MUSIC_H
#include <stdint.h>
typedef struct {
uint16_t track_id;
const char *artist;
const char *title;
const char *path;
uint32_t size;
} service_offline_music_track_t;
int service_offline_music_init(void);
int service_offline_music_play(uint16_t list_index);
int service_offline_music_play_by_track_id(uint16_t track_id);
int service_offline_music_play_next(void);
uint16_t service_offline_music_count(void);
int service_offline_music_get_track(uint16_t list_index, service_offline_music_track_t *track);
int service_offline_music_get_track_by_id(uint16_t track_id, service_offline_music_track_t *track);
#endif
新建文件:apps/arcs-mini/services/service_offline_music.c
推荐做法:
int service_offline_music_init(void)
功能:
romfs_init 挂载 0xA00000 分区(大小 5MB)track_id/artist/titlemem://addr=...size=... 播放 URLnext_index=0int service_offline_music_play_next(void)
{
uint16_t list_index = s_offline_music_ctx.next_index;
s_offline_music_ctx.next_index = (s_offline_music_ctx.next_index + 1U) % s_offline_music_ctx.count;
return service_offline_music_play(list_index);
}
int service_offline_music_play_by_track_id(uint16_t track_id)
功能:
track_idservice_offline_music_play(i)文件:apps/arcs-mini/services/CMakeLists.txt
确认 listenai_library_sources(...) 中包含:
service_offline_music.c
文件:apps/arcs-mini/main.c
#include "service_offline_music.h"
if (service_offline_music_init() != 0) {
LISA_LOGW(TAG, "service_offline_music_init failed, quadruple-click play will be unavailable");
}
case VOICE_MSG_BUTTON_ACTION_QUADRUPLE_CLICK:
{
LISA_LOGI(TAG, "power button quadruple click, play offline music");
if (service_offline_music_play_next() != 0) {
LISA_LOGW(TAG, "offline music play failed");
}
break;
}
./build.sh -S ./apps/arcs-mini -B build
利用 cskburn desktop 将
./build/arcs-mini.bin 烧录到 0x600000
./res/arcs-mini/music.bin 烧录到 0xA00000
42337746edda293632e3058565c2547b.mp4
indextools/list 时端侧动态上报 编号=歌手-歌名新建文件:apps/arcs-mini/mcp-tools/mcp_tool_offline_music.c
复制以下完整代码(已在当前工程验证):
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "cJSON.h"
#include "lisa_log.h"
#include "mcp.h"
#include "service_offline_music.h"
#define TAG "mcp_tool_offline_music"
static cJSON *offline_music_result_create(const char *name, const char *text, bool is_error)
{
cJSON *result = mcp_tool_call_result_create(name);
if (!result) {
return NULL;
}
cJSON *content_array = cJSON_CreateArray();
cJSON *content_item = cJSON_CreateObject();
if (!content_array || !content_item) {
cJSON_Delete(content_array);
cJSON_Delete(content_item);
cJSON_Delete(result);
return NULL;
}
cJSON_AddStringToObject(content_item, "type", "text");
cJSON_AddStringToObject(content_item, "text", text);
cJSON_AddItemToArray(content_array, content_item);
cJSON_AddItemToObject(result, "content", content_array);
cJSON_AddBoolToObject(result, "isError", is_error);
return result;
}
static void offline_music_catalog_to_text(char *text, size_t text_len)
{
uint16_t count = service_offline_music_count();
int used = 0;
if (!text || text_len == 0) {
return;
}
used = snprintf(text, text_len, "共%u首:", count);
if (used < 0 || used >= (int)text_len) {
text[text_len - 1] = '\0';
return;
}
for (uint16_t i = 0; i < count; i++) {
service_offline_music_track_t track = {0};
int append_len = 0;
if (service_offline_music_get_track(i, &track) != 0) {
continue;
}
append_len = snprintf(text + used, text_len - (size_t)used, " %03u=%s-%s;", track.track_id, track.artist, track.title);
if (append_len <= 0 || (size_t)append_len >= text_len - (size_t)used) {
break;
}
used += append_len;
}
}
static cJSON *offline_music_list(const char *name)
{
char catalog[1024] = {0};
char tool_desc[1200] = {0};
char index_desc[1200] = {0};
if (service_offline_music_init() != 0 || service_offline_music_count() == 0) {
snprintf(tool_desc, sizeof(tool_desc), "播放端侧离线音乐。云端只需下发 index(来自文件名前3位数字)。");
} else {
offline_music_catalog_to_text(catalog, sizeof(catalog));
snprintf(tool_desc, sizeof(tool_desc), "播放端侧离线音乐。云端只需下发 index。当前曲库:%s", catalog);
}
cJSON *tool = mcp_tool_list_info_create_default(name, tool_desc);
if (!tool) {
return NULL;
}
if (catalog[0] != '\0') {
snprintf(index_desc, sizeof(index_desc), "歌曲编号(ROMFS 文件名前3位数字)。%s", catalog);
} else {
snprintf(index_desc, sizeof(index_desc), "歌曲编号(ROMFS 文件名前3位数字)。");
}
mcp_tool_info_add_property(tool, "index", index_desc, "number", true);
return tool;
}
static cJSON *offline_music_call(const char *id, const char *name, cJSON *args)
{
(void)id;
int index = -1;
if (service_offline_music_init() != 0) {
LOGE("service_offline_music_init failed");
return offline_music_result_create(name, "offline music service init failed", true);
}
if (args == NULL) {
LOGE("args is null");
return offline_music_result_create(name, "index missing or invalid", true);
}
const cJSON *index_json = mcp_tool_call_args_get(args, "index");
if (!index_json || !cJSON_IsNumber(index_json)) {
LOGE("index missing or invalid");
return offline_music_result_create(name, "index missing or invalid", true);
}
index = index_json->valueint;
if (index < 0 || index > 999) {
LOGE("index out of range: %d", index);
return offline_music_result_create(name, "index out of range, expected 0~999", true);
}
if (service_offline_music_play_by_track_id((uint16_t)index) != 0) {
LOGE("service_offline_music_play_by_track_id failed, index=%03d", index);
return offline_music_result_create(name, "play offline music failed, index not found or play error", true);
}
service_offline_music_track_t track = {0};
char text[160] = {0};
if (service_offline_music_get_track_by_id((uint16_t)index, &track) == 0) {
snprintf(text, sizeof(text), "played offline music: %s-%s (index=%03d)", track.artist, track.title, index);
} else {
snprintf(text, sizeof(text), "played offline music index=%03d", index);
}
LOGI("play offline music success, index=%03d", index);
return offline_music_result_create(name, text, false);
}
MCP_TOOL_DEFINE(play_offline_music, offline_music_list, offline_music_call);
offline_music_list():
service_offline_music_init() 完成service_offline_music_count/get_track 遍历曲库000=周杰伦-晴天; 001=周杰伦-枫; ...index(number, required)offline_music_call():
index 参数是否存在且是 number0~999)service_offline_music_play_by_track_id(index)文件:apps/arcs-mini/mcp-tools/CMakeLists.txt
确认包含:
mcp_tool_offline_music.c
./build.sh -S ./apps/arcs-mini -B build
利用 cskburn desktop 将
./build/arcs-mini.bin 烧录到 0x600000
c9e6cd78f861e50aecfef21a968b76d2.mp4
offline music service init failed
music.bin 非法index missing or invalid
index not found
tools/list 看不到曲库
service_offline_music_init() 成功music.bin 是否是最新打包结果