main_aiui.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. """
  2. 调用讯飞演示笔获取录音
  3. QObject::connect: Cannot queue arguments of type 'QTextCursor'
  4. (Make sure 'QTextCursor' is registered using qRegisterMetaType().)
  5. """
  6. import os
  7. import struct
  8. import sys
  9. from functools import partial
  10. from pathlib import Path
  11. import json
  12. from PyQt5 import uic, QtCore
  13. from PyQt5.QtCore import QUrl
  14. from PyQt5.QtGui import QIcon, QDesktopServices
  15. from PyQt5.QtWidgets import QMainWindow, QApplication
  16. import mspeech_ui_thr as mspeech
  17. from libs import lib_opus, lib_aiui, lib_player
  18. from util import constants
  19. from BusinessHadler import BusinessHadler
  20. socketHadler = None # 全局变量
  21. OPUS_FLAG = False
  22. TTS_FLAG = False
  23. class MainUI(QMainWindow):
  24. def __init__(self):
  25. super().__init__()
  26. self._run_flag = False
  27. self.tts_player = None
  28. self.init_thr()
  29. def init_thr(self):
  30. # 子线程
  31. self._mspeech = mspeech.Mspeech()
  32. self._mspeech.daemon = True
  33. self._mspeech.start()
  34. # 处理子线程发送的数据
  35. self._mspeech.sign_thread_send.connect(self._proc)
  36. # 业务处理(处理子进程和子线程发送的数据)
  37. def _proc(self, result):
  38. try:
  39. code = result["code"]
  40. if code != constants.MSPEECH_AIUI_SEND_DATA:
  41. print("UI_main_proc_real", code)
  42. if code == constants.MSPEECH_AIUI_SEND_DATA: # aiui
  43. QtCore.QTimer.singleShot(0, partial(self._ist.audio_write, result["data"]))
  44. elif code == constants.MSPEECH_AIUI_RESET_DICTATION: # aiui
  45. self._start_record()
  46. elif code == constants.MSPEECH_AIUI_STOP_WS: # aiui
  47. self._stop_record()
  48. except Exception as e:
  49. print(e)
  50. pass
  51. def _load_ui(self):
  52. self.ui = uic.loadUi("ui/form.ui")
  53. self.ui.setWindowTitle("测试工具")
  54. self.ui.setWindowIcon(QIcon(str(Path.cwd().joinpath("images", "logo.ico"))))
  55. # self.ui.btn_filedialog.clicked.connect(self._open_dir)
  56. # self.ui.btn_start.clicked.connect(self._start_record)
  57. # self.ui.btn_stop.clicked.connect(self._stop_record)
  58. self.ui.show()
  59. def _open_dir(self):
  60. _dir = self.gen_home_doc_path()
  61. if sys.platform == "win32":
  62. filepath = _dir.replace("\\", "/")
  63. QDesktopServices.openUrl(QUrl(filepath, QUrl.StrictMode))
  64. else:
  65. os.popen("open %s" % _dir)
  66. def _logger(self, text):
  67. if self.ui:
  68. self.ui.textBrowser.append(text)
  69. def gen_home_doc_path(self):
  70. dir_name = "VoiceAssistant"
  71. if sys.platform == "win32":
  72. import ctypes.wintypes
  73. path_id = 5 # 5:文档 0:桌面
  74. buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
  75. ctypes.windll.shell32.SHGetFolderPathW(None, path_id, None, 0, buf)
  76. path = buf.value
  77. elif sys.platform == "darwin":
  78. path = os.path.join(os.environ[self.get_home_env()], dir_name)
  79. elif sys.platform == "linux":
  80. path = os.path.join(os.environ[self.get_home_env()], dir_name)
  81. else:
  82. path = os.path.join(os.environ[self.get_home_env()], dir_name)
  83. dir_path = os.path.join(path, dir_name)
  84. if not os.path.exists(dir_path):
  85. os.makedirs(dir_path)
  86. return dir_path
  87. @staticmethod
  88. def get_home_env():
  89. if sys.platform == "win32":
  90. return 'APPDATA'
  91. return 'HOME'
  92. # 初始化ist引擎
  93. def _init_ist(self):
  94. self._ist = lib_aiui.AiuiManager()
  95. self._ist.trigger.connect(self._proc_aiui)
  96. self._logger("ISR引擎初始化成功!")
  97. # 初始化opus压缩工具
  98. def _init_opus(self):
  99. self.opus = lib_opus.Opus()
  100. self.enc = self.opus.create_state(constants.SAMPLE_RATE, constants.CHANNEL_NUM, constants.OPUS_APPLICATION_VOIP)
  101. self.opus.encoder_ctl(self.enc, lib_opus.set_signal, lib_opus.OPUS_SIGNAL_VOICE)
  102. self.frame_size = int(constants.SAMPLE_RATE * 0.02)
  103. self.pcm_length = int(constants.SAMPLE_RATE / 8 * 16 * 1 * 0.02)
  104. self._logger("OPUS始化成功!")
  105. def _tts_player_callback(self, msg):
  106. print(msg)
  107. if msg == "--end--":
  108. # self._tts_play_end = True
  109. # self._send_data(my_util.gen_q_data(constants.TTS_STEAM_END, None))
  110. self.tts_player.quit()
  111. elif msg == "--stop--":
  112. self._tts_play_end = False
  113. def tts_play(self, text, vcn=0, speed_mode=0):
  114. _duration = lib_player.calculate_duration(text, speed_mode)
  115. # self._send_data(my_util.gen_q_data(constants.TTS_STEAM_DURATION, _duration))
  116. _info = {
  117. "text": text,
  118. "vcn": vcn,
  119. "speed": speed_mode
  120. }
  121. # 上一个播放器处于暂停状态
  122. if self.tts_player and self.tts_player.is_paused():
  123. self.tts_player.resume()
  124. return
  125. self.tts_player = lib_player.TTSSteamPlayer(self._tts_player_callback)
  126. self.tts_player.play(_info)
  127. def tts_pause(self):
  128. if self.tts_player:
  129. self.tts_player.pause()
  130. def tts_stop(self):
  131. if self.tts_player:
  132. self.tts_player.stop()
  133. self.tts_player.quit()
  134. # 开启录音
  135. def _start_record(self):
  136. # self._logger("开始录音")
  137. print("开始录音")
  138. if not self._run_flag:
  139. self._run_flag = True
  140. self._start_ist()
  141. # 停止录音
  142. def _stop_record(self):
  143. # self._logger("结束录音")
  144. print("结束录音")
  145. self._run_flag = False
  146. self._start_ist(stop=True)
  147. def _proc_aiui(self, msg):
  148. code = msg.get("code")
  149. data = msg.get("data")
  150. # IST引擎开始
  151. if code == constants.AITEST_AIUI_START:
  152. if data:
  153. self._logger(f"IST引擎启动成功!")
  154. # IST引擎异常
  155. elif code == constants.AITEST_AIUI_ERROR:
  156. self._logger(f"IST错误:{data}")
  157. # IST引擎结果
  158. elif code == constants.AITEST_AIUI_RESULT:
  159. self._logger(f"IST转写结果:{data}")
  160. self._stop_record()
  161. if TTS_FLAG:
  162. self.tts_play(data)
  163. elif code == constants.AITEST_AIUI_LOG:
  164. self._logger(data)
  165. elif code == constants.AITEST_AIUI_NLP:
  166. self._logger(data)
  167. print('开始业务逻辑')
  168. nlp_date = json.loads(str(data))
  169. intent = nlp_date['intent']
  170. if intent != {} and intent['rc'] == 0:
  171. intent_action = intent['semantic'][0]['intent']
  172. print("intent_action: ", intent_action)
  173. # 遍历词槽
  174. intent_solts = intent['semantic'][0]['slots']
  175. print("intent_solts: ", intent_solts)
  176. # # 方式1:使用这种方式,当语音命令为打开浏览器操作时,
  177. # # webbrowser.open()会阻塞_proc方法,导致AIUI重新发送消息给websocket,会导致_proc再执行一次,从而打开2次浏览器的BUG
  178. # # tts_text = socketHadler.handler(intent_action, intent_solts)
  179. # 方式2:为解决方式1的BUG,在调用业务方式时,先断开再重连的方式
  180. # 断开信号连接
  181. # PYQT6 中测试没有这个问题,断开再重连的代码注释掉
  182. self._ist.trigger.disconnect(self._proc_aiui)
  183. tts_text = socketHadler.handler(intent_action, intent_solts)
  184. # 重新连接信号
  185. self._ist.trigger.connect(self._proc_aiui)
  186. # # 方式3:在socketHadler里面使用以下方式打开,也可以避免打开2次web页面问题
  187. # # import os
  188. # # os.system('start http://101.37.148.192:8080/')
  189. if len(tts_text) != 0:
  190. self.tts_stop()
  191. self.tts_play(tts_text)
  192. else:
  193. self.tts_stop()
  194. self.tts_play("我没有理解您说的话")
  195. print("我没有理解你说的话啊")
  196. else:
  197. pass
  198. def _start_ist(self, stop=False):
  199. if stop:
  200. print("stop")
  201. self._ist.stop_speech()
  202. else:
  203. print("start")
  204. if OPUS_FLAG:
  205. ext_params = "opus-wb"
  206. else:
  207. ext_params = "raw"
  208. self._ist.regist_engine(ext_params)
  209. self._ist.start_speech()
  210. # 压缩音频
  211. def _compress_buf(self, data):
  212. out = self.opus.encode(self.enc, bytes(data), self.frame_size, self.pcm_length)
  213. data = bytearray(len(out) + 2)
  214. data[0:2] = struct.pack(">H", len(out))
  215. data[2:] = out
  216. buf = bytes(data)
  217. return buf
  218. def show(self):
  219. self._load_ui()
  220. self._init_ist()
  221. if OPUS_FLAG:
  222. self._init_opus()
  223. def closeEvent(self, event):
  224. super().closeEvent(event)
  225. QApplication.quit()
  226. def create_socket_handler():
  227. print('create_socket_handler')
  228. global socketHadler
  229. if socketHadler is None:
  230. socketHadler = BusinessHadler()
  231. app.aboutToQuit.connect(socketHadler.cleanup) # 关闭应用时进行清理操作
  232. if __name__ == '__main__':
  233. app = QApplication(sys.argv)
  234. win = MainUI()
  235. win.show()
  236. create_socket_handler()
  237. sys.exit(app.exec_())