License模式C SDK

重要

本文中含有需要您注意的重要提示信息,忽略该信息可能对您的业务造成影响,请务必仔细阅读。

本文介绍如何使用阿里云百炼大模型服务提供的嵌入式C SDK进行实时多模态交互。重点说明了在License模式下如何进行设备管理与实时多模态交互,包括SDK下载和安装,License模式下全托管和半托管方式,云端和设备端关键接口及代码示例。

前提条件

重要
  1. 开通阿里云百炼实时多模交互应用,获取Workspace IDAPP IDAPI Key

  2. 根据应用创建的文档完成应用创建。

  3. 根据应用配置的文档完成应用的配置。

  • 已适配的硬件列表

序号

硬件平台信息

SDK包下载地址

厂商

芯片平台

架构

发布版本

博通

BK7258

Armv8

V1.0.1

SDK下载链接

安凯

AV100

Armv8

V1.0.1

SDK下载链接

安凯

AV200

RISC-V 64

V1.0.1

SDK下载链接

杰理

AC7911

PI32V2

V1.0.1

SDK下载链接

杰理

JL7014

PI32V2

V1.0.1

SDK下载链接

爱旗

AiW626X

RISC-V 64

V1.0.1

SDK下载链接

翱捷

ASR1606

Armv7

V1.0.1

SDK下载链接

全志

F133

RISC-V 64

V1.0.1

SDK下载链接

全志

V821

RISC-V 32

V1.0.1

SDK下载链接

全志

V853

Armv7

V1.0.1

SDK下载链接

全志

XR872

Armv7

V1.0.1

SDK下载链接

瑞芯微

RK3506

Armv7

V1.0.1

SDK下载链接

芯迈微

XMW718

Armv7

V1.0.1

SDK下载链接

星宸科技

SSC309QL

Armv7

V1.0.1

SDK下载链接

移芯

EC718PM

Armv7

V1.0.1

SDK下载链接

紫光展锐

UMS9117

Armv7

V1.0.1

SDK下载链接

紫光展锐

W217

Armv7

V1.0.1

SDK下载链接

创芯慧联

LM600

RISC-V 64

V1.0.1

SDK下载链接

创芯慧联

LM620

RISC-V 64

V1.0.1

SDK下载链接

1. 接入模式说明

半托管模式

适用场景:客户自有云服务,能对自己的设备进行管理和鉴权,客户云服务和设备端有双向通信通道;

警告

同一个DeviceName注册过后,无法再次注册去获取设备证书

image.jpeg

  • 服务端开发:

    • 参考云端接口说明部分完成云端接口对接,设备计量管理服务提供设备注册和获取Token两个接口

  • 设备端开发

    • 参考芯片平台HAL层对接部分进行芯片适配(若采用阿里云推荐芯片/模组,则可以省略此步骤)

    • 参考设备端业务逻辑对接部分在集成SDK后完成业务逻辑开发,设备按一型一密进行注册,设备预置创建应用时生成的AppIdAppSecret

全托管模式

适用场景:客户没有云服务,无法对设备进行管理,由阿里云进行设备管理和鉴权,需联系阿里云商务手动开通后使用

警告

同一个DeviceName注册过后,无法再次注册去获取设备证书

yuque_diagram (3)

  • 服务端开发:

  • 设备端开发

    • 参考芯片平台HAL层对接部分进行芯片适配(若采用阿里云推荐芯片/模组,则可以省略此步骤)

    • 参考设备端业务逻辑对接部分在集成SDK后完成业务逻辑开发,设备按一型一密进行注册,设备预置创建应用时生成的AppIdAppSecret

说明
  1. 什么是一型一密?

    就是同一产品型号下所有设备预烧录相同的产品信息(AppId、AppSecret)。当设备到计量管理服务进行注册时,会对其携带的设备信息(AppId、AppSecret、DeviceName)进行认证。认证通过后,会下发设备唯一的设备证书(即三元组AppId、DeviceNameDeviceSecret),后续设备通过该证书才能完成后续所有通信链路的鉴权。

  1. 如何查看AppIdAppSecret?

    在我的应用界面能够直接复制应用ID(AppID)。在配置应用按钮边上有三个小点的按钮,点击后能够查看AppSecret

    截屏2025-08-04 16

2. 云端接口开发说明

导入依赖包

java版本

<dependency>
  <groupId>com.aliyun</groupId>
  <artifactId>bailianmodelonchip20240816</artifactId>
  <version>1.0.2</version>
</dependency>
说明

maven仓库地址:https://repo1.maven.org/maven2

endPoint:bailianmodelonchip.cn-beijing.aliyuncs.com

regionId:cn-beijing

python版本

pip install alibabacloud-bailianmodelonchip20240816==1.0.2

2.1 设备注册接口 deviceRegister

在设备首次联网后,设备需要通过客户云服务调用该接口,向百炼设备计量管理服务进行注册,以获取设备的三元组信息。鉴权采用阿里云POP网关鉴权,需集成POP SDK

接口入参:DeviceRegisterRequest对象

参数

类型

必填

描述

nonce

String

设备端上传的16字节随机数(32字符),参考设备注册接口

appId

String

产品标识,在百炼控制台创建应用之后会生成该ID

requestTime

String

设备端上传的请求时间戳(ms),参考设备注册接口

signature

String

JSON加密信息,由设备端生成并上传,参考设备注册接口

接口出参:DeviceRegisterResponseBody对象

参数

类型

必填

描述

code

String

错误码

httpStatusCode

Integer

http状态码

200 成功

其他 失败

message

String

错误信息

requestId

String

请求ID

success

Boolean

成功标志

data

DeviceRegisterResponseBodyData

注册结果,直接透传给设备端

DeviceRegisterResponseBodyData

参数

类型

描述

nonce

String

16字节随机数(32字符)

responseTime

String

响应时间戳(ms)

appId

String

产品标识

deviceName

String

设备唯一标识

signature

String

设备三元组信息,设备端SDK可解析,参考设备注册接口

Java示例

package pop;

import com.alibaba.fastjson.JSON;
import com.aliyun.bailianmodelonchip20240816.Client;
import com.aliyun.bailianmodelonchip20240816.models.*;
import com.aliyun.teaopenapi.models.Config;

public class DeviceTest {

    public static void main(String[] args) {
        Config config = new Config();
        config.setAccessKeyId("your-ak");
        config.setAccessKeySecret("your-as");
        config.setEndpoint("bailianmodelonchip.cn-beijing.aliyuncs.com");

        try {
            Client client = new Client(config);
            deviceRegister(client);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void deviceRegister(Client client) throws Exception {
        DeviceRegisterRequest request = new DeviceRegisterRequest();
        request.setNonce("Your Nonce");
        request.setRequestTime("1748312026868");
        request.setAppId("Your AppId");
        request.setSignature("Your Signature");

        DeviceRegisterResponse response = client.deviceRegister(request);
        DeviceRegisterResponseBody.DeviceRegisterResponseBodyData data = response.getBody().getData();
        
        //以json格式,透传data给设备端,厂商自行实现
        System.out.println("透传结果:" + JSON.toJSONString(data));
    }
}

Python示例

import sys
from typing import List

from Tea.exceptions import UnretryableException, TeaException
from alibabacloud_bailianmodelonchip20240816 import models as bailian_model_on_chip_20240816_models
from alibabacloud_bailianmodelonchip20240816.client import Client as BailianModelOnChip20240816Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_tea_util import models as util_models


class PopDeviceRegister:
    def __init__(self):
        pass

    @staticmethod
    def create_client() -> BailianModelOnChip20240816Client:
        config = open_api_models.Config(
            access_key_id='your-ak',
            access_key_secret='your-as'
        )

        config.endpoint = f'bailianmodelonchip.cn-beijing.aliyuncs.com'
        return BailianModelOnChip20240816Client(config)

    @staticmethod
    def main(
        args: List[str],
    ) -> None:
        client = PopDeviceRegister.create_client()
        device_register_request = bailian_model_on_chip_20240816_models.DeviceRegisterRequest(
            nonce='Your Nonce',
            request_time='1750313647162',
            app_id='Your AppId',
            signature='Your Signature'
        )
        headers = {}
        try:
            # 复制代码运行请自行打印 API 的返回值
            res = client.device_register_with_options(device_register_request, headers, util_models.RuntimeOptions())
            print('body', res.body)
            if res.body.success:
                print('data', res.body.data)
        except UnretryableException as e:
            # 网络异常
            print(e)
        except TeaException as e:
            # 业务异常
            print(e)
        except Exception as e:
            # 其他异常
            print(e)

if __name__ == '__main__':
    PopDeviceRegister.main(sys.argv[1:])
重要
  1. your-akyour-as可从阿里云官网控制台获取,具体请参见:链接

  2. endPoint:bailianmodelonchip.cn-beijing.aliyuncs.com

  3. DeviceRegisterResponseBodyData需要通过JSON.toJSONString转换成json格式,再下发给设备端,这部分逻辑需要厂商自行实现

2.2 获取访问业务交互令牌接口 getToken

设备完成注册之后,在使用多模态交互相关能力时需要动态获取访问令牌,可以通过该接口从百炼设备计量管理服务获取对应的令牌。采用阿里云POP网关鉴权,需集成POP SDK

接口入参:

GetTokenRequest

参数

类型

必填

描述

nonce

String

16字节随机数(32字符)

appId

String

应用标识,在百炼控制台创建应用之后会生成该ID

deviceName

String

从设备上传的设备唯一标识

requestTime

String

请求时间戳(ms)

signature

String

JSON加密信息,由设备端SDK生成,参考获取交互令牌接口

tokenType

String

请求令牌的类型(当前仅支持MMI类型)

MMI:多模态交互令牌

tokenKey

String

用来换取令牌的Key,需要根据不同的令牌类型传递不同的值(当前仅支持MMI类型)

MMI:传入百炼的API Key

接口出参

GetTokenResponseBody

参数

类型

必填

描述

code

String

错误码

httpStatusCode

Integer

http状态码

200 成功

其他 失败

message

String

错误信息

requestId

String

请求ID

success

Boolean

成功标志

data

GetTokenResponseBodyData

结果,可直接透传给设备端解析,参考获取交互令牌接口

GetTokenResponseBodyData

参数

类型

描述

nonce

String

16字节随机数(32字符)

responseTime

String

响应时间戳(ms)

appId

String

应用标识

deviceName

String

设备唯一标识

signature

String

token加密信息,设备端SDK可解析

Java示例

package pop;

import com.alibaba.fastjson.JSON;
import com.aliyun.bailianmodelonchip20240816.Client;
import com.aliyun.bailianmodelonchip20240816.models.*;
import com.aliyun.teaopenapi.models.Config;

public class TokenTest {

    public static void main(String[] args) {
        Config config = new Config();
        config.setAccessKeyId("your-ak");
        config.setAccessKeySecret("your-as");
        config.setEndpoint("bailianmodelonchip.cn-beijing.aliyuncs.com");

        try {
            Client client = new Client(config);
            getToken(client);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void getToken(Client client) throws Exception {
        GetTokenRequest request = new GetTokenRequest();
        request.setNonce("Your Nonce");
        request.setRequestTime("1748570866553");
        request.setAppId("Your AppId");
        request.setDeviceName("Your DeviceName");
        request.setTokenType("MMI");
        request.setSignature("Your Signature");

        GetTokenResponse response = client.getToken(request);
        GetTokenResponseBody.GetTokenResponseBodyData data = response.getBody().getData();
        
        //以json格式,透传data给设备端,厂商自行实现
        System.out.println("执行结果:" + JSON.toJSONString(data));
    }
}

Python示例

import sys
from typing import List

from Tea.exceptions import UnretryableException, TeaException
from alibabacloud_bailianmodelonchip20240816 import models as bailian_model_on_chip_20240816_models
from alibabacloud_bailianmodelonchip20240816.client import Client as BailianModelOnChip20240816Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_tea_util import models as util_models


class PopGetToken:
    def __init__(self):
        pass

    @staticmethod
    def create_client() -> BailianModelOnChip20240816Client:
        config = open_api_models.Config(
            access_key_id='your-ak',
            access_key_secret='your-as'
        )

        config.endpoint = f'bailianmodelonchip.cn-beijing.aliyuncs.com'
        return BailianModelOnChip20240816Client(config)

    @staticmethod
    def main(
        args: List[str],
    ) -> None:
        client = PopGetToken.create_client()
        get_token_request = bailian_model_on_chip_20240816_models.GetTokenRequest(
            nonce='Your Nonce',
            request_time='1750313823168',
            app_id='Your AppId',
            device_name='Your DeviceName',
            token_type='MMI',
            token_key='Your Token Key',
            signature='Your Signature'
        )
        headers = {}
        try:
            # 复制代码运行请自行打印 API 的返回值
            res = client.get_token_with_options(get_token_request, headers, util_models.RuntimeOptions())
            print('body', res.body)
            if res.body.success:
                print('data', res.body.data)
        except UnretryableException as e:
            # 网络异常
            print(e)
        except TeaException as e:
            # 业务异常
            print(e)
        except Exception as e:
            # 其他异常
            print(e)

if __name__ == '__main__':
    PopGetToken.main(sys.argv[1:])
重要
  1. your-akyour-as可从阿里云官网控制台获取,具体请参见:链接

  2. endPoint:bailianmodelonchip.cn-beijing.aliyuncs.com

  3. GetTokenResponseBodyData需要通过JSON.toJSONString转换成json格式,再下发给设备端,这部分逻辑需要厂商自行实现

2.3 云端错误码

错误码

错误说明

处理方法

100007

产品没有额度可激活的设备

购买额度

100008

设备已注册

检查设备唯一标识是否已注册,或修改设备唯一标识

100009

设备未注册

注册设备

100010

请求时间需要五分钟内

修改请求时间重新提交

100011

签名为空

检查请求签名数据

100012

设备唯一标识最长32个字符

检查设备唯一标识长度

100013

设备名称无效

设备名称包含非法字符

500001

加密数据和请求参数值不匹配

检查加密参数和请求参数值

500002

解密失败

检查加密数据

3. 设备端接口说明

3.1. SDK获取

不同芯片平台需要使用对应平台的toolchain进行编译,目前百炼已经支持的芯片清单,可直接下载SDK。

如果是新的芯片平台,请联系阿里云销售寻求技术支持

SDK目录结构

aliyun_sdk/
├── include/
│   └── ...
├── libaliyun_sdk.a
├── libhal.a
├── libsdk_test.a
├── libtinycrypt.a
└── libcjson.a
说明
  • include目录下包含使用SDK所需要的头文件,需要将该目录添加至工程头文件目录下。

  • libaliyun_sdk.a包含SDK核心代码,必须加载。

  • libhal.a包含dummy hal代码,在完成hal移植后可以删除该库。

  • libsdk_test.a包含测试代码,自动化测试过程需要加载该库,正式生产需要去除。

  • libtinycrypt.a包含加解密依赖接口,如果平台未集成该三方库则必须加载。

  • libcjson.a包含SDK相关依赖接口,如果平台未集成该三方库则必须加载。

3.2. 厂商完成HAL层开发

SDK中抽象了HAL层,需要各个厂商在自己的芯片平台上完成对应的开发

3.2.1. 内存

/**
 * util_malloc - 分配指定大小的内存块。
 * @size: 需要分配的内存大小,以字节为单位。
 *
 * 本函数通过调用标准库函数malloc来分配内存,目的是为了提供一个更健壮的内存分配方法。
 * 它可能包含了额外的错误检查或者内存管理策略,以提高程序的稳定性和性能。
 * 
 * 返回值: 返回指向所分配内存的指针,如果内存分配失败,则返回NULL。
 */
void * util_malloc(int32_t size);

/**
 * 释放动态分配的内存。
 * 
 * 本函数旨在释放之前通过动态分配获得的内存空间,以避免内存泄漏。
 * 它接受一个指向动态分配内存区域的指针,并将其设置为NULL,以防止悬挂指针的出现。
 * 
 * @param ptr 指向动态分配内存区域的指针。如果为NULL,函数将不执行任何操作。
 *            在释放内存后,此指针将被设置为NULL。
 */
void util_free(void *ptr);
警告

内存数模块为阿里提供的SDK所依赖的模块,需要各厂商在自己的硬件平台上实现

3.2.2. 随机数模块

/**
 * 初始化随机数生成器
 * 
 * @param seed 用于初始化随机数生成器的种子值
 * 
 * @return 返回初始化结果,0表示成功,非0表示失败
 * 
 * 此函数通过对随机数生成器进行初始化,以确保后续生成的随机数序列具有良好的随机性
 * 种子值的选择对生成的随机数序列有重要影响,相同的种子值会生成相同的随机数序列
 */
int32_t util_random_init(uint32_t seed);

/**
 * 生成一个随机数
 * 
 * @return 返回生成的随机数
 * 
 * 在调用此函数之前,应确保随机数生成器已经通过util_random_init函数成功初始化
 * 此函数生成的随机数是基于初始化时提供的种子值产生的
 */
uint32_t util_random(void);
警告

随机数模块为阿里提供的SDK所依赖的模块,需要各厂商在自己的硬件平台上实现

3.2.3. 存储模块

/**
 * @brief擦除存储器
 * 
 * 该函数用于擦除存储器中的所有数据。在调用此函数之前,应确保不再需要存储器中的任何信息,
 * 因为擦除操作将删除所有数据,且此操作不可逆。
 * 
 * @return int32_t 返回擦除操作的结果。如果返回值为0,表示擦除成功;如果返回值非0,表示擦除过程中出现错误。
 */
int32_t util_storage_erase(void);

/**
 * @brief存储数据到存储器
 * 
 * 该函数将指定的数据存储到存储器中。在调用此函数之前,应确保数据的正确性和完整性,
 * 因为存储操作将覆盖存储器中的现有数据。
 * 
 * @param data 指向要存储的数据的指针。数据类型为uint8_t,即无符号的8位整数。
 * @param size 要存储的数据的大小,以字节为单位。数据类型为uint32_t,即无符号的32位整数。
 * @return int32_t 返回存储操作的结果。如果返回值为0,表示存储成功;如果返回值非0,表示存储过程中出现错误。
 */
int32_t util_storage_storage(uint8_t *data, uint32_t size);

/**
 * @brief从存储器加载数据
 * 
 * 该函数从存储器中加载指定大小的数据。在调用此函数之前,应确保提供的数据指针指向的内存区域足够大,
 * 以容纳从存储器加载的数据。
 * 
 * @param data 指向用于存储从存储器加载的数据的缓冲区的指针。数据类型为uint8_t,即无符号的8位整数。
 * @param size 要加载的数据的大小,以字节为单位。数据类型为uint32_t,即无符号的32位整数。
 * @return int32_t 返回加载操作的结果。如果返回值为0,表示加载成功;如果返回值非0,表示加载过程中出现错误。
 */
int32_t util_storage_load(uint8_t *data, uint32_t size);
警告

存储模块为阿里提供的SDK所依赖的模块,需要各厂商在自己的硬件平台上实现

3.2.4. 时间模块

/**
 * 获取当前时间戳(毫秒级)
 * 
 * 此函数用于获取当前的时间戳,精确到毫秒该时间戳通常用于计算时间差、
 * 记录事件发生时间等场景
 * 
 * 返回:当前时间戳(毫秒级)
 */
int64_t util_now_ms(void);

/**
 * 毫秒级睡眠函数
 * 
 * 此函数使当前线程暂停执行指定毫秒数,用于控制程序执行节奏、等待事件发生等
 * 
 * 参数 ms:需要暂停的毫秒数
 */
void util_msleep(uint32_t ms);

/**
 * 获取当前时间戳
 * 
 * 此函数用于获取当前的时间戳,即从1970年1月1日00:00:00 UTC开始到现在的毫秒数
 * 它没有输入参数,返回一个int64_t类型的值,代表当前的时间戳
 * 
 * @return int64_t 当前时间戳,单位为毫秒
 */
int64_t util_get_timestamp(void);

/**
 * 检查时间戳功能是否已初始化
 * 
 * 此函数用于检查时间戳相关功能是否已经初始化如果返回真(非零),则表示
 * 时间戳功能可用;如果返回假(零),则可能需要进行初始化操作或者避免使用时间戳功能
 * 
 * 返回:如果时间戳功能已初始化,则返回非零,否则返回零
 */
uint8_t util_timestamp_inited(void);
警告

时间模块为阿里提供的SDK所依赖的模块,需要各厂商在自己的硬件平台上实现

3.2.5. 互斥锁模块

/* 互斥锁结构体定义 */
typedef struct _util_mutex_t {
    void *mutex_handle; /* 互斥锁句柄,具体实现依赖于平台 */
} util_mutex_t;

/*****************************************************
 * Function: util_mutex_create
 * Description: 创建一个互斥锁对象。
 * Parameter: 无。
 * Return: util_mutex_t * --- 返回指向互斥锁结构体的指针。
 ****************************************************/
util_mutex_t * util_mutex_create(void);

/*****************************************************
 * Function: util_mutex_delete
 * Description: 删除指定的互斥锁对象。
 * Parameter:
 *     mutex --- 指向互斥锁结构体的指针。
 * Return: 无。
 ****************************************************/
void util_mutex_delete(util_mutex_t *mutex);

/*****************************************************
 * Function: util_mutex_lock
 * Description: 对指定的互斥锁进行加锁操作,带超时机制。
 * Parameter:
 *     mutex --- 指向互斥锁结构体的指针。
 *     timeout --- 加锁等待超时的时间,单位为毫秒(ms),可设为 MUTEX_WAIT_FOREVER 表示无限等待。
 * Return: int32_t --- 返回操作结果(util_result_t)。
 ****************************************************/
int32_t util_mutex_lock(util_mutex_t *mutex, int32_t timeout);

/*****************************************************
 * Function: util_mutex_unlock
 * Description: 对指定的互斥锁进行解锁操作。
 * Parameter:
 *     mutex --- 指向互斥锁结构体的指针。
 * Return: int32_t --- 返回操作结果(util_result_t)。
 ****************************************************/
int32_t util_mutex_unlock(util_mutex_t *mutex);
警告

互斥锁模块为阿里提供的SDK所依赖的模块,需要各厂商在自己的硬件平台上实现

3.2.6. HAL层移植验收标准

在实现以上各模块之后,再加载libsdk_test.a,在主程序中直接调用接口aliyun_sdk_test()接口,就会对以上工作模块进行测试,输出日志可查看测试结果。

将输出日志反馈给阿里云进行确认。

测试成功的输出日志示例:

image

3.3. 设备端业务逻辑对接

20250611152221

说明

对应的模块可参考图中标记的示例代码1-示例代码5,在详细接口说明中找到对应的示例代码

3.3.1. 初始化接口

/**
 * 初始化阿里云SDK。
 * 
 * 返回: 初始化结果,0表示成功,非0表示失败。
 */
int32_t c_mmi_sdk_init(void);

/**
 * @brief 禁用许可证
 *
 * 此函数用于将付费模式从预付费切换为后付费模式
 * 
 * @return int32_t 返回操作结果,0表示成功,非0表示失败
 */
int32_t c_mmi_license_disable(void);

/**
 * @brief 配置MMI模块参数
 *
 * 此函数用于初始化MMI模块的各项配置参数,包括事件回调、工作模式、文本模式、
 * 音色设置、音频流模式以及缓冲区大小等
 * 
 * @param config 指向mmi_user_config_t结构体的指针,包含配置参数
 * @return int32_t 返回操作结果,0表示成功,非0表示失败
 */
int32_t c_mmi_config(mmi_user_config_t *config);
说明

SDK兼容后付费模式,可以在SDK初始化的时候调用c_mmi_license_disable接口进行切换

3.3.2. 产品信息预置接口

/**
 * 检查设备是否已注册
 * 
 * @return 返回设备注册状态,1表示已注册,0表示未注册或发生错误
 * 
 * 此函数用于检查设备是否已完成注册流程
 */
uint8_t c_storage_device_is_registered(void);

/**
 * 保存配置
 * 
 * @return 返回操作结果,非零表示失败
 * 
 * 此函数用于保存之前设置的配置信息只有当调用此函数后,所设置的配置信息才会被实际写入存储
 */
int32_t c_storage_save(void);

/**
 * 重置配置
 * 
 * 此函数用于清除所有已保存的设备配置信息
 * 调用此函数后,配置将恢复为默认状态,需要重新设置相关参数
 * 
 * @return 返回操作结果,非零表示失败
 */
int32_t c_storage_reset(void);

/**
 * 设置应用ID
 * 
 * @param app_id_str 应用ID,由阿里云颁发,字符串格式
 * @return 返回操作结果,非零表示失败
 * 
 * 此函数用于设置应用ID,完成设置后需要调用c_storage_save进行保存
 */
int32_t c_storage_set_app_id_str(char *app_id_str);

/**
 * 设置应用密钥appSecret
 * 
 * @param app_secret_str 应用密钥,由阿里云颁发,字符串格式
 * @return 返回操作结果,非零表示失败
 * 
 * 此函数用于设置应用密钥,完成设置后需要调用c_storage_save进行保存
 */
int32_t c_storage_set_app_secret_str(char *app_secret_str);

/**
 * 设置设备名称DN
 * 
 * @param dn 设备名称DN,用户可自行设定,长度不超过32字符
 * @return 返回操作结果,非零表示失败
 * 
 * 此函数用于设置设备名称DN,完成设置后需要调用c_storage_save进行保存
 */
int32_t c_storage_set_device_name(char *device_name);

示例代码1

#include "lib_c_storage.h"

// 初始化SDK
c_mmi_sdk_init();
// 如果采用后付费模式,需要添加c_mmi_license_disable
c_mmi_license_disable();

if (c_storage_device_is_registered() == 0) {
    // 清空存储信息
    c_storage_reset();

    // 预置AppId
    c_storage_set_app_id_str("Your AppId");
    // 预置AppSecret
    c_storage_set_app_secret_str("Your AppSecret");
    // 预置DeviceName
    c_storage_set_device_name("Your DeviceName");
    
    // 存储设备信息
    c_storage_save();
}

// 初始化音频配置
mmi_user_config_t mmi_config = {
  .evt_cb = _mmi_event_callback, // 注册事件回调函数,详细说明见下文

  .work_mode = C_MMI_MODE_PUSH2TALK,
  .text_mode = C_MMI_TEXT_MODE_BOTH,
  .voice_id = "longxiaochun_v2",

  .upstream_mode = C_MMI_STREAM_MODE_PCM,
  .downstream_mode = C_MMI_STREAM_MODE_PCM,

  .recorder_rb_size = 8 * 1024,
  .player_rb_size = 8 * 1024,
};
c_mmi_config(&mmi_config);

3.3.3. 设备注册接口

/**
 * 生成设备注册请求所需的字符串。
 * 
 * 参数 req_str: 用于存储生成的设备注册请求字符串的缓冲区。
 * 参数 time_ms_str: 时间戳字符串,单位为毫秒
 * 
 * 返回: 生成结果,0表示成功,非0表示失败。
 */
int32_t c_device_gen_register_req(char req_str[C_SDK_REQ_LEN_REGISTER], char *time_ms_str);

/**
 * 解析云端的设备注册响应信息。
 * 
 * 参数 rsp_str: 指向设备注册响应字符串的指针。
 * 
 * 返回: 分析结果,0表示成功,非0表示失败。
 */
int32_t c_device_analyze_register_rsp(char *rsp_str);

示例代码2

#include "lib_c_sdk.h"

char req[C_SDK_REQ_LEN_REGISTER];

if (c_storage_device_is_registered() == 0) {
    // 根据服务端下发时间戳timestamp,生成注册信息字串req
    c_device_gen_register_req(req, timestamp);
    // 通过客户自己的服务端间接获取设备注册信息,http接口需客户自行实现
    char *rsp = dummy_http_request(req);
    // 解析并完成注册
    int32_t err = c_device_analyze_register_rsp(rsp);
    if (err) {
        return err;
    }
}
说明

关于示例代码中dummy_http_request接口

  • 半托管模式下,该dummy_http_request则应访问客户已有的设备管理服务端提供的http接口,来间接完成设备端注册

  • 全托管模式下,该dummy_http_request则应访问阿里云提供的设备管理服务接口,该接口需要联系阿里云商务进行加白后使用

设备端SDK生成的请求数据包示例

{
    "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>"
}
   
说明
  1. http_request需客户自行实现,负责从客户云端接收返回的数据

  2. 最终传递给c_device_analyze_register_rsp的时候,需要保持rsp示例数据相同的格式

3.3.4. 获取交互令牌接口

/**
 * @brief 检查mmi登录状态。
 *
 * 返回值表示当前mmi是否已完成登录流程,1为已登录,0为未登录。
 * 
 * @return uint8_t 
 *         返回1表示已登录,返回0表示未登录。
 */
uint8_t c_mmi_is_token_expire(void);

/**
 * @brief 生成mmi登录请求数据。
 *
 * 此函数根据提供的当前时间戳生成mmi登录请求字符串,
 * 
 *
 * @param req_str 存储生成的登录请求字符串的缓冲区
 * @param time_ms_str 时间戳字符串,单位为毫秒
 * @return int32_t 
 *         成功返回0,失败返回非零的错误码
 */
int32_t c_mmi_gen_get_token_req(char req_str[C_SDK_REQ_LEN_GET_TOKEN], char *time_ms_str);

/**
 * @brief 解析mmi登录响应数据。
 *
 * 解析登录响应字符串,并触发事件:
 * - C_MMI_EVENT_USER_CONFIG: 用户配置初始化
 * - C_MMI_EVENT_DATA_INIT: 数据初始化完成
 *
 * @param rsp_str 包含mmi登录响应的字符串
 * @return int32_t 
 *         成功返回0,失败返回非零的错误码
 */
int32_t c_mmi_analyze_get_token_rsp(char *rsp_str);

示例代码3

#include "lib_c_sdk.h"

char req[C_SDK_REQ_LEN_LOGIN];

// 根据服务端下发时间戳timestamp,生成注册信息字串req
c_mmi_gen_get_token_req(req, timestamp);
// 获取服务端返回设备信息,该接口需客户自行实现
char *rsp = dummy_http_request(req);
// 解析交互令牌
int32_t err = c_mmi_analyze_get_token_rsp(rsp);
if (err) {
    return err;
}
说明

关于示例代码中dummy_http_request接口

  • 半托管模式下,该dummy_http_request则应访问客户已有的设备管理服务端提供的http接口,来间接获取token

  • 全托管模式下,该dummy_http_request则应访问阿里云提供的获取令牌接口,该接口需要联系阿里云商务进行加白后使用

设备端SDK生成的请求数据包示例

{
    "appId":"<YOUR APP ID>",
    "deviceName":"<YOUR DEVICE NAME>",
    "payMode":"LICENSE",
    "requestTime":"1753327457730",
    "sdkVersion":"0.3.2",
    "tokenType":"MMI",
    "signature": "<YOUR SIGNATURE>"
}

云端补充完tokenKey字段后,再将请求发给阿里云Pop接口

{
    "appId":"<YOUR APP ID>",
    "deviceName":"<YOUR DEVICE NAME>",
    "payMode":"LICENSE",
    "requestTime":"1753327457730",
    "sdkVersion":"0.3.2",
    "tokenType":"MMI",
    "signature": "<YOUR SIGNATURE>",
    "tokenKey": "<YOUR TOKEN KEY>"
}
重要

当设备将SDK生成数据传输至云端后,在云端需要添加对应的tokenKey字段后,才能访问阿里云提供的接口获取令牌,设备端尽量不存储TokenKey,避免泄露

设备端收到云端透传的数据包示例

{
    "nonce": "<YOUR NONCE>",
    "responseTime": "1753327458081",
    "appId": "<YOUR APP ID>",
    "deviceName": "<YOUR DEVICE NAME>",
    "requestIp": "<YOUR IP>",
    "signature": "<YOUR SIGNATURE>"
}
说明
  1. http_request需客户自行实现,负责从客户云端接收返回的数据

  2. 最终传递给c_mmi_analyze_rsp的时候,需要保持rsp示例数据相同的格式

3.3.5. 建立业务连接

/**
 * @brief 获取WSS服务器主机名字符串。
 *
 * 此函数返回配置的WSS服务器主机名字符串。
 * 
 * @return char* 
 *         返回指向主机名字符串的指针,若未配置则返回NULL。
 */
char *c_mmi_get_wss_host(void);

/**
 * @brief 获取WSS服务器端口字符串。
 *
 * 此函数返回配置的WSS服务器端口字符串。
 * 
 * @return char* 
 *         返回指向端口字符串的指针,若未配置则返回NULL。
 */
char *c_mmi_get_wss_port(void);

/**
 * @brief 获取WSS服务API路径字符串。
 *
 * 此函数返回配置的WSS服务API路径字符串。
 * 
 * @return char* 
 *         返回指向API路径字符串的指针,若未配置则返回NULL。
 */
char *c_mmi_get_wss_api(void);

/**
 * @brief 获取WSS请求头信息字符串。
 *
 * 此函数返回配置的WSS请求头信息字符串。
 * 
 * @return char* 
 *         返回指向请求头字符串的指针,若未配置则返回NULL。
 */
char *c_mmi_get_wss_header(void);

示例代码4

#include "lib_c_sdk.h"

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连接,dummy_wss_connect接口需客户自行实现
WSS_HANDLE *wss = dummy_wss_connect(wss_host, wss_port, wss_api, wss_header);
重要

本 SDK 与云端的 websocket 通信需要建立 TLS隧道,用户需自行做如下配置:

  • TLS 版本要求TLS1.2 或以上

  • 开启SNI(SERVER NAME INDICATION)

  • 配置CA证书(GlobalSign Root CA - R3)

  • 务必通过这三个接口获取websocket建联所需要的端口,API,和header

  • wss_connect、wss_register_recv_func、wss_register_send_func、wss_send等相关的接口可复用各模商或芯片已有方案,需客户自行实现

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

3.3.7. 事件回调

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,        // 当音频完成下行时触发此事件
};


/**
 * @brief mmi事件回调函数类型
 *
 * 当mmi模块发生状态变化或事件时触发的回调函数
 * 
 * @param event 事件类型,取值为C_MMI_EVENT_xxx系列宏定义
 * @param param 事件参数,根据事件类型不同指向不同数据结构
 * @return int32_t 返回0表示处理成功,非0表示处理失败
 */
typedef int32_t(*c_mmi_event_callback)(uint32_t event, void *param);
说明

该回调接口的注册是在初始化阶段c_mmi_config配置的时候注册的

3.3.8. 多模态数据交互

/**
 * @brief 向录音缓冲区写入数据
 *
 * 当音频模式为C_mmi_AUDIO_MODE_RINGBUFFER时使用
 * 
 * @param data 待写入的数据指针
 * @param size 数据长度(字节)
 * @return uint32_t 实际写入的字节数
 */
uint32_t c_mmi_put_recorder_data(uint8_t *data, uint32_t size);

/**
 * @brief 从播放缓冲区读取数据
 *
 * 当音频模式为C_mmi_AUDIO_MODE_RINGBUFFER时使用
 * 
 * @param data 用于存储读取数据的缓冲区
 * @param size 可用缓冲区大小(字节)
 * @return uint32_t 实际读取的字节数
 */
uint32_t c_mmi_get_player_data(uint8_t *data, uint32_t size);


/**
 * 获取需要通过websocket发送的数据
 * 
 * 本函数用于根据指定的类型获取数据,准备通过websocket进行发送
 * 它会根据传入的类型参数,将相应类型的数据填充到提供的数据缓冲区中
 * 
 * @param type 用于返回websocket数据类型,如:WS_DATA_TYPE_TEXT、WS_DATA_TYPE_BINARY
 * @param data 指向一个uint8_t数组的指针,该数组用于存储获取的数据
 * @param size 表示数据数组的最大容量,以字节为单位
 * 
 * @return 返回实际填充到数据数组中的字节数
 */
uint32_t c_mmi_get_send_data(uint8_t *type, uint8_t *data, uint32_t size);

/**
 * 分析接收到的websocket数据函数
 * 
 * 此函数根据提供的数据类型和数据内容,分析接收到的数据包
 * 它的主要作用是解析数据内容,以便进一步处理或使用
 * 
 * @param type websocket数据类型,如:WS_DATA_TYPE_TEXT、WS_DATA_TYPE_BINARY
 * @param data 指向接收到的数据的指针,数据的内容将根据type参数进行解析
 * @param size 数据的长度,以字节为单位,用于确定数据的范围
 * 
 * @return 返回实际解析的字节数
 */
uint32_t c_mmi_analyze_recv_data(uint8_t type, uint8_t *data, uint32_t size);

示例代码5

int dummy_wss_task_recv(void)
{
    int opcode;
    char payload_data[8 * 1024];
    int recv_size;

    while(1){
        // 通过websocket接收服务端下行数据
        dummy_wss_recv(&opcode, payload_data, &recv_size);
        if(recv_size)
            // 将接收服务端下行数据送入SDK进行解析
            c_mmi_analyze_recv_data(opcode, payload_data, recv_size);
        else
            util_msleep(10);
    }
    return 0;
}

int dummy_wss_task_send(void)
{
    uint8_t opcode;
    uint8_t payload_data[8 * 1024];
    uint32_t size;

    while(1){
        // 从SDK获取打包好的payload数据
        size = c_mmi_get_send_data(&opcode, payload_data, sizeof(payload_data));
        if (size == 0) {
            util_msleep(10);
        } else {
            // 将payload数据传入websocket发送函数,自行打包帧头进行发送
            dummy_wss_send(opcode, data, size);
        }
    }
    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;
}
说明

c_mmi_analyze_recv_data接口中会在SDK内部触发各种事件回调,回调到_mmi_event_callback中,再由用户自己处理