Unity之使用火山引擎实现流式语音合成|优化版本

作者:心前阳光日期:2026/6/23

初始版本

Unity之使用火山引擎实现流式语音合成

优化版本

优化内容:

  1. 原始版本使用NativeWebSocket插件,无法获取X-Tt-Logid,使用WebSocketSharp插件解决这个问题
  2. 提取两个插件的共性,可依据需要替换

示例

通信类

websocket通信基类

1using UnityEngine;
2using System;
3/// <summary>
4/// 文字转语音
5/// </summary>
6public abstract class BaseTextToSpeech : MonoBehaviour
7{
8    public event Action OnConnectWeb;
9    public event Action OnConnectTTSSucess;
10
11    public event Action OnCreateSessionSucess;
12    public event Action OnSessionFinished;
13
14    public event Action OnSentenceStart;
15    public event Action OnSentenceEnd;
16
17    public event Action<byte[]> OnReceiveAudio;
18
19    public abstract void WebSocketConnect();
20
21    public abstract void WebSocketDisconnect();
22
23    public abstract void BeginConnectTTS();
24
25    public abstract void BeginSession();
26
27    public abstract void Session(string info);
28
29    public abstract void EndSession();
30
31    public abstract void EndConnectTTS();
32
33    protected void InvokeConnectWeb()
34    {
35        OnConnectWeb?.Invoke();
36    }
37    protected void InvokeConnectTTSSucess()
38    {
39        OnConnectTTSSucess?.Invoke();
40    }
41    protected void InvokeCreateSessionSucess()
42    {
43        OnCreateSessionSucess?.Invoke();
44    }
45    protected void InvokeSessionFinished()
46    {
47        OnSessionFinished?.Invoke();
48    }
49    protected void InvokeSentenceStart()
50    {
51        OnSentenceStart?.Invoke();
52    }
53    protected void InvokeSentenceEnd()
54    {
55        OnSentenceEnd?.Invoke();
56    }
57    protected void InvokeReceiveAudio(byte[] data)
58    {
59        OnReceiveAudio?.Invoke(data);
60    }
61}
62

使用NativeWebSocket插件

1using UnityEngine;
2using NativeWebSocket;
3using System;
4using System.Collections.Generic;
5/// <summary>
6/// 文字转语音 
7/// NativeSocket
8/// </summary>
9public class TextToSpeech : BaseTextToSpeech
10{
11    string wss = "wss://openspeech.bytedance.com/api/v3/tts/bidirection";
12    string XApiAppId = "";
13    string XApiAccessKey = "";
14    string XApiResourceId = "seed-tts-2.0";
15    [Header("发音人")]
16    [SerializeField] string speaker = "zh_female_vv_uranus_bigtts";
17    [Header("采样率")]
18    [SerializeField] int sampleRate = 24000;
19
20    WebSocket webSocket;
21    string currentSessionId = null;
22
23    public override async void WebSocketConnect()
24    {
25        //请求参数
26        var id = Guid.NewGuid().ToString();
27
28        //请求头
29        var headers = new Dictionary<string, string>();
30        headers.Add("X-Api-App-Key", XApiAppId);
31        headers.Add("X-Api-Access-Key", XApiAccessKey);
32        headers.Add("X-Api-Resource-Id", XApiResourceId);
33        headers.Add("X-Api-Connect-Id", id);
34
35        //回调注册
36        webSocket = new WebSocket(wss, headers);
37        webSocket.OnError += OnWebSocketError;
38        webSocket.OnClose += OnWebSocketClose;
39        webSocket.OnOpen += OnWebSocketOpen;
40        webSocket.OnMessage += OnReceiveWebSocketMessage;
41
42        await webSocket.Connect();
43    }
44
45    public override async void WebSocketDisconnect()
46    {
47        if (webSocket != null)
48        {
49            webSocket.OnError -= OnWebSocketError;
50            webSocket.OnClose -= OnWebSocketClose;
51            webSocket.OnOpen -= OnWebSocketOpen;
52            webSocket.OnMessage -= OnReceiveWebSocketMessage;
53            try
54            {
55                await webSocket.Close();
56            }
57            catch (System.Exception e)
58            {
59                Debug.LogError(e.Message);
60            }
61        }
62        webSocket = null;
63    }
64
65    public override void BeginConnectTTS()
66    {
67        var msg = new Speech.Protocols.Message
68        {
69            MsgType = Speech.Protocols.MsgType.FullClientRequest,
70            MsgTypeFlag = Speech.Protocols.MsgTypeFlagBits.WithEvent,
71            EventType = Speech.Protocols.EventType.StartConnection,
72            Payload = System.Text.Encoding.UTF8.GetBytes("{}"),
73        };
74        Send(msg.Marshal());
75    }
76
77    public override void BeginSession()
78    {
79        currentSessionId = Guid.NewGuid().ToString();
80        var payload = new SessionPayload
81        {
82            user = new UserInfo { uid = Guid.NewGuid().ToString() },
83            @event = (int)Speech.Protocols.EventType.StartSession,
84            req_params = new ReqParams
85            {
86                speaker = speaker,
87                text = "",
88                audio_params = new AudioParams
89                {
90                    format = "pcm",
91                    sample_rate = sampleRate
92                },
93            }
94        };
95        string json = JsonUtility.ToJson(payload);
96        byte[] payloadBytes = System.Text.Encoding.UTF8.GetBytes(json);
97
98        var msg = new Speech.Protocols.Message
99        {
100            MsgType = Speech.Protocols.MsgType.FullClientRequest,
101            MsgTypeFlag = Speech.Protocols.MsgTypeFlagBits.WithEvent,
102            EventType = Speech.Protocols.EventType.StartSession,
103            SessionId = currentSessionId,
104            Payload = payloadBytes
105        };
106        Send(msg.Marshal());
107    }
108
109    public override void Session(string info)
110    {
111        var taskRequest = new SessionPayload();
112        taskRequest.req_params = new ReqParams();
113        taskRequest.req_params.audio_params = new AudioParams
114        {
115            format = "pcm",
116            sample_rate = 24000
117        };
118        taskRequest.req_params.addtions = new Addtions()
119        {
120            disable_markdown_filter = true,
121        };
122        taskRequest.req_params.text = info;
123        taskRequest.user = new UserInfo() { uid = Guid.NewGuid().ToString() };
124        taskRequest.@event = (int)Speech.Protocols.EventType.TaskRequest;
125
126        string json = JsonUtility.ToJson(taskRequest);
127        byte[] payloadBytes = System.Text.Encoding.UTF8.GetBytes(json);
128        var msg = new Speech.Protocols.Message
129        {
130            MsgType = Speech.Protocols.MsgType.FullClientRequest,
131            MsgTypeFlag = Speech.Protocols.MsgTypeFlagBits.WithEvent,
132            EventType = Speech.Protocols.EventType.TaskRequest,
133            SessionId = currentSessionId,
134            Payload = payloadBytes
135        };
136        Send(msg.Marshal());
137    }
138
139    public override void EndSession()
140    {
141        byte[] payloadBytes = System.Text.Encoding.UTF8.GetBytes("{}");
142        var msg = new Speech.Protocols.Message
143        {
144            MsgType = Speech.Protocols.MsgType.FullClientRequest,
145            MsgTypeFlag = Speech.Protocols.MsgTypeFlagBits.WithEvent,
146            EventType = Speech.Protocols.EventType.FinishSession,
147            SessionId = currentSessionId,
148            Payload = payloadBytes
149        };
150        Send(msg.Marshal());
151    }
152
153    public override void EndConnectTTS()
154    {
155        byte[] payloadBytes = System.Text.Encoding.UTF8.GetBytes("{}");
156        var msg = new Speech.Protocols.Message
157        {
158            MsgType = Speech.Protocols.MsgType.FullClientRequest,
159            MsgTypeFlag = Speech.Protocols.MsgTypeFlagBits.WithEvent,
160            EventType = Speech.Protocols.EventType.FinishConnection,
161            Payload = payloadBytes
162        };
163        Send(msg.Marshal());
164    }
165
166    void OnWebSocketError(string info)
167    {
168        Debug.LogError("错误信息:" + info);
169    }
170
171    void OnWebSocketClose(WebSocketCloseCode closeCode)
172    {
173        Debug.Log("关闭:" + closeCode);
174    }
175
176    void OnWebSocketOpen()
177    {
178       InvokeConnectWeb();
179    }
180
181    void OnReceiveWebSocketMessage(byte[] data)
182    {
183        try
184        {
185            var msg = Speech.Protocols.Message.FromBytes(data);
186            Debug.Log($"接收语音合成消息:{msg.MsgType},{msg.EventType},{msg.Payload.Length}");
187            //接受音频数据
188            if (msg.MsgType == Speech.Protocols.MsgType.AudioOnlyServer)
189            {
190                Debug.Log("接收到音频消息");
191                if (msg.Payload != null && msg.Payload.Length > 0)
192                {
193                    Debug.Log("音频数据回调");
194                    //OnReceiveAuido?.Invoke(msg.Payload);
195                    InvokeReceiveAudio(msg.Payload);
196                }
197            }
198
199            if (msg.MsgType == Speech.Protocols.MsgType.Error)
200            {
201                string reason = msg.Payload.Length > 0
202          ? System.Text.Encoding.UTF8.GetString(msg.Payload)
203          : "未知错误";
204                Debug.LogError($"{msg.EventType}: {reason}");
205            }
206
207            switch (msg.EventType)
208            {
209                case Speech.Protocols.EventType.ConnectionStarted:
210                    //Debug.Log("连接成功,可以发送 StartSession");
211                    InvokeConnectTTSSucess();
212                    break;
213                case Speech.Protocols.EventType.SessionStarted:
214                    //Debug.Log("会话已启动,可以发送 TaskRequest");
215                    InvokeCreateSessionSucess();
216                    break;
217                case Speech.Protocols.EventType.SessionFinished:
218                    //Debug.Log("会话结束");
219                    InvokeSessionFinished();
220                    break;
221                case Speech.Protocols.EventType.TTSSentenceStart:
222                    InvokeSentenceStart();//句子开始
223                    break;
224                case Speech.Protocols.EventType.TTSSentenceEnd:
225                    InvokeSentenceEnd();//句子结束
226                    break;
227                case Speech.Protocols.EventType.ConnectionFailed:
228                case Speech.Protocols.EventType.SessionFailed:
229                    string reason = msg.Payload.Length > 0
230                        ? System.Text.Encoding.UTF8.GetString(msg.Payload)
231                        : "未知错误";
232                    Debug.LogError($"{msg.EventType}: {reason}");
233                    break;
234            }
235        }
236        catch (Exception e)
237        {
238            Debug.LogError("接受消息:" + e.Message);
239        }
240    }
241
242    async void Send(byte[] bytes)
243    {
244        if (webSocket.State == WebSocketState.Open)
245            await webSocket.Send(bytes);
246    }
247
248    [System.Serializable]
249    public class SessionPayload
250    {
251        public UserInfo user;
252        public int @event;
253        public ReqParams req_params;
254    }
255
256    [System.Serializable]
257    public class UserInfo
258    {
259        public string uid;
260    }
261
262    [System.Serializable]
263    public class ReqParams
264    {
265        public string speaker;
266        public string text;                // 可选,可以先不填或空字符串
267        public AudioParams audio_params;
268        public Addtions addtions;
269    }
270
271    [System.Serializable]
272    public class AudioParams
273    {
274        public string format;
275        public int sample_rate;
276    }
277
278    [System.Serializable]
279    public class Addtions
280    {
281        public bool disable_markdown_filter = true;
282    }
283}
284

使用WebSocketSharp插件

1using UnityEngine;
2using System;
3using WebSocketSharp;
4/// <summary>
5/// 文字转语音 
6/// websocket-sharp
7/// </summary>
8public class TextToSpeechSharp : BaseTextToSpeech
9{
10    string wss = "wss://openspeech.bytedance.com/api/v3/tts/bidirection";
11    string XApiAppId = "";
12    string XApiAccessKey = "";
13    string XApiResourceId = "seed-tts-2.0";
14    [Header("发音人")]
15    [SerializeField] string speaker = "zh_female_vv_uranus_bigtts";
16    [Header("采样率")]
17    [SerializeField] int sampleRate = 24000;
18
19    WebSocket webSocket;
20    string currentSessionId = null;
21
22    public override void WebSocketConnect()
23    {
24        //请求参数
25        var id = Guid.NewGuid().ToString();
26
27        //回调注册
28        webSocket = new WebSocket(wss);
29        //请求头
30        webSocket.SetUserHeader("X-Api-App-Key", XApiAppId);
31        webSocket.SetUserHeader("X-Api-Access-Key", XApiAccessKey);
32        webSocket.SetUserHeader("X-Api-Resource-Id", XApiResourceId);
33        webSocket.SetUserHeader("X-Api-Connect-Id", id);
34
35        webSocket.OnError += OnWebSocketError;
36        webSocket.OnClose += OnWebSocketClose;
37        webSocket.OnOpen += OnWebSocketOpen;
38        webSocket.OnMessage += OnReceiveWebSocketMessage;
39
40        webSocket.Connect();
41    }
42
43    public override void WebSocketDisconnect()
44    {
45        if (webSocket != null)
46        {
47            webSocket.OnError -= OnWebSocketError;
48            webSocket.OnClose -= OnWebSocketClose;
49            webSocket.OnOpen -= OnWebSocketOpen;
50            webSocket.OnMessage -= OnReceiveWebSocketMessage;
51            try
52            {
53                webSocket.Close();
54            }
55            catch (System.Exception e)
56            {
57                Debug.LogError(e.Message);
58            }
59        }
60        webSocket = null;
61    }
62
63    public override void BeginConnectTTS()
64    {
65        var msg = new Speech.Protocols.Message
66        {
67            MsgType = Speech.Protocols.MsgType.FullClientRequest,
68            MsgTypeFlag = Speech.Protocols.MsgTypeFlagBits.WithEvent,
69            EventType = Speech.Protocols.EventType.StartConnection,
70            Payload = System.Text.Encoding.UTF8.GetBytes("{}"),
71        };
72        Send(msg.Marshal());
73    }
74
75    public override void BeginSession()
76    {
77        currentSessionId = Guid.NewGuid().ToString();
78        var payload = new SessionPayload
79        {
80            user = new UserInfo { uid = Guid.NewGuid().ToString() },
81            @event = (int)Speech.Protocols.EventType.StartSession,
82            req_params = new ReqParams
83            {
84                speaker = speaker,
85                text = "",
86                audio_params = new AudioParams
87                {
88                    format = "pcm",
89                    sample_rate = sampleRate
90                },
91            }
92        };
93        string json = JsonUtility.ToJson(payload);
94        byte[] payloadBytes = System.Text.Encoding.UTF8.GetBytes(json);
95
96        var msg = new Speech.Protocols.Message
97        {
98            MsgType = Speech.Protocols.MsgType.FullClientRequest,
99            MsgTypeFlag = Speech.Protocols.MsgTypeFlagBits.WithEvent,
100            EventType = Speech.Protocols.EventType.StartSession,
101            SessionId = currentSessionId,
102            Payload = payloadBytes
103        };
104        Send(msg.Marshal());
105    }
106
107    public override void Session(string info)
108    {
109        var taskRequest = new SessionPayload();
110        taskRequest.req_params = new ReqParams();
111        taskRequest.req_params.audio_params = new AudioParams
112        {
113            format = "pcm",
114            sample_rate = 24000
115        };
116        taskRequest.req_params.addtions = new Addtions()
117        {
118            disable_markdown_filter = true,
119        };
120        taskRequest.req_params.text = info;
121        taskRequest.user = new UserInfo() { uid = Guid.NewGuid().ToString() };
122        taskRequest.@event = (int)Speech.Protocols.EventType.TaskRequest;
123
124        string json = JsonUtility.ToJson(taskRequest);
125        byte[] payloadBytes = System.Text.Encoding.UTF8.GetBytes(json);
126        var msg = new Speech.Protocols.Message
127        {
128            MsgType = Speech.Protocols.MsgType.FullClientRequest,
129            MsgTypeFlag = Speech.Protocols.MsgTypeFlagBits.WithEvent,
130            EventType = Speech.Protocols.EventType.TaskRequest,
131            SessionId = currentSessionId,
132            Payload = payloadBytes
133        };
134        Send(msg.Marshal());
135    }
136
137    public override void EndSession()
138    {
139        byte[] payloadBytes = System.Text.Encoding.UTF8.GetBytes("{}");
140        var msg = new Speech.Protocols.Message
141        {
142            MsgType = Speech.Protocols.MsgType.FullClientRequest,
143            MsgTypeFlag = Speech.Protocols.MsgTypeFlagBits.WithEvent,
144            EventType = Speech.Protocols.EventType.FinishSession,
145            SessionId = currentSessionId,
146            Payload = payloadBytes
147        };
148        Send(msg.Marshal());
149    }
150
151    public override void EndConnectTTS()
152    {
153        byte[] payloadBytes = System.Text.Encoding.UTF8.GetBytes("{}");
154        var msg = new Speech.Protocols.Message
155        {
156            MsgType = Speech.Protocols.MsgType.FullClientRequest,
157            MsgTypeFlag = Speech.Protocols.MsgTypeFlagBits.WithEvent,
158            EventType = Speech.Protocols.EventType.FinishConnection,
159            Payload = payloadBytes
160        };
161        Send(msg.Marshal());
162    }
163
164    void OnWebSocketError(object sender, ErrorEventArgs args)
165    {
166        Debug.LogError("错误信息:" + args.Message);
167    }
168
169    void OnWebSocketClose(object sender, CloseEventArgs args)
170    {
171        Debug.Log("关闭:" + args.Code);
172    }
173
174    void OnWebSocketOpen(object sender, EventArgs args)
175    {
176        var headers = webSocket.HandshakeResponseHeaders;
177        Debug.Log(("X-Tt-Logid",headers["X-Tt-Logid"]));
178        InvokeConnectWeb();
179    }
180
181    void OnReceiveWebSocketMessage(object sender, MessageEventArgs args)
182    {
183        try
184        {
185            var msg = Speech.Protocols.Message.FromBytes(args.RawData);
186            Debug.Log($"接收语音合成消息:{msg.MsgType},{msg.EventType},{msg.Payload.Length}");
187            //接受音频数据
188            if (msg.MsgType == Speech.Protocols.MsgType.AudioOnlyServer)
189            {
190                Debug.Log("接收到音频消息");
191                if (msg.Payload != null && msg.Payload.Length > 0)
192                {
193                    Debug.Log("音频数据回调");
194                    //OnReceiveAuido?.Invoke(msg.Payload);
195                    InvokeReceiveAudio(msg.Payload);
196                }
197            }
198
199            if (msg.MsgType == Speech.Protocols.MsgType.Error)
200            {
201                string reason = msg.Payload.Length > 0
202          ? System.Text.Encoding.UTF8.GetString(msg.Payload)
203          : "未知错误";
204                Debug.LogError($"{msg.EventType}: {reason}");
205            }
206
207            switch (msg.EventType)
208            {
209                case Speech.Protocols.EventType.ConnectionStarted:
210                    //Debug.Log("连接成功,可以发送 StartSession");
211                    InvokeConnectTTSSucess();
212                    break;
213                case Speech.Protocols.EventType.SessionStarted:
214                    //Debug.Log("会话已启动,可以发送 TaskRequest");
215                    InvokeCreateSessionSucess();
216                    break;
217                case Speech.Protocols.EventType.SessionFinished:
218                    //Debug.Log("会话结束");
219                    InvokeSessionFinished();
220                    break;
221                case Speech.Protocols.EventType.TTSSentenceStart:
222                    InvokeSentenceStart();//句子开始
223                    break;
224                case Speech.Protocols.EventType.TTSSentenceEnd:
225                    InvokeSentenceEnd();//句子结束
226                    break;
227                case Speech.Protocols.EventType.ConnectionFailed:
228                case Speech.Protocols.EventType.SessionFailed:
229                    string reason = msg.Payload.Length > 0
230                        ? System.Text.Encoding.UTF8.GetString(msg.Payload)
231                        : "未知错误";
232                    Debug.LogError($"{msg.EventType}: {reason}");
233                    break;
234            }
235        }
236        catch (Exception e)
237        {
238            Debug.LogError("接受消息:" + e.Message);
239        }
240    }
241
242    void Send(byte[] bytes)
243    {
244        if (webSocket.ReadyState == WebSocketState.Open)
245            webSocket.Send(bytes);
246    }
247
248    [System.Serializable]
249    public class SessionPayload
250    {
251        public UserInfo user;
252        public int @event;
253        public ReqParams req_params;
254    }
255
256    [System.Serializable]
257    public class UserInfo
258    {
259        public string uid;
260    }
261
262    [System.Serializable]
263    public class ReqParams
264    {
265        public string speaker;
266        public string text;                // 可选,可以先不填或空字符串
267        public AudioParams audio_params;
268        public Addtions addtions;
269    }
270
271    [System.Serializable]
272    public class AudioParams
273    {
274        public string format;
275        public int sample_rate;
276    }
277
278    [System.Serializable]
279    public class Addtions
280    {
281        public bool disable_markdown_filter = true;
282    }
283}
284

语音合成流程类

连接TTS服务器成功后,不需要立即创建会话。

1using System.Text;
2using UnityEngine;
3
4public class TextToSpeechOut : MonoBehaviour
5{
6    [Header("文字合成语音")]
7    [SerializeField] BaseTextToSpeech textToSpeech;
8
9    [Header("TTS音频流播放")]
10    [SerializeField] TTSAudioStreamPlay tTSAudioStreamPlay;
11
12    bool hasSession = false;//会话存在
13    bool isCreateSession;//创建会话
14    StringBuilder sentenceBuffer;//缓存文本
15
16    bool receivedAudioData = false;
17    bool requestEndSession = false;
18    float endSessionTimer = 0;
19    [Header("结束会话等待时间")]
20    [SerializeField] float endSessionRequestTime = 2f;
21
22
23    void Awake()
24    {
25        tTSAudioStreamPlay.CreateAudioStream();
26        sentenceBuffer = new StringBuilder();
27        textToSpeech.OnConnectWeb += OnConnectWeb;
28        textToSpeech.OnConnectTTSSucess += OnConnectTTSSucess;
29        textToSpeech.OnCreateSessionSucess += OnCreateSession;
30        textToSpeech.OnSessionFinished += OnSessionFinish;
31        textToSpeech.OnReceiveAudio += OnReceiveData;
32        textToSpeech.WebSocketConnect();
33    }
34
35    void OnDestroy()
36    {
37        tTSAudioStreamPlay.ReleaseAudioStream();
38        textToSpeech.OnConnectWeb -= OnConnectWeb;
39        textToSpeech.OnConnectTTSSucess -= OnConnectTTSSucess;
40        textToSpeech.OnCreateSessionSucess -= OnCreateSession;
41        textToSpeech.OnSessionFinished -= OnSessionFinish;
42        textToSpeech.OnReceiveAudio -= OnReceiveData;
43        textToSpeech.WebSocketDisconnect();
44    }
45
46    void OnConnectWeb()
47    {
48        Debug.Log("连接服务器成功,开始连接TTS");
49        textToSpeech.BeginConnectTTS();
50    }
51
52    void OnConnectTTSSucess()
53    {
54        Debug.Log("连接TTS成功");
55        //避免重复建立会话 
56        //5s内不发送文本,服务器不返回音频数据,超出5s,再次发送也不会返回音频数据
57        //会话10s内有效,不提前建立会话,需要时建立
58        // if (hasSession == false && isCreateSession == false)
59        // {
60        //     isCreateSession = true;
61        //     //请求建立会话
62        //     textToSpeech.BeginSession();
63        // }
64    }
65
66    void OnCreateSession()
67    {
68        Debug.Log("建立会话成功");
69        hasSession = true;
70        isCreateSession = false;
71
72        // 重置所有会话状态
73        receivedAudioData = false;
74        requestEndSession = false;
75        endSessionTimer = 0f;
76
77        //发送缓存内容
78        if (sentenceBuffer.Length > 0)
79        {
80            var content = sentenceBuffer.ToString();
81            sentenceBuffer.Clear();
82            Debug.Log("Send TaskRequest: " + content);
83            textToSpeech.Session(content);
84        }
85    }
86
87    void OnSessionFinish()
88    {
89        Debug.Log("会话结束");
90        hasSession = false;
91        isCreateSession = false;
92        receivedAudioData = false;
93        requestEndSession = false;
94        endSessionTimer = 0;
95    }
96
97    void OnReceiveData(byte[] data)
98    {
99        receivedAudioData = true;
100        tTSAudioStreamPlay.WriteAudioData(data);
101    }
102
103    public void ReadText(string text)
104    {
105        if (hasSession)
106        {
107            Debug.Log("Send TaskRequest: " + text);
108            textToSpeech.Session(text);
109        }
110        else
111        {
112            //缓存文本
113            sentenceBuffer.Append(text);
114            //避免重复建立会话
115            if (isCreateSession == false)
116            {
117                isCreateSession = true;
118                //请求建立会话
119                textToSpeech.BeginSession();
120            }
121        }
122    }
123
124    public void EndReadText()
125    {
126        if (hasSession)
127        {
128            endSessionTimer = 0;
129            requestEndSession = true;
130            //请求结束会话
131            //textToSpeech.EndSession();
132        }
133    }
134
135    void Update()
136    {
137        if (requestEndSession)
138        {
139            if (receivedAudioData)
140            {
141                requestEndSession = false;
142                //请求结束会话
143                textToSpeech.EndSession();
144            }
145            else
146            {
147                endSessionTimer += Time.deltaTime;
148                if (endSessionTimer >= endSessionRequestTime)
149                {
150                    requestEndSession = false;
151                    endSessionTimer = 0;
152                    //请求结束会话
153                    textToSpeech.EndSession();
154                }
155            }
156        }
157    }
158}
159
160

Unity之使用火山引擎实现流式语音合成|优化版本》 是转载文章,点击查看原文


相关推荐


LLM —— 多模态(文本、图片、音频、视频)
kishu_iOS&AI2026/6/15

目录 一、什么是多模态 二、常见模态分类 三、多模态原理 单模态 vs 多模态 四、文本 五、图片 核心原理: 流程:         ① 图像预处理(统一输入格式)         ② 加载预训练视觉编码器         ③ 前向传播,抽取特征向量         ④ 向量归一化(L2 归一化,必做)         ⑤ 持久化存储 方案代码: 关键细节         ① 向量维度怎么选?         ② 相似度计算方式 容易出错点 其他轻量化


别再乱套ScrollViewer了!WPF中ItemsControl滚动条失效的3个隐藏坑与终极修复方案
许筱淳2026/6/8

WPF中ItemsControl滚动条失效的深度剖析与实战解决方案 当你在WPF项目中遇到ItemsControl滚动条"罢工"时,是否也经历过这样的场景:明明按照标准做法添加了ScrollViewer,滚动条却像被施了隐身术一样消失不见?或者鼠标滚轮在嵌套控件中滑动时毫无反应?这些问题往往源于WPF视觉树和事件路由机制的深层特性。本文将带你深入这些"坑"的本质,并提供真正有效的解决方案。 1. 为什么简单的ScrollViewer包裹会失效? 许多开发者第一次遇到ItemsControl


python爬虫-基本库-urllib库(常用速查)
bigfootyazi2026/6/1

urllib库 库的用途四个基本模块request模块error模块parse模块robotparser模块 库的用途 实现HTTP请求的发送 扩展:基本HTTP库有urllib、requests、httpx等 四个基本模块 request 基本的HTTP请求模块error 异常处理模块parse 工具模块,提供URL的处理方法robotparser 识别网站的robost.txt文件 request模块 urlopen (function) def urlopen(


📱随时随地大小编:TraeSolo 移动端初体验
海石2026/5/12

1、前言 最早认识Trae SOLO 还是在Trae IDE 内测 SOLO 模式的时候 (大概是2025年的11月) 这一转眼,Trae SOLO 居然都已经单独出了 桌面端 和 移动端 了 除了劳动节那阵联动星巴克☕吸引我之外不是 咳咳,最有魅力的还是双端协同这一点上 本文就是一篇“利用Trae SOLO的双端协同能力,进行随时随地开发微信小程序”的实践分享文章, 非常欢迎掘友们一起交流交流,共同探讨Trae SOLO的最佳使用“范式”~ 2、结果先行 1、移动端可以随时查看微信


window管理开发环境篇 - 持续更新
pe7er2026/5/2

我个人非常喜欢那种一键部署开发环境的方式,但时间一长,我们会淡忘如何部署开发环境,它会让我们失去对开发环境的控制。 下面我记录window环境下我是如何管理开发环境的。 安装Scoop 设置前提条件 Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser 典型安装 irm get.scoop.sh | iex 使用代理安装 iex "& {$(irm get.scoop.sh -Proxy 'http://<ip:


NineData 新增支持 GaussDB 到 StarRocks 实时数据复制能力
NineData2026/4/23

很多企业在完成核心系统国产化之后,业务已经稳定跑在 GaussDB 上,但很快会进入下一阶段:经营分析、实时看板、主题查询、风控报表、数据服务层都需要尽快接上。 如何实现呢?把业务数据实时复制到数仓即可。但通常会有如下挑战: 历史数据需要快速初始化到位。 业务持续写入时,目标端要持续、稳定地追平变化。 任务运行出了问题,要能第一时间感知,如果等到下游发现数据不对那就晚了。 正式上线前,要能全自动化对复制结果做核验,人工抽样费时费力还容易出错。 这个时候,一条能长期稳定的实时数据复制链路就很


告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
GetcharZp2026/4/14

想用 Go 语言开发大模型应用却找不到好用的框架?本文深度解析 LangChainGo,手把手教你快速上手,涵盖 RAG、智能体等核心场景,助你轻松跨入 AI 开发大门! 在人工智能大行其道的今天,提到 LLM(大语言模型)应用开发,很多人脑海中浮现的第一反应就是 Python。确实,Python 拥有得天独厚的生态。但随着 AI 应用进入“工程化”下半场,开发者们开始面临新的挑战:并发性能瓶颈、部署环境复杂、内存消耗大…… 这时候,Go 语言的优势便凸显了出来。其天生的并发处理能力(Gor


**发散创新:基于以太坊侧链的高性能去中心化应用部署实战**在区块链生态中,*
好家伙VCC2026/4/6

发散创新:基于以太坊侧链的高性能去中心化应用部署实战 在区块链生态中,主链性能瓶颈一直是制约大规模 DApp 发展的核心问题。为突破这一限制,8*侧链(Sidechain)技术应运而生**,它通过与主链的安全通信机制,在保证去中心化前提下实现高吞吐量和低延迟交易处理。 本文将以 Solidity + Golang + Polygon SDK 为例,构建一个完整的侧链开发流程,并展示如何将智能合约部署到自定义侧链节点上,同时确保与 Ethereum 主网的状态同步验证。 🔧 一、为什么


彻底搞懂大模型 Temperature、Top-p、Top-k 的区别!
Surmon2026/3/29

调用大模型的时候,总会看到几个耳熟能详的参数:Temperature、Top-p、Top-k。文档里通常的解释都是:控制输出的随机性。也就是:Temperature 和 Top-p 的值越高,模型输出的结果会越随机、越富有创造性,反之,数值越低,输出的结果就越确定、越保守。 随机性,到底是个什么意思?为啥随机性就可以表现为创造性? 回答这个问题,得先从一个最朴素的问题开始:模型是如何回答问题的。 我之前在 《从统计学习到通用智能》 中曾经提到过,大模型在输出文本的时候,本质上是在 滚动地预测下


当我开始像写代码一样和AI对话,一切都变了
lbh2026/3/21

当AI成为你的开发伙伴,如何让它真正听懂你的需求? 身为一名前端开发。在日常开发中,我经常和AI打交道——用它写代码、调试bug、优化性能、设计方案。但说实话,很长一段时间里,我和AI的对话就像这样: 我:“帮我写一个响应式导航栏。” AI:(给出一个基础版) 我:“不是这样的,要带下拉菜单。” AI:(加上下拉菜单) 我:“还要在移动端变成汉堡菜单。” AI:(加上汉堡菜单) 我:“……能不能一次说完?” 你是不是也有类似的经历? 后来我慢慢发现,问题不在AI,而在我自己。就像写代码一样

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读