Files

183 lines
7.7 KiB
Python

from multiprocessing import Process, shared_memory, Value
from pynput.mouse import Controller
import numpy as np
import time
import sounddevice as sd
from .args import args
from .utils.shared_mem_guard import SharedMemoryGuard
from .utils.timer_wait import wait_until
from OneEuroFilter import OneEuroFilter
from .utils.fps import FPS
from .utils.timer_wait import wait_until
class MouseClientProcess(Process):
def __init__(self, pose_position_shm: shared_memory.SharedMemory):
super().__init__()
self.pose_position_shm = pose_position_shm
self.fps = Value('f', 60.0)
self.audio_volume = Value('f', 0.0)
self.audio_running = False
self.audio_callback_fps = FPS(60)
def audio_callback(self, indata, frames, time_info, status):
"""音频回调函数,计算音量"""
if status:
print(f"Audio status: {status}")
self.audio_callback_fps()
# 计算 RMS (均方根) 音量
volume_norm = np.linalg.norm(indata) * 10
# 应用阈值和灵敏度
if volume_norm < args.audio_threshold:
volume_norm = 0
else:
volume_norm = (volume_norm - args.audio_threshold) * args.audio_sensitivity
# 限制在 0-1 范围
volume_norm = np.clip(volume_norm, 0, 1)
self.audio_volume.value = volume_norm
def run(self):
mouse = Controller()
pose_position_shm_guard = SharedMemoryGuard(self.pose_position_shm, ctrl_name="pose_position_shm_ctrl")
np_pose_shm = np.ndarray((45,), dtype=np.float32, buffer=self.pose_position_shm.buf[:45 * 4])
np_position_shm = np.ndarray((4,), dtype=np.float32, buffer=self.pose_position_shm.buf[45 * 4:45 * 4 + 4 * 4])
# posLimit = [0, 0, 1920, 1080]
posLimit = [int(x) for x in args.mouse_input.split(',')]
if args.mouse_audio_input:
# 启动音频流 (使用 WASAPI)
try:
# 获取默认输入设备(麦克风或系统音频)
# 如果要捕获系统音频输出,需要使用 loopback 设备
audio_stream = sd.InputStream(
callback=self.audio_callback,
channels=1,
samplerate=16000,
blocksize=1024,
)
audio_stream.start()
self.audio_running = True
wait_until(time.perf_counter() + 1.0) # 等待一秒以稳定音频输入
print("Audio capture started (WASAPI)")
except Exception as e:
print(f"Failed to start audio capture: {e}")
print("Continuing without audio input...")
self.audio_running = False
audio_filter = OneEuroFilter(freq=self.audio_callback_fps.view(), mincutoff=10.0, beta=0.0)
prev = {
'eye_l_h_temp': 0,
'eye_r_h_temp': 0,
'mouth_ratio': 0,
'eye_y_ratio': 0,
'eye_x_ratio': 0,
'x_angle': 0,
'y_angle': 0,
'z_angle': 0,
}
last_time : float = time.perf_counter()
interval : float = 1.0 / 60 # 60 FPS
# 眨眼相关参数
blink_interval = args.blink_interval # 眨眼间隔时间(秒)
blink_duration = 0.25 # 眨眼持续时间(秒)
last_blink_time = time.perf_counter()
# 呼吸循环参数
breath_start_time = time.perf_counter()
print("Mouse Input Running at 60 FPS")
position_vector = np.array([0, 0, 0, 1], dtype=np.float32)
while True:
pos = mouse.position
# print(pos)
eye_limit = [0.8, 0.5]
head_eye_reduce = 0.6
head_slowness = 0.2
# 计算眨眼效果
current_time = time.perf_counter()
time_since_last_blink = current_time - last_blink_time
# 每隔 blink_interval 秒眨一次眼
if time_since_last_blink >= blink_interval:
last_blink_time = current_time
time_since_last_blink = 0
# 计算眨眼动画(使用尖锐的 sin 函数)
if time_since_last_blink < blink_duration:
# 在眨眼持续时间内,使用 sin 函数从 0 升到接近 1 再返回 0
# 使用 sin^2 使曲线更尖锐
blink_progress = time_since_last_blink / blink_duration * np.pi
blink_value = np.sin(blink_progress) ** 2
eye_close = blink_value * 1.0 # 升到1,返回到 0
else:
eye_close = 0.0
# 计算呼吸效果(使用 sin 函数,在 breath_cycle 时间内从 0 到 1 再到 0)
breath_elapsed = (current_time - breath_start_time) % args.breath_cycle
# 使用 sin 函数,让值在一个周期内从 0 -> 1 -> 0
# sin 在 0 到 π 之间从 0 到 1 到 0
breath_value = np.sin(breath_elapsed / args.breath_cycle * np.pi)
# 获取音频音量并映射到 mouth_ratio
current_mouth_ratio = audio_filter(self.audio_volume.value, time.perf_counter()) if self.audio_running else 0
mouse_data = {
'eye_l_h_temp': eye_close,
'eye_r_h_temp': eye_close,
'mouth_ratio': current_mouth_ratio,
'eye_y_ratio': np.interp(pos[1], [posLimit[1], posLimit[3]], [1, -1]) * eye_limit[1],
'eye_x_ratio': np.interp(pos[0], [posLimit[0], posLimit[2]], [1, -1]) * eye_limit[0],
'x_angle': np.interp(pos[1], [posLimit[1], posLimit[3]], [1, -1]),
'y_angle': np.interp(pos[0], [posLimit[0], posLimit[2]], [1, -1]),
'z_angle': 0,
}
mouse_data['x_angle'] = np.interp(head_slowness, [0, 1], [prev['x_angle'], mouse_data['x_angle']])
mouse_data['y_angle'] = np.interp(head_slowness, [0, 1], [prev['y_angle'], mouse_data['y_angle']])
mouse_data['eye_y_ratio'] -= mouse_data['x_angle'] * eye_limit[1] * head_eye_reduce
mouse_data['eye_x_ratio'] -= mouse_data['y_angle'] * eye_limit[0] * head_eye_reduce
if args.bongo:
mouse_data['y_angle'] += 0.05
mouse_data['x_angle'] += 0.05
prev = mouse_data
eye_l_h_temp = mouse_data['eye_l_h_temp']
eye_r_h_temp = mouse_data['eye_r_h_temp']
mouth_ratio = mouse_data['mouth_ratio']
eye_y_ratio = mouse_data['eye_y_ratio']
eye_x_ratio = mouse_data['eye_x_ratio']
x_angle = mouse_data['x_angle']
y_angle = mouse_data['y_angle']
z_angle = mouse_data['z_angle']
eyebrow_vector = [0.0] * 12
mouth_eye_vector = [0.0] * 27
pose_vector = [0.0] * 6
mouth_eye_vector[2] = eye_l_h_temp
mouth_eye_vector[3] = eye_r_h_temp
mouth_eye_vector[14] = mouth_ratio * 1.5
mouth_eye_vector[25] = eye_y_ratio
mouth_eye_vector[26] = eye_x_ratio
pose_vector[0] = x_angle
pose_vector[1] = y_angle
pose_vector[2] = z_angle
pose_vector[3] = pose_vector[1]
pose_vector[4] = pose_vector[2]
pose_vector[5] = breath_value
model_input_arr = eyebrow_vector
model_input_arr.extend(mouth_eye_vector)
model_input_arr.extend(pose_vector)
with pose_position_shm_guard.lock():
np_pose_shm[:] = np.array(model_input_arr, dtype=np.float32)
np_position_shm[:] = position_vector
wait_until(last_time + interval)
last_time += interval