LiveTalking icon indicating copy to clipboard operation
LiveTalking copied to clipboard

打断能力太弱了,响应最快3秒,最慢更多,没法直接用在对语音有实时性很高追求的项目上

Open zhengyiwei1987 opened this issue 4 months ago • 20 comments

还得继续爆改,

先说说打断, 在测试场景稍微复杂一点的环境,人多七嘴八舌,就需要极快速度的打断,然后保持静默继续asr持续识别,直到在合适的时候断开,然后再次把之前识别到的内容合并重新发给大模型,这样和大模型的对话才不显得突兀

关于这种语音识别的究极体验,比如豆包也没法做到100%不去胡乱识别,但起码周围在说话的时候能够快速打断,然后静默->持续偷听->持续转换,拾取之前的内容,再次统一发送这个流程做的最好

我不懂python,但观察了代码很久,作者思路挺好,把一切都通过流处理,但是数字人在说话对唇形的时候、你想要极速打断基本很困难,单纯的清空tts和说话的缓冲区,没用,还是有将近3秒延迟,因为webrtc也存在缓冲区,数字人的推理还在进行,想要极速打断只处理其中某个链条好像做不到,有高手知道感谢能提供个思路

目前改了语音使用webrtc的语音通道来发送到asr,我目前用的豆包的语音识别大模型,因为不懂python踩了不少坑,采样率和颗粒度很怪异,用ai去改代码改了很久很久才能正常快速识别,可能是app.py中语音通道是50fps的原因,再发送asr前的采样率都很奇怪,需要设置成为8000才行,不然豆包的流式语音识别大模型很不准确

另外我修改了,再数字人说话期间,将语音的能量减弱, 或者完全设置为0,让他数字热人说话的时候减少被干扰,或者完全不被干扰,(我也是webrtc没有在服务端关闭语音通道的方法 只能通过降低音量的方法)

另外数字人说话状态有抖动,尤其是tts慢了一点,说话->静音之间的切换就有抖动,我目前做了个延迟1秒,然后做个演示+阻塞方法,能够准确的通知前端数字人说话的状态,但延迟意味着,精准度再度降低!!!

另外,修改了动作编排里的json文件,多个audiotype:1的静音动作组合按顺序执行动作

zhengyiwei1987 avatar Aug 05 '25 03:08 zhengyiwei1987

打断有识别时间,先缩短识别时间吧。

Dustyposa avatar Aug 05 '25 03:08 Dustyposa

我的思路是,前端通过js立即停止语音播放,然后再调用后端清空流。效果就是:声音会立即停止,视频上他的嘴还会动几下(可以接受) https://github.com/lipku/LiveTalking/issues/387#issuecomment-2874974254

byronv5 avatar Aug 11 '25 03:08 byronv5

@byronv5 哈哈,我也是通过js让video音量平滑变小,延迟3-4秒,嘴还在动,我只能说体验很差啊,依然是你说你的我说我的, 做实时演示的时候,那叫一个尴尬,后端的清空流不管用,你清空的时候,实际上这时候,流都已经读出来了,估计在进行运算合成对嘴型,这个时候很难停下来,

zhengyiwei1987 avatar Aug 11 '25 07:08 zhengyiwei1987

@byronv5 哈哈,我也是通过js让video音量平滑变小,延迟3-4秒,嘴还在动,我只能说体验很差啊,依然是你说你的我说我的, 做实时演示的时候,那叫一个尴尬,后端的清空流不管用,你清空的时候,实际上这时候,流都已经读出来了,估计在进行运算合成对嘴型,这个时候很难停下来,

我的应用场景是用户需要打断然后立即说话,所以我的做法是立即停止声音然后激活ASR,而不是让声音平滑变小,否则会导致用户的声音和数字人的声音有重叠导致意想不到的结果

byronv5 avatar Aug 11 '25 08:08 byronv5

@byronv5 就在刚刚,我找到了通过程序立即停止说话的地方了,如果用的事wav2lip,那在 lipreal.py 的 inference 方法里

只要设计一个准确一点的说话状态管理器就行了,那个 stopSpeaking 是我加的,只要让他False,数字人立马闭嘴🤣,这个方法我看不懂,猜测是从列队里拉出帧,处理了什么,然后在塞进列队 ,只然后再从 process_frames 方法里拉出来,要是流从 process_frames ,好像就来不及了 ,要做个状态管理器,把帧都丢弃掉就好了,

for i,res_frame in enumerate(pred): #self.__pushmedia(res_frame,loop,audio_track,video_track) print('stopSpeaking', stopSpeaking) if stopSpeaking: res_frame_queue.put((res_frame,__mirror_index(length,index),audio_frames[i2:i2+2])) index = index + 1

zhengyiwei1987 avatar Aug 11 '25 11:08 zhengyiwei1987

在lipreal.py的inference函数中,系统的处理流程是这样的: lipreal.py:114-182 这个方法从音频特征队列(audio_feat_queue)获取数据,进行Wav2Lip模型推理,然后将结果放入结果帧队列(res_frame_queue), stopSpeaking在推理完成但未放入队列前进行控制实现了立即打断,好的思路。

heyyyyou avatar Aug 12 '25 07:08 heyyyyou

有没有大佬把livekit agent接入进来。使用livekit agent的打断逻辑加模型回复,只使用livetalking的human 播放部分。 目前我能够实现两个应用的交互,还需要将livetalking的视频推送到livekit agent上。这一块还没完成[捂脸]

WThirteen avatar Aug 26 '25 08:08 WThirteen

@WThirteen 你无论怎么样你只要需要对口型,就都要把livekit的音频流推进来驱动口型,这不本质还是用tts驱动对口型么, 只要进入livetalking ,模型需要实时推理,以现在的架构就很难实现,我是设计了一个task_id的模式,从llm->tts每次请求都生成一个task_id,如果执行打断,就抛弃之前的,即便这样,打断也只能比原来快一点(大概2-3秒,但不会因为llm持续返回句子,一条一条的读),程序原本甚至能够到达5-6秒(基本是读完整个上下游游tts的音频流驱动后的列队,打断时间长短取决于llm断句生成的tts断句),

有时候我真怀疑,为了对口型值得么,哈哈

zhengyiwei1987 avatar Aug 26 '25 09:08 zhengyiwei1987

@WThirteen 你无论怎么样你只要需要对口型,就都要把livekit的音频流推进来驱动口型,这不本质还是用tts驱动对口型么, 只要进入livetalking ,模型需要实时推理,以现在的架构就很难实现,我是设计了一个task_id的模式,从llm->tts每次请求都生成一个task_id,如果执行打断,就抛弃之前的,即便这样,打断也只能比原来快一点(大概2-3秒,但不会因为llm持续返回句子,一条一条的读),程序原本甚至能够到达5-6秒(基本是读完整个上下游游tts的音频流驱动后的列队,打断时间长短取决于llm断句生成的tts断句),

有时候我真怀疑,为了对口型值得么,哈哈

是的,就是为了对口型。现在接入livekit之后,整体的打断逻辑变成了 llm生成完文本->调用livetalking 语音播报接口,当生成完文本之后才把上一个没讲完的打断。如果是用的私有化部署的模型再加上知识库,这部分就更慢了 :(

WThirteen avatar Aug 27 '25 00:08 WThirteen

@WThirteen 这部分我研究过,我大模型用了FASTGPT(不用设计上下文逻辑),你别说用知识库了,就开启工作流模式,什么都不加节点,速度都要慢0.7-1.2秒,对于实时性很高要求的数字人来说,不合适,只能选用聪明的大模型+尽可能在系统初始提示词上做文章,要求不能太高,这就是豆包目前有ip形象,有语音通话,现在也有视频功能,就是没有开启虚拟ip形象数字人?大厂还搞不定这个?一是对口型确实影响打断和应答效率,即便asr调教的再好,也禁不住对推理口型拖后腿,二是体验提升微乎其微徒增算力。

至于非得私有化查询知识库慢,还是多在界面交互或者动作编排切换上做文章吧,比如设计优雅的等待动画,或者给任务编排思考的动作衔接

至于你那个还是换个思路吧,你调用了livetalking 就是先走了调用livetalking的tts,tts生成流式语音,然后驱动wav2lip,而wav2lip就是语音驱动口型的,本质还是得改进程序的底层架构,否则就只能用js检测麦克风音量让video标签音量变小的妥协办法

zhengyiwei1987 avatar Aug 29 '25 18:08 zhengyiwei1987

@WThirteen 这部分我研究过,我大模型用了FASTGPT(不用设计上下文逻辑),你别说用知识库了,就开启工作流模式,什么都不加节点,速度都要慢0.7-1.2秒,对于实时性很高要求的数字人来说,不合适,只能选用聪明的大模型+尽可能在系统初始提示词上做文章,要求不能太高,这就是豆包目前有ip形象,有语音通话,现在也有视频功能,就是没有开启虚拟ip形象数字人?大厂还搞不定这个?一是对口型确实影响打断和应答效率,即便asr调教的再好,也禁不住对推理口型拖后腿,二是体验提升微乎其微徒增算力。

至于非得私有化查询知识库慢,还是多在界面交互或者动作编排切换上做文章吧,比如设计优雅的等待动画,或者给任务编排思考的动作衔接

至于你那个还是换个思路吧,你调用了livetalking 就是先走了调用livetalking的tts,tts生成流式语音,然后驱动wav2lip,而wav2lip就是语音驱动口型的,本质还是得改进程序的底层架构,否则就只能用js检测麦克风音量让video标签音量变小的妥协办法

感谢大佬解惑

WThirteen avatar Aug 30 '25 00:08 WThirteen

比较好的办法是不要一次性喂太多,srs缓存太多会不好打断。真要实时得改SRS源码。

wewaa avatar Sep 08 '25 03:09 wewaa

@wewaa 我已经做好了,打断能在1.5秒内打断就是改的地方有点多,tts里要加task_id,抛弃列队,basereal.py中要添加更新task_id的方法,task_id的作用主要针对连续像llm发送多条信息时,打断后取消之前一切对话,不会傻乎乎的一条一条的读几个字然后打断(asr识别到的消息发给llm之前我都会执行一次打断,即便这样,也会读取llm返回的每一条消息,说两个字就打断这样的过程),实现立即闭嘴, 而且做了再5秒内的asr识别出的消息合并(每个消息都有一个talkid,5秒内的消息沿用上一次的talkid),合并文字消息再次发送给大模型,这样降低因为asr因为断句,大模型对话的碎片化,在前端ui上,根据talkid合并chat消息气泡,

在baseasr.py 里self.feat_queue = mp.Queue(2),把2改成1,目前看没有什么副作用,mp.Queue 2改成1对打断速度提升巨大,

zhengyiwei1987 avatar Sep 13 '25 14:09 zhengyiwei1987

大佬厉害👍

WThirteen avatar Sep 13 '25 22:09 WThirteen

我这边也改好了,试过了很多方法,想要实时停主要还是得改process_frames方法同时减小batch_size,收到停止信号立即将视频流切到预设得静止视频,把音频切换到静音帧,tts部分的队列可以直接清空,推理和asr部分的队列还是尽量用task_id来标识一下每一帧的归属,打断后用过滤的形式丢掉老帧,尽量不要去清空或者修改存放推理生成视频帧队列,因为很可能导致音画不同步,改起来比较麻烦。

YoungWWan avatar Sep 24 '25 09:09 YoungWWan

我这边也改好了,试过了很多方法,想要实时停主要还是得改process_frames方法同时减小batch_size,收到停止信号立即将视频流切到预设得静止视频,把音频切换到静音帧,tts部分的队列可以直接清空,推理和asr部分的队列还是尽量用task_id来标识一下每一帧的归属,打断后用过滤的形式丢掉老帧,尽量不要去清空或者修改存放推理生成视频帧队列,因为很可能导致音画不同步,改起来比较麻烦。

大佬们有没有出现内存溢出的情况,我现在出现这样的情况: 运行时间一长,服务器的内存被占满了。考虑是与livekit交互过程中,某一部分的资源未释放掉

WThirteen avatar Sep 25 '25 00:09 WThirteen

@WThirteen 设计的时候就要考虑销毁,例如livetalking的nerfreal,你可以吧你的服务构建进去,销毁的时候随着sessionid一同销毁,也可以绑定sessionid销毁,不过我已经把程序思路摸透了,将wav2lip搞到了小智ai里,webrtc启动链接,1秒打断,24000采样率tts,豆包asr语音识别,急速响应,识别率也非常高

zhengyiwei1987 avatar Oct 29 '25 16:10 zhengyiwei1987

@zhengyiwei1987 感谢大佬

WThirteen avatar Oct 30 '25 01:10 WThirteen

@WThirteen 有,我也发现了,webrtc每次链接释放要增加1-2%的内存占用.....,让ai分析没成功慢慢查吧

zhengyiwei1987 avatar Nov 08 '25 17:11 zhengyiwei1987

@zhengyiwei1987 是的,写个脚本,检测这个进程的内存,达到一定的之后kill掉再启动这个进程。属于是没辙了

WThirteen avatar Nov 11 '25 07:11 WThirteen