杰理 AC792N 使用 WebSocket 连接百度语音大模型实现 AI 对话功能
前言
本文详细介绍了如何基于杰理 AC792N SDK 接入百度智能云语音大模型,实现类似"小度 AI"的实时语音对话功能。全文涵盖从账号开通、接口对接、数据流设计到代码实现的完整流程,方便开发者快速复现和二次开发。
适用场景: 智能音箱、语音助手、车载语音交互、智能家居控制等。
1. 开通百度智能云服务
1.1 注册与订阅
首次开通可获赠 500 万 Token,足够用于功能验证和初期测试。
操作步骤:
- 注册账号访问百度智能云官网:https://cloud.baidu.com/完成账号注册并登录
- 订阅服务 在服务列表中找到"端到端语音大模型服务"并点击订阅


- 创建服务实例 完成实名认证后,点击"快速接入服务",选择"大模型实时语音交互",创建服务实例

- 应用授权 在服务列表中对应用进行授权

- 获取密钥 进入应用列表,找到并记录 API Key 与 Secret Key ,这两个密钥后续代码中会用到

- 查阅技术文档
进入技术文档中心,可以查看详细的接口说明和官方示例代码

1.2 音频规格要求(实测)
根据实际测试,百度语音大模型的音频格式要求如下:
- 上行音频(设备→百度): 16kHz / 16bit / 单声道 PCM,Base64 编码
- 下行音频(百度→设备): 24kHz / 16bit / 单声道 PCM,Base64 编码
注意: 上下行采样率不同,需要在代码中做相应处理。
2. AC792N 与百度语音大模型交互
2.1 数据流设计
为了实现实时语音交互,我们采用麦克风音效数据流来采集语音并上传到百度服务器:
数据流路径:
麦克风 → ADC(16kHz) → EffectDev0 节点 → WebSocket 上传
↓
混音播放 ← 百度返回音频
核心流程:
- ADC 采样率设置为 16kHz
- 数据进入
EffectDev0节点进行处理 - 在
EffectDev0内部:- 将 PCM 音频数据上传到百度服务器
- 接收百度返回的语音数据并混入播放链路

2.2 WebSocket 连接实现
SDK 提供了 WebSocket 客户端示例,路径为:
apps/common/example/network_protocols/websocket/main.c
我们需要在此基础上修改,将连接目标改为百度的 WebSocket 服务器。
2.3 获取 Access Token
百度接口采用 OAuth 2.0 认证方式,需要先通过 API Key 和 Secret Key 获取 access_token。
实现代码示例:
c
// 通过 HTTP 请求获取百度 access_token
static int http_get_baidu_token(char *token_out, int token_size)
{
int ret = FALSE;
http_body_obj http_body_buf;
httpcli_ctx *ctx = (httpcli_ctx *)calloc(1, sizeof(httpcli_ctx));
if (!ctx) {
printf("Failed to allocate httpcli_ctx\n");
return FALSE;
}
memset(&http_body_buf, 0x0, sizeof(http_body_obj));
http_body_buf.recv_len = 0;
http_body_buf.buf_len = 2 * 1024;
http_body_buf.buf_count = 1;
http_body_buf.p = (char *)malloc(http_body_buf.buf_len * http_body_buf.buf_count);
if (!http_body_buf.p) {
free(ctx);
return FALSE;
}
// 构建 Token 请求 URL
snprintf(token_url, sizeof(token_url),
"https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s",
BAIDU_API_KEY, BAIDU_SECRET_KEY);
ctx->url = token_url;
ctx->timeout_millsec = 10000;
ctx->priv = &http_body_buf;
ctx->connection = "close";
// 发送 POST 请求
if (httpcli_post(ctx) == HERROR_OK) {
if (http_body_buf.recv_len > 0) {
// 解析返回的 JSON,提取 access_token
if (parse_access_token(http_body_buf.p, token_out, token_size)) {
ret = TRUE;
}
}
}
// 清理资源
httpcli_close(ctx);
if (http_body_buf.p) {
free(http_body_buf.p);
}
if (ctx) {
free(ctx);
}
return ret;
}
2.4 构建 WebSocket 连接 URL
获取到 access_token 后,构建百度语音大模型的 WebSocket 连接 URL:
c
// 构建 WebSocket URL
snprintf(baidu_ws_url, sizeof(baidu_ws_url),
"wss://aip.baidubce.com/ws/2.0/speech/v1/realtime?model=%s&access_token=%s",
BAIDU_MODEL_NAME, access_token);
参数说明:
BAIDU_MODEL_NAME:模型名称(根据订阅的服务选择)access_token:前面获取的访问令牌
将 baidu_ws_url 传入 websockets_client_init() 接口的 url 参数即可。
完成配置后,直接运行 baidu_websockets_client_main_thread() 即可建立连接。
3. 数据交互格式
百度语音大模型采用 JSON 格式进行数据交互,以下是主要的消息类型。
3.1 发送到百度(Request)
1) 更新会话参数
连接建立后,首先发送此消息初始化会话:
json
{
"type": "session.update",
"session": {
"input_audio_transcription": {
"model": "default"
}
}
}
2) 上传音频数据
实时上传 Base64 编码的 PCM 音频数据:
json
{
"type": "input_audio_buffer.append",
"audio": "BASE64_PCM_DATA"
}
提示: 音频数据需要先进行 Base64 编码后再发送。
3) 音频结束标记
用户停止说话后,发送此消息表示本轮输入结束:
json
{
"type": "input_audio_buffer.commit"
}
3.2 从百度接收(Response)
1) 音频数据
AI 返回的语音数据(Base64 编码的 PCM):
json
{
"type": "response.audio.delta",
"delta": "BASE64_PCM_DATA"
}
接收后需要进行 Base64 解码,然后送入音频播放链路。
2) 实时转写
AI 实时识别用户语音的文本结果(分段返回):
json
{
"type": "response.audio_transcript.delta",
"delta": "你好"
}
3) 转写完成
完整的语音识别文本:
json
{
"type": "response.audio_transcript.done",
"transcript": "你好,我是百度语音助手。"
}
4) 会话状态
系统状态通知:
json
{ "type": "input_audio_buffer.speech_started" } // 检测到语音输入
{ "type": "response.done" } // 响应完成
5) 错误信息
当出现错误时返回:
json
{
"type": "error",
"code": 1001,
"message": "Access token invalid"
}
常见错误码:
1001:Token 无效或过期1002:音频格式不支持1003:服务配额不足
4. 完整交互流程
4.1 准备工作
在代码中配置你的百度 API 密钥:
c
#define BAIDU_API_KEY "your_api_key_here"
#define BAIDU_SECRET_KEY "your_secret_key_here"
重要: 请妥善保管密钥,不要将其提交到公开的代码仓库。
4.2 交互时序图
完整的语音对话流程如下:
1. 获取 access_token
↓
2. 建立 WebSocket 连接
↓
3. 发送 session.update 初始化会话
↓
4. 用户开始说话
├─ PCM 音频 → Base64 编码
└─ 循环发送 input_audio_buffer.append
↓
5. 服务器实时返回
├─ response.audio_transcript.delta (实时转写)
├─ response.audio.delta (实时语音)
└─ input_audio_buffer.speech_started
↓
6. 用户停止说话
└─ 发送 input_audio_buffer.commit
↓
7. 服务器完成响应
├─ response.audio_transcript.done (完整文本)
├─ response.done (响应完成)
└─ 如有错误返回 error
4.3 关键代码接口
-
数据接收回调 所有从百度服务器返回的数据在
baidu_websockets_callback中解析处理 -
音频上传接口
在
EffectDev0节点中调用以下函数上传音频:cbaidu_websocket_audio_data_write(s16 *data, int len)data:PCM 音频数据指针(16bit 有符号整数数组)len:数据长度(采样点数)
5. 关键代码文件位置
为了方便开发者快速定位和修改,这里列出项目中的关键文件:
| 功能模块 | 文件路径 |
|---|---|
| WebSocket 连接与协议处理 | apps/common/example/network_protocols/websocket/main.c |
| 音频数据流处理 | audio/framework/nodes/effect_dev0_node.c |
| HTTP 客户端(获取 Token) | SDK 内置 HTTP 客户端库 |
建议修改顺序:
- 先在
main.c中实现 WebSocket 连接和消息收发 - 再在
effect_dev0_node.c中集成音频上传和混音播放 - 最后进行端到端联调测试
5.1 apps/common/example/network_protocols/websocket/main.c源码
c
#include "web_socket/websocket_api.h"
#include "wifi/wifi_connect.h"
#include "system/includes.h"
#include "http/http_cli.h"
#ifdef WEBSOCKET_MEMHEAP_DEBUG
#include "mem_leak_test.h"
#endif
#include "app_config.h"
#define USE_WEBSOCKET_TEST
#ifdef USE_WEBSOCKET_TEST
/**** websoket "ws://" websokets "wss://" *****/
// 百度大模型配置
#define BAIDU_API_KEY "you api key"
#define BAIDU_SECRET_KEY "you secret key"
#define BAIDU_MODEL_NAME "audio-realtime"
// 百度大模型 WebSocket URL (需要先获取 access_token)
static char baidu_ws_url[512] = {0};
static char access_token[512] = {0};
/*******************************************************************************
* 回调函数类型定义
*******************************************************************************/
// 文本回调:每次接收到指定长度的文本数据时调用
typedef void (*baidu_text_callback_t)(const char *text, int len, void *user_data);
// 音频回调:每次接收到指定长度的音频数据时调用
typedef void (*baidu_audio_callback_t)(const u8 *audio, int len, void *user_data);
/*******************************************************************************
* 全局回调配置
*******************************************************************************/
static baidu_text_callback_t g_text_callback = NULL;
static void *g_text_callback_user_data = NULL;
static int g_text_chunk_size = 0; // 期望的文本块大小(字节)
static baidu_audio_callback_t g_audio_callback = NULL;
static void *g_audio_callback_user_data = NULL;
static int g_audio_chunk_size = 0; // 期望的音频块大小(字节)
// 全局 WebSocket 上下文指针,用于音频数据发送
static struct websocket_struct *g_baidu_websockets_info = NULL;
static OS_MUTEX g_baidu_ws_mutex;
static u8 g_ws_write_enabled = 0;
// 全局静态缓冲区,避免栈溢出
static char g_audio_base64[1024];
static char g_json_msg[2048];
// 发送统计和流控
static u32 g_send_error_count = 0;
// 构建请求 URL
static char token_url[512];
const char *origin_str = "http://coolaf.com";
// Base64 编码表
static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char base64_decode_table[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
static int websockets_client_send(struct websocket_struct *websockets_info, u8 *buf, int len, char type);
// Base64 编码函数(重写,更安全)
static int base64_encode(const u8 *input, int input_len, char *output, int output_size)
{
if (!input || !output || input_len <= 0 || output_size <= 0) {
return -1;
}
int i = 0;
int j = 0;
int output_len = 0;
u8 char_array_3[3];
u8 char_array_4[4];
// 计算需要的输出缓冲区大小
int required_size = ((input_len + 2) / 3) * 4 + 1; // +1 for null terminator
if (required_size > output_size) {
printf("base64_encode: output buffer too small (need %d, have %d)\n", required_size, output_size);
return -1;
}
int input_pos = 0;
while (input_pos < input_len) {
// 读取最多3个字节
for (i = 0; i < 3 && input_pos < input_len; i++) {
char_array_3[i] = input[input_pos++];
}
if (i > 0) {
// 填充剩余字节为0
for (j = i; j < 3; j++) {
char_array_3[j] = 0;
}
// 转换为4个6位的值
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
// 输出base64字符
for (j = 0; j < (i + 1); j++) {
output[output_len++] = base64_chars[char_array_4[j]];
}
// 添加填充字符
while (i++ < 3) {
output[output_len++] = '=';
}
}
}
output[output_len] = '\0';
return output_len;
}
/*******************************************************************************
* 注册文本回调
* chunk_size: 期望每次回调传出的文本长度(字节),0表示立即传出
* callback: 回调函数
* user_data: 用户自定义数据
*******************************************************************************/
void baidu_register_text_callback(int chunk_size, baidu_text_callback_t callback, void *user_data)
{
g_text_chunk_size = chunk_size;
g_text_callback = callback;
g_text_callback_user_data = user_data;
}
/*******************************************************************************
* 注册音频回调
* chunk_size: 期望每次回调传出的音频长度(字节),0表示立即传出
* callback: 回调函数
* user_data: 用户自定义数据
*******************************************************************************/
void baidu_register_audio_callback(int chunk_size, baidu_audio_callback_t callback, void *user_data)
{
g_audio_chunk_size = chunk_size;
g_audio_callback = callback;
g_audio_callback_user_data = user_data;
}
/*******************************************************************************
* 查找 JSON 字段的值(返回指针和长度,不复制数据)
* 更精确的匹配:确保字段名前后有引号和冒号
* 返回:找到返回字段值的起始指针,field_len 返回长度;未找到返回 NULL
*******************************************************************************/
static const char *find_json_string_field(const char *json, const char *field_name, int *field_len)
{
*field_len = 0;
// 构建完整的搜索模式:"field_name":"
char search_pattern[128];
int pattern_len = snprintf(search_pattern, sizeof(search_pattern), "\"%s\":\"", field_name);
if (pattern_len >= sizeof(search_pattern)) {
return NULL; // 字段名太长
}
// 查找匹配的模式
const char *field_start = strstr(json, search_pattern);
if (!field_start) {
return NULL; // 未找到字段
}
// 跳过 "field_name":" 部分,指向值的起始位置
const char *value_start = field_start + pattern_len;
// 查找结束引号(需要处理转义字符)
const char *value_end = value_start;
while (*value_end && *value_end != '"') {
if (*value_end == '\\' && *(value_end + 1)) {
value_end += 2; // 跳过转义字符
} else {
value_end++;
}
}
if (*value_end != '"') {
return NULL; // 没有找到结束引号
}
*field_len = value_end - value_start;
return value_start;
}
/*******************************************************************************
* 解析并处理文本转录数据(直接在原始数据上操作,边解析边回调)
*******************************************************************************/
static void process_text_transcript(const char *json_msg)
{
if (!g_text_callback) {
return; // 没有注册回调
}
// 查找 delta 或 transcript 字段
int text_len = 0;
const char *text_data = find_json_string_field(json_msg, "delta", &text_len);
if (!text_data || text_len == 0) {
text_data = find_json_string_field(json_msg, "transcript", &text_len);
}
if (!text_data || text_len == 0) {
return; // 没有文本数据
}
// 如果 chunk_size 为 0,直接回调整个字符串
if (g_text_chunk_size == 0) {
g_text_callback(text_data, text_len, g_text_callback_user_data);
return;
}
// 按照 chunk_size 分段回调
int offset = 0;
while (offset < text_len) {
int chunk_len = (text_len - offset) > g_text_chunk_size ? g_text_chunk_size : (text_len - offset);
g_text_callback(text_data + offset, chunk_len, g_text_callback_user_data);
offset += chunk_len;
}
}
// 使用全局静态缓冲区(只用于累积到 chunk_size)
static u8 decode_buffer[1024]; // 固定大小,用于累积
static int decode_buffer_len = 0;
/*******************************************************************************
* Base64 解码(边解码边回调,不使用临时缓冲区)
*******************************************************************************/
static void base64_decode_and_callback(const char *input, int input_len, int chunk_size,
baidu_audio_callback_t callback, void *user_data)
{
int buffer_len = decode_buffer_len;
int i = 0;
u8 char_array_4[4], char_array_3[3];
int j = 0;
while (i < input_len && input[i] != '=') {
char c = input[i];
// 处理 JSON 转义(例如 \\n),避免把 'n' 当作有效 Base64 字符
if (c == '\\' && (i + 1) < input_len) {
char esc = input[i + 1];
if (esc == 'n' || esc == 'r' || esc == 't') {
i += 2;
continue; // 转义的换行/回车/制表,直接丢弃
}
if (esc == '\\' || esc == '/' || esc == '"') {
c = esc;
i += 2;
} else {
i += 1;
continue;
}
} else {
i++;
}
// 明确跳过换行符、回车符、空格等
if (c == '\n' || c == '\r' || c == ' ' || c == '\t') {
continue;
}
// 检查是否是有效的 Base64 字符
if (base64_decode_table[(u8)c] == -1) {
continue;
}
char_array_4[j++] = c;
if (j == 4) {
for (j = 0; j < 4; j++) {
char_array_4[j] = base64_decode_table[(u8)char_array_4[j]];
}
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
// 将解码的 3 字节添加到缓冲区
for (j = 0; j < 3; j++) {
if (buffer_len < (int)sizeof(decode_buffer)) {
decode_buffer[buffer_len++] = char_array_3[j];
}
// 如果达到 chunk_size,立即回调
if (chunk_size > 0 && buffer_len >= chunk_size) {
callback(decode_buffer, chunk_size, user_data);
// 移动剩余数据
buffer_len -= chunk_size;
if (buffer_len > 0) {
memmove(decode_buffer, decode_buffer + chunk_size, buffer_len);
}
}
}
j = 0;
}
}
// 处理剩余的字节
if (j) {
int k;
for (k = j; k < 4; k++) {
char_array_4[k] = 0;
}
for (k = 0; k < 4; k++) {
char_array_4[k] = base64_decode_table[(u8)char_array_4[k]];
}
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
for (k = 0; k < j - 1; k++) {
if (buffer_len < (int)sizeof(decode_buffer)) {
decode_buffer[buffer_len++] = char_array_3[k];
}
if (chunk_size > 0 && buffer_len >= chunk_size) {
callback(decode_buffer, chunk_size, user_data);
buffer_len -= chunk_size;
if (buffer_len > 0) {
memmove(decode_buffer, decode_buffer + chunk_size, buffer_len);
}
}
}
}
// 如果 chunk_size 为 0,回调所有剩余数据
if (chunk_size == 0 && buffer_len > 0) {
callback(decode_buffer, buffer_len, user_data);
buffer_len = 0;
}
decode_buffer_len = buffer_len;
}
/*******************************************************************************
* 刷新音频缓冲区(在会话结束时调用)
*******************************************************************************/
static void flush_audio_buffer(void)
{
// 在 response.done 时刷新剩余的音频数据,避免尾包残留到下一次会话
if (g_audio_callback && decode_buffer_len > 0) {
g_audio_callback(decode_buffer, decode_buffer_len, g_audio_callback_user_data);
}
decode_buffer_len = 0;
}
/*******************************************************************************
* 解析并处理音频数据(直接在原始数据上操作,边解码边回调)
*******************************************************************************/
static void process_audio_data(const char *json_msg)
{
if (!g_audio_callback) {
return; // 没有注册回调
}
// 查找 delta 字段(base64 编码的音频数据)
int base64_len = 0;
const char *audio_base64_data = find_json_string_field(json_msg, "delta", &base64_len);
if (!audio_base64_data || base64_len == 0) {
return; // 没有音频数据
}
// 调试信息:打印 base64 数据的前几个字符
// printf("Audio base64 (%d bytes): %.50s...\n", base64_len, audio_base64_data);
// 边解码边回调,不使用临时缓冲区
base64_decode_and_callback(audio_base64_data, base64_len, g_audio_chunk_size,
g_audio_callback, g_audio_callback_user_data);
}
// 百度大模型回调函数
static void baidu_websockets_callback(u8 *buf, u32 len, u8 type)
{
const char *json_msg = (const char *)buf;
// 更精确地匹配消息类型(使用完整的字段匹配)
if (strstr(json_msg, "\"type\":\"error\"")) {
printf("==============================================\n\n");
printf("Baidu AI Error: %s\n", json_msg);
} else if (strstr(json_msg, "\"type\":\"response.audio_transcript.delta\"")) {
// 处理文本转录增量
// process_text_transcript(json_msg);
} else if (strstr(json_msg, "\"type\":\"response.audio_transcript.done\"")) {
g_ws_write_enabled = 0;
printf("Baidu AI: Text transcription completed\n");
// 处理文本转录完成
process_text_transcript(json_msg);
} else if (strstr(json_msg, "\"type\":\"response.audio.delta\"")) {
// 处理音频数据增量(注意:必须在 audio_transcript 之后检查)
g_ws_write_enabled = 0;
printf("Baidu AI: Audio data received\n");
process_audio_data(json_msg);
// printf("Baidu AI: Audio data received: %s\n", json_msg);
} else if (strstr(json_msg, "\"type\":\"conversation.item.input_audio_transcription.delta\"")) {
// 处理输入音频转录增量
// process_text_transcript(json_msg);
} else if (strstr(json_msg, "\"type\":\"conversation.item.input_audio_transcription.completed\"")) {
// 处理输入音频转录完成
g_ws_write_enabled = 0;
printf("Baidu AI: Input audio transcription completed\n");
process_text_transcript(json_msg);
} else if (strstr(json_msg, "\"type\":\"input_audio_buffer.speech_started\"")) {
printf("Baidu AI: Speech started\n");
// 重要:检测到语音开始后,需要继续发送音频(包括静音)
// 直到收到 response.done,否则服务器会断开连接
} else if (strstr(json_msg, "\"type\":\"response.done\"")) {
printf("Baidu AI: Response done, will reconnect soon\n");
flush_audio_buffer();
// g_ws_write_enabled = 1;
// 现在可以停止发送音频了
} else {
printf("Baidu AI: Unknown message: %s\n", json_msg);
}
// 其他消息不打印,避免刷屏
}
/*******************************************************************************
* 使用示例:回调函数
*******************************************************************************/
// 文本回调示例
static void my_text_callback(const char *text, int len, void *user_data)
{
printf("Received text (%d bytes): %.*s\n", len, len, text);
// 这里可以处理文本数据,例如显示在屏幕上
}
// 音频回调示例
static void my_audio_callback(const u8 *audio, int len, void *user_data)
{
printf("Received audio (%d bytes)\n", len);
int audio_effect_dev0_data_write(s16 *data, int len);
// 接口按字节数输入
if ((len & 0x1) == 0) {
audio_effect_dev0_data_write((s16 *)audio, len);
} else {
printf("\n\n================== \naudio_effect_dev0_data_write error, len %d\n", len);
}
// 这里可以处理音频数据,例如播放音频
// 音频格式:24kHz, 16bit, 单声道 PCM(设备侧可按需重采样)
}
/*******************************************************************************
* 手动触发 AI 回复(当用户说完话后调用)
*******************************************************************************/
int baidu_trigger_response(void)
{
if (!g_baidu_websockets_info) {
return -1;
}
// 发送 response.create 消息,触发 AI 回复
const char *response_create = "{\"type\":\"response.create\"}";
os_mutex_pend(&g_baidu_ws_mutex, 10);
int err = websockets_client_send(g_baidu_websockets_info, (u8 *)response_create,
strlen(response_create), WCT_TXTDATA);
os_mutex_post(&g_baidu_ws_mutex);
if (err == FALSE) {
printf("Failed to trigger response\n");
return -1;
}
printf("AI response triggered\n");
return 0;
}
static void baidu_callbacks_init(void)
{
// 注册文本回调:每收到 100 字节文本就回调一次
// 如果设置为 0,则每次收到文本立即回调
baidu_register_text_callback(100, my_text_callback, NULL);
// 注册音频回调:每收到 1024 字节音频就回调一次
// 如果设置为 0,则每次收到音频立即回调
baidu_register_audio_callback(1024, my_audio_callback, NULL);
}
/*******************************************************************************
* Websocket Client api
*******************************************************************************/
static void websockets_client_reg(struct websocket_struct *websockets_info, char mode)
{
memset(websockets_info, 0, sizeof(struct websocket_struct));
websockets_info->_init = websockets_client_socket_init;
websockets_info->_exit = websockets_client_socket_exit;
websockets_info->_handshack = webcockets_client_socket_handshack;
websockets_info->_send = websockets_client_socket_send;
websockets_info->_recv_thread = websockets_client_socket_recv_thread;
websockets_info->_heart_thread = websockets_client_socket_heart_thread;
websockets_info->_recv_cb = baidu_websockets_callback;
websockets_info->_recv = NULL;
websockets_info->websocket_mode = mode;
#if 0
//应用层指定申请接收内存大小(申请即可,不用释放,内部会释放),默认不使用
websockets_info->recv_buf_size = 30 * 1024;
websockets_info->recv_buf = malloc(websockets_info->recv_buf_size);
#endif
}
static int websockets_client_init(struct websocket_struct *websockets_info, u8 *url, const char *origin_str, const char *user_agent_str)
{
websockets_info->ip_or_url = url;
websockets_info->origin_str = origin_str;
websockets_info->user_agent_str = user_agent_str;
websockets_info->recv_time_out = 1000;
//应用层和库的版本检测,结构体不一样则返回出错
int err = websockets_struct_check(sizeof(struct websocket_struct));
if (err == FALSE) {
return err;
}
return websockets_info->_init(websockets_info);
}
static int websockets_client_handshack(struct websocket_struct *websockets_info)
{
return websockets_info->_handshack(websockets_info);
}
static int websockets_client_send(struct websocket_struct *websockets_info, u8 *buf, int len, char type)
{
//SSL加密时一次发送数据不能超过16K,用户需要自己分包
return websockets_info->_send(websockets_info, buf, len, type);
}
static void websockets_client_exit(struct websocket_struct *websockets_info)
{
websockets_info->_exit(websockets_info);
}
/*******************************************************************************
* 获取百度 Access Token
*
* 方法1(推荐):在 PC 上先获取 token,然后填入下面的 BAIDU_ACCESS_TOKEN
* 在 PC 上运行:
* curl -X POST "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=cTo00rOzevd8KgKbYZOms6Ar&client_secret=6kYppMqvD4V9FKFAgZqXfzuoNQ8Bd8tl"
*
* 方法2:在设备上实现 HTTP 请求获取 token(需要 HTTP 客户端支持)
*******************************************************************************/
// 方法选择:0=通过HTTP请求获取token,1=使用预设token
#define USE_PRESET_TOKEN 0
#if USE_PRESET_TOKEN
// 方法1:直接填入预先获取的 access_token(token 有效期30天)
#define BAIDU_ACCESS_TOKEN "you access tokey" // 请替换为实际获取的 token
#endif
// 简单的 JSON 解析函数,提取 access_token
static int parse_access_token(const char *json_str, char *token_out, int token_size)
{
// 查找 "access_token":"xxx"
const char *token_start = strstr(json_str, "\"access_token\"");
if (!token_start) {
return FALSE;
}
// 找到冒号后的引号
token_start = strchr(token_start, ':');
if (!token_start) {
return FALSE;
}
token_start = strchr(token_start, '"');
if (!token_start) {
return FALSE;
}
token_start++; // 跳过引号
// 找到结束引号
const char *token_end = strchr(token_start, '"');
if (!token_end) {
return FALSE;
}
int token_len = token_end - token_start;
if (token_len >= token_size) {
return FALSE;
}
memcpy(token_out, token_start, token_len);
token_out[token_len] = '\0';
return TRUE;
}
// 通过 HTTP 请求获取百度 access_token
static int http_get_baidu_token(char *token_out, int token_size)
{
int ret = FALSE;
http_body_obj http_body_buf;
httpcli_ctx *ctx = (httpcli_ctx *)calloc(1, sizeof(httpcli_ctx));
if (!ctx) {
printf("Failed to allocate httpcli_ctx\n");
return FALSE;
}
memset(&http_body_buf, 0x0, sizeof(http_body_obj));
http_body_buf.recv_len = 0;
http_body_buf.buf_len = 2 * 1024;
http_body_buf.buf_count = 1;
http_body_buf.p = (char *)malloc(http_body_buf.buf_len * http_body_buf.buf_count);
if (!http_body_buf.p) {
free(ctx);
return FALSE;
}
snprintf(token_url, sizeof(token_url),
"https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s",
BAIDU_API_KEY, BAIDU_SECRET_KEY);
ctx->url = token_url;
ctx->timeout_millsec = 10000; // 10秒超时
ctx->priv = &http_body_buf;
ctx->connection = "close";
printf("Requesting access_token from Baidu...\n");
// 发送 POST 请求
if (httpcli_post(ctx) == HERROR_OK) {
if (http_body_buf.recv_len > 0) {
printf("Received response (%d bytes):\n%s\n", http_body_buf.recv_len, http_body_buf.p);
// 解析 JSON 响应,提取 access_token
if (parse_access_token(http_body_buf.p, token_out, token_size)) {
printf("Successfully got access_token: %s\n", token_out);
ret = TRUE;
} else {
printf("Failed to parse access_token from response\n");
}
}
} else {
printf("HTTP request failed\n");
}
// 清理资源
httpcli_close(ctx);
if (http_body_buf.p) {
free(http_body_buf.p);
}
if (ctx) {
free(ctx);
}
return ret;
}
static int get_baidu_access_token(void)
{
#if USE_PRESET_TOKEN
// 使用预设的 access_token
snprintf(access_token, sizeof(access_token), "%s", BAIDU_ACCESS_TOKEN);
printf("Using preset access_token\n");
#else
// 通过 HTTP 请求获取 token
printf("Getting access_token via HTTP request...\n");
if (!http_get_baidu_token(access_token, sizeof(access_token))) {
printf("ERROR: Failed to get access_token!\n");
return FALSE;
}
#endif
// 构建 WebSocket URL
snprintf(baidu_ws_url, sizeof(baidu_ws_url),
"wss://aip.baidubce.com/ws/2.0/speech/v1/realtime?model=%s&access_token=%s",
BAIDU_MODEL_NAME, access_token);
printf("Baidu WebSocket URL ready\n");
return TRUE;
}
/*
输入16k采样率16bit位宽的单声道数据
data: 音频pcm数据指针
len:数据长度,单位byte(可以是任意大小,函数内部会自动分包)
返回值:成功返回发送的字节数,失败返回负数
说明:
- 不限制每次发送的数据大小,可以直接传入任意长度的 PCM 数据
- 函数内部会自动分包(每包最大 640 字节,约 20ms 音频)
- 640 字节 = 16kHz × 16bit × 1声道 × 0.02秒
*/
int baidu_websocket_audio_data_write(s16 *data, int len)
{
// 参数检查
if (!data || len <= 0) {
// printf("Invalid parameters: data=%p, len=%d\n", data, len);
return -1;
}
if (!g_ws_write_enabled) {
// printf("WebSocket write is disabled\n");
return -1;
}
if (!g_baidu_websockets_info) {
printf("WebSocket info is NULL\n");
return -1;
}
// 建议每次发送 640 字节(20ms @ 16kHz, 16bit),但不是强制的
const int max_chunk_size = 640; // 每包 640 字节(可调整)
int total_sent = 0;
// 使用非阻塞方式获取互斥锁,避免阻塞音频线程
if (os_mutex_pend(&g_baidu_ws_mutex, 10) != OS_NO_ERR) { // 10ms 超时
// 互斥锁被占用,可能正在发送心跳包,跳过本次发送
return -1;
}
// 将 s16* 转换为 u8* 用于字节操作
u8 *byte_data = (u8 *)data;
while (total_sent < len) {
int chunk_size = (len - total_sent) > max_chunk_size ? max_chunk_size : (len - total_sent);
// 检查缓冲区大小是否足够
int required_base64_size = (chunk_size * 4 / 3) + 4;
if (required_base64_size > sizeof(g_audio_base64)) {
printf("Chunk size too large for base64 buffer: %d > %d\n", required_base64_size, sizeof(g_audio_base64));
os_mutex_post(&g_baidu_ws_mutex);
return -2;
}
// Base64 编码(使用全局缓冲区)
int encoded_len = base64_encode(byte_data + total_sent, chunk_size, g_audio_base64, sizeof(g_audio_base64));
if (encoded_len <= 0) {
printf("Base64 encode failed: encoded_len=%d\n", encoded_len);
os_mutex_post(&g_baidu_ws_mutex);
return -3;
}
// 构建 JSON 消息(使用全局缓冲区)
int json_len = snprintf(g_json_msg, sizeof(g_json_msg),
"{\"type\":\"input_audio_buffer.append\",\"audio\":\"%s\"}",
g_audio_base64);
if (json_len >= sizeof(g_json_msg)) {
printf("JSON message too large: %d >= %d\n", json_len, sizeof(g_json_msg));
os_mutex_post(&g_baidu_ws_mutex);
return -4;
}
// 发送到百度服务器
int err = websockets_client_send(g_baidu_websockets_info, (u8 *)g_json_msg, json_len, WCT_TXTDATA);
if (FALSE == err) {
g_send_error_count++;
if (g_send_error_count % 10 == 1) { // 每10次错误打印一次
printf("Failed to send audio data to Baidu (error count: %d)\n", g_send_error_count);
}
os_mutex_post(&g_baidu_ws_mutex);
return -5;
}
total_sent += chunk_size;
}
os_mutex_post(&g_baidu_ws_mutex);
return total_sent;
}
/*******************************************************************************
* 百度大模型 WebSocket 客户端主线程(带自动重连)
*******************************************************************************/
void baidu_websockets_client_main_thread(void *priv)
{
int err;
char mode = WEBSOCKETS_MODE; // 百度使用 wss 加密连接
printf(" . ----------------- Baidu AI Websocket Client ------------------\r\n");
enum wifi_sta_connect_state state;
state = wifi_get_sta_connect_state();
if (WIFI_STA_NETWORK_STACK_DHCP_SUCC != state) {
printf("Network is not connected !!!\n");
return;
}
// 初始化回调函数
baidu_callbacks_init();
// 初始化互斥锁(只初始化一次)
err = os_mutex_create(&g_baidu_ws_mutex);
if (err != OS_NO_ERR) {
printf(" . ! Failed to create mutex !!!\r\n");
return;
}
// 获取 access token(只获取一次)
if (!get_baidu_access_token()) {
printf(" . ! Failed to get access token !!!\r\n");
goto _err0;
}
g_send_error_count = 0;
printf(" . Connecting to Baidu AI...\r\n");
/* 0 . malloc buffer */
struct websocket_struct *websockets_info = malloc(sizeof(struct websocket_struct));
if (!websockets_info) {
printf(" . ! Failed to malloc websocket_struct !!!\r\n");
goto _err0;
}
/* 1 . register */
websockets_client_reg(websockets_info, mode);
websockets_info->_recv_cb = baidu_websockets_callback; // 使用百度专用回调
/* 2 . init */
err = websockets_client_init(websockets_info, (u8 *)baidu_ws_url, origin_str, NULL);
if (FALSE == err) {
printf(" . ! Client websocket init error, retry in 5s !!!\r\n");
goto _err1;
}
/* 3 . handshake */
err = websockets_client_handshack(websockets_info);
if (FALSE == err) {
printf(" . ! Handshake error !!!\r\n");
goto _err2;
}
printf(" . Handshake success \r\n");
/* 4 . CreateThread */
thread_fork("baidu_ws_heart", 19, 512, 0,
&websockets_info->ping_thread_id,
websockets_info->_heart_thread,
websockets_info);
thread_fork("baidu_ws_recv", 18, 512, 0,
&websockets_info->recv_thread_id,
websockets_info->_recv_thread,
websockets_info);
websockets_sleep(100);
/* 5 . 发送会话配置 */
// 完整的会话配置:启用音频输出和文本转录
const char *session_config =
"{\"type\":\"session.update\","
"\"session\":{"
"\"modalities\":[\"text\",\"audio\"]," // 启用文本和音频输出
"\"input_audio_transcription\":{\"model\":\"default\"}"
"}}";
err = websockets_client_send(websockets_info, (u8 *)session_config, strlen(session_config), WCT_TXTDATA);
if (FALSE == err) {
printf(" . ! Failed to send session config, retry in 5s !!!\r\n");
goto _err3;
}
printf(" . Session config sent (audio output enabled)\r\n");
websockets_sleep(100);
g_ws_write_enabled = 1;
// 保存全局指针
g_baidu_websockets_info = websockets_info;
printf(" . Baidu WebSocket ready for audio data\r\n");
return;
/* 6 . 保持连接,等待外部调用 baidu_websocket_audio_data_write() 发送音频数据 */
// 检测连接状态,如果断开则重连
// while (1) {
// os_time_dly(100); // 每 1 秒检查一次
// // 检查接收线程是否还在运行
// if (websockets_info->recv_thread_id == 0) {
// printf(" . Connection lost, reconnecting in 3s...\r\n");
// break; // 跳出循环,进行重连
// }
// }
_err3:
if (websockets_info->ping_thread_id) {
thread_kill(&websockets_info->ping_thread_id, KILL_REQ);
}
if (websockets_info->recv_thread_id) {
thread_kill(&websockets_info->recv_thread_id, KILL_REQ);
}
_err2:
if (websockets_info) {
websockets_client_exit(websockets_info);
}
_err1:
if (websockets_info) {
free(websockets_info);
}
_err0:
os_mutex_del(&g_baidu_ws_mutex, 0);
}
void baidu_websockets_client_close(void *priv)
{
if (g_baidu_websockets_info) {
g_ws_write_enabled = 0;
websockets_client_exit(g_baidu_websockets_info);
thread_kill(&g_baidu_websockets_info->ping_thread_id, KILL_REQ);
thread_kill(&g_baidu_websockets_info->recv_thread_id, KILL_REQ);
free(g_baidu_websockets_info);
g_baidu_websockets_info = NULL;
os_mutex_del(&g_baidu_ws_mutex, 0);
}
}
static void baidu_websocket_client_thread_create(void)
{
thread_fork("baidu_ws_client_main", 15, 512 * 3, 0, 0, baidu_websockets_client_main_thread, NULL);
}
static void http_websocket_start(void *priv)
{
enum wifi_sta_connect_state state;
while (1) {
printf("Connecting to the network...\n");
state = wifi_get_sta_connect_state();
if (WIFI_STA_NETWORK_STACK_DHCP_SUCC == state) {
printf("Network connection is successful!\n");
break;
}
os_time_dly(500);
}
// 初始化回调函数
baidu_callbacks_init();
// 选择启动哪个客户端
// websocket_client_thread_create(); // 原始测试客户端
baidu_websocket_client_thread_create(); // 百度大模型客户端
}
//应用程序入口,需要运行在STA模式下
void c_main(void *priv)
{
if (thread_fork("http_websocket_start", 10, 512, 0, NULL, http_websocket_start, NULL) != OS_NO_ERR) {
printf("thread fork fail\n");
}
}
// late_initcall(c_main);
#endif//USE_WEBSOCKET_TEST
5.1 audio/framework/nodes/effect_dev0_node.c源码
c
#include "jlstream.h"
#include "media/audio_base.h"
#include "effects/effects_adj.h"
#include "app_config.h"
#include "effect_dev_node.h"
#include "effects_dev.h"
#include "audio_splicing.h"
#include "circular_buf.h"
#include "Resample_api.h"
#if TCFG_EFFECT_DEV0_NODE_ENABLE
#define LOG_TAG_CONST EFFECTS
#define LOG_TAG "[EFFECT_DEV0-NODE]"
#define LOG_ERROR_ENABLE
#define LOG_INFO_ENABLE
#define LOG_DUMP_ENABLE
#include "debug.h"
#include "web_socket/websocket_api.h"
#ifdef MEDIA_SUPPORT_MS_EXTENSIONS
#pragma code_seg(".effect_dev0.text")
#pragma data_seg(".effect_dev0.data")
#pragma const_seg(".effect_dev0.text.const")
#endif
/* 音效算法处理帧长
* 0 : 等长输入输出,输入数据算法需要全部处理完
* 非0 : 按照帧长输入数据到算法处理接口
*/
#define EFFECT_DEV0_FRAME_POINTS 0 //(256)
int websockets_client_send(struct websocket_struct *websockets_info, u8 *buf, int len, char type);
/*
*声道转换类型选配
*支持立体声转4声道协商使能,须在第三方音效节点后接入声道拆分节点
* */
#define CHANNEL_ADAPTER_AUTO 0 //自动协商,通常用于无声道数转换的场景,结果随数据流配置自动适配
#define CHANNEL_ADAPTER_2TO4 1 //立体声转4声道协商使能,支持2to4,结果随数据流配置自动适配
#define CHANNEL_ADAPTER_1TO2 2 //单声道转立体声协商使能,支持1to2,结果随数据流配置自动适配
#define CHANNEL_ADAPTER_2TO6 3 //立体声转6声道协商使能,支持2to6,结果随数据流配置自动适配
#define CHANNEL_ADAPTER_2TO8 4 //立体声转8声道协商使能,支持2to8,结果随数据流配置自动适配
#define CHANNEL_ADAPTER_TYPE CHANNEL_ADAPTER_AUTO //默认无声道转换
struct effect_dev0_node_hdl {
char name[16];
void *user_priv; //用户可使用该变量做模块指针传递
struct user_effect_tool_param cfg;//工具界面参数
struct packet_ctrl dev;
cbuffer_t cbuf;
};
struct effect_dev0_node_hdl *g_effect_dev0_hdl = NULL;
/* 自定义算法,初始化
* hdl->dev.sample_rate:采样率
* hdl->dev.in_ch_num:通道数,单声道 1,立体声 2, 四声道 4
* hdl->dev.out_ch_num:通道数,单声道 1,立体声 2, 四声道 4
* hdl->dev.bit_width:位宽 0,16bit 1,32bit
**/
static u8 mydata_buf[1024 * 1000];
static RS_STUCT_API *sw_src_api = NULL;
static u8 *sw_src_buf = NULL;
static void audio_effect_dev0_init(struct effect_dev0_node_hdl *hdl)
{
sw_src_api = get_rsfast_context();
int sw_len = sw_src_api->need_buf();
printf("sw_len:%d\n", sw_len);
sw_src_buf = zalloc(sw_len);
RS_PARA_STRUCT rs_para = {
.nch = 1,
.new_insample = 24000,
.new_outsample = 16000,
.dataTypeobj.IndataBit = 0,
.dataTypeobj.OutdataBit = 0,
.dataTypeobj.IndataInc = 1,
.dataTypeobj.OutdataInc = 1,
.dataTypeobj.Qval = 15,
};
sw_src_api->open(sw_src_buf, &rs_para);
g_effect_dev0_hdl = hdl;
//do something
cbuf_init(&hdl->cbuf, mydata_buf, sizeof(mydata_buf));
void baidu_websockets_client_main_thread(void *priv);
baidu_websockets_client_main_thread(NULL);
}
static u8 sw_src_tmp_buf[1024];
int audio_effect_dev0_data_write(s16 *data, int len)
{
if (g_effect_dev0_hdl == NULL || data == NULL || len <= 0) {
return -1;
}
int sw_len = sw_src_api->run(sw_src_buf, data, len >> 1, sw_src_tmp_buf);
sw_len <<= 1;
int wlen = cbuf_write(&g_effect_dev0_hdl->cbuf, sw_src_tmp_buf, sw_len);
if (wlen < sw_len) {
printf("cbuf write fail, wlen %d, sw_len %d\n", wlen, sw_len);
}
return wlen;
}
/* 自定义算法,运行
* hdl->dev.sample_rate:采样率
* hdl->dev.in_ch_num:通道数,单声道 1,立体声 2, 四声道 4
* hdl->dev.out_ch_num:通道数,单声道 1,立体声 2, 四声道 4
* hdl->dev.bit_width:位宽 0,16bit 1,32bit
* *indata:输入数据地址
* *outdata:输出数据地址
* indata_len :输入数据长度,byte
* */
static void audio_effect_dev0_run(struct effect_dev0_node_hdl *hdl, s16 *indata, s16 *outdata, u32 indata_len)
{
// 正确的函数声明:参数是指针类型
int baidu_websocket_audio_data_write(s16 *data, int len);
// printf("audio_effect_dev0_run indata=%p, len=%d\n", indata, indata_len);
baidu_websocket_audio_data_write(indata, indata_len);
memset(outdata, 0, indata_len);
// memcpy(outdata, indata, indata_len);
int cbuf_len = cbuf_get_data_len(&hdl->cbuf);
// printf("run cbuf: %d, inlen %d\n", cbuf_len, indata_len);
if (cbuf_len >= indata_len){
// printf("audio_effect_dev0_run cbuf data len=%d\n", get_cbuf_data_len(&hdl->cbuf));
cbuf_read(&hdl->cbuf, outdata, indata_len);
putchar('O');
} else if (cbuf_len > 0){
// printf("audio_effect_dev0_run cbuf data len=%d\n", get_cbuf_data_len(&hdl->cbuf));
cbuf_read(&hdl->cbuf, outdata, cbuf_len);
putchar('L');
}
// memcpy(outdata, indata, indata_len);
//do something
/* printf("effect dev0 do something here\n"); */
}
/* 自定义算法,关闭
**/
static void audio_effect_dev0_exit(struct effect_dev0_node_hdl *hdl)
{
void baidu_websockets_client_close(void *priv);
baidu_websockets_client_close(NULL);
//do something
if (sw_src_buf) {
free(sw_src_buf);
sw_src_buf = NULL;
sw_src_api = NULL;
}
g_effect_dev0_hdl = NULL;
}
/* 自定义算法,更新参数
**/
static void audio_effect_dev0_update(struct effect_dev0_node_hdl *hdl)
{
//打印在线调音发送下来的参数
printf("effect dev0 name : %s \n", hdl->name);
for (int i = 0 ; i < 8; i++) {
printf("cfg.int_param[%d] %d\n", i, hdl->cfg.int_param[i]);
}
for (int i = 0 ; i < 8; i++) {
printf("cfg.float_param[%d] %d.%02d\n", i, (int)hdl->cfg.float_param[i], debug_digital(hdl->cfg.float_param[i]));
}
//do something
}
/*节点输出回调处理,可处理数据或post信号量*/
static void effect_dev0_handle_frame(struct stream_iport *iport, struct stream_note *note)
{
struct effect_dev0_node_hdl *hdl = (struct effect_dev0_node_hdl *)iport->node->private_data;
effect_dev_process(&hdl->dev, iport, note); //音效处理
}
/*节点预处理-在ioctl之前*/
static int effect_dev0_adapter_bind(struct stream_node *node, u16 uuid)
{
struct effect_dev0_node_hdl *hdl = (struct effect_dev0_node_hdl *)node->private_data;
memset(hdl, 0, sizeof(*hdl));
return 0;
}
/*打开改节点输入接口*/
static void effect_dev0_ioc_open_iport(struct stream_iport *iport)
{
iport->handle_frame = effect_dev0_handle_frame; //注册输出回调
}
/*节点参数协商*/
static int effect_dev0_ioc_negotiate(struct stream_iport *iport)
{
int ret = 0;
ret = NEGO_STA_ACCPTED;
struct stream_oport *oport = iport->node->oport;
struct stream_fmt *in_fmt = &iport->prev->fmt;
struct effect_dev0_node_hdl *hdl = (struct effect_dev0_node_hdl *)iport->node->private_data;
if (oport->fmt.channel_mode == 0xff) {
return 0;
}
hdl->dev.out_ch_num = AUDIO_CH_NUM(oport->fmt.channel_mode);
hdl->dev.in_ch_num = AUDIO_CH_NUM(in_fmt->channel_mode);
#if (CHANNEL_ADAPTER_TYPE == CHANNEL_ADAPTER_2TO4)
if (hdl->dev.out_ch_num == 4) {
if (hdl->dev.in_ch_num != 2) {
in_fmt->channel_mode = AUDIO_CH_LR;
ret = NEGO_STA_CONTINUE;
}
}
#elif (CHANNEL_ADAPTER_TYPE == CHANNEL_ADAPTER_1TO2)
if (hdl->dev.out_ch_num == 2) {
if (hdl->dev.in_ch_num != 1) {
in_fmt->channel_mode = AUDIO_CH_MIX;
ret = NEGO_STA_CONTINUE;
}
}
#elif (CHANNEL_ADAPTER_TYPE == CHANNEL_ADAPTER_2TO6)
if (hdl->dev.out_ch_num == 6) {
if (hdl->dev.in_ch_num != 2) {
in_fmt->channel_mode = AUDIO_CH_LR;
ret = NEGO_STA_CONTINUE;
}
}
#elif (CHANNEL_ADAPTER_TYPE == CHANNEL_ADAPTER_2TO8)
if (hdl->dev.out_ch_num == 8) {
if (hdl->dev.in_ch_num != 2) {
in_fmt->channel_mode = AUDIO_CH_LR;
ret = NEGO_STA_CONTINUE;
}
}
#endif
if (in_fmt->sample_rate != 16000) {
in_fmt->sample_rate = 16000;
oport->fmt.sample_rate = in_fmt->sample_rate;
ret = NEGO_STA_CONTINUE;
}
log_debug("effecs_dev in_ch_num %d, out_ch_num %d", hdl->dev.in_ch_num, hdl->dev.out_ch_num);
return ret;
}
/*节点start函数*/
static void effect_dev0_ioc_start(struct effect_dev0_node_hdl *hdl)
{
struct stream_fmt *fmt = &hdl_node(hdl)->oport->fmt;
/* struct jlstream *stream = jlstream_for_node(hdl_node(hdl)); */
hdl->dev.sample_rate = fmt->sample_rate;
/*
*获取配置文件内的参数,及名字
* */
int len = jlstream_read_node_data_new(hdl_node(hdl)->uuid, hdl_node(hdl)->subid, (void *)&hdl->cfg, hdl->name);
if (!len) {
log_error("%s, read node data err\n", __FUNCTION__);
return;
}
/*
*获取在线调试的临时参数
* */
if (config_audio_cfg_online_enable) {
if (jlstream_read_effects_online_param(hdl_node(hdl)->uuid, hdl->name, &hdl->cfg, sizeof(hdl->cfg))) {
log_debug("get effect dev0 online param\n");
}
}
printf("effect dev0 name : %s \n", hdl->name);
for (int i = 0 ; i < 8; i++) {
printf("cfg.int_param[%d] %d\n", i, hdl->cfg.int_param[i]);
}
for (int i = 0 ; i < 8; i++) {
printf("cfg.float_param[%d] %d.%02d\n", i, (int)hdl->cfg.float_param[i], debug_digital(hdl->cfg.float_param[i]));
}
hdl->dev.bit_width = hdl_node(hdl)->iport->prev->fmt.bit_wide;
hdl->dev.qval = hdl_node(hdl)->iport->prev->fmt.Qval;
printf("effect_dev0_ioc_start, sr: %d, in_ch: %d, out_ch: %d, bitw: %d, %d", hdl->dev.sample_rate, hdl->dev.in_ch_num, hdl->dev.out_ch_num, hdl->dev.bit_width, hdl->dev.qval);
hdl->dev.node_hdl = hdl;
hdl->dev.effect_run = (void (*)(void *, s16 *, s16 *, u32))audio_effect_dev0_run;
effect_dev_init(&hdl->dev, EFFECT_DEV0_FRAME_POINTS);
audio_effect_dev0_init(hdl);
}
/*节点stop函数*/
static void effect_dev0_ioc_stop(struct effect_dev0_node_hdl *hdl)
{
audio_effect_dev0_exit(hdl);
effect_dev_close(&hdl->dev);
}
static int effect_dev0_ioc_update_parm(struct effect_dev0_node_hdl *hdl, int parm)
{
int ret = false;
return ret;
}
static int get_effect_dev0_ioc_parm(struct effect_dev0_node_hdl *hdl, int parm)
{
int ret = 0;
return ret;
}
static int effect_ioc_update_parm(struct effect_dev0_node_hdl *hdl, int parm)
{
int ret = false;
struct user_effect_tool_param *cfg = (struct user_effect_tool_param *)parm;
if (hdl) {
memcpy(&hdl->cfg, cfg, sizeof(struct user_effect_tool_param));
audio_effect_dev0_update(hdl);
ret = true;
}
return ret;
}
/*节点ioctl函数*/
static int effect_dev0_adapter_ioctl(struct stream_iport *iport, int cmd, int arg)
{
int ret = 0;
struct effect_dev0_node_hdl *hdl = (struct effect_dev0_node_hdl *)iport->node->private_data;
switch (cmd) {
case NODE_IOC_OPEN_IPORT:
effect_dev0_ioc_open_iport(iport);
break;
case NODE_IOC_OPEN_OPORT:
break;
case NODE_IOC_CLOSE_IPORT:
break;
case NODE_IOC_SET_SCENE:
break;
case NODE_IOC_NEGOTIATE:
*(int *)arg |= effect_dev0_ioc_negotiate(iport);
break;
case NODE_IOC_START:
effect_dev0_ioc_start(hdl);
break;
case NODE_IOC_SUSPEND:
case NODE_IOC_STOP:
effect_dev0_ioc_stop(hdl);
break;
case NODE_IOC_NAME_MATCH:
if (!strcmp((const char *)arg, hdl->name)) {
ret = 1;
}
break;
case NODE_IOC_SET_PARAM:
ret = effect_ioc_update_parm(hdl, arg);
break;
}
return ret;
}
/*节点用完释放函数*/
static void effect_dev0_adapter_release(struct stream_node *node)
{
}
/*节点adapter 注意需要在sdk_used_list声明,否则会被优化*/
REGISTER_STREAM_NODE_ADAPTER(effect_dev0_node_adapter) = {
.name = "effect_dev0",
.uuid = NODE_UUID_EFFECT_DEV0,
.bind = effect_dev0_adapter_bind,
.ioctl = effect_dev0_adapter_ioctl,
.release = effect_dev0_adapter_release,
.hdl_size = sizeof(struct effect_dev0_node_hdl),
#if (CHANNEL_ADAPTER_TYPE != CHANNEL_ADAPTER_AUTO)
.ability_channel_out = 0x80 | 1 | 2 | 4 | 6 | 8,
.ability_channel_convert = 1,
#endif
};
REGISTER_ONLINE_ADJUST_TARGET(effect_dev0) = {
.uuid = NODE_UUID_EFFECT_DEV0,
};
#endif
6. 常见问题与注意事项
6.1 音频格式问题
- 确保上行音频为 16kHz / 16bit / 单声道
- 下行音频为 24kHz,如需播放可能需要重采样
- Base64 编码时注意去除换行符
6.2 Token 过期
- Access Token 有效期为 30 天
- 建议在代码中实现自动刷新机制
- 收到
1001错误码时重新获取 Token
6.3 网络稳定性
- WebSocket 连接可能因网络波动断开
- 建议实现断线重连机制
- 可以添加心跳包保持连接活跃
6.4 音频延迟
- 建议音频缓冲区不要设置过大(<500ms)
- 及时发送
commit消息以触发 AI 响应 - 播放端可以采用流式播放减少延迟
7. 参考文档
8. 总结
本文详细介绍了杰理 AC792N 芯片接入百度语音大模型的完整方案,从账号申请到代码实现都有详细说明。通过本教程,开发者可以快速搭建一套完整的 AI 语音对话系统。
如果在实现过程中遇到问题,建议:
- 先检查网络连接和 Token 是否有效
- 使用抓包工具查看 WebSocket 数据交互
- 参考百度官方文档和 SDK 示例代码
- 在社区寻求帮助或查阅相关技术博客
希望本文对你有所帮助,欢迎在评论区交流讨论!
作者声明: 本文为原创技术分享,转载请注明出处。
更新日期: 2026-01-20