GPT-SoVITS
GPT-SoVITS- v2(v3也可以,两者对模型文件具有兼容)
点击后 会进入新的游览器网页
-----
看了一圈,发现主要问题集中在模型的训练很需要CPU,也就是模型的制作上,问题很多,如果有现有的模型,直接引用,使用“推理”即可
基于GPT-SoVITS一键包的合成语音基础 - 哔哩哔哩
就比如用GPT-SoVITS 一色彩羽语音模型分享_哔哩哔哩_bilibili
这个up主练出来的模型
自己要做的其实就很少了
导入之后重启webui.bat
再开启TTS推理WebUI,就进入了UI界面,自己点击交互设置,然后产出结果可以直接用
但这是SoVITS的界面交互,想在unity里使用,必须要用提供的API(不同于网络API的本地API,不需要联网,可以直接处理)
【unity+gpt-sovits接口集成】AI二次元小姐姐项目集成gpt-sovits的api模块,可以实现局域网的丝滑互动了_哔哩哔哩_bilibili
这个up的项目现在也在维护,进项目里了解是可以的,但是在里面的使用过程总是有一些报错,想解决比较麻烦
但是还是可以了解重要的区域是哪一块,哪里要改
而且up对功能的拆分也很不错,LLM类和TTS类分开,大概率是用了继承,然后下面的多个模型都可以使用,在各自里写网络传递请求的方法
---
GPT-SoVITS教程5-如何调用API_哔哩哔哩_bilibili
ViTS的Api调用,是和webui分开的一种更偏向接口功能切割,整块提供的,所以只需要api.bat文件打开,就可以连接api了
对于这个http
http://localhost:9880/docs用这个代替才能进fastapi
然后就进入了api的模块,,这里有参数设置然后自动生成请求格式的
这里的路径是可以填绝对路径的
prompt都是传入的材料的
之后的text则是需要生成的文本、语言(再后面的可以调整也可以不动)
api的格式,,在这生成的是一模一样的
URL 传输时,会对特殊字符进行 百分号编码(Percent-Encoding),比如:
\
(反斜杠) →%5C
:
(冒号) →%3A
- 日文、中文等非 ASCII 字符 会用 UTF-8 编码 转成 %E7%等形式
- 空格 →
%20
所以会出现乱码一样的情况,但是信息没有变
把这一段直接粘贴到游览器,可以直接得到生成 的音频文件,
(换行清晰格式
localhost:9880/
?refer_wav_path=D%3A%5CBaiduNetdiskDownload%5CUnity_Related%5CIroha%5C参考音频%5C测试参考音频%5C001.私としては奉仕部が協力してくれるのが一番%E3%80%80面倒がないんです.wav
&prompt_text=私としては奉仕部が協力してくれるのが一番%E3%80%80面倒がないんです
&prompt_language=日文
&text=コネクタの使い方すら知らないなんてバカなの?
&text_language=日文
&top_k=15
&top_p=1
&temperature=1
&speed=1
TITS里的切分,是为了生成的时候不跳字吞字而切分的
但是默认的基础api里是不切分的,很容易出现问题
那问题来了,我要怎么整合到unity里用,通过这个网站上的这种格式简化肯定不现实
下面是api的注释内容,
基于 HTTP 请求的语音合成 API,支持 GET 和 POST 请求,并返回音频流或 JSON 结果。
在 Unity 里调用它,需要构建 HTTP 请求 并处理 返回的音频数据。
简单测试的,用GET 请求,参数放在 URL 里(可见),要传的数据量少
URL 可直接访问,可以用浏览器打开测试
POST 请求,参数放在请求体里(Body),不会出现在 URL,不能用游览器打开
适用于复杂请求(大数据/JSON)浏览器不会缓存,数据不会暴露在 URL
支持 JSON、文件上传等复杂数据
直接按照api注释里的来不行
在unity中要使用http,在这里改成allow,,默认是为了安全不允许使用的
改了之后,就可以收到并和api产生交互了
本来以为这些填进去的东西一次只能一填,但并不是这样的
这个网站 是api文档,,对传输的输入字符串 的格式规则的介绍
并没有什么很复杂的地方
要做的是在unity的C#脚本里调用传入这段字符串的时候,这段字符串是符合格式的,是可以丢给UnityWebRequest去做访问请求,并且拿到返回的数据的
也就是说,在APi文档介绍下,我复制这种格式,然后把需要的部分变成变量,其实就已经可以做到APi较好的交互了
现在unity里已经可以发出声音了,,不过这个效果的确是一言难尽,
原来那些B站上发视频这么像的,都是自己一句一句挑最好的表现出来,,
那确实很肝,
---
处理GPTAPI的SSE响应流
功能是串通了,可以做到输入文本并且朗读出来了
但是没有使用流, 等待文本彻底发回----将文本整个进行TTS,有较长的等待周期,(对于DeepSeek这点更加严重,GPT回复的相对更快,两者的API文档差距并没有很多,接下来还是以GPT的API调用来develop)
为了改进,所以先去了解一下gpt的api调用里,是怎么规定实现流的传输的
想让GPT一边生成文本,TTS立刻 解析并且读出来
对于这个的解决方案就是多种的
比如GPT每生成一段文本到逗号和句号的位置,TTS就拆开了一段,并且解析成需要播放的资源
GPT生成文本的速度一般快于TTS转--读的速度,那是否需要一个queue来装载后续的每个文件,然后一直播放?这样会不会出现语句衔接不自然的问题?(但现在主要是解决等待问题,这一方面可以不考虑)
--
先是得到SSE的响应,这在OpenAI的API请求上有变化
下面是第一次成功用cmd接收了SSE响应的格式
curl -N -X POST "https://api.openai.com/v1/chat/completions" ^
-x http://127.0.0.1:7890 ^
-H "Authorization: Bearer sk-你的API密钥" ^
-H "Content-Type: application/json" ^
-H "Accept: text/event-stream" ^
-H "Cache-Control: no-cache" ^
-d "{ \"model\": \"gpt-4o\", \"messages\": [ { \"role\": \"user\", \"content\": \"和我讲讲陶喆是谁\" } ], \"stream\": true }"
这里坑最多的还是curl的测试,对语法的忽视,而这一点AI同样经常忽视
换行符,在很多地方粘贴为了美观会自动加上(或者自动删除没有内容的连续空格),这就让curl真的没有用都浑然不知,,比如我粘贴到了CSDN上,然后再复制,就不能在cmd窗口运行了
所以很有必要了解cmd的书写规则
cmd
把Enter
解析为命令的结束换行必须用^(
^
后面不能有多余的空格)
路径中有空格,必须用双引号包裹(
-d
里JSON 里的空格不影响)
curl
允许多个空格,但少部分情况下会出现问题,出现More?的时候才是说明你是想为了美观用 ^换行了,本质上还是一串黏在一起的
在之后的测试里(注意这里^后面的空格是为了分开,在cmd里绝对不能加)
-X POST 并不必要特意声明post请求类型
-N 也不必要特意声明是(逐行读取流式数据,对于curl)
-x http://127.0.0.1:7890 ^ 至关重要 这段对代理的引用,在cmd里curl发送请求给服务器(应该是cmd自己的问题,curl总是不按照代理的来走,手动set Proxy代理也出问题)
-H "Accept: text/event-stream" ^ 这个也并不重要,可以去掉
-H "Cache-Control: no-cache" ^ 这句也不重要(大部分应该都是openai的api网站已经配置好了)
最后保留的的确只有官方文档里的了curl请求格式了(注意一下换行符和空格)
-------curl可以访问之后,是想c#如何可以接收这种持久输出的格式--
可以接受响应了(apikey的粘贴复制容易出现一些莫名其妙的误差,会提示unauthorized就说明响应是没问题的,只是apikey有格式的问题,去了解一下key传过来的复制粘贴会不会额外动里面的数据)
在请求根据官方api格式发了之后,
目前接受响应的办法,但是这些都是一次性发送到unity的,没有做到逐条更新
var response = await httpClient.PostAsync(apiUrl, new StringContent(jsondata, Encoding.UTF8, "application/json"));
Debug.Log(response.StatusCode);
Debug.Log(response.Content.ReadAsStringAsync().Result);
using (var stream = await response.Content.ReadAsStreamAsync())
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
string line;
while ((line = await reader.ReadLineAsync()) != null)
{
if (line.StartsWith("data:"))
{
string data = line.Substring(5);
// 在这里处理接收到的数据
Debug.Log(data);
}
}
}
完结,HttpClient.SendAsync 方法 (System.Net.Http) | Microsoft Learn
的确是因为httpclient本身的方法里没有可以设置接受请求头就返回的方法
要换成SendAsync(HttpRequestMessage requst)
必须 自己构建这个Message的内部消息,让httpclient帮忙发送
整个完整的函数,在各个类功能正常运行的情况下逻辑一定是没问题的(如果有问题就检查自己的httpclient要不要额外设置代理Proxy,还有就是apikey的复制粘贴可能自动修改内容的问题)
async Task GetChatStream(string userInput)
{
// 请求的 JSON 数据
var requestData = new
{
model = "gpt-4",
messages = new[]
{
new { role = "system",content = CharacterSetting },
new { role = "user", content = userInput }
},
stream = true
};
string jsondata = JsonConvert.SerializeObject(requestData);
// Print the complete request in a format that can be used directly with curl
//string curlCommand = $"curl -X POST \"{apiUrl}\" -H \"Authorization: Bearer {apiKey}\" -H \"Content-Type: application/json\" -d '{jsondata}'";
Debug.Log("jsondataBody_Ready");
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, apiUrl)
{
Content = new StringContent(jsondata, Encoding.UTF8, "application/json")
};
httpRequestMessage.Headers.TryAddWithoutValidation("Authorization", $"Bearer {apiKey}");
//*****************************************
var response = await httpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead);
Debug.Log("response.StatusCode:" + response.StatusCode);
//Debug.Log(response.Content.ReadAsStringAsync().Result);
using (var stream = await response.Content.ReadAsStreamAsync())
{
// Debug.Log("stream");
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
// Debug.Log("reader");
string line;
while ((line = await reader.ReadLineAsync()) != null)
{
// Debug.Log("line");
if (line.StartsWith("data:"))
{
string chunkdata = line.Substring(5);
// 在这里处理接收到的数据
var openAIChunk = JsonConvert.DeserializeObject<OpenAIChunk>(chunkdata);
Debug.Log(openAIChunk.Choices[0].Delta.Content);
text.text += openAIChunk.Choices[0].Delta.Content;
}
}
}
}
}
优化整理
需要的功能:玩家的对话,AI记得聊天历史
项目启动的时候自动打开SoVITS,可以的话不要显示控制台
目前暂时定这两个需求
之后的打算:把这个2d的交互场景设置到3D场景里,在3D的场景里实现这个2D窗口的所有功能
---
目前先做一个Scroll view,可以滚动查看聊天记录
配置好了,接下来接入SoVITS,还有角色动嘴
Process 类 (System.Diagnostics) | Microsoft Learn
//关于SoVITS的API打开,想了一下还是不管窗口显隐的问题了,还有打开会需要10秒左右的时间,不能产生空判断,这一段的过程比较重复,是一些使用信息的积累,可以做但是还是先管更重要的东西
---所以接下来都假设在SoVITS已经打开好的条件下(正常来说游戏的进入动画就可以把这些给做了,我在这做了也不知道实际如何)
Process类也不用了,直接到队列读取那一块了
这里把对话内容的 实时更新Text组件 和 拆分句子给SoVITS去转换成audioClip文件播放 分开
而且返回的响应一般是一个字的居多,也就不考虑一次来的响应里有多个字符,标点在前面了
很好,看的到播放的队列正常更新了,Clip队列也一直在运作
现在就差口型的绑定了,这一部分就直接绑定audioSource就可以了
就会根据音量调整嘴巴的闭合
存在嘴巴没有跟着张开的情况,这里的模式调整为override,就可以避免视线 追踪 对2D模型的动画状态一直在调整
这里是调整声音大小的标准,让嘴巴在正确的区间里开闭