211 lines
7.5 KiB
Python
211 lines
7.5 KiB
Python
![]() |
import sys
|
|||
|
import pvporcupine
|
|||
|
import pyaudio
|
|||
|
import struct
|
|||
|
import os
|
|||
|
import socket
|
|||
|
import time
|
|||
|
import threading
|
|||
|
import configparser
|
|||
|
|
|||
|
# 获取基础目录
|
|||
|
base_dir = os.path.dirname(os.path.abspath(__file__)) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(sys.executable))
|
|||
|
config_file_path = os.path.join(base_dir, 'config.ini')
|
|||
|
# 读取配置文件
|
|||
|
config = configparser.ConfigParser()
|
|||
|
try:
|
|||
|
config.read(config_file_path, encoding='utf-8')
|
|||
|
HOST = config.get('Server', 'address')
|
|||
|
PORT = config.getint('Server', 'port')
|
|||
|
KEY = config.get('Server', 'key')
|
|||
|
CPORT = config.getint('Server', 'client-port')
|
|||
|
except (configparser.NoSectionError, configparser.NoOptionError) as e:
|
|||
|
print(f"读取配置文件时出错: {e}")
|
|||
|
sys.exit(1)
|
|||
|
except FileNotFoundError:
|
|||
|
print("配置文件未找到")
|
|||
|
sys.exit(1)
|
|||
|
|
|||
|
def resource_path(relative_path):
|
|||
|
"""获取资源文件的绝对路径"""
|
|||
|
try:
|
|||
|
base_path = sys._MEIPASS
|
|||
|
except Exception:
|
|||
|
base_path = os.path.abspath(".")
|
|||
|
return os.path.join(base_path, relative_path)
|
|||
|
|
|||
|
class UDPClientTool:
|
|||
|
def __init__(self, server_host=HOST, server_port=PORT, client_port=CPORT, wake_word_detector=None):
|
|||
|
self.SERVER_HOST = server_host
|
|||
|
self.SERVER_PORT = server_port
|
|||
|
self.CLIENT_PORT = client_port
|
|||
|
self.client_socket = None
|
|||
|
self.wake_word_detector = wake_word_detector
|
|||
|
self.heartbeat_interval = 30 # 心跳间隔(秒)
|
|||
|
self.start_communication()
|
|||
|
|
|||
|
def connect(self):
|
|||
|
try:
|
|||
|
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|||
|
if self.CLIENT_PORT != 0:
|
|||
|
self.client_socket.bind(('0.0.0.0', self.CLIENT_PORT))
|
|||
|
# 发送连接测试消息
|
|||
|
test_message = "连接测试"
|
|||
|
self.client_socket.sendto(test_message.encode('utf-8'), (self.SERVER_HOST, self.SERVER_PORT))
|
|||
|
buffer_size = 4096
|
|||
|
data, addr = self.client_socket.recvfrom(buffer_size)
|
|||
|
decoded_data = data.decode('utf-8')
|
|||
|
print(f"成功连接到 {self.SERVER_HOST}:{self.SERVER_PORT},收到测试响应: {decoded_data}")
|
|||
|
return True
|
|||
|
except Exception as e:
|
|||
|
print(f"连接出错: {e}")
|
|||
|
return False
|
|||
|
|
|||
|
def send_message(self, message):
|
|||
|
if not self.client_socket:
|
|||
|
while not self.connect():
|
|||
|
print("连接失败,3 秒后尝试重新连接...")
|
|||
|
time.sleep(3)
|
|||
|
try:
|
|||
|
self.client_socket.sendto(message.encode('utf-8'), (self.SERVER_HOST, self.SERVER_PORT))
|
|||
|
buffer_size = 4096
|
|||
|
data, addr = self.client_socket.recvfrom(buffer_size)
|
|||
|
decoded_data = data.decode('utf-8')
|
|||
|
print(f"收到来自 {addr} 的响应: {decoded_data}")
|
|||
|
|
|||
|
# 处理特殊指令
|
|||
|
if decoded_data == "打开助手":
|
|||
|
self.toggle_microphone(True)
|
|||
|
elif decoded_data == "关闭助手":
|
|||
|
self.toggle_microphone(False)
|
|||
|
return decoded_data
|
|||
|
except Exception as e:
|
|||
|
print(f"发送消息出错: {e}")
|
|||
|
self.reconnect()
|
|||
|
return None
|
|||
|
|
|||
|
def toggle_microphone(self, state):
|
|||
|
if self.wake_word_detector:
|
|||
|
if state:
|
|||
|
self.wake_word_detector.open_PyAudio()
|
|||
|
else:
|
|||
|
self.wake_word_detector.close_PyAudio()
|
|||
|
|
|||
|
def start_communication(self):
|
|||
|
while not self.connect():
|
|||
|
print("连接失败,3 秒后尝试重新连接...")
|
|||
|
time.sleep(3)
|
|||
|
|
|||
|
# 启动接收消息线程
|
|||
|
receive_thread = threading.Thread(target=self.receive_messages)
|
|||
|
receive_thread.daemon = True
|
|||
|
receive_thread.start()
|
|||
|
|
|||
|
# 启动心跳线程
|
|||
|
heartbeat_thread = threading.Thread(target=self.send_heartbeat)
|
|||
|
heartbeat_thread.daemon = True
|
|||
|
heartbeat_thread.start()
|
|||
|
|
|||
|
def receive_messages(self):
|
|||
|
buffer_size = 4096
|
|||
|
print("开始接收服务器消息...")
|
|||
|
try:
|
|||
|
while True:
|
|||
|
data, addr = self.client_socket.recvfrom(buffer_size)
|
|||
|
decoded_data = data.decode('utf-8')
|
|||
|
print(f"收到来自 {addr} 的消息: {decoded_data}")
|
|||
|
|
|||
|
# 处理特殊指令
|
|||
|
if decoded_data in ["打开助手", "关闭助手"]:
|
|||
|
self.toggle_microphone(decoded_data == "打开助手")
|
|||
|
except KeyboardInterrupt:
|
|||
|
print("手动停止接收消息。")
|
|||
|
finally:
|
|||
|
self.reconnect()
|
|||
|
|
|||
|
def send_heartbeat(self):
|
|||
|
while True:
|
|||
|
if self.client_socket:
|
|||
|
try:
|
|||
|
heartbeat_message = "heartbeat"
|
|||
|
self.client_socket.sendto(heartbeat_message.encode('utf-8'), (self.SERVER_HOST, self.SERVER_PORT))
|
|||
|
print(f"发送心跳消息: {heartbeat_message}")
|
|||
|
except Exception as e:
|
|||
|
print(f"发送心跳出错: {e}")
|
|||
|
self.reconnect()
|
|||
|
time.sleep(self.heartbeat_interval)
|
|||
|
|
|||
|
def reconnect(self):
|
|||
|
print("尝试重新连接服务器...")
|
|||
|
self.client_socket.close()
|
|||
|
self.client_socket = None
|
|||
|
while not self.connect():
|
|||
|
print("重新连接失败,3 秒后重试...")
|
|||
|
time.sleep(3)
|
|||
|
print("重新连接成功。")
|
|||
|
|
|||
|
class WakeWordDetector:
|
|||
|
def __init__(self):
|
|||
|
# 配置唤醒词相关参数
|
|||
|
keyword_paths = [resource_path('hello.ppn')]
|
|||
|
model_file = resource_path('porcupine_params_zh.pv')
|
|||
|
wake_words = ["你好"]
|
|||
|
sensitivities = [0.9]
|
|||
|
access_key = KEY
|
|||
|
|
|||
|
# 创建 Porcupine 实例
|
|||
|
self.porcupine = pvporcupine.create(
|
|||
|
access_key=access_key,
|
|||
|
keyword_paths=keyword_paths,
|
|||
|
model_path=model_file,
|
|||
|
keywords=wake_words,
|
|||
|
sensitivities=sensitivities
|
|||
|
)
|
|||
|
|
|||
|
# 创建 UDP 客户端工具实例
|
|||
|
self.client_tool = UDPClientTool(wake_word_detector=self)
|
|||
|
|
|||
|
# 音频流参数
|
|||
|
self.sample_rate = 16000
|
|||
|
self.frame_length = 512
|
|||
|
self.isOpen = False
|
|||
|
|
|||
|
def open_PyAudio(self):
|
|||
|
self.p = pyaudio.PyAudio()
|
|||
|
self.stream = self.p.open(format=pyaudio.paInt16,
|
|||
|
channels=1,
|
|||
|
rate=self.sample_rate,
|
|||
|
input=True,
|
|||
|
frames_per_buffer=self.frame_length)
|
|||
|
self.isOpen = True
|
|||
|
print("开启麦克风...")
|
|||
|
|
|||
|
def close_PyAudio(self):
|
|||
|
if self.stream and self.stream.is_active():
|
|||
|
self.stream.stop_stream()
|
|||
|
self.stream.close()
|
|||
|
self.p.terminate()
|
|||
|
self.isOpen = False
|
|||
|
print("关闭麦克风...")
|
|||
|
|
|||
|
def start_detection(self):
|
|||
|
print("正在等待唤醒词...")
|
|||
|
try:
|
|||
|
while True:
|
|||
|
if self.isOpen:
|
|||
|
pcm = self.stream.read(self.frame_length)
|
|||
|
pcm = struct.unpack_from("h" * self.frame_length, pcm)
|
|||
|
result = self.porcupine.process(pcm)
|
|||
|
if result >= 0:
|
|||
|
print("检测到唤醒词!")
|
|||
|
message = "打开助手"
|
|||
|
self.client_tool.send_message(message)
|
|||
|
except KeyboardInterrupt:
|
|||
|
print("程序已停止。")
|
|||
|
finally:
|
|||
|
self.close_PyAudio()
|
|||
|
self.porcupine.delete()
|
|||
|
|
|||
|
if __name__ == "__main__":
|
|||
|
detector = WakeWordDetector()
|
|||
|
detector.start_detection()
|