当前位置: 首页 > news >正文

Unity--GPT-SoVITS接入、处理GPTAPI的SSE响应流

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,支持 GETPOST 请求,并返回音频流或 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的书写规则


cmdEnter 解析为命令的结束换行必须用^(^ 后面不能有多余的空格)

路径中有空格,必须用双引号包裹( -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模型的动画状态一直在调整

这里是调整声音大小的标准,让嘴巴在正确的区间里开闭

相关文章:

  • 操作系统——进程优先级切换调度与调度算法
  • 【从零开始学习计算机科学】设计模式(二)工厂模式、抽象工厂模式、单例模式、建造者模型、原型模式
  • 一级运动员最小几岁·棒球1号位
  • ThinkPad T480s更换开机BIOS图片的详细步骤
  • KNN算法原理及python代码实现
  • PyTorch 实现 Conditional DCGAN(条件深度卷积生成对抗网络)进行图像到图像转换的示例代码
  • RabbitMQ可靠性进制
  • C语言每日一练——day_9
  • 【AHE数据集】 NCAR Anthropogenic Heat Flux (AHF) 数据集
  • Flask应用调试模式下外网访问的技巧
  • Day5 结构体、文字显示与GDT/IDT初始化
  • MySQL查询语句之like
  • Flask从入门到精通--初始Flask
  • 黑马node.js教程(nodejs教程)——AJAX-Day01-04.案例_地区查询——查询某个省某个城市所有地区(代码示例)
  • 五种最新优化算法(ALA、AE、DOA、GOA、OX)求解多个无人机协同路径规划(可以自定义无人机数量及起始点),MATLAB代码
  • dubbo nacos配置详解
  • 【electron】vue项目中使用electron打包报错的解决办法
  • 用pyqt做个日期输入控件,实现公农历转换及干支纪时功能
  • python微分方程求解,分别用显式欧拉方法、梯形法、改进欧拉方法、二阶龙格库塔方法、四阶龙格库塔方法求解微分方程
  • [oeasy]python074_ai辅助编程_水果程序_fruits_apple_banana_加法_python之禅
  • 中国海油总裁:低油价短期影响利润,但也催生资产并购机会
  • 交行一季度净利253.72亿元增1.54%,不良率微降
  • 北京朝阳涉住宅组团地块126亿元成交
  • 五万吨级半潜船在沪完成装备装载
  • 新任浙江省委常委、杭州市委书记刘非开展循迹溯源学习调研
  • 今年3月全国查处违反中央八项规定精神问题16994起