Websocket API接入大模型互动服务最佳实践
该文档介绍Websocket API接入大模型互动服务的最佳实践,引导用户快速跑通流程,解答常见的问题。
基础:跑通全流程
第一步:控制台开通云服务,配置互动智能体
1.注册和登陆百度智能云账号:个人用户和企业用户均需先注册百度智能云账号。具体步骤见注册账号, 登录账号
2.开通大模型互动服务:首次使用服务需要先开通,开通步骤见开通服务
3.创建和配置智能体:
第一步:填写基本信息、选择互动类型(创建成功后不可更改)
第二步:选择模型、填写人设、选择function call模版和组件,具体内容详见 创建互动应用、 控制台快速接入
4.云上测试智能体 【互动应用】应用列表处找到要体验的应用,在【操作】点击体验Demo,进行体验
5.领取测试license和购买正式license: 新用户自动下发10个license,如要购买,在【资源列表】-【购买资源】按需配置购买。详情见领取和购买license
6.给已创建的应用绑定资源:应用创建成功后,必须绑定资源才可正常使用。绑定操作见 绑定资源
第二步:运行云对接服务示例
说明:百度智能云服务API接口,均采用AK/SK鉴权,为了增强安全性,要求在服务器上调用(不建议在设备上)。该示例代码实现一个访问中转,接收设备端连接请求,代理访问百度大模型互动API接口创建互动智能体,返回设备端侧访问云服务所需的token、agentid等信息。该代码可作为服务端对接的实现参考。
1.下载云端对接服务示例代码
本示例代码基于JDK17开发,需要先安装JDK17环境,确保可运行java -version命令等。具体安装过程可直接搜索JDK17安装,参考开源链接进行安装。
官网下载云对接服务代码示例,解压缩之后获取到rtc-aiagent-userserver-version.jar包,本JAR包可以直接用于部署启动。
[!NOTE]
本节后续操作均依赖于前一步骤所获取的参数,请在执行后续操作之前,确保之前步骤均已成功执行。
2.配置云对接服务参数
获取到用于百度智能云接口鉴权的AK/SK,如何获取AK/SK。
获取到用于执行接口调用的互动应用AppId,查看互动应用ID。
3.运行云对接服务示例
将本节步骤2获取到的AK/SK参数替换下面执行命令中的对应部分
1## 访问BCE国内节点启动命令
2BCE_APP_AK=your_ak \
3BCE_APP_SK=your_sk \
4nohup java -Djava.net.preferIPv4Stack=true -jar rtc-aiagent-userserver-version.jar \
5--server.address=0.0.0.0 > app.log 2>&1 &
1## 访问BCE香港节点启动命令
2BCE_APP_AK=your_ak \
3BCE_APP_SK=your_sk \
4BCE_ENDPOINT=http://rtc-aiagent-out.baidubce.com \
5nohup java -Djava.net.preferIPv4Stack=true -jar rtc-aiagent-userserver-version.jar \
6--server.address=0.0.0.0 > app.log 2>&1 &
[!NOTE]
启动成功之后,console/cmd/ssh界面,会打印进程ID, 日志信息会保存到当前目录的app.log中
服务启动之后,可以通过接口调用工具,或使用curl命令,测试服务是否可以正常创建token、agentId等。命令中your_app_id需要替换为本节步骤2获取到的互动应用appId。
1curl --location 'http://localhost:8936/api/v1/aiagent/generateAIAgentCall' \
2--header 'Content-Type: application/json' \
3--data '{
4 "config": "",
5 "app_id": "your_app_id"
6}'
1curl --location 'http://localhost:8936/api/v1/aiagent/stopAIAgentInstance' \
2--header 'Content-Type: application/json' \
3--data '{
4 "app_id": "your_app_id",
5 "ai_agent_instance_id": 2568137789341696
6}'
服务启动成功之后,会占据8936端口,如需停止/重启服务,可参考以下命令
1# 查看占用端口的进程Id
2lsof -i:8936
3
4kill -9 [进程id]
第三步:下载和运行设备端代码示例
1.下载设备SDK和DEMO代码
官网下载Websocket API代码示例,解压缩之后可以获取到对应的Demo代码。
websocket在各平台说明如下:
1). 在嵌入式平台建议使用平台的websocket库进行接入, 或者使用librws,或者使用mongoose接入。
2). 在java平台建议使用okhttp库接入。
3). 在Python 平台建议使用websockets.asyncio.client接入。
4). 在Go平台建议使用github.com/gorilla/websocket接入。
5). 在浏览器或者nodejs平台建议用系统原生websocket库接入即可。
2.接入代码修改参数示例
Demo解压后需要更换成客户申请的appId/licenskey以及客户搭建的服务端创建智能体实例值,具体如下:
方式一: 使用“第二步:运行云对接服务示例” 生成的appid, id, 已经token 作为URL参数连接WS服务器: wss://rtc-aiotgw.exp.bcelive.com/v1/realtime?a=APPID&id=ID&t=TOKEN&ac=raw16k
方式二: 连接方式二
C 代码示例:
1#include <stdio.h>
2#include <stdlib.h>
3#include "mongoose.h"
4
5#define debug printf
6
7struct FConnection
8{
9 char *url;
10};
11
12int main(int argc, char* argv[])
13{
14 struct mg_mgr mgr; // Event manager
15 struct FConnection C; // object
16
17 if (argc != 2) {
18 debug("Help: \n ./realtime 'ws://rtc-aiotgw.exp.bcelive.com/v1/realtime?a=APPID_GET_FROM_BAIDU&ak=AK&sk=SK'\n\n");
19 return -1;
20 }
21
22 // mg_log_set("4");
23 mg_mgr_init(&mgr); // Initialise event manager
24
25 C.url = argv[1]; // "ws://rtc-aiotgw.exp.bcelive.com/v1/realtime?a=APPID_GET_FROM_BAIDU&ak=AK&sk=SK";
26 struct mg_connection * mg_c = mg_ws_connect(&mgr, C.url, &FConnection_cb, &C, NULL);
27
28 int cnt = 0;
29 char *question = "[T]:你是谁?";
30
31 while (1) {
32 mg_mgr_poll(&mgr, 50);
33
34 if (cnt++ % 200 == 40) {
35 debug("tx: Question: %s\n", question);
36 mg_ws_send(mg_c, question, strlen(question), WEBSOCKET_OP_TEXT);
37 }
38 }
39
40 return 0;
41}
42
43
44static void FConnection_cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
45 struct FConnection* conn = (struct FConnection*) fn_data;
46 if (conn) {
47 if (ev == MG_EV_WS_OPEN) {
48 debug("MG_EV_WS_OPEN.\n");
49 } else if (ev == MG_EV_CONNECT) {
50 struct mg_str host = mg_url_host(conn->url);
51 debug("MG_EV_CONNECT: [%.*s]\n", host.len, host.ptr);
52 // If s_url is https://, tell client connection to use TLS
53 if (mg_url_is_ssl(conn->url)) {
54 struct mg_tls_opts opts = {.ca = "ca_for_tls", .srvname = host};
55 mg_tls_init(c, &opts);
56 }
57 } else if (ev == MG_EV_WS_MSG) {
58 struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
59 // debug("GOT ECHO REPLY: %d, [%.*s]\n", wm->data.len, (int)wm->data.len, wm->data.ptr);
60
61 switch (wm->flags & WEBSOCKET_FLAGS_MASK_OP) {
62 case WEBSOCKET_OP_TEXT:
63 debug("rx: Text: %.*s\n", (int)wm->data.len, wm->data.ptr);
64 if (strncmp("[E]:[LIC]:[MUST]", wm->data.ptr, 16) == 0) {
65 char *lic = "[E]:[LIC]:[ACTIVE]:{\"devId\":\"DDD\",\"uId\":\"YYY\",\"licKey\":\"xxx\"}";
66 debug("tx: do Active: %s\n", lic);
67 mg_ws_send(c, lic, strlen(lic), WEBSOCKET_OP_TEXT);
68 }
69 break;
70 case WEBSOCKET_OP_BINARY:
71 debug("rx: Binary len: %d\n", (int)wm->data.len);
72 // todo, binary data
73 break;
74 }
75 } else if (ev == MG_EV_CLOSE) {
76 debug("MG_EV_CLOSE, conn:%p\n", conn);
77 } else if (ev == MG_EV_ERROR) {
78 debug("MG_EV_ERROR, conn:%p\n", conn);
79 }
80
81 if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE) {
82 debug("close done, conn:%p\n", conn);
83 //fn_data = NULL; // Signal that we're done
84 }
85 }
86}
C代码编译执行运行:
1# Connect online Server must with ak,sk
2# Get ak,sk from here: https://console.bce.baidu.com/iam/#/iam/accesslist
3cd src
4make linux
5./realtime 'ws://rtc-aiotgw.exp.bcelive.com/v1/realtime?a=APPID_GET_FROM_BAIDU&ak=AK&sk=SK'
Java 代码示例:
1package com.baidu.rtc.realtime;
2
3import okhttp3.OkHttpClient;
4import okhttp3.Request;
5import okhttp3.Response;
6import okhttp3.WebSocket;
7import okhttp3.WebSocketListener;
8import okio.ByteString;
9
10import org.json.JSONObject;
11
12import java.nio.ByteBuffer;
13import java.util.concurrent.TimeUnit;
14import java.util.logging.Level;
15import java.util.logging.Logger;
16
17public class Realtime {
18 private static Logger logger = Logger.getLogger("REALTIME");
19 private static Realtime sInstance;
20 private static WebSocket globalWebSocket;
21
22 private volatile boolean isWebSocketClosed = false;
23
24 private Realtime() {
25 // Private constructor for singleton
26 }
27
28 public static Realtime getInstance() {
29 if (sInstance == null) {
30 synchronized(Realtime.class) {
31 if (sInstance == null) {
32 sInstance = new Realtime();
33 }
34 }
35 }
36 return sInstance;
37 }
38
39 private void setupWebSocket(String url) {
40 OkHttpClient client = new OkHttpClient.Builder()
41 .connectTimeout(5, TimeUnit.SECONDS)
42 .build();
43
44 logger.info("Initializing WebSocket connection with URL: " + url);
45
46 try {
47 Request request = new Request.Builder().url(url).build();
48 globalWebSocket = client.newWebSocket(request, new WebSocketListenerImpl());
49 client.dispatcher().executorService().shutdown();
50 } catch (Exception e) {
51 logger.log(Level.SEVERE, "Failed to initialize WebSocket connection: " + e.getMessage(),
52 e);
53 throw new RuntimeException("WebSocket initialization failed", e);
54 }
55 }
56
57 private void sendAudioFrame(ByteBuffer buffer) {
58 logger.fine("Sending audio frame, size: " + buffer.remaining());
59 globalWebSocket.send(ByteString.of(buffer));
60 }
61
62 public static void main(String []args){
63 Realtime rt = getInstance();
64
65 // https://cloud.baidu.com/doc/RTC/s/Jmakuvimy
66 // wss://rtc-aiotgw.exp.bcelive.com/v1/realtime?a=APPID&ak=AK&sk=SK&ac=raw16k
67 // OR wss://rtc-aiotgw.exp.bcelive.com/v1/realtime?a=APPID&id=YYYY&t=ZZZZ&ac=raw16k
68 rt.setupWebSocket("");
69 }
70
71 private class WebSocketListenerImpl extends WebSocketListener {
72 @Override
73 public void onOpen(WebSocket webSocket, Response response) {
74 super.onOpen(webSocket, response);
75 logger.info("WebSocket connection established.");
76 isWebSocketClosed = false;
77 }
78
79 @Override
80 public void onMessage(WebSocket webSocket, String msg) {
81 super.onMessage(webSocket, msg);
82 // 这里千万别阻塞,包括WebSocketListener其它回调
83 logger.info("WebSocket onMessage:" + msg);
84 if (msg.startsWith("[E]:[MEDIA]:[READY]")) {
85 webSocket.send("[T]:你是谁?");
86 } else if (msg.startsWith("[E]:[LIC]:[MUST]")) {
87 JSONObject req = new JSONObject();
88 req.put("devId", "DEVID1234567890");
89 req.put("uId", "1234567890");
90 req.put("licKey", "LICKEYXXXX");
91 webSocket.send("[E]:[LIC]:[ACTIVE]:" + req.toString());
92 }
93 }
94
95 @Override
96 public void onMessage(WebSocket webSocket, ByteString bytes) {
97 super.onMessage(webSocket, bytes);
98 logger.info("WebSocket onMessage. Binary len:" + bytes.size());
99 }
100
101 @Override
102 public void onClosing(WebSocket webSocket, int code, String reason) {
103 super.onClosing(webSocket, code, reason);
104 logger.info("WebSocket is closing: " + code + ", reason: " + reason);
105 isWebSocketClosed = true;
106 }
107
108 @Override
109 public void onClosed(WebSocket webSocket, int code, String reason) {
110 super.onClosed(webSocket, code, reason);
111 logger.info("WebSocket closed: " + code + ", reason: " + reason);
112 isWebSocketClosed = true;
113 }
114
115 @Override
116 public void onFailure(WebSocket webSocket, Throwable t, Response response) {
117 super.onFailure(webSocket, t, response);
118 logger.log(Level.SEVERE, "WebSocket failure: " + t.getMessage(), t);
119 isWebSocketClosed = true;
120 }
121 }
122}
Python 代码示例:
1#!/usr/bin/env python
2"""Chat using realtime API and SoundDevice."""
3# -*- coding: utf-8 -*-
4
5import asyncio
6import json
7import sys
8import sounddevice as sd
9from websockets.asyncio.client import connect
10
11# =================配置区域=================
12# 百度 RTC 参数
13RATE = 16000
14CHANNELS = 1
15DTYPE = 'int16' # 16位深度
16CHUNK = 3200 # 每次读写的数据块大小
17
18# 授权信息 (替换为真实Key)
19LIC_KEY = "lickey"
20DEV_ID = "myDev"
21USER_ID = "User01"
22
23speaking = False
24
25# ==========================================
26
27async def send_audio_loop(websocket, stream_in, loop):
28 """发送端:使用 sounddevice 读取麦克风"""
29 print(">>> 正在监听麦克风...")
30
31 def read_mic():
32 # sounddevice 的 read 是阻塞的,读取指定数量的帧
33 # 返回值是 (data, overflow_flag)
34 data, _ = stream_in.read(CHUNK)
35 return bytes(data) # 转为 bytes 发送
36
37 try:
38 while True:
39 # 放到线程池运行,避免阻塞
40 pcm_bytes = await loop.run_in_executor(None, read_mic)
41 if speaking:
42 continue
43 await websocket.send(pcm_bytes)
44 except Exception as e:
45 print(f"发送循环停止: {e}")
46
47
48async def receive_loop(websocket, stream_out, loop):
49 """接收端"""
50 print(">>> 准备接收回复...")
51
52 global speaking
53
54 def play_pcm(data):
55 stream_out.write(data)
56
57 try:
58 while True:
59 msg = await websocket.recv()
60
61 if isinstance(msg, str):
62 # 1. 鉴权
63 if msg.startswith("[E]:[LIC]:[MUST]"):
64 print("[系统] 发送授权...")
65 lic = f'[E]:[LIC]:[ACTIVE]:{{"devId":"{DEV_ID}","uId":"{USER_ID}","licKey":"{LIC_KEY}"}}'
66 await websocket.send(lic)
67
68 # 2. 识别到的用户语音
69 elif msg.startswith("[Q]:"):
70 if not msg.startswith("[Q]:[M]:"):
71 print(f"--------\n问: {msg[4:]}")
72
73 # 3. AI 的回答文本
74 elif msg.startswith("[A]:"):
75 print(f"AI答: {msg[4:]}")
76
77 # 4. 【关键】识别到的意图/Function Call
78 # 这里的格式通常是 [F]:[C]:{json数据}
79 elif msg.startswith("[F]:[C]:"):
80 # 去掉前缀 [F]:[C]: 拿到后面的 JSON 字符串
81 json_str = msg[8:]
82 try:
83 data = json.loads(json_str)
84 # 打印原始数据方便调试
85 print(f"\n★ 命中意图 (Function Call) ★")
86 print(f"原始数据: {json_str}")
87
88 # 解析具体字段 (根据你的截图配置)
89 if "fname" in data and data["fname"] == "add_clock":
90 params = data.get("param", {})
91 time_val = params.get("time", "未知时间")
92 date_val = params.get("date", "未知日期")
93 print(f"指令: 添加闹钟")
94 print(f" -> 时间: {time_val}")
95 print(f" -> 日期: {date_val}")
96
97 # 在这里可以写代码真的去设置电脑闹钟
98 # ...
99
100 except json.JSONDecodeError:
101 print(f"收到FC指令但解析JSON失败: {json_str}")
102
103 elif msg.startswith('[E]:[TTS_BEGIN_SPEAKING]'):
104 speaking = True
105 elif msg.startswith('[E]:[TTS_END_SPEAKING]'):
106 speaking = False
107 else :
108 print(f"RX: {msg}")
109
110 elif isinstance(msg, bytes):
111 if len(msg) > 0:
112 await loop.run_in_executor(None, play_pcm, msg)
113
114 except Exception as e:
115 print(f"接收循环停止: {e}")
116
117async def run_chat(uri):
118 """Chat Main."""
119 print("正在打开音频设备 (SoundDevice)...")
120
121 # 打开原始输入流 (RawInputStream) 处理 bytes
122 # 打开原始输出流 (RawOutputStream) 处理 bytes
123 with sd.RawInputStream(samplerate=RATE, channels=CHANNELS, dtype=DTYPE, blocksize=CHUNK) as stream_in, \
124 sd.RawOutputStream(samplerate=RATE, channels=CHANNELS, dtype=DTYPE, blocksize=CHUNK) as stream_out:
125 print("音频设备就绪。")
126 loop = asyncio.get_running_loop()
127
128 async with connect(uri) as websocket:
129 print(">> 已连接到", uri)
130
131 send_task = asyncio.create_task(send_audio_loop(websocket, stream_in, loop))
132 recv_task = asyncio.create_task(receive_loop(websocket, stream_out, loop))
133
134 done, pending = await asyncio.wait([send_task, recv_task], return_when=asyncio.FIRST_COMPLETED)
135 for t in pending:
136 t.cancel()
137
138
139if __name__ == "__main__":
140 try:
141 uri = "ws://rtc-aiotgw.exp.bcelive.com/v1/realtime?a=APPID&ak=AK&sk=SK"
142 if len(sys.argv) > 1:
143 uri = sys.argv[1]
144
145 if sys.platform == 'win32':
146 asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
147
148 asyncio.run(run_chat(uri))
149 except KeyboardInterrupt:
150 print("\n已退出")
151 except Exception as e:
152 print(f"\n发生错误: {e}")
Golang 接入方式
1package main
2
3import (
4 "context"
5 "flag"
6 "io"
7 "log"
8 "os"
9 "os/signal"
10 "strings"
11 "time"
12
13 "github.com/gorilla/websocket"
14)
15
16var uri string
17var ac string = "pcmu"
18var appid string
19var id string
20var token string
21
22func init() {
23 flag.StringVar(&appid, "a", "", "AppID")
24 flag.StringVar(&id, "id", "", "ID")
25 flag.StringVar(&token, "t", "", "token")
26 flag.StringVar(&uri, "s", "wss://rtc-aiotgw.exp.bcelive.com/v1/realtime", "ws service address")
27}
28
29type myListener struct {
30 wsCon *websocket.Conn
31 aw io.Writer
32 PubConnectedCtx context.Context
33 PubConnectedCtxCancel context.CancelFunc
34 agentid string
35}
36
37func (c *myListener) OnAudioData(audio []byte) {
38 if c.aw != nil {
39 c.aw.Write(audio)
40 }
41}
42
43func PrepareSendMedia(c *websocket.Conn, l *myListener, afn string) {
44 var (
45 audioFileName = afn
46 )
47
48 _, err := os.Stat(audioFileName)
49 haveAudioFile := !os.IsNotExist(err)
50
51 if !haveAudioFile {
52 log.Println("Could not find `" + audioFileName)
53 return
54 }
55
56 l.PubConnectedCtx , l.PubConnectedCtxCancel = context.WithCancel(context.Background())
57
58 if haveAudioFile {
59 if strings.HasSuffix(afn, ".pcmu"){
60 go func() {
61 // Open a file and start reading
62 file, Err := os.Open(audioFileName)
63 if Err != nil {
64 return
65 }
66
67 // Wait for connection established
68 <-l.PubConnectedCtx.Done()
69
70 fi, _ := os.Stat(audioFileName)
71
72 payload := make([]byte, fi.Size())
73 _, Err = io.ReadFull(file, payload)
74 if Err != nil {
75 return
76 }
77 var Pos = 0;
78
79 time.Sleep(1500 * time.Millisecond)
80 ticker := time.NewTicker(time.Millisecond * 20)
81 for ; true; <-ticker.C {
82
83 c.WriteMessage(websocket.BinaryMessage, payload[Pos:Pos + 160])
84 Pos += 160;
85
86 if Pos + 160 > int(fi.Size()) {
87 Pos = 0
88 time.Sleep(8 * time.Second)
89 }
90 }
91 }()
92 return
93 }
94 }
95}
96
97func main() {
98 flag.Parse()
99 log.SetFlags(log.LstdFlags | log.Lmicroseconds)
100
101 interrupt := make(chan os.Signal, 1)
102 signal.Notify(interrupt, os.Interrupt)
103
104 var splitChar = "?"
105 if strings.Contains(uri, "?") {
106 splitChar = "&"
107 }
108 if appid != "" {
109 uri += splitChar + "a=" + appid
110 splitChar = "&"
111 }
112 if id != "" {
113 uri += splitChar + "id=" + id
114 splitChar = "&"
115 }
116 if token != "" {
117 uri += splitChar + "t=" + token
118 splitChar = "&"
119 }
120
121 log.Println("Connecting uri:", uri)
122 c, _, err := websocket.DefaultDialer.Dial(uri, nil)
123 if err != nil {
124 log.Fatal("dial:", err)
125 }
126 defer c.Close()
127
128 done := make(chan struct{})
129
130 // ffplay -f mulaw -ar 8000 dump_audio.pcmu
131 af, _ := os.Create("dump_audio." + ac)
132 my := &myListener{wsCon: c, aw:af}
133 afn := "myaudio." + ac
134 PrepareSendMedia(c, my, afn)
135
136 go func() {
137 defer close(done)
138 for {
139 mt, message, err := c.ReadMessage()
140 if err != nil {
141 log.Println("read:", err)
142 return
143 }
144 if mt == websocket.TextMessage {
145 log.Printf("rx: %s", message)
146 if strings.HasPrefix(string(message), "[E]:[LIC]:[MUST]") {
147 lic := `[E]:[LIC]:[ACTIVE]:{"devId":"DDD","uId":"YYY","licKey":"xxx"}`
148 log.Printf("tx: do Active: %s\n", lic)
149 c.WriteMessage(websocket.TextMessage, []byte(lic))
150 }
151 } else if mt == websocket.BinaryMessage {
152 my.OnAudioData(message)
153 }
154 }
155 }()
156
157 if my.PubConnectedCtxCancel != nil {
158 my.PubConnectedCtxCancel()
159 }
160
161 ticker := time.NewTicker(120 * time.Second)
162 defer ticker.Stop()
163
164 for {
165 select {
166 case <-done:
167 return
168 case t := <-ticker.C:
169 log.Println(t.String())
170 // c.WriteMessage(websocket.TextMessage, []byte(`[TTS]:你是谁?`))
171 case <-interrupt:
172 log.Println("interrupt")
173
174 // Cleanly close the connection by sending a close message and then
175 // waiting (with timeout) for the server to close the connection.
176 err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
177 if err != nil {
178 log.Println("write close:", err)
179 return
180 }
181 select {
182 case <-done:
183 case <-time.After(time.Second):
184 }
185 return
186 }
187 }
188}
进阶:常见问题及解决办法
海外节点需要设置修改对应的接入点如下:
| 可用区域 | 接入地址 | 说明 |
|---|---|---|
| 中国大陆 | wss://rtc-aiotgw.exp.bcelive.com/v1/realtime | |
| 中国香港 | wss://rtc-out-aiotgw.exp.bcelive.com/v1/realtime | |
| 美国 | ws://biolifeagent.com:8654/v1/realtime | 美国节点在测试阶段,如有问题请联系销售人员 |
语音识别不准确,一般是MIC收音问题:噪音或杂音太大,或者是声音能量值偏小(建议声音能量均值在40~50%),建议在端上优化降噪或增益效果;如端侧无法处理可尝试开启云上3A功能优化。
方法1:设备端上调优降噪ANS、增益AGC
./src/brtc_esp32_chat_agent_demo.c通过es7210_adc_set_gain调整MIC的增益参数。
1# 调整增益接口
2static int create_rec_pipeline(void)
3{
4...
5 es7210_adc_set_gain(ES7210_INPUT_MIC3, GAIN_0DB);
6 es7210_adc_set_gain(ES7210_INPUT_MIC2 | ES7210_INPUT_MIC1, GAIN_33DB); //调整增益接口函数,一般设置GAIN_33DB
7...
8}
方法2:云上调优自动降噪ANS、自动增益AGC
- 开启云上自动降噪与自动增益,请参见云端音频3A功能最佳实践
-
问题:端设备自言自语对话(播放的声音,回采后重新回答)
这种现象一般是回声消除异常导致,建议在端上优化AEC,端侧无法处理可尝试云上优化;或是采用采用单工对话方式。
方法1:设备上调优回声消除AEC
./src/brtc_esp32_chat_agent_demo.c通过调用audio_pipeline_register注册函数可以打开AEC功能。
1# 调整增益接口
2static int create_rec_pipeline(void)
3{
4...
5 const char *link_tag[] = { "aec", "raw"};
6 audio_pipeline_register(rec_res.rec_pipe, rec_res.aec_stream , link_tag[0]); //打开AEC接口函数
7 audio_pipeline_register(rec_res.rec_pipe, rec_res.raw_stream , link_tag[1]);
8...
9}
方法2:云上调优回声消除AEC
- 开启云上回声消除AEC功能,请参见云端音频3A功能最佳实践
方法3:切换到对话模式:按键说话、连续对话(唤醒词打断)
按键说话、连续对话(唤醒词打断),这两种是单工对话,不依赖回声消除功能,可解决这个问题。 可参考:语音对话集成模式介绍
在控制台上配置人设,高质量编写人设提示词
如何使用已定义的人设
创建智能体指定已定义的人设
1 - 在服务器上调用generateAIAgentCall接口,传入config参数`sceneRole:人设名称`。 人设名称就是控制台定义的人设名称。
动态切换已定义的人设及音色
在服务器上调用operation?switchSceneRole接口, 指定对应的人设名称、音色名。
-
设备未授权提示及处理办法
设备连接到服务器后,服务器会下发鉴权请求,如鉴权失败,设备有未授权提示,且服务器主动与设备断开连接。造成这个原因一般有3种情况:
Plain Text1* 设备连接服务未正确传入license、DeviceID、UserID信息,或者license无效。 注意:DeviceID可能是SDK自动获取硬件码,无需填写;Websocket对接则需显示传入。DeviceID、UserID要求appid纬度唯一。 2* 网络原因导致服务端未及时收到鉴权的响应。 建议切换到良好的网络。 3* 多台设备非法共用同一个license。 云端会检测到异常,主动断开设备连接。 -
判断对话响应延时正常与否,及优化建议
-
更多的资料从哪里找
- 集成流程完整介绍
- 声纹使用最佳实践
- 定时器使用最佳实践
- 多语言使用最佳实践
- 产品功能特性表介绍
- 更多的服务端接口
