lib_player.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. """
  2. 新版播放器,包含划词朗读/AI朗读的TTS实时播放和语音合成的音频文件播放
  3. sudo apt-get install python3-pyqt5.qtmultimedia
  4. sudo apt-get install libqt5multimedia5-plugins
  5. """
  6. import threading
  7. from queue import Queue
  8. import sounddevice as sd
  9. import time
  10. import os
  11. import sys
  12. import config
  13. from PyQt5.QtWidgets import QApplication
  14. from libs import lib_tts, lib_tts_dipai
  15. import lib_tts_grpc
  16. from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
  17. from PyQt5.QtCore import QUrl, QObject, pyqtSignal
  18. # 获取家目录
  19. def get_home_env():
  20. if sys.platform == "win32":
  21. return 'APPDATA'
  22. return 'HOME'
  23. # 生成临时音频文件保存路径
  24. def gen_audio_save_path():
  25. _dir = os.path.join(os.environ[get_home_env()], "xAssistant", "tts_cache")
  26. if not os.path.exists(_dir):
  27. os.makedirs(_dir)
  28. _fp = f"{int(time.time())}.pcm"
  29. _audio_fp = os.path.join(_dir, _fp)
  30. print(_audio_fp)
  31. return _audio_fp
  32. # 计算待合成文本的音频时长(预估)
  33. def calculate_duration(text: str, speed_mode: int):
  34. """
  35. :param text: 待合成文本
  36. :param speed_mode: 语速模式 1:较快 2:较慢 其他为正常
  37. """
  38. if speed_mode == 1:
  39. _speed = 8.145
  40. elif speed_mode == 2:
  41. _speed = 4.064
  42. else:
  43. _speed = 5.358
  44. _duration = len(text) / _speed
  45. return int(_duration)
  46. # TTS临时文件播放线程
  47. class PlayTmpFile(threading.Thread):
  48. def __init__(self, fp, queue):
  49. super().__init__()
  50. self.daemon = True
  51. self._tmp_file = fp
  52. self._run_flag = True
  53. self._audio_queue = queue
  54. def run(self):
  55. if self._tmp_file is None or not os.path.exists(self._tmp_file):
  56. return
  57. self._audio_queue.queue.clear()
  58. with open(self._tmp_file, "rb") as f:
  59. while self._run_flag:
  60. data = f.read(4096)
  61. if not data:
  62. self._audio_queue.put("--end--")
  63. break
  64. self._audio_queue.put(data)
  65. time.sleep(0.02)
  66. def stop(self):
  67. self._run_flag = False
  68. # TTS播放器(支持播放、暂停、继续、停止)
  69. class TTSSteamPlayer(QObject):
  70. playing_signal = pyqtSignal()
  71. paused_signal = pyqtSignal()
  72. stopped_signal = pyqtSignal()
  73. def __init__(self, cb_fn):
  74. super().__init__()
  75. self._audio_queue = Queue()
  76. self._tmp_file = None
  77. self._is_paused = False
  78. # self._select_default_output_device()
  79. self._player = sd.RawOutputStream(samplerate=16000, channels=1, dtype='int16')
  80. self._player.start()
  81. self._tts_thread = None
  82. self._run_flag = True
  83. self._callback = cb_fn
  84. self.engine_type = config.config["ENGINE_TYPE"]
  85. # 设置默认输出设备
  86. def _select_default_output_device(self):
  87. try:
  88. system_output_device = sd.query_devices(kind='output')
  89. sd.default.device = system_output_device['index']
  90. except Exception as e:
  91. pass
  92. # 播放
  93. def play(self, tts_info: dict):
  94. self.playing_signal.emit()
  95. if self._is_paused:
  96. self._is_paused = False
  97. return
  98. self._audio_queue.queue.clear()
  99. if tts_info: # 有文本时,调用语音合成来播放实时音频
  100. self._tmp_file = gen_audio_save_path()
  101. text = tts_info.get("text")
  102. vcn = tts_info.get("vcn")
  103. speed = tts_info.get("speed")
  104. # self._tts_thread = lib_tts.TTSManagerNew(text, vcn, speed, self._audio_queue)
  105. # self._tts_thread = lib_tts_dipai.TTSManagerNew(text, vcn, speed, self._audio_queue)
  106. # self._tts_thread = lib_tts_grpc.TTSManagerNew(text, vcn, speed, self._audio_queue)
  107. if self.engine_type == "1":
  108. self._tts_thread = lib_tts.TTSManagerNew(text, vcn, speed, self._audio_queue)
  109. elif self.engine_type == "2":
  110. self._tts_thread = lib_tts_dipai.TTSManagerNew(text, vcn, speed, self._audio_queue)
  111. else:
  112. self._tts_thread = lib_tts_grpc.TTSManagerNew(text, vcn, speed, self._audio_queue)
  113. self._tts_thread.start()
  114. threading.Thread(target=self._write_stream, name="_write_stream", args=(False,), daemon=True).start()
  115. else: # 无文本时,播放临时文件
  116. self._tts_thread = PlayTmpFile(self._tmp_file, self._audio_queue)
  117. self._tts_thread.start()
  118. threading.Thread(target=self._write_stream, name="_write_stream", args=(True,), daemon=True).start()
  119. # 暂停
  120. def pause(self):
  121. self.paused_signal.emit()
  122. self._is_paused = True
  123. def resume(self):
  124. self._is_paused = False
  125. def is_paused(self):
  126. return self._is_paused
  127. # 停止
  128. def stop(self):
  129. self._is_paused = False
  130. self._audio_queue.queue.clear()
  131. self._audio_queue.put("--stop--")
  132. # 销毁播放器
  133. def quit(self):
  134. self._is_paused = False
  135. self._run_flag = False
  136. self._audio_queue.queue.clear()
  137. self._player.stop()
  138. # 向播放器写入音频流
  139. def _write_stream(self, is_file=False):
  140. while self._run_flag:
  141. if self._is_paused:
  142. time.sleep(0.02)
  143. continue
  144. if not self._audio_queue.empty():
  145. buf = self._audio_queue.get()
  146. if isinstance(buf, str):
  147. if buf == "--end--" or buf == "--stop--":
  148. if self._tts_thread:
  149. self._tts_thread.stop()
  150. self._callback(buf)
  151. else: # 异常
  152. self._callback(buf)
  153. break
  154. if not is_file:
  155. self._write_tmp(buf)
  156. try:
  157. self._player.write(buf)
  158. except:
  159. continue
  160. self.stopped_signal.emit()
  161. self._audio_queue.queue.clear()
  162. # 写入临时文件用于重新播放
  163. def _write_tmp(self, data):
  164. with open(self._tmp_file, "ab") as f:
  165. f.write(data)
  166. # 音频文件播放器
  167. class AudioPlayer(QObject):
  168. playing_signal = pyqtSignal()
  169. paused_signal = pyqtSignal()
  170. stopped_signal = pyqtSignal()
  171. rate_changed_signal = pyqtSignal(dict)
  172. def __init__(self):
  173. super().__init__()
  174. self.duration = 1
  175. self.position = 0
  176. self.media_player = QMediaPlayer()
  177. self.play_state = None
  178. self.media_player.stateChanged.connect(self.state_changed) # 获取播放状态
  179. self.media_player.positionChanged.connect(self.position_changed) # 获取播放进度
  180. self.media_player.durationChanged.connect(self.duration_changed) # 获取音频总时长
  181. def set_media(self, audio_path: str):
  182. self.media_player.setMedia(QMediaContent(QUrl.fromLocalFile(audio_path.replace('\\', '/'))))
  183. # 播放状态改变
  184. def state_changed(self, state):
  185. if state == QMediaPlayer.State.PlayingState:
  186. self.play_state = QMediaPlayer.State.PlayingState
  187. self.playing_signal.emit()
  188. elif state == QMediaPlayer.State.PausedState:
  189. self.play_state = QMediaPlayer.State.PausedState
  190. self.paused_signal.emit()
  191. elif state == QMediaPlayer.State.StoppedState:
  192. self.play_state = QMediaPlayer.State.StoppedState
  193. self.stopped_signal.emit()
  194. # 播放进度改变
  195. def position_changed(self, position):
  196. try:
  197. self.position = position
  198. rate = position / self.duration
  199. # 格式化时间
  200. # duration = self.format_time(self.duration)
  201. # position = self.format_time(position)
  202. info = {"rate": rate, "position": position, "duration": self.duration}
  203. self.rate_changed_signal.emit(info)
  204. except ZeroDivisionError:
  205. pass
  206. @staticmethod
  207. def format_time(input_time):
  208. minute = int(input_time / 60000)
  209. second = int((input_time - minute * 60000) / 1000)
  210. # 单数前面加0
  211. if minute < 10:
  212. minute = f"0{minute}"
  213. if second < 10:
  214. second = f"0{second}"
  215. output_time = f"{minute}:{second}"
  216. return output_time
  217. # 音频总时长改变
  218. def duration_changed(self, duration):
  219. self.duration = duration
  220. # 设置播放进度
  221. def set_position(self, float_val: float):
  222. val = float_val * self.duration
  223. self.media_player.setPosition(val)
  224. def set_time(self, val):
  225. self.media_player.setPosition(val)
  226. # 设置播放倍速
  227. def set_playback_speed(self, speed: float):
  228. if speed == 0.5:
  229. speed = 0.8
  230. self.media_player.setPlaybackRate(speed)
  231. # 播放
  232. def play(self, *args):
  233. self.media_player.play()
  234. # 暂停
  235. def pause(self):
  236. self.media_player.pause()
  237. # 继续
  238. def resume(self):
  239. self.media_player.play()
  240. # 停止
  241. def stop(self):
  242. try:
  243. self.media_player.stop()
  244. except:
  245. pass
  246. # 销毁播放器
  247. def quit(self):
  248. pass
  249. def is_playing(self):
  250. return self.play_state == QMediaPlayer.State.PlayingState
  251. if __name__ == '__main__':
  252. app = QApplication([])
  253. _play_audio_vlc_thread = AudioPlayer()
  254. _play_audio_vlc_thread.set_media(r"/media/llh/data/test/19.wav")
  255. _play_audio_vlc_thread.play()
  256. app.exec_()