audioRateController.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import time
  2. import asyncio
  3. from collections import deque
  4. from config.logger import setup_logging
  5. TAG = __name__
  6. logger = setup_logging()
  7. class AudioRateController:
  8. """
  9. 音频速率控制器 - 按照60ms帧时长精确控制音频发送
  10. 解决高并发下的时间累积误差问题
  11. """
  12. def __init__(self, frame_duration=60):
  13. """
  14. Args:
  15. frame_duration: 单个音频帧时长(毫秒),默认60ms
  16. """
  17. self.frame_duration = frame_duration
  18. self.queue = deque()
  19. self.play_position = 0 # 虚拟播放位置(毫秒)
  20. self.start_timestamp = None # 开始时间戳(只读,不修改)
  21. self.pending_send_task = None
  22. self.logger = logger
  23. self.queue_empty_event = asyncio.Event() # 队列清空事件
  24. self.queue_empty_event.set() # 初始为空状态
  25. self.queue_has_data_event = asyncio.Event() # 队列数据事件
  26. self._last_queue_empty_time = 0 # 上次队列清空的时间(秒)
  27. def reset(self):
  28. """重置控制器状态"""
  29. if self.pending_send_task and not self.pending_send_task.done():
  30. self.pending_send_task.cancel()
  31. # 取消任务后,任务会在下次事件循环时清理,无需阻塞等待
  32. self.queue.clear()
  33. self.play_position = 0
  34. self.start_timestamp = None # 由首个音频包设置
  35. self._last_queue_empty_time = 0 # 重置时间
  36. # 相关事件处理
  37. self.queue_empty_event.set()
  38. self.queue_has_data_event.clear()
  39. def add_audio(self, opus_packet):
  40. """添加音频包到队列"""
  41. # 如果队列之前为空,需要调整时间戳以保持播放时间连续
  42. # 这样工具调用等待期间,新加入的音频不会提前播放
  43. # 如果间隔很短(<1帧),说明是正常的流式传输,不需要重置
  44. if len(self.queue) == 0 and self.play_position > 0:
  45. elapsed_since_empty = (time.monotonic() - self._last_queue_empty_time) * 1000
  46. # 只有间隔超过1帧时长,才认为是真正的"暂停恢复"
  47. if elapsed_since_empty >= self.frame_duration:
  48. self.start_timestamp = time.monotonic() - (self.play_position / 1000)
  49. self.logger.bind(tag=TAG).debug(
  50. f"队列从空恢复,重置时间戳,当前播放位置: {self.play_position}ms,间隔: {elapsed_since_empty:.0f}ms"
  51. )
  52. self.queue.append(("audio", opus_packet))
  53. # 相关事件处理
  54. self.queue_empty_event.clear()
  55. self.queue_has_data_event.set()
  56. def add_message(self, message_callback):
  57. """
  58. 添加消息到队列(立即发送,不占用播放时间)
  59. Args:
  60. message_callback: 消息发送回调函数 async def()
  61. """
  62. if len(self.queue) == 0 and self.play_position > 0:
  63. elapsed_since_empty = (time.monotonic() - self._last_queue_empty_time) * 1000
  64. if elapsed_since_empty >= self.frame_duration:
  65. self.start_timestamp = time.monotonic() - (self.play_position / 1000)
  66. self.logger.bind(tag=TAG).debug(
  67. f"队列从空恢复,重置时间戳,当前播放位置: {self.play_position}ms,间隔: {elapsed_since_empty:.0f}ms"
  68. )
  69. self.queue.append(("message", message_callback))
  70. # 相关事件处理
  71. self.queue_empty_event.clear()
  72. self.queue_has_data_event.set()
  73. def _get_elapsed_ms(self):
  74. """获取已经过的时间(毫秒)"""
  75. if self.start_timestamp is None:
  76. return 0
  77. return (time.monotonic() - self.start_timestamp) * 1000
  78. async def check_queue(self, send_audio_callback):
  79. """
  80. 检查队列并按时发送音频/消息
  81. Args:
  82. send_audio_callback: 发送音频的回调函数 async def(opus_packet)
  83. """
  84. while self.queue:
  85. item = self.queue[0]
  86. item_type = item[0]
  87. if item_type == "message":
  88. # 消息类型:立即发送,不占用播放时间
  89. _, message_callback = item
  90. self.queue.popleft()
  91. try:
  92. await message_callback()
  93. except Exception as e:
  94. self.logger.bind(tag=TAG).error(f"发送消息失败: {e}")
  95. raise
  96. elif item_type == "audio":
  97. if self.start_timestamp is None:
  98. self.start_timestamp = time.monotonic()
  99. _, opus_packet = item
  100. # 循环等待直到时间到达
  101. while True:
  102. # 计算时间差
  103. elapsed_ms = self._get_elapsed_ms()
  104. output_ms = self.play_position
  105. if elapsed_ms < output_ms:
  106. # 还不到发送时间,计算等待时长
  107. wait_ms = output_ms - elapsed_ms
  108. # 等待后继续检查(允许被中断)
  109. try:
  110. await asyncio.sleep(wait_ms / 1000)
  111. except asyncio.CancelledError:
  112. self.logger.bind(tag=TAG).debug("音频发送任务被取消")
  113. raise
  114. # 等待结束后重新检查时间(循环回到 while True)
  115. else:
  116. # 时间已到,跳出等待循环
  117. break
  118. # 时间已到,从队列移除并发送
  119. self.queue.popleft()
  120. self.play_position += self.frame_duration
  121. try:
  122. await send_audio_callback(opus_packet)
  123. except Exception as e:
  124. self.logger.bind(tag=TAG).error(f"发送音频失败: {e}")
  125. raise
  126. # 队列处理完后清除事件
  127. self.queue_empty_event.set()
  128. self.queue_has_data_event.clear()
  129. self._last_queue_empty_time = time.monotonic() # 记录队列清空时间
  130. def start_sending(self, send_audio_callback):
  131. """
  132. 启动异步发送任务
  133. Args:
  134. send_audio_callback: 发送音频的回调函数
  135. Returns:
  136. asyncio.Task: 发送任务
  137. """
  138. async def _send_loop():
  139. try:
  140. while True:
  141. # 等待队列数据事件,不轮询等待占用CPU
  142. await self.queue_has_data_event.wait()
  143. await self.check_queue(send_audio_callback)
  144. except asyncio.CancelledError:
  145. self.logger.bind(tag=TAG).debug("音频发送循环已停止")
  146. except Exception as e:
  147. self.logger.bind(tag=TAG).error(f"音频发送循环异常: {e}")
  148. self.pending_send_task = asyncio.create_task(_send_loop())
  149. return self.pending_send_task
  150. def stop_sending(self):
  151. """停止发送任务"""
  152. if self.pending_send_task and not self.pending_send_task.done():
  153. self.pending_send_task.cancel()
  154. self.logger.bind(tag=TAG).debug("已取消音频发送任务")