本文介绍如何基于RTOS SDK (License模式) 实现聊天能力。
1. 开发准备
根据应用创建的文档,创建应用,购买 license,获取 APP ID和 AppSecret。
参考RTOS&Linux SDK(License模式),根据芯片型号,下载相应的 SDK 包。
如采用半托管模式,需要根据RTOS&Linux SDK(License模式),完成服务端相关接口对接。
2. SDK 适配
本文档示例和日志中所有形如<ID>
中的ID
均为数据,文档中展示的为脱敏数据。
不同芯片平台需要使用对应平台的toolchain进行编译,目前百炼已经支持的芯片清单,可直接下载SDK。
如果是新的芯片平台,请联系阿里云销售寻求技术支持。通过联系阿里云相关工作人员获得SDK,SDK包名称如下:
aliyun_sdk_<PLATFORM>_<SDK_VERSION>.tar.xz
2.1. SDK 目录结构说明
获取的 SDK 目录结构如下
aliyun_sdk/
├── include
│ ├── c_utils
│ │ ├── c_utils.h
│ │ ├── hal_util_mem.h
│ │ ├── hal_util_mutex.h
│ │ ├── hal_util_random.h
│ │ ├── hal_util_storage.h
│ │ ├── hal_util_time.h
│ │ ├── util_list.h
│ │ ├── util_log.h
│ │ ├── util_ringbuffer.h
│ │ ├── util_string.h
│ │ └── util_type_trans.h
│ ├── lib_c_sdk.h
│ ├── lib_c_storage.h
│ └── ...
├── libaliyun_sdk.a
├── libcjson.a
├── libhal.a
├── libsdk_test.a
├── libtinycrypt.a
└──...
说明:
include目录下包含使用SDK所需要的头文件,需要将该目录添加至工程头文件目录下。
libaliyun_sdk.a包含SDK核心代码,必须加载。
libhal.a包含dummy hal相关函数,在完成hal移植后可以删除该库。
libsdk_test.a包含测试相关函数,自动化测试过程需要加载该库,正式生产需要去除。
libtinycrypt.a包含加解密依赖相关函数,如果平台未集成该三方库则必须加载。
libcjson.a包含SDK相关依赖相关函数,如果平台未集成该三方库则必须加载。
SDK 依赖 mbedtls 三方库,用户需自行移植该三方库。
SDK 中hal库以弱函数方式露出,方便开发者重载自己的平台函数,样式如下
__attribute__((weak)) void* util_malloc(int32_t size)
{
UTIL_LOG_E("ERROR");
assert(0);
return NULL;
}
2.2. SDK HAL 层适配
SDK 包中如下列出的五个头文件中的函数声明,开发者必须根据自己的开发平台进行同函数实现。
aliyun_sdk/include/c_utils/
├──hal_util_mem.h
├──hal_util_mutex.h
├──hal_util_random.h
├──hal_util_storage.h
├──hal_util_time.h
└──...
以下函数需要开发者自行适配,不适配会导致 SDK无法正常工作。
需要实现的函数列表如下,具体函数说明请参考 SDK 包中对应头文件:
内存模块(
aliyun_sdk/include/c_utils/hal_util_mem.h
)
void * util_malloc(int32_t size);
void util_free(void *ptr);
随机数模块(
aliyun_sdk/include/c_utils/hal_util_random.h
)
int32_t util_random_init(uint32_t seed);
uint32_t util_random(void);
存储模块(
aliyun_sdk/include/c_utils/hal_util_storage.h
)
int32_t util_storage_erase(void);
int32_t util_storage_storage(uint8_t *data, uint32_t size);
int32_t util_storage_load(uint8_t *data, uint32_t size);
时间模块(
aliyun_sdk/include/c_utils/hal_util_time.h
)
void util_msleep(uint32_t ms);
int64_t util_get_timestamp(void);
uint8_t util_timestamp_inited(void);
互斥锁模块(
aliyun_sdk/include/c_utils/hal_util_mutex.h
)
/* 互斥锁结构体定义 */
typedef struct _util_mutex_t {
void *mutex_handle; /* 互斥锁句柄,具体实现依赖于平台 */
} util_mutex_t;
util_mutex_t * util_mutex_create(void);
void util_mutex_delete(util_mutex_t *mutex);
int32_t util_mutex_lock(util_mutex_t *mutex, int32_t timeout);
int32_t util_mutex_unlock(util_mutex_t *mutex);
2.3. HAL 层移植验收标准
在实现以上各模块之后,加载libsdk_test.a
,在主程序中直接调用函数aliyun_sdk_test()
,就会对以上工作模块进行测试,输出日志可查看测试结果。
将输出日志反馈给阿里云进行确认。
测试成功的输出日志示例:
[UT][I][aliyun_sdk_test]********************* Hal Test Start *********************
[UT][I][aliyun_sdk_test]********************* memory test done *******************
[UT][I][aliyun_sdk_test]time is 1753344478377
[UT][I][aliyun_sdk_test]********************* time test done *********************
[UT][I][aliyun_sdk_test]********************* storage test done ******************
[UT][I][aliyun_sdk_test]********************* random test done *******************
[UT][I][aliyun_sdk_test]********************* mutex test done ********************
[UT][I][aliyun_sdk_test]********************* Hal Test End ***********************
3. 设备初始化
在使用 SDK 前,需要通过阿里云百炼平台——多模态控制台,创建应用并获取 AppId 及AppSecret
预付费模式,通过购买 license 获得对应的 AppSecret
后付费模式,通过联系阿里云技术获得对应的 AppSecret
初始化示例代码如下:
int dummy_aliyun_sdk_init(void)
{
// 初始化SDK
c_mmi_sdk_init();
// 配置设备信息
c_storage_set_app_id_str("<YOUR APP ID>");
c_storage_set_app_secret_str("<YOUR APP SECRET>");
c_storage_set_device_name("<YOUR DEVICE NAME>");
// 初始化音频配置
mmi_user_config_t mmi_config = {
.evt_cb = _mmi_event_callback, // 注册事件回调函数,详细说明见下文
.work_mode = C_MMI_MODE_PUSH2TALK, // PUSH TO TALK模式
.text_mode = C_MMI_TEXT_MODE_BOTH, // 同时显示ASR文本和LLM文本
.voice_id = "longxiaochun_v2",
.upstream_mode = C_MMI_STREAM_MODE_PCM, // 上行PCM数据
.downstream_mode = C_MMI_STREAM_MODE_PCM, // 下行PCM数据
.recorder_rb_size = 8 * 1024, // 录音数据缓冲区大小
.player_rb_size = 8 * 1024, // 音频数据缓冲区大小
};
c_mmi_config(&mmi_config);
//... 其他代码
}
注:DeviceName 为每个设备唯一标识符,由开发者自定义,要求不超过 32 字符即可;建议采用 MAC 地址、IMEI 等设备唯一信息。
参考日志
[UT][I][c_storage_init]sdk ver [0x00010001]
[UT][I][c_storage_init]flag [0x0000003f]
[UT][I][c_storage_init]app_id [<APP ID>]
[UT][I][c_storage_init]app_secret [<APP SECRET>]
[UT][I][c_storage_init]device_name [<DEVICE NAME>]
[UT][I][c_storage_init]nonce [<NONCE>]
[UT][I][c_storage_init]dialog_id [<DIALOG ID>]
[UT][I][c_storage_init]load [<DATA>]
[UT][I][c_storage_init]device_secret [<DEVICE SECRET>]
[UT][I][c_storage_set_app_id_str]app_id [<APP ID>]
[UT][I][c_storage_set_app_secret_str]app_secret [<APP SECRET>]
[UT][I][c_storage_set_device_name]device_name [<DEVICE NAME>]
[UT][I][c_mmi_register_event_callback]register event callback [<POINT ADDRESS>]
[UT][I][c_mmi_set_work_mode]work_mode[push2talk]
[UT][D][c_mmi_set_text_mode]text_mode[]
[UT][D][c_mmi_set_voice_id]voice_id[longxiaochun_v2]
[UT][D][c_mmi_set_upstream_mode]upstream_mode[pcm]
[UT][D][c_mmi_set_downstream_mode]downstream_mode[pcm]
[UT][I][c_mmi_config]device_name [<DEVICE NAME>]
[UT][I][c_mmi_config]load dialog_id [<DIALOG ID>]
[UT][I][c_mmi_config]done
4. 设备侧网络通信构建
SDK使用中需要使用HTTP
和WEBSOCKET
协议,需要开发者实现相应收发流程,SDK 仅负责发送数据的封装和接收数据的解析。
4.1. HTTP 通信
文档中将 http 通信相关功能简化为接收和发送两个函数,具体不同平台实现需要开发者自行适配,以下示例代码仅用于说明交互流程。
// http发送函数
int dummy_http_request(char* method, char* host, char* api, char *port, char* header, \
char* body, int(*rsp_async_cb)(char* rsp_data, int rsp_len));
// http接收函数,实现设备注册
int dummy_http_response_for_register(char* rsp_data, int rsp_len);
// http接收函数,实现获取Token
int dummy_http_response_for_token(char* rsp_data, int rsp_len);
4.1.1. 设备注册
设备注册示例代码如下:
int dummy_http_response_for_register(int status, char* rsp_data, int rsp_len)
{
int err;
// ... 客户其他业务逻辑
// 解析并完成注册
// SDK中的解析函数只需要报文中的data字段,如下http接收报文所示
err = c_device_analyze_register_rsp(rsp_body_data);
// ... 客户其他业务逻辑
return err;
}
int dummy_aliyun_sdk_init(void)
{
// 初始化SDK
c_mmi_sdk_init();
// 设备注册
if (c_storage_device_is_registered() == 0) {
// 配置设备信息
c_storage_set_app_id_str("<YOUR APP ID>");
c_storage_set_app_secret_str("<YOUR APP SECRET>");
c_storage_set_device_name("<YOUR DEVICE NAME>");
// 根据当前时间戳timestamp(毫秒),生成注册信息字串req
c_device_gen_register_req(req, timestamp);
// 获取服务端返回设备信息
dummy_http_request(METHOD, HOST, API, PORT, HEADER, req, dummy_http_response_for_register);
}
// 初始化音频配置
mmi_user_config_t mmi_config = {
.evt_cb = _mmi_event_callback, // 注册事件回调函数,详细说明见下文
.work_mode = C_MMI_MODE_PUSH2TALK, // PUSH TO TALK模式
.text_mode = C_MMI_TEXT_MODE_BOTH, // 同时显示ASR文本和LLM文本
.voice_id = "longxiaochun_v2",
.upstream_mode = C_MMI_STREAM_MODE_PCM, // 上行PCM数据
.downstream_mode = C_MMI_STREAM_MODE_PCM, // 下行PCM数据
.recorder_rb_size = 8 * 1024, // 录音数据缓冲区大小
.player_rb_size = 8 * 1024, // 音频数据缓冲区大小
};
c_mmi_config(&mmi_config);
//... 其他代码,完整示例见下文
}
上述代码中:
如采用半托管模式,http 通信的参数根据开发者
自行开发的服务端
注册接口进行配置如采用全托管模式,http 通信参数由阿里云提供,请与阿里云商务沟通具体对接事宜
示例数据
设备端SDK生成的请求数据包示例,如下是http请求报文中body字段
{
"appId": "<YOUR APP ID>",
"deviceName": "<YOUR DEVICE NAME>",
"nonce": "<YOUR NONCE>",
"requestTime": "1753326620619",
"sdkVersion": "0.3.2",
"signature": "<YOUR SIGNATURE>"
}
设备端解析的注册信息数据包示例,如下是http回应报文中data字段
{
"nonce": "<YOUR NONCE>",
"responseTime": "1753326621269",
"appId": "<YOUR APP ID>",
"deviceName": "<YOUR DEVICE NAME>",
"signature": "<YOUR SIGNATURE>"
}
注意:调用c_device_analyze_register_rsp 时,入参数据必须与示例数据格式保持一致
调试日志如下:
[UT][I][c_device_gen_register_req]req_str [383][{"appId":"<YOUR APPID>","deviceName":"<YOUR DEVICE NAME>","nonce":"<YOUR NONCE>","requestTime":"1753326620619","sdkVersion":"0.3.2","signature":"<YOUR SIGNATURE>"}]
[UT][I][c_device_analyze_register_rsp]rsp_str [403][{"nonce":"<YOUR NONCE>","responseTime":"1753326621269","appId":"<YOUR APPID>","deviceName":"<YOUR DEVICE NAME>","signature":"<YOUR SIGNATURE>"}]
[UT][I][c_device_analyze_register_rsp]nonce [<NONCE>]
完整的 http 发送报文如下所示:
POST /api/device/v1/register HTTP/1.1
Host: bailian.multimodalagent.aliyuncs.com
Accept: */*
Content-Type: application/json
Content-Length: 383
{"appId":"<YOUR APPID>","deviceName":"<YOUR DEVICE NAME>","nonce":"<YOUR NONCE>","requestTime":"1740129838000","sdkVersion":"0.3.2","signature":"<YOUR SIGNATURE>"}
完整的 http 接收报文如下所示:
HTTP/1.1 200 OK
content-type: application/json
date: Thu, 24 Jul 2025 03:10:21 GMT
req-cost-time: 383
req-arrive-time: 1753326620901
resp-start-time: 1753326621285
x-envoy-upstream-service-time: 383
server: istio-envoy
x-request-id: eabdaedc-f1ac-41af-9a47-633b511110b0
transfer-encoding: chunked
205
{"code":200,"success":true,"message":"success","localizedMsg":null,"data":{"nonce":"<NONCE>","responseTime":"1753326621269","appId":"<YOUR APP ID>","deviceName":"<YOUR DEVICE NAME>","signature":"YOUR SIGNATURE"},"requestId":"<AUTO GENERATED REQUESTED ID>"}
4.1.2. 设备登录
设备每次在连接阿里云多模态交互 AI 应用前,需要先获取 token 才能进行连接,获取 token示例代码如下:
int dummy_http_response_for_token(int status, char* rsp_data, int rsp_len)
{
int err;
// ... 客户其他业务逻辑
// 解析并获取token,数据要求同上
err = c_mmi_analyze_get_token_rsp(rsp_data);
// ... 客户其他业务逻辑
return err;
}
int dummy_aliyun_sdk_init(void)
{
// 初始化SDK
c_mmi_sdk_init();
// 设备注册
if (c_storage_device_is_registered() == 0) {
// 配置设备信息
c_storage_set_app_id_str("<YOUR APP ID>");
c_storage_set_app_secret_str("<YOUR APP SECRET>");
c_storage_set_device_name("<YOUR DEVICE NAME>");
// 根据当前时间戳timestamp(毫秒),生成注册信息字串req
c_device_gen_register_req(req, timestamp);
// 获取服务端返回设备信息
dummy_http_request(METHOD, HOST, API, PORT, HEADER, req, dummy_http_response_for_register);
}
// 设备登录
if (c_mmi_is_token_expire() == 0) {
// 根据当前时间戳timestamp(毫秒),生成登录信息字串req
c_mmi_gen_get_token_req(req, timestamp);
// 获取服务端返回登录信息
dummy_http_request(METHOD, HOST, API, PORT, HEADER, req, dummy_http_response_for_token);
}
// 初始化音频配置
mmi_user_config_t mmi_config = {
.evt_cb = _mmi_event_callback, // 注册事件回调函数,详细说明见下文
.work_mode = C_MMI_MODE_PUSH2TALK, // PUSH TO TALK模式
.text_mode = C_MMI_TEXT_MODE_BOTH, // 同时显示ASR文本和LLM文本
.voice_id = "longxiaochun_v2",
.upstream_mode = C_MMI_STREAM_MODE_PCM, // 上行PCM数据
.downstream_mode = C_MMI_STREAM_MODE_PCM, // 下行PCM数据
.recorder_rb_size = 8 * 1024, // 录音数据缓冲区大小
.player_rb_size = 8 * 1024, // 音频数据缓冲区大小
};
c_mmi_config(&mmi_config);
//... 其他代码,完整示例见下文
}
上述代码中:
如采用半托管模式,http 通信的参数根据开发者
自行开发的服务端
设备登录(getToken)进行配置如采用全托管模式,http 通信参数由阿里云提供,请与阿里云商务沟通具体对接事宜
4.1.3. 示例数据
设备端SDK生成的请求数据包示例,如下是http请求报文中body字段
{
"appId":"<YOUR APP ID>",
"deviceName":"<YOUR DEVICE NAME>",
"payMode":"LICENSE",
"requestTime":"1753327457730",
"sdkVersion":"0.3.2",
"tokenType":"MMI",
"signature": "<YOUR SIGNATURE>"
}
设备端解析的注册信息数据包示例,,如下是http回应报文中data字段
{
"nonce": "<YOUR NONCE>",
"responseTime": "1753327458081",
"appId": "<YOUR APP ID>",
"deviceName": "<YOUR DEVICE NAME>",
"requestIp": "<YOUR IP>",
"signature": "<YOUR SIGNATURE>"
}
注意:调用c_device_analyze_register_rsp 时,入参数据必须与示例数据格式保持一致。
调试日志如下:
[UT][I][c_dev_gen_get_token_req]plaintext [164][{"appId":"<YOUR APP ID>","deviceName":"<YOUR DEVICE NAME>","payMode":"LICENSE","requestTime":"1753327457730","sdkVersion":"0.3.2","tokenType":"MMI"}]
[UT][I][c_dev_gen_get_token_req]req_str [420][{"appId":"<YOUR APP ID>","deviceName":"<YOUR DEVICE NAME>","nonce":"<NONCE>","requestTime":"1753327457730","sdkVersion":"0.3.2","tokenType":"MMI","signature":"<SIGNTURE>"}]
[UT][I][c_mmi_analyze_get_token_rsp]rsp_str [589][{"nonce":"<NONCE>","responseTime":"1753327458081","appId":"<YOUR APP ID>","deviceName":"<YOUR DEVICE NAME>","requestIp":"YOUR IP","signature":"<SIGNATURE>"}]
[UT][I][c_mmi_analyze_get_token_rsp]nonce [<NONCE>]
完整的 http 发送报文如下所示:
POST /api/token/v1/getToken HTTP/1.1
Host: bailian.multimodalagent-pre.aliyuncs.com
Accept: */*
Content-Type: application/json
Content-Length: 420
{"appId":"<YOUR APP ID>","deviceName":"<YOUR DEVICE NAME>","nonce":"<NONCE>","requestTime":"1753327457730","sdkVersion":"0.3.2","tokenType":"MMI","signature":"<SIGNATURE>"}
完整的 http 接收报文如下所示:
HTTP/1.1 200 OK
content-type: application/json
date: Thu, 24 Jul 2025 03:24:18 GMT
req-cost-time: 348
req-arrive-time: 1753327457981
resp-start-time: 1753327458330
x-envoy-upstream-service-time: 294
server: istio-envoy
x-request-id: 9ea14105-6c5e-428c-bfe1-4cf6f4dcdbc6
transfer-encoding: chunked
2bf
{"code":200,"success":true,"message":"success","localizedMsg":null,"data":{"nonce":"<NONCE>","responseTime":"1753327458081","appId":"<YOUR APP ID>","deviceName":"<YOUR DEVICE NAME>","requestIp":"<YOUR IP>","signature":"<SIGNATURE>"},"requestId":"<AUTO GENERATED REQUESTED ID>"}
4.2. WEBSOCKET 通信
在完成设备登录功能对接后,开始进行 websocket 通信调试。
阿里云百炼多模态交互 SDK 仅进行 websocket 数据的处理,不负责数据的收发动作;建议开发者创建相应的 websocket 数据收发线程以实现 websocket 数据的接收与发送;本文档示例代码按该交互方式进行说明。
websocket 示例代码
// 进行websocket连接
int dummy_wss_connect(char* host, char* port, char* api, char* header);
// 开启websocket收发线程
int dummy_wss_thread_start(void* params);
// 实际执行的websocket发送函数
int dummy_wss_send(int data_type, char* payload_data, int size);
// 实际执行的websocket接收函数
int dummy_wss_recv(int* opcode, char* payload_data, int* recv_size);
// 此函数基于websocket协议异步发送数据
int dummy_wss_task_send(void);
// 此函数基于websocket协议异步接收数据
int dummy_wss_task_recv(void);
4.2.1. 建立 websocket 连接
以下示例代码描述了如何建立 websocket连接,SDK会提供host、port、api以及header字段,其余字段需要开发者自己填充打包并完成发送流程。
int dummy_wss_init(void)
{
// 建立websocket连接,获取连接字段
char *wss_host = c_mmi_get_wss_host();
char *wss_port = c_mmi_get_wss_port();
char *wss_api = c_mmi_get_wss_api();
char *wss_header = c_mmi_get_wss_header();
UTIL_LOG_I("work");
// 建立wss连接
int ret = dummy_wss_connect(wss_host, wss_port, wss_api, wss_header);
return ret;
}
注意,本 SDK 与云端的 websocket 通信需要建立 TLS 隧道,需要做如下配置:
TLS 版本要求 TLS1.2 或以上
开启 SNI(SERVER NAME INDICATION)
配置 CA 证书(GlobalSign Root CA - R3)(也可至GlobalSign官网下载)
websocket建立连接时upgrade请求报文示例
GET <WSS API> HTTP/1.1
Host: <WSS HOST>
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: FhVlQeR4S1N06+1/SU79XA==
Sec-WebSocket-Version: 13
<WSS HEADER>
websocket建立连接时回应报文示例
HTTP/1.1 101 Switching Protocols
upgrade: websocket
connection: upgrade
sec-websocket-accept: sqchBdVDX8kKBgi90/PFl5+/4VI=
date: Thu, 24 Jul 2025 08:25:24 GMT
server: istio-envoy
日志示例
[UT][I][dummy_wss_init]work
[UT][I][dummy_wss_connect]wss update
[UT][I][dummy_wss_connect]request[239][GET <wss_api> HTTP/1.1
Host: <WSS HOST>
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: FhVlQeR4S1N06+1/SU79XA==
Sec-WebSocket-Version: 13
<WSS HEADER>
]
[UT][I][dummy_wss_connect]Reading response...
[UT][I][dummy_wss_connect]response[MMI][187][HTTP/1.1 101 Switching Protocols
upgrade: websocket
connection: upgrade
sec-websocket-accept: sqchBdVDX8kKBgi90/PFl5+/4VI=
date: Thu, 24 Jul 2025 08:25:24 GMT
server: istio-envoy
]
[UT][I][dummy_wss_connect][MMI]done
正确建立 websocket 后,可以观察平台相应的日志,正确场景下该连接会保持 1 分钟,后被服务端主动断开。
4.2.2. websocket 数据交互
使用阿里云百炼多模态交互 SDK 时 ,所有通过 websocket 进行交互的数据均需通过 SDK 进行处理,否则可能导致不可预知的问题。
以下示例代码描述了如何通过创建线程实现 websocket 数据交互。
int dummy_wss_task_recv(void)
{
int opcode;
char data[8 * 1024];
int recv_size;
while(1) {
// 通过websocket接收服务端下行数据
dummy_wss_recv(&opcode, data, &recv_size);
if(recv_size) {
// 将接收服务端下行数据送入SDK进行解析
c_mmi_analyze_recv_data(opcode, _data, recv_size);
} else {
util_msleep(10);
}
}
return 0;
}
int dummy_wss_task_send(void)
{
uint8_t opcode;
uint8_t data[8 * 1024];
uint32_t size;
while(1) {
// 从SDK获取打包好的payload数据
size = c_mmi_get_send_data(&opcode, data, sizeof(payload_data));
if (size == 0) {
util_msleep(10);
} else {
// 将payload数据传入websocket发送函数,自行打包帧头进行发送
dummy_wss_send(opcode, data, size);
}
}
return 0;
}
注意事项:
websocket 接收的data 大小根据下行的数据格式不同,建议值如下:
下行 PCM 格式数据时,data 大小需要 8K 及以上。
下行 MP3 格式数据时,data 大小需要 4K 及以上。
下行 opus 格式数据时,data 大小需要 2K 及以上。
websocket 发送的data 大小不得低于 1.5K
调用
c_mmi_analyze_recv_data
或c_mmi_get_send_data
函数时,opcode
为WS_DATA_TYPE_TEXT
或WS_DATA_TYPE_BINARY
,该值在 SDK 头文件中定义;开发者需要参考具体平台的定义实现及 websocket 协议,理解该值。
当完成数据对接后,可以看到如下日志
[UT][I][dummy_wss_thread_start][dummy_wss_task_send] send task [0x135e27180]
[UT][I][dummy_wss_thread_start][dummy_wss_task_recv] recv task [0x135e271a0]
[UT][I][_gen_cmd_start]task_id [<TASK ID>]
[UT][I][_send_cmd_start]send [run-task] [0-2] [0]
[UT][D][c_mmi_analyze_recv_data]recv[109][{"header":{"task_id":"<TASK ID>","event":"task-started","attributes":{}},"payload":{}}]
[UT][I][c_mmi_analyze_recv_data]recv [task-started] [0-2]
[UT][D][c_mmi_analyze_recv_data]recv[192][{"header":{"task_id":"<TASK ID>","event":"result-generated","attributes":{}},"payload":{"output":{"event":"Started","dialog_id":"<DIALOG_ID>"}}}]
[UT][I][_on_payload_event_start]recv [Started] [0-3]
[UT][D][c_mmi_analyze_recv_data]recv[223][{"header":{"task_id":"<TASK ID>","event":"result-generated","attributes":{}},"payload":{"output":{"event":"DialogStateChanged","state":"Listening","dialog_id":"<DIALOG_ID>"}}}]
[UT][I][_on_payload_event_state_change]recv [Listening] [0-4]
5. 语音交互流程开发
5.1. 音频数据接口
音频数据接口主要包含麦克风和播放器的实现函数,需要开发者自行实现。
以下代码仅作演示,实现和业务逻辑仅供参考。
//当recorder start后,麦克风开始异步获取数据
int dummy_recorder_async_callback(void);
// 启动麦克风录音
int dummy_recorder_start(void);
// 停止麦克风录音
int dummy_recorder_stop(void);
// 从硬件麦克风获取数据
int dummy_hw_recorder_get_data(uint8_t* data, uint32_t size);
// 获取麦克风工作状态
int dummy_recoder_is_work(void);
// 当player start后,播放器开始异步播放音频
int dummy_player_async_callback(void);
// 启动喇叭播放音频
int dummy_player_start(void);
// 停止喇叭播放音频
int dummy_player_stop(void);
// 将数据放入喇叭硬件进行播放
int dummy_hw_player_put_data(uint8_t* data, uint32_t size);
// 获取喇叭工作状态
int dummy_palyer_is_work(void);
本文档使用多线程实现喇叭播放与麦克风录音,因此构建以下示例
void dummy_recorder_task(void)
{
uint32_t send_size =0;
uint32_t size = 640;
uint8_t* data = (uint8_t*) util_malloc(size);
while(1) {
if(dummy_recoder_is_work()) {
send_size = dummy_hw_recorder_get_data(data, size);
if(send_size){
// 将音频采集硬件采集到的数据输出至SDK ringbuffer
c_mmi_put_recorder_data(data, send_size);
} else {
util_msleep(10);
}
} else {
util_msleep(10);
}
}
}
void dummy_player_task(void)
{
uint8_t data[640];
uint32_t size = 640;
uint32_t recv_size = 0;
while(1) {
if (dummy_player_is_work()) {
recv_size = c_mmi_get_player_data(data, size);
if(recv_size){
// 将SDK ringbuffer中的音频数据输出给到播放器进行播放
dummy_hw_player_put_data(data, size)
} else {
util_msleep(10);
}
} else {
util_msleep(10);
}
}
}
5.2. 按键接口
本文档示例为 PUSH TO TALK,因此需要有按键按下和按键抬起两个事件的捕捉,本文档采用事件回调形式实现,此处只给出回调函数,具体逻辑与实现需要开发者自行实现。
以下代码仅做演示:
// 按键抬起时触发
int dummy_button_up(void);
// 按键按下时触发
int dummy_button_down(void);
示例实现与调用如下所示:
int dummy_button_up(void)
{
// 关闭麦克风
dummy_recorder_stop();
// 通知云端服务开始处理音频数据
c_mmi_stop_speech();
return 0;
}
int dummy_button_down(void)
{
// 关闭喇叭
dummy_player_stop();
// 通知云端即将开始发送音频数据,SDK会根据云端对该指令的响应触发C_MMI_EVENT_SPEECH_START
c_mmi_start_speech();
return 0;
}
5.3. 事件回调
阿里云百炼多模态交互 SDK 语音交互相关的事件回调如下:
enum {
C_MMI_EVENT_USER_CONFIG, // 用户对于sdk的配置应该在该事件回调中实现,如音频缓冲区大小、工作模式、音色等
C_MMI_EVENT_DATA_INIT, // 当SDK完成初始化后触发该事件,可在该事件回调中开始建立业务连接
C_MMI_EVENT_SPEECH_START, // 当SDK开始进行音频上行时触发此事件
C_MMI_EVENT_DATA_DEINIT, // 当SDK注销后触发此事件
C_MMI_EVENT_ASR_START, // 当ASR开始返回数据时触发此事件
C_MMI_EVENT_ASR_INCOMPLETE, // 此事件返回尚未完成ASR的文本数据(全量)
C_MMI_EVENT_ASR_COMPLETE, // 此事件返回完成ASR的全部文本数据(全量)
C_MMI_EVENT_ASR_END, // 当ASR结束时触发此事件
C_MMI_EVENT_LLM_INCOMPLETE, // 此事件返回尚未处理完成的LLM文本数据(全量)
C_MMI_EVENT_LLM_COMPLETE, // 此事件返回处理完成的LLM全部文本数据(全量)
C_MMI_EVENT_TTS_START, // 当开始音频下行时触发此事件
C_MMI_EVENT_TTS_END, // 当音频完成下行时触发此事件
};
参考示例代码如下:
int _mmi_event_callback(uint32_t event, void *param)
{
char *text;
text = param;
switch (event) {
case C_MMI_EVENT_USER_CONFIG:
// 开始新对话
c_mmi_reset_dialog_id();
break;
case C_MMI_EVENT_DATA_INIT:
// Mmi data ready, 开始网络连接
dummy_wss_init();
break;
case C_MMI_EVENT_DATA_DEINIT:
UTIL_LOG_W("will disconnect");
break;
case C_MMI_EVENT_SPEECH_START:
UTIL_LOG_D("enable recorder when send speech");
dummy_player_stop();
dummy_recorder_start();
break;
case C_MMI_EVENT_ASR_START:
UTIL_LOG_I("event [C_MMI_EVENT_ASR_START]");
break;
case C_MMI_EVENT_ASR_INCOMPLETE:
UTIL_LOG_D("ASR [%s]", text);
break;
case C_MMI_EVENT_ASR_COMPLETE:
if (text) {
UTIL_LOG_D("ASR C [%s]", text);
} else {
UTIL_LOG_D("ASR C [NULL]");
}
break;
case C_MMI_EVENT_ASR_END:
UTIL_LOG_D("disable record when ASR complete");
dummy_recorder_stop();
break;
case C_MMI_EVENT_LLM_INCOMPLETE:
UTIL_LOG_D("LLM [%s]", text);
break;
case C_MMI_EVENT_LLM_COMPLETE:
UTIL_LOG_D("LLM C [%s]", text);
break;
case C_MMI_EVENT_TTS_START:
UTIL_LOG_I("enable player when dialog start");
dummy_player_start();
break;
case C_MMI_EVENT_TTS_END:
break;
default:
break;
}
return UTIL_SUCCESS;
}
示例日志如下
[UT][I][_on_payload_event_state_change]recv [Listening] [0-4]
[UT][D][dummy_button_down]
[UT][I][dummy_player_stop]
[UT][I][_send_cmd_req2spk]ready to send [1-4]
[UT][I][_send_cmd_speech]send [SendSpeech] [1-5] [0]
[UT][D][dummy_mmi_event_callback]enable recorder when send speech
[UT][I][dummy_recorder_start]
[UT][I][_on_payload_event_speech_start]recv [SpeechStarted][ASR Start] [1-5]
[UT][I][dummy_mmi_event_callback]event [C_MMI_EVENT_ASR_START]
[UT][D][dummy_button_up]
[UT][I][dummy_recorder_stop]
[UT][I][_send_cmd_stop_speech]send [StopSpeech] [1-5] [0]
[UT][I][_on_payload_event_speech_content]recv [SpeechContent][ASR Text] [1-5]
[UT][D][dummy_mmi_event_callback]ASR C [今天天气怎么样?]
[UT][I][_on_payload_event_speech_end]recv [SpeechEnded][ASR End] [1-6]
[UT][D][dummy_mmi_event_callback]disable record when ASR complete
[UT][I][dummy_recorder_stop]
[UT][I][_on_payload_event_state_change]recv [Thinking] [1-7]
[UT][D][_on_payload_event_state_change]prepare player rb
[UT][I][_on_payload_event_state_change]recv [Responding][Audio Start] [1-8]
[UT][I][dummy_mmi_event_callback]enable player when dialog start
[UT][I][dummy_player_start]
[UT][I][_on_payload_event_respond_start]recv [RespondingStarted][Audio Start] [1-8]
[UT][I][_on_payload_event_respond_content]recv [RespondingContent][LLM Text] [1-8]
[UT][D][dummy_mmi_event_callback]LLM C [今天上海市天气多云,白天最高气温33℃,夜间有小雨,气温27℃。东风吹,风力1-3级。]
[UT][I][_on_payload_event_respond_end]recv [RespondingEnded][Audio End] [1-9]
[UT][D][_on_payload_event_respond_end]recv audio data size [371200]
6. SDK 完整构建流程
6.1. 完整示例代码
示例代码如下
#include "lib_c_storage.h"
#include "lib_c_sdk.h"
#define C_SDK_REQ_LEN_REGISTER 500
char req[C_SDK_REQ_LEN_REGISTER];
int dummy_http_response_for_register(int status, char* rsp_data, int rsp_len){
int err;
// ... 客户其他业务逻辑
// 解析并完成注册
// SDK中的解析函数只需要报文中的data字段,如下http接收报文所示
err = c_device_analyze_register_rsp(rsp_body_data);
// ... 客户其他业务逻辑
return err;
}
int dummy_http_response_for_token(int status, char* rsp_data, int rsp_len){
int err;
// ... 客户其他业务逻辑
// 解析并获取token,数据要求同上
err = c_mmi_analyze_get_token_rsp(rsp_data);
// ... 客户其他业务逻辑
return err;
}
int dummy_aliyun_sdk_init(void)
{
// 初始化SDK
c_mmi_sdk_init();
// 设备注册,在此之前需要完成SDK的初始化
if (c_storage_device_is_registered() == 0) {
// 配置设备信息
c_storage_set_app_id_str("<YOUR APP ID>");
c_storage_set_app_secret_str("<YOUR APP SECRET>");
c_storage_set_device_name("<YOUR DEVICE NAME>");
// 根据当前时间戳timestamp(毫秒),生成注册信息字串req
c_device_gen_register_req(req, timestamp);
// 获取服务端返回设备信息
dummy_http_request(METHOD, HOST, API, PORT, HEADER, req, dummy_http_response_for_register);
}
// 设备登录
if (c_mmi_is_token_expire() == 0) {
// 根据当前时间戳timestamp(毫秒),生成登录信息字串req
c_mmi_gen_get_token_req(req, timestamp);
// 获取服务端返回登录信息
dummy_http_request(METHOD, HOST, API, PORT, HEADER, req, dummy_http_response_for_token);
}
// 初始化音频配置
mmi_user_config_t mmi_config = {
.evt_cb = _mmi_event_callback, // 注册事件回调函数,详细说明见下文
.work_mode = C_MMI_MODE_PUSH2TALK, // PUSH TO TALK模式
.text_mode = C_MMI_TEXT_MODE_BOTH, // 同时显示ASR文本和LLM文本
.voice_id = "longxiaochun_v2",
.upstream_mode = C_MMI_STREAM_MODE_PCM, // 上行PCM数据
.downstream_mode = C_MMI_STREAM_MODE_PCM, // 下行PCM数据
.recorder_rb_size = 8 * 1024, // 录音数据缓冲区大小
.player_rb_size = 8 * 1024, // 音频数据缓冲区大小
};
c_mmi_config(&mmi_config);
return 0;
}
int dummy_wss_init(void)
{
// 建立websocket连接,获取连接字段
char *wss_host = c_mmi_get_wss_host();
char *wss_port = c_mmi_get_wss_port();
char *wss_api = c_mmi_get_wss_api();
char *wss_header = c_mmi_get_wss_header();
// 建立wss连接
int ret = dummy_wss_connect(wss_host, wss_port, wss_api, wss_header);
return ret;
}
void dummy_recorder_task(void)
{
uint32_t send_size =0;
uint32_t size = 640;
uint8_t* data = (uint8_t*) util_malloc(size);
while(1){
if(dummy_recoder_is_work()){
send_size = dummy_hw_recorder_get_data(data, size);
if(send_size){
// 将音频采集硬件采集到的数据输出至SDK ringbuffer
c_mmi_put_recorder_data(data, send_size);
}
else{
util_msleep(10);
}
} else {
util_msleep(10);
}
}
}
void dummy_player_task(void)
{
uint8_t data[640];
uint32_t size = 640;
uint32_t recv_size = 0;
while(1) {
if(dummy_player_is_work()){
recv_size = c_mmi_get_player_data(data, size);
if(recv_size){
// 将SDK ringbuffer中的音频数据输出给到播放器进行播放
dummy_hw_player_put_data(data, size)
} else {
util_msleep(10);
}
} else {
util_msleep(10);
}
}
}
int dummy_wss_task_recv(void)
{
int opcode;
char* payload_data;
int recv_size;
while(1){
dummy_wss_recv(&opcode, &pauload_data, &recv_size);
if(recv_size)
// 将收到的websocket数据送入SDK进行解析,其中只需要送入opcode,payload数据.
c_mmi_analyze_recv_data(opcode, payload_data, recv_size);
else
util_msleep(10);
}
return 0;
}
int dummy_wss_task_send(void)
{
uint8_t data_type;
uint8_t payload_data[1024];
uint32_t size;
while(1){
// 从SDK获取打包好的payload数据
size = c_mmi_get_send_data(&data_type, payload_data, size);
if (size == 0) {
util_msleep(10);
} else {
// 将payload数据传入websocket发送函数,自行打包帧头进行发送
dummy_wss_send(data_type, data, size);
}
}
return 0;
}
int dummy_button_up(void)
{
// 关闭麦克风
recorder_stop();
// 通知云端服务开始处理音频数据
c_mmi_stop_speech();
return 0;
}
int dummy_button_down(void)
{
// 关闭喇叭
player_stop();
// 通知云端即将开始发送音频数据,SDK会根据云端对该指令的响应触发C_MMI_EVENT_SPEECH_START
c_mmi_start_speech();
return 0;
}
int _mmi_event_callback(uint32_t event, void *param)
{
char *text;
text = param;
switch (event) {
case C_MMI_EVENT_USER_CONFIG:
// 开始新对话
c_mmi_reset_dialog_id();
break;
case C_MMI_EVENT_DATA_INIT:
// Mmi data ready, 开始网络连接
dummy_wss_init();
break;
case C_MMI_EVENT_DATA_DEINIT:
UTIL_LOG_W("will disconnect");
break;
case C_MMI_EVENT_SPEECH_START:
UTIL_LOG_D("enable recorder when send speech");
dummy_player_stop();
dummy_recorder_start();
break;
case C_MMI_EVENT_ASR_START:
UTIL_LOG_I("event [C_MMI_EVENT_ASR_START]");
break;
case C_MMI_EVENT_ASR_INCOMPLETE:
UTIL_LOG_D("ASR [%s]", text);
break;
case C_MMI_EVENT_ASR_COMPLETE:
if (text) {
UTIL_LOG_D("ASR C [%s]", text);
} else {
UTIL_LOG_D("ASR C [NULL]");
}
break;
case C_MMI_EVENT_ASR_END:
UTIL_LOG_D("disable record when ASR complete");
dummy_recorder_stop();
break;
case C_MMI_EVENT_LLM_INCOMPLETE:
UTIL_LOG_D("LLM [%s]", text);
break;
case C_MMI_EVENT_LLM_COMPLETE:
UTIL_LOG_D("LLM C [%s]", text);
break;
case C_MMI_EVENT_TTS_START:
UTIL_LOG_I("enable player when dialog start");
du mmy_player_start();
break;
case C_MMI_EVENT_TTS_END:
break;
default:
break;
}
return UTIL_SUCCESS;
}
int main(void)
{
int ret = dummy_aliyun_sdk_init();
// 开启websocket收发线程,由开发者自己实现
dummy_wss_thread_start();
return ret;
}
6.2. 完整示例日志
完整示例日志如下:
[UT][I][c_storage_init]sdk ver [0x00000300]
[UT][I][c_storage_init]flag [0x0000003f]
[UT][I][c_storage_init]app_id [<APP ID>]
[UT][I][c_storage_init]app_secret [<APP SECRET>]
[UT][I][c_storage_init]device_name [<DEVICE NAME>]
[UT][I][c_storage_init]nonce [<NONCE>]
[UT][I][c_storage_init]dialog_id [<DIALOG ID>]
[UT][I][c_storage_init]load [<DATA>]
[UT][I][c_storage_init]device_secret [<DEVICE SECRET>]
[UT][I][c_mmi_register_event_callback]register event callback [<POINT ADDRESS>]
[UT][I][c_mmi_set_work_mode]work_mode[push2talk]
[UT][D][c_mmi_set_text_mode]text_mode[]
[UT][D][c_mmi_set_voice_id]voice_id[longxiaochun_v2]
[UT][D][c_mmi_set_upstream_mode]upstream_mode[pcm]
[UT][D][c_mmi_set_downstream_mode]downstream_mode[pcm]
[UT][I][c_mmi_config]device_name [<DEVICE NAME>]
[UT][I][c_mmi_config]load dialog_id [<DIALOG ID>]
[UT][I][c_mmi_config]done
[UT][I][c_device_gen_register_req]req_str [383][{"appId":"<YOUR APPID>","deviceName":"<YOUR DEVICE NAME>","nonce":"<YOUR NONCE>","requestTime":"1753326620619","sdkVersion":"0.3.2","signature":"<YOUR SIGNATURE>"}]
[UT][I][c_device_analyze_register_rsp]rsp_str [403][{"nonce":"<YOUR NONCE>","responseTime":"1753326621269","appId":"<YOUR APPID>","deviceName":"<YOUR DEVICE NAME>","signature":"<YOUR SIGNATURE>"}]
[UT][I][c_device_analyze_register_rsp]nonce [<NONCE>]
[UT][I][c_dev_gen_get_token_req]plaintext [164][{"appId":"<YOUR APP ID>","deviceName":"<YOUR DEVICE NAME>","payMode":"LICENSE","requestTime":"1753327457730","sdkVersion":"0.3.2","tokenType":"MMI"}]
[UT][I][c_dev_gen_get_token_req]req_str [420][{"appId":"<YOUR APP ID>","deviceName":"<YOUR DEVICE NAME>","nonce":"<NONCE>","requestTime":"1753327457730","sdkVersion":"0.3.2","tokenType":"MMI","signature":"<SIGNTURE>"}]
[UT][I][c_mmi_analyze_get_token_rsp]rsp_str [589][{"nonce":"<NONCE>","responseTime":"1753327458081","appId":"<YOUR APP ID>","deviceName":"<YOUR DEVICE NAME>","requestIp":"YOUR IP","signature":"<SIGNATURE>"}]
[UT][I][c_mmi_analyze_get_token_rsp]nonce [<NONCE>]
[UT][I][_mmi_event_callback]C_MMI_EVENT_DATA_INIT
[UT][I][dummy_wss_init]work
[UT][I][dummy_wss_connect]wss update
[UT][I][dummy_wss_connect]request[239][GET <wss_api> HTTP/1.1
Host: <WSS HOST>
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: FhVlQeR4S1N06+1/SU79XA==
Sec-WebSocket-Version: 13
<WSS HEADER>
]
[UT][I][dummy_wss_connect]Reading response...
[UT][I][dummy_wss_connect]response[MMI][187][HTTP/1.1 101 Switching Protocols
upgrade: websocket
connection: upgrade
sec-websocket-accept: sqchBdVDX8kKBgi90/PFl5+/4VI=
date: Thu, 24 Jul 2025 08:25:24 GMT
server: istio-envoy
]
[UT][I][dummy_wss_connect][MMI]done
[UT][I][dummy_wss_thread_start][dummy_wss_task_send] send task [0x135e27180]
[UT][I][dummy_wss_thread_start][dummy_wss_task_recv] recv task [0x135e271a0]
[UT][I][_gen_cmd_start]task_id [<TASK ID>]
[UT][I][_send_cmd_start]send [run-task] [0-2] [0]
[UT][D][c_mmi_analyze_recv_data]recv[109][{"header":{"task_id":"<TASK ID>","event":"task-started","attributes":{}},"payload":{}}]
[UT][I][c_mmi_analyze_recv_data]recv [task-started] [0-2]
[UT][D][c_mmi_analyze_recv_data]recv[192][{"header":{"task_id":"<TASK ID>","event":"result-generated","attributes":{}},"payload":{"output":{"event":"Started","dialog_id":"<DIALOG_ID>"}}}]
[UT][I][_on_payload_event_start]recv [Started] [0-3]
[UT][D][c_mmi_analyze_recv_data]recv[223][{"header":{"task_id":"<TASK ID>","event":"result-generated","attributes":{}},"payload":{"output":{"event":"DialogStateChanged","state":"Listening","dialog_id":"<DIALOG_ID>"}}}]
[UT][I][_on_payload_event_state_change]recv [Listening] [0-4]
[UT][D][dummy_button_down]
[UT][I][dummy_player_stop]
[UT][I][_send_cmd_req2spk]ready to send [1-4]
[UT][I][_send_cmd_speech]send [SendSpeech] [1-5] [0]
[UT][D][_mmi_event_callback]enable recorder when send speech
[UT][I][dummy_recorder_start]
[UT][I][_on_payload_event_speech_start]recv [SpeechStarted][ASR Start] [1-5]
[UT][I][_mmi_event_callback]event [C_MMI_EVENT_ASR_START]
[UT][D][dummy_button_up]
[UT][I][dummy_recorder_stop]
[UT][I][_send_cmd_stop_speech]send [StopSpeech] [1-5] [0]
[UT][I][_on_payload_event_speech_content]recv [SpeechContent][ASR Text] [1-5]
[UT][D][_mmi_event_callback]ASR C [今天天气怎么样?]
[UT][I][_on_payload_event_speech_end]recv [SpeechEnded][ASR End] [1-6]
[UT][D][_mmi_event_callback]disable record when ASR complete
[UT][I][dummy_recorder_stop]
[UT][I][_on_payload_event_state_change]recv [Thinking] [1-7]
[UT][D][_on_payload_event_state_change]prepare player rb
[UT][W][c_mmi_analyze_recv_data]recv [8000] in thinking
[UT][W][c_mmi_analyze_recv_data]recv [1788] in thinking
[UT][I][_on_payload_event_state_change]recv [Responding][Audio Start] [1-8]
[UT][I][_mmi_event_callback]enable player when dialog start
[UT][I][dummy_player_start]
[UT][I][_on_payload_event_respond_start]recv [RespondingStarted][Audio Start] [1-8]
[UT][I][_on_payload_event_respond_content]recv [RespondingContent][LLM Text] [1-8]
[UT][D][_mmi_event_callback]LLM C [今天上海市天气多云,白天最高气温33℃,夜间有小雨,气温27℃。东风吹,风力1-3级。]
[UT][I][_on_payload_event_respond_end]recv [RespondingEnded][Audio End] [1-9]
[UT][D][_on_payload_event_respond_end]recv audio data size [371200]