LiveTalking icon indicating copy to clipboard operation
LiveTalking copied to clipboard

数字人形象实时切换所遇问题与解决

Open LiuEhe opened this issue 8 months ago • 20 comments

目标:在不重启后端的状况下,通过前端选择切换数字人 参考 这个页面

LipReal类中添加方法如下: ` def change_avatar(self, avatar): self.frame_list_cycle, self.face_list_cycle, self.coord_list_cycle = avatar

    # 当切换数字人形象时,旧形象相关的帧数据不再有用
    while not self.res_frame_queue.empty():
        try:
            self.res_frame_queue.get_nowait()
        except queue.Empty:
            break

    logger.info('数字人形象切换完成')`

在前端切换数字人形象,会出现两个问题

  1. 切换后,数字人说话,贴图会是最初始的数字人脸部,这是100多张切换到900多张数字人情况
  2. 切换失败,报错list index out of range,这是900多张切换到100多张数字人情况 Exception in thread Thread-3 (process_frames): Traceback (most recent call last): File "E:\tools\anaconda3\envs\nerfstream\lib\threading.py", line 1016, in _bootstrap_inner self.run() File "E:\tools\anaconda3\envs\nerfstream\lib\threading.py", line 953, in run self._target(*self._args, **self._kwargs) File "E:\LZ\project\LiveTalking\lipreal.py", line 227, in process_frames combine_frame = self.frame_list_cycle[idx] IndexError: list index out of range

原因在lipreal.py中的render()函数中推理线程仍在用旧的图像进行推理。

解决:

  1. 停止当前运行的 inference 线程

  2. 更新 avatar 数据

  3. 启动新的 inference 线程使用新的 avatar 数据

LiuEhe avatar Apr 11 '25 07:04 LiuEhe

def __init__(self, opt, model, avatar):,初始化新的变量 def __init__(self, opt, model, avatar): # 原先代码 # 添加用于渲染线程的退出事件 self._inference_thread = None self._process_thread = None self._render_quit_event = None

修改render函数,修改成如下 ` def render(self, quit_event, loop=None, audio_track=None, video_track=None): self.loop = loop self.audio_track = audio_track self.video_track = video_track

    self.tts.render(quit_event)
    self.init_customindex()

    # 使用新的退出事件
    self._render_quit_event = Event()
    self._start_threads(self._render_quit_event)

    # 主渲染循环
    count = 0
    totaltime = 0
    _starttime = time.perf_counter()

    while not quit_event.is_set():
        t = time.perf_counter()
        self.asr.run_step()

        if video_track._queue.qsize() >= 5:
            logger.debug('sleep qsize=%d', video_track._queue.qsize())
            time.sleep(0.04 * video_track._queue.qsize() * 0.8)

    # 渲染结束时清理
    if self._render_quit_event:
        self._render_quit_event.set()
    logger.info('lipreal thread stop')`

添加新函数 ` def change_avatar(self, avatar): # 1. 停止所有处理线程 if self._render_quit_event: self._render_quit_event.set()

    # 等待线程结束
    if self._inference_thread and self._inference_thread.is_alive():
        self._inference_thread.join(timeout=1.0)
    if self._process_thread and self._process_thread.is_alive():
        self._process_thread.join(timeout=1.0)

    # 2. 清空所有队列
    self._clear_queues()

    # 3. 更新avatar数据
    self.frame_list_cycle, self.face_list_cycle, self.coord_list_cycle = avatar

    # 4. 重新创建退出事件和启动线程
    self._render_quit_event = Event()
    self._start_threads(self._render_quit_event)

    logger.info('数字人形象切换完成')

def _clear_queues(self):
    """清空所有相关队列"""
    while not self.res_frame_queue.empty():
        try:
            self.res_frame_queue.get_nowait()
        except queue.Empty:
            break
    # 清空ASR相关队列
    while not self.asr.feat_queue.empty():
        try:
            self.asr.feat_queue.get_nowait()
        except queue.Empty:
            break
    while not self.asr.output_queue.empty():
        try:
            self.asr.output_queue.get_nowait()
        except queue.Empty:
            break

def _start_threads(self, quit_event):
    """启动所有处理线程"""
    # 启动处理线程
    self._process_thread = Thread(
        target=self.process_frames,
        args=(quit_event, self.loop, self.audio_track, self.video_track)
    )
    self._process_thread.start()

    # 启动推理线程
    self._inference_thread = Thread(
        target=inference,
        args=(quit_event, self.batch_size, self.face_list_cycle,
              self.asr.feat_queue, self.asr.output_queue, self.res_frame_queue,
              self.model)
    )
    self._inference_thread.start()`

LiuEhe avatar Apr 11 '25 07:04 LiuEhe

在app.py中添加新的异步函数 `#切换数字人形象 async def change_avatar(request): try: params = await request.json() sessionid = params.get('sessionid', 0) new_avatar_id = params.get('avatar_id')

    if sessionid not in nerfreals:
        return web.Response(
            content_type="application/json",
            text=json.dumps({"code": -1, "msg": "Session not found"}),
            status=404
        )

    # 加载新的形象数据
    new_avatar = await asyncio.get_event_loop().run_in_executor(
        None,
        lambda: load_avatar(new_avatar_id)
    )

    # 更新当前会话的数字人形象
    nerfreals[sessionid].change_avatar(new_avatar)

    return web.Response(
        content_type="application/json",
        text=json.dumps({"code": 0, "msg": "Avatar changed successfully"})
    )
except FileNotFoundError as e:
    return web.Response(
        content_type="application/json",
        text=json.dumps({"code": -1, "msg": f"Avatar not found: {str(e)}"}),
        status=404
    )
except Exception as e:
    return web.Response(
        content_type="application/json",
        text=json.dumps({"code": -1, "msg": str(e)}),
        status=500
    )`

主函数对应位置添加路由 appasync.router.add_post("/change_avatar", change_avatar) #数字人形象切换

LiuEhe avatar Apr 11 '25 07:04 LiuEhe

所用模型为wav2lip @lipku 在我修改 def render(self, quit_event, loop=None, audio_track=None, video_track=None) 后 会对整体其他的功能带来影响吗

LiuEhe avatar Apr 11 '25 07:04 LiuEhe

似乎切换人物后有延迟,请问修改后的render函数中,使用的还是 while not quit_event.is_set() 么,那它下面的内容似乎仍在多线程运行

Chengyang852 avatar Apr 11 '25 08:04 Chengyang852

@Chengyang852 你的意思点击切换后,需要等待时间才能切换好新的数字人形象,还是说,数字人在说话时有延迟?

LiuEhe avatar Apr 11 '25 09:04 LiuEhe

@Chengyang852 你的意思点击切换后,需要等待时间才能切换好新的数字人形象,还是说,数字人在说话时有延迟?

说话时有延迟。虽然在调用 change_avatar 后,已经对 process_frames 和 inference 两个部分进行了控制,但 render 函数中的 self.asr.run_step() 仍然持续运行,没有被停止。此外,quit_event 似乎是用来控制停止流程的,但现在的实现方式可能导致 stop 功能失效。不知道我理解的对不对

Chengyang852 avatar Apr 11 '25 09:04 Chengyang852

@Chengyang852 说话有延迟问题也未解决,无论切换前后,大约延迟2-3s,数字人才张口说话。如果你成功解决,非常乐意你在此分享。感谢

LiuEhe avatar Apr 12 '25 00:04 LiuEhe

@Chengyang852 说话有延迟问题也未解决,无论切换前后,大约延迟2-3s,数字人才张口说话。如果你成功解决,非常乐意你在此分享。感谢

不好意思,是我前面表述有问题,我想说的是形象切换后,声音和画面不匹配,画面显示有延迟(先有声音)

Chengyang852 avatar Apr 14 '25 01:04 Chengyang852

@Chengyang852 关于音画不同步问题,我没遇到,这边目前状况是音画同步,但是会在文字显示出来后2-3s才播放音频与视频。建议你搜搜音画不同步的议题。😀

LiuEhe avatar Apr 14 '25 07:04 LiuEhe

大佬,请问前端是怎么设置的,小白不太懂,求指教

dksodjo avatar Apr 15 '25 16:04 dksodjo

俺也一样,遇到了音画不同步的问题,声音先行,画面延后出现

xufengnian avatar Apr 21 '25 09:04 xufengnian

俺也一样,遇到了音画不同步的问题,声音先行,画面延后出现

有一个sleep,你把系数调小一点就好了

dksodjo avatar Apr 21 '25 10:04 dksodjo

@dksodjo 请教老哥,是哪里的sleep做调整?

xufengnian avatar Apr 22 '25 01:04 xufengnian

请问老哥有试过musetalk模式下的切换么? 我参考你的方法切换后脸花了

thorory avatar Apr 25 '25 08:04 thorory

@dksodjo 请教老哥,是哪里的sleep做调整?

lipreal里面,你搜索一下

dksodjo avatar Apr 26 '25 08:04 dksodjo

请问老哥有试过musetalk模式下的切换么? 我参考你的方法切换后脸花了

musetalk下的切换其实很简单,同一个进程下启动二个MuseReal实例,每个实例的角色不一样就可以,但是前提是启动推流那块代码不要和启动数字人流程放在一起

liuxiaohan00 avatar Apr 30 '25 10:04 liuxiaohan00

目标:在不重启后端的状况下,通过前端选择切换数字人 参考 这个页面

LipReal类中添加方法如下: ` def change_avatar(self, avatar): self.frame_list_cycle, self.face_list_cycle, self.coord_list_cycle = avatar

    # 当切换数字人形象时,旧形象相关的帧数据不再有用
    while not self.res_frame_queue.empty():
        try:
            self.res_frame_queue.get_nowait()
        except queue.Empty:
            break

    logger.info('数字人形象切换完成')`

在前端切换数字人形象,会出现两个问题

  1. 切换后,数字人说话,贴图会是最初始的数字人脸部,这是100多张切换到900多张数字人情况
  2. 切换失败,报错list index out of range,这是900多张切换到100多张数字人情况 Exception in thread Thread-3 (process_frames): Traceback (most recent call last): File "E:\tools\anaconda3\envs\nerfstream\lib\threading.py", line 1016, in _bootstrap_inner self.run() File "E:\tools\anaconda3\envs\nerfstream\lib\threading.py", line 953, in run self._target(*self._args, **self._kwargs) File "E:\LZ\project\LiveTalking\lipreal.py", line 227, in process_frames combine_frame = self.frame_list_cycle[idx] IndexError: list index out of range

原因在lipreal.py中的render()函数中推理线程仍在用旧的图像进行推理。

解决:

  1. 停止当前运行的 inference 线程
  2. 更新 avatar 数据
  3. 启动新的 inference 线程使用新的 avatar 数据

我照你的方法实现了一遍,效果挺好的,就是会存在当数字人说话的时候去切换,会存在短暂的当前人物的脸上是上个人物的下半张脸。请问一下这个该怎么解决呢,我尝试让推理的线程完全结束再开启新的,还是会有这种

kkkwjr avatar May 21 '25 09:05 kkkwjr

目标:在不重启后端的状况下,通过前端选择切换数字人 参考 这个页面

LipReal类中添加方法如下: ` def change_avatar(self, avatar): self.frame_list_cycle, self.face_list_cycle, self.coord_list_cycle = avatar

    # 当切换数字人形象时,旧形象相关的帧数据不再有用
    while not self.res_frame_queue.empty():
        try:
            self.res_frame_queue.get_nowait()
        except queue.Empty:
            break

    logger.info('数字人形象切换完成')`

在前端切换数字人形象,会出现两个问题

  1. 切换后,数字人说话,贴图会是最初始的数字人脸部,这是100多张切换到900多张数字人情况
  2. 切换失败,报错list index out of range,这是900多张切换到100多张数字人情况 Exception in thread Thread-3 (process_frames): Traceback (most recent call last): File "E:\tools\anaconda3\envs\nerfstream\lib\threading.py", line 1016, in _bootstrap_inner self.run() File "E:\tools\anaconda3\envs\nerfstream\lib\threading.py", line 953, in run self._target(*self._args, **self._kwargs) File "E:\LZ\project\LiveTalking\lipreal.py", line 227, in process_frames combine_frame = self.frame_list_cycle[idx] IndexError: list index out of range

原因在lipreal.py中的render()函数中推理线程仍在用旧的图像进行推理。

解决:

  1. 停止当前运行的 inference 线程
  2. 更新 avatar 数据
  3. 启动新的 inference 线程使用新的 avatar 数据

我照你的方法实现了一遍,效果挺好的,就是会存在当数字人说话的时候去切换,会存在短暂的当前人物的脸上是上个人物的下半张脸。请问一下这个该怎么解决呢,我尝试让推理的线程完全结束再开启新的,还是会有这种情况。

kkkwjr avatar May 21 '25 09:05 kkkwjr

@Chengyang852 说话有延迟问题也未解决,无论切换前后,大约延迟2-3s,数字人才张口说话。如果你成功解决,非常乐意你在此分享。感谢

这个问题请问怎么解决的啊? @Chengyang852

xuty6 avatar Oct 13 '25 09:10 xuty6

老哥想问一下,我想实现的每个窗口的数字人互不干扰,每个端口都能自由切换各自的数字人这个有思路么?

DrinkIceCoke avatar Oct 21 '25 07:10 DrinkIceCoke